Files
BIPMaterialManager/tests/unit/update-service.test.ts
2026-03-21 18:26:13 +08:00

197 lines
5.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { UpdateConfig } from '../../src/main/types/config.schema'
import type { UpdateCatalog, UpdateRelease, UpdateStatus } from '../../src/main/types/update.types'
const mockPublishUpdateStatus = vi.fn()
const mockReadText = vi.fn()
const mockDownloadToFile = vi.fn()
const mockLoadCatalog = vi.fn()
const mockGetDialogCatalog = vi.fn()
const mockResolveUserStatus = vi.fn()
const mockResolveAdminStatus = vi.fn()
const mockGetDownloadPath = vi.fn()
const mockCalculateSha256 = vi.fn()
const mockInstallDownloadedRelease = vi.fn()
const mockGetValidDownloadedRelease = vi.fn()
let currentUpdateConfig: UpdateConfig = {
enabled: true,
allowDevMode: true,
endpoint: 'http://localhost:9000',
accessKey: 'key',
secretKey: 'secret',
bucket: 'bucket',
region: 'us-east-1',
basePrefix: 'updates/win-portable',
checkIntervalMinutes: 30,
maxAdminHistoryPerChannel: 10
}
function createRelease(version: string, channel: 'stable' | 'preview' = 'stable'): UpdateRelease {
return {
version,
channel,
artifactKey: `${channel}/${version}.exe`,
sha256: `${channel}-${version}-sha`,
size: 1,
publishedAt: '2026-03-21T10:00:00Z',
changelogKey: `${channel}/${version}.md`
}
}
vi.mock('../../src/main/services/config/config-manager', () => ({
ConfigManager: {
getInstance: vi.fn(() => ({
getConfig: () => ({ update: currentUpdateConfig })
}))
}
}))
vi.mock('../../src/main/services/logger', () => ({
createLogger: vi.fn(() => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn()
}))
}))
vi.mock('../../src/main/services/update/update-status-publisher', () => ({
publishUpdateStatus: mockPublishUpdateStatus
}))
vi.mock('../../src/main/services/update/update-storage-client', () => ({
UpdateStorageClient: class {
readText = mockReadText
downloadToFile = mockDownloadToFile
}
}))
vi.mock('../../src/main/services/update/update-catalog-service', () => ({
UpdateCatalogService: class {
loadCatalog = mockLoadCatalog
getDialogCatalog = mockGetDialogCatalog
resolveUserStatus = mockResolveUserStatus
resolveAdminStatus = mockResolveAdminStatus
}
}))
vi.mock('../../src/main/services/update/update-installer', () => ({
UpdateInstaller: class {
getDownloadPath = mockGetDownloadPath
calculateSha256 = mockCalculateSha256
installDownloadedRelease = mockInstallDownloadedRelease
getValidDownloadedRelease = mockGetValidDownloadedRelease
}
}))
describe('UpdateService', () => {
beforeEach(() => {
vi.clearAllMocks()
currentUpdateConfig = {
enabled: true,
allowDevMode: true,
endpoint: 'http://localhost:9000',
accessKey: 'key',
secretKey: 'secret',
bucket: 'bucket',
region: 'us-east-1',
basePrefix: 'updates/win-portable',
checkIntervalMinutes: 30,
maxAdminHistoryPerChannel: 10
}
mockReadText.mockReset()
mockDownloadToFile.mockReset()
mockLoadCatalog.mockReset()
mockGetDialogCatalog.mockReset()
mockResolveUserStatus.mockReset()
mockResolveAdminStatus.mockReset()
mockGetDownloadPath.mockReset()
mockCalculateSha256.mockReset()
mockInstallDownloadedRelease.mockReset()
mockGetValidDownloadedRelease.mockReset()
})
async function loadService() {
vi.resetModules()
const module = await import('../../src/main/services/update/update-service')
;(module.UpdateService as unknown as { instance: unknown }).instance = null
return module.UpdateService.getInstance()
}
it('initializes enabled update status from config', async () => {
const service = await loadService()
service.initialize()
expect(service.getStatus()).toMatchObject({
enabled: true,
supported: true
})
})
it('clears update state when user context becomes guest-like', async () => {
const service = await loadService()
await service.setUserContext(null)
expect(service.getStatus()).toMatchObject({
currentUserType: null,
phase: 'idle',
recommendedRelease: undefined,
downloadedRelease: undefined
})
expect(mockPublishUpdateStatus).toHaveBeenCalled()
})
it('checks updates for user and auto-downloads available recommendation', async () => {
const recommended = createRelease('1.1.0')
const catalog: UpdateCatalog = {
stable: [recommended],
preview: []
}
const userStatus: Partial<UpdateStatus> = {
phase: 'available',
recommendedRelease: recommended,
latestVersion: recommended.version,
latestChannel: recommended.channel,
message: `发现稳定版 ${recommended.version}`
}
mockLoadCatalog.mockResolvedValue(catalog)
mockResolveUserStatus.mockResolvedValue(userStatus)
mockGetDownloadPath.mockReturnValue('D:/downloads/stable-1.1.0.exe')
mockCalculateSha256.mockResolvedValue(recommended.sha256)
const service = await loadService()
await service.setUserContext('User')
expect(mockLoadCatalog).toHaveBeenCalledWith('User')
expect(mockResolveUserStatus).toHaveBeenCalled()
expect(mockDownloadToFile).toHaveBeenCalledWith(
recommended.artifactKey,
'D:/downloads/stable-1.1.0.exe'
)
expect(service.getStatus()).toMatchObject({
phase: 'downloaded',
latestVersion: '1.1.0',
latestChannel: 'stable'
})
})
it('returns disabled catalog when update services are unavailable', async () => {
currentUpdateConfig = {
...currentUpdateConfig,
enabled: false,
allowDevMode: false
}
const service = await loadService()
service.initialize()
const result = service.getCatalog()
expect(result).toEqual({ mode: 'disabled' })
expect(mockGetDialogCatalog).not.toHaveBeenCalled()
})
})