197 lines
5.7 KiB
TypeScript
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()
|
|
})
|
|
})
|