test: cover electron boundary modules
This commit is contained in:
86
tests/unit/auth-handler.test.ts
Normal file
86
tests/unit/auth-handler.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { IPC_CHANNELS } from '../../src/shared/ipc-channels'
|
||||
|
||||
const handleMock = vi.fn()
|
||||
const withErrorHandlingMock = vi.fn(async (handler: () => Promise<unknown>) => {
|
||||
const data = await handler()
|
||||
return { success: true, data }
|
||||
})
|
||||
|
||||
const serviceMethods = {
|
||||
getComputerName: vi.fn(async () => 'TEST-PC'),
|
||||
silentLogin: vi.fn(async () => ({ success: true })),
|
||||
login: vi.fn(async (username: string) => ({ success: true, username })),
|
||||
logout: vi.fn(async () => undefined),
|
||||
getCurrentUser: vi.fn(async () => ({ success: true })),
|
||||
getAllUsers: vi.fn(async () => []),
|
||||
switchUser: vi.fn(async (user: unknown) => ({ success: true, user })),
|
||||
isAdmin: vi.fn(async () => true)
|
||||
}
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
ipcMain: {
|
||||
handle: handleMock
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/ipc/index', () => ({
|
||||
withErrorHandling: withErrorHandlingMock
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/auth/auth-application-service', () => ({
|
||||
AuthApplicationService: class {
|
||||
getComputerName = serviceMethods.getComputerName
|
||||
silentLogin = serviceMethods.silentLogin
|
||||
login = serviceMethods.login
|
||||
logout = serviceMethods.logout
|
||||
getCurrentUser = serviceMethods.getCurrentUser
|
||||
getAllUsers = serviceMethods.getAllUsers
|
||||
switchUser = serviceMethods.switchUser
|
||||
isAdmin = serviceMethods.isAdmin
|
||||
}
|
||||
}))
|
||||
|
||||
describe('registerAuthHandlers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('registers auth IPC handlers and delegates login requests', async () => {
|
||||
const { registerAuthHandlers } = await import('../../src/main/ipc/auth-handler')
|
||||
|
||||
registerAuthHandlers()
|
||||
|
||||
expect(handleMock).toHaveBeenCalledWith(IPC_CHANNELS.AUTH_LOGIN, expect.any(Function))
|
||||
expect(handleMock).toHaveBeenCalledWith(IPC_CHANNELS.AUTH_SILENT_LOGIN, expect.any(Function))
|
||||
expect(handleMock).toHaveBeenCalledTimes(8)
|
||||
|
||||
const loginHandler = handleMock.mock.calls.find(
|
||||
([channel]) => channel === IPC_CHANNELS.AUTH_LOGIN
|
||||
)?.[1]
|
||||
|
||||
const result = await loginHandler?.({}, { username: 'alice', password: 'secret' })
|
||||
|
||||
expect(serviceMethods.login).toHaveBeenCalledWith('alice', 'secret')
|
||||
expect(withErrorHandlingMock).toHaveBeenCalled()
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { success: true, username: 'alice' }
|
||||
})
|
||||
})
|
||||
|
||||
it('delegates switch user requests through the service layer', async () => {
|
||||
const { registerAuthHandlers } = await import('../../src/main/ipc/auth-handler')
|
||||
const userInfo = { id: 1, username: 'admin' }
|
||||
|
||||
registerAuthHandlers()
|
||||
|
||||
const switchUserHandler = handleMock.mock.calls.find(
|
||||
([channel]) => channel === IPC_CHANNELS.AUTH_SWITCH_USER
|
||||
)?.[1]
|
||||
|
||||
await switchUserHandler?.({}, userInfo)
|
||||
|
||||
expect(serviceMethods.switchUser).toHaveBeenCalledWith(userInfo)
|
||||
})
|
||||
})
|
||||
95
tests/unit/bootstrap-runtime.test.ts
Normal file
95
tests/unit/bootstrap-runtime.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { join } from 'path'
|
||||
|
||||
const mkdirSyncMock = vi.fn()
|
||||
const existsSyncMock = vi.fn()
|
||||
const readdirSyncMock = vi.fn()
|
||||
const showErrorBoxMock = vi.fn()
|
||||
const setAppUserModelIdMock = vi.fn()
|
||||
const appOnMock = vi.fn()
|
||||
const configInitializeMock = vi.fn(async () => undefined)
|
||||
const updateInitializeMock = vi.fn()
|
||||
const registerIpcHandlersMock = vi.fn()
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
default: {
|
||||
mkdirSync: mkdirSyncMock,
|
||||
existsSync: existsSyncMock,
|
||||
readdirSync: readdirSyncMock
|
||||
},
|
||||
mkdirSync: mkdirSyncMock,
|
||||
existsSync: existsSyncMock,
|
||||
readdirSync: readdirSyncMock
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getPath: vi.fn(() => 'D:/userData'),
|
||||
setAppUserModelId: setAppUserModelIdMock,
|
||||
on: appOnMock,
|
||||
isPackaged: false
|
||||
},
|
||||
dialog: {
|
||||
showErrorBox: showErrorBoxMock
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/config/config-manager', () => ({
|
||||
ConfigManager: {
|
||||
getInstance: vi.fn(() => ({
|
||||
initialize: configInitializeMock
|
||||
}))
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/update/update-service', () => ({
|
||||
UpdateService: {
|
||||
getInstance: vi.fn(() => ({
|
||||
initialize: updateInitializeMock
|
||||
}))
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/ipc', () => ({
|
||||
registerIpcHandlers: registerIpcHandlersMock
|
||||
}))
|
||||
|
||||
describe('bootstrap runtime', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
existsSyncMock.mockReset()
|
||||
readdirSyncMock.mockReset()
|
||||
})
|
||||
|
||||
it('configures Playwright browser path under app userData', async () => {
|
||||
const { configurePlaywrightBrowsersPath } = await import('../../src/main/bootstrap/runtime')
|
||||
|
||||
const result = configurePlaywrightBrowsersPath()
|
||||
|
||||
const expectedPath = join('D:/userData', 'ms-playwright')
|
||||
expect(result).toBe(expectedPath)
|
||||
expect(process.env.PLAYWRIGHT_BROWSERS_PATH).toBe(expectedPath)
|
||||
})
|
||||
|
||||
it('initializes config, update service and IPC registration', async () => {
|
||||
const { initializeMainProcessServices } = await import('../../src/main/bootstrap/runtime')
|
||||
|
||||
await initializeMainProcessServices()
|
||||
|
||||
expect(configInitializeMock).toHaveBeenCalledTimes(1)
|
||||
expect(updateInitializeMock).toHaveBeenCalledTimes(1)
|
||||
expect(registerIpcHandlersMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows an error dialog when no Playwright browser is found', async () => {
|
||||
const { ensurePlaywrightRuntime } = await import('../../src/main/bootstrap/runtime')
|
||||
|
||||
existsSyncMock.mockReturnValue(false)
|
||||
readdirSyncMock.mockReturnValue([])
|
||||
|
||||
ensurePlaywrightRuntime('D:/userData/ms-playwright')
|
||||
|
||||
expect(mkdirSyncMock).toHaveBeenCalledWith('D:/userData/ms-playwright', { recursive: true })
|
||||
expect(showErrorBoxMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
75
tests/unit/cleaner-handler.test.ts
Normal file
75
tests/unit/cleaner-handler.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { IPC_CHANNELS } from '../../src/shared/ipc-channels'
|
||||
|
||||
const handleMock = vi.fn()
|
||||
const withErrorHandlingMock = vi.fn(async (handler: () => Promise<unknown>) => {
|
||||
const data = await handler()
|
||||
return { success: true, data }
|
||||
})
|
||||
|
||||
const serviceMethods = {
|
||||
runCleaner: vi.fn(async (_sender: unknown, input: unknown) => ({ success: true, input })),
|
||||
exportResults: vi.fn(async (items: unknown[]) => ({ success: true, total: items.length }))
|
||||
}
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
ipcMain: {
|
||||
handle: handleMock
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/ipc/index', () => ({
|
||||
withErrorHandling: withErrorHandlingMock
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/cleaner/cleaner-application-service', () => ({
|
||||
CleanerApplicationService: class {
|
||||
runCleaner = serviceMethods.runCleaner
|
||||
exportResults = serviceMethods.exportResults
|
||||
}
|
||||
}))
|
||||
|
||||
describe('registerCleanerHandlers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('registers cleaner IPC handlers and forwards the sender to the application service', async () => {
|
||||
const { registerCleanerHandlers } = await import('../../src/main/ipc/cleaner-handler')
|
||||
const sender = { id: 99 }
|
||||
const event = { sender }
|
||||
const input = { orderNumbers: ['MO-001'] }
|
||||
|
||||
registerCleanerHandlers()
|
||||
|
||||
expect(handleMock).toHaveBeenCalledTimes(2)
|
||||
expect(handleMock).toHaveBeenCalledWith(IPC_CHANNELS.CLEANER_RUN, expect.any(Function))
|
||||
|
||||
const runHandler = handleMock.mock.calls.find(
|
||||
([channel]) => channel === IPC_CHANNELS.CLEANER_RUN
|
||||
)?.[1]
|
||||
|
||||
const result = await runHandler?.(event, input)
|
||||
|
||||
expect(serviceMethods.runCleaner).toHaveBeenCalledWith(sender, input)
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { success: true, input }
|
||||
})
|
||||
})
|
||||
|
||||
it('forwards export requests to the cleaner application service', async () => {
|
||||
const { registerCleanerHandlers } = await import('../../src/main/ipc/cleaner-handler')
|
||||
const items = [{ id: '1' }, { id: '2' }]
|
||||
|
||||
registerCleanerHandlers()
|
||||
|
||||
const exportHandler = handleMock.mock.calls.find(
|
||||
([channel]) => channel === IPC_CHANNELS.CLEANER_EXPORT_RESULTS
|
||||
)?.[1]
|
||||
|
||||
await exportHandler?.({}, items)
|
||||
|
||||
expect(serviceMethods.exportResults).toHaveBeenCalledWith(items)
|
||||
})
|
||||
})
|
||||
136
tests/unit/update-catalog-service.test.ts
Normal file
136
tests/unit/update-catalog-service.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { UpdateCatalogService } from '../../src/main/services/update/update-catalog-service'
|
||||
import type { UpdateConfig } from '../../src/main/types/config.schema'
|
||||
import type { UpdateCatalog, UpdateRelease, UpdateStatus } from '../../src/main/types/update.types'
|
||||
|
||||
const config: UpdateConfig = {
|
||||
enabled: true,
|
||||
allowDevMode: false,
|
||||
endpoint: 'http://localhost:9000',
|
||||
accessKey: 'key',
|
||||
secretKey: 'secret',
|
||||
bucket: 'bucket',
|
||||
region: 'us-east-1',
|
||||
basePrefix: 'updates/win-portable',
|
||||
checkIntervalMinutes: 30,
|
||||
maxAdminHistoryPerChannel: 2
|
||||
}
|
||||
|
||||
function createRelease(
|
||||
version: string,
|
||||
channel: 'stable' | 'preview',
|
||||
overrides: Partial<UpdateRelease> = {}
|
||||
): UpdateRelease {
|
||||
return {
|
||||
version,
|
||||
channel,
|
||||
artifactKey: `${channel}/${version}.exe`,
|
||||
sha256: `${channel}-${version}-sha`,
|
||||
size: 1,
|
||||
publishedAt: '2026-03-21T10:00:00Z',
|
||||
changelogKey: `${channel}/${version}.md`,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function createStatus(overrides: Partial<UpdateStatus> = {}): UpdateStatus {
|
||||
return {
|
||||
enabled: true,
|
||||
supported: true,
|
||||
phase: 'idle',
|
||||
currentVersion: '1.0.0',
|
||||
currentChannel: 'stable',
|
||||
currentUserType: 'User',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('UpdateCatalogService', () => {
|
||||
it('returns disabled dialog catalog when updates are unavailable', () => {
|
||||
const storageClient = { readText: vi.fn() }
|
||||
const installer = { getValidDownloadedRelease: vi.fn() }
|
||||
const service = new UpdateCatalogService(config, storageClient as never, installer as never)
|
||||
|
||||
const result = service.getDialogCatalog(
|
||||
createStatus({ enabled: false, currentUserType: null }),
|
||||
{ stable: [], preview: [] }
|
||||
)
|
||||
|
||||
expect(result).toEqual({ mode: 'disabled' })
|
||||
})
|
||||
|
||||
it('loads stable and preview catalogs for admin users', async () => {
|
||||
const storageClient = {
|
||||
readText: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(JSON.stringify({ releases: [createRelease('1.1.0', 'stable')] }))
|
||||
.mockResolvedValueOnce(JSON.stringify({ releases: [createRelease('1.2.0', 'preview')] }))
|
||||
}
|
||||
const installer = { getValidDownloadedRelease: vi.fn() }
|
||||
const service = new UpdateCatalogService(config, storageClient as never, installer as never)
|
||||
|
||||
const result = await service.loadCatalog('Admin')
|
||||
|
||||
expect(storageClient.readText).toHaveBeenCalledTimes(2)
|
||||
expect(result.stable[0]?.version).toBe('1.1.0')
|
||||
expect(result.preview[0]?.version).toBe('1.2.0')
|
||||
})
|
||||
|
||||
it('marks user update as downloaded when a verified package already exists', async () => {
|
||||
const recommended = createRelease('1.1.0', 'stable')
|
||||
const storageClient = { readText: vi.fn() }
|
||||
const installer = {
|
||||
getValidDownloadedRelease: vi.fn().mockResolvedValue({
|
||||
...recommended,
|
||||
localPath: 'D:/downloads/stable-1.1.0.exe'
|
||||
})
|
||||
}
|
||||
const service = new UpdateCatalogService(config, storageClient as never, installer as never)
|
||||
|
||||
const result = await service.resolveUserStatus(createStatus(), {
|
||||
stable: [recommended],
|
||||
preview: []
|
||||
})
|
||||
|
||||
expect(result.phase).toBe('downloaded')
|
||||
expect(result.downloadedRelease?.localPath).toContain('stable-1.1.0.exe')
|
||||
expect(result.recommendedRelease?.version).toBe('1.1.0')
|
||||
})
|
||||
|
||||
it('returns available admin status and trims catalog history', () => {
|
||||
const storageClient = { readText: vi.fn() }
|
||||
const installer = { getValidDownloadedRelease: vi.fn() }
|
||||
const service = new UpdateCatalogService(config, storageClient as never, installer as never)
|
||||
const catalog: UpdateCatalog = {
|
||||
stable: [
|
||||
createRelease('1.0.0', 'stable'),
|
||||
createRelease('1.1.0', 'stable'),
|
||||
createRelease('1.2.0', 'stable')
|
||||
],
|
||||
preview: [
|
||||
createRelease('1.3.0', 'preview'),
|
||||
createRelease('1.4.0', 'preview'),
|
||||
createRelease('1.5.0', 'preview')
|
||||
]
|
||||
}
|
||||
|
||||
const adminStatus = service.resolveAdminStatus(
|
||||
createStatus({ currentUserType: 'Admin', currentVersion: '1.0.0' }),
|
||||
catalog
|
||||
)
|
||||
const dialogCatalog = service.getDialogCatalog(
|
||||
createStatus({
|
||||
currentUserType: 'Admin',
|
||||
currentVersion: '1.0.0',
|
||||
recommendedRelease: createRelease('1.5.0', 'preview')
|
||||
}),
|
||||
catalog
|
||||
)
|
||||
|
||||
expect(adminStatus.phase).toBe('available')
|
||||
expect(adminStatus.recommendedRelease?.version).toBe('1.5.0')
|
||||
expect(dialogCatalog.mode).toBe('admin')
|
||||
expect(dialogCatalog.channels?.stable).toHaveLength(2)
|
||||
expect(dialogCatalog.channels?.preview).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
33
tests/unit/update-installer.test.ts
Normal file
33
tests/unit/update-installer.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import path from 'path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { UpdateInstaller } from '../../src/main/services/update/update-installer'
|
||||
|
||||
describe('UpdateInstaller', () => {
|
||||
it('builds downloaded package path under userData pending-update', () => {
|
||||
const installer = new UpdateInstaller()
|
||||
|
||||
const result = installer.getDownloadPath({
|
||||
version: '1.2.3',
|
||||
channel: 'stable'
|
||||
})
|
||||
|
||||
expect(result).toContain(path.join('logs', 'pending-update'))
|
||||
expect(result).toContain('stable-1.2.3.exe')
|
||||
})
|
||||
|
||||
it('returns null when downloaded package does not exist', async () => {
|
||||
const installer = new UpdateInstaller()
|
||||
|
||||
const result = await installer.getValidDownloadedRelease({
|
||||
version: '9.9.9',
|
||||
channel: 'preview',
|
||||
artifactKey: 'preview/9.9.9.exe',
|
||||
sha256: 'missing',
|
||||
size: 1,
|
||||
publishedAt: '2026-03-21T10:00:00Z',
|
||||
changelogKey: 'preview/9.9.9.md'
|
||||
})
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
196
tests/unit/update-service.test.ts
Normal file
196
tests/unit/update-service.test.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
36
tests/unit/update-status-publisher.test.ts
Normal file
36
tests/unit/update-status-publisher.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mockSend = vi.fn()
|
||||
const mockGetAllWindows = vi.fn(() => [
|
||||
{ webContents: { send: mockSend } },
|
||||
{ webContents: { send: mockSend } }
|
||||
])
|
||||
|
||||
vi.mock('electron', async () => {
|
||||
const actual = await vi.importActual<typeof import('electron')>('electron')
|
||||
return {
|
||||
...actual,
|
||||
BrowserWindow: {
|
||||
getAllWindows: mockGetAllWindows
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('update-status-publisher', () => {
|
||||
it('broadcasts status to all renderer windows', async () => {
|
||||
const { publishUpdateStatus } =
|
||||
await import('../../src/main/services/update/update-status-publisher')
|
||||
|
||||
publishUpdateStatus({
|
||||
enabled: true,
|
||||
supported: true,
|
||||
phase: 'checking',
|
||||
currentVersion: '1.0.0',
|
||||
currentChannel: 'stable',
|
||||
currentUserType: 'Admin'
|
||||
})
|
||||
|
||||
expect(mockGetAllWindows).toHaveBeenCalledTimes(1)
|
||||
expect(mockSend).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user