Files
goodbuddy/src/main/assistant-storage-startup.test.ts
T
mesalogo 4c7193e1ea fix: avoid history reclamation during structure-only upgrades
Ordinary database schema upgrades previously ran VACUUM on free pages and
showed misleading history-cleanup progress. Separate structure upgrades from
required legacy conversions, retaining reclamation intent only for retries
within the same startup. Preserve released data without new schema versions
or persistent migration markers; Agent and normal writes remain unchanged.

Add real SQLite and Worker regressions, bilingual operation-specific startup
copy, and migration documentation. Synchronize native Electron drag input
with actual resize state without weakening layout assertions.

Validation: 47 focused migration tests passed; typecheck, lint and diff checks
passed. Final full suite: 5137 passed, 74 skipped, 2 unrelated Electron failures.
Both failing Electron tests passed an isolated rerun; full CI remains unverified.

Release note: 修复普通数据库结构升级再次整理历史和回收空间的问题;仅在需要转换旧数据时执行相关处理,启动页准确显示当前升级操作。
2026-09-26 12:42:08 +08:00

78 lines
3.7 KiB
TypeScript

import type { EventEmitter } from 'node:events'
import type { BrowserWindow, IpcMainInvokeEvent } from 'electron'
import { beforeEach, expect, it, vi } from 'vitest'
import { ipcChannels } from '../shared/ipc-channels'
import { prepareAssistantStorage } from './assistant-storage-startup'
import { getPendingAssistantStorageUpgrade } from './assistant/assistant-storage-upgrade'
import { loadMainWindow } from './window'
const mocks = vi.hoisted(() => ({
workers: [] as Array<EventEmitter & { options: { workerData: { upgrade: unknown } } }>,
handlers: new Map<string, (event: unknown, input?: unknown) => unknown>(),
quit: vi.fn()
}))
vi.mock('electron', () => ({
app: { quit: mocks.quit },
ipcMain: {
handle: (channel: string, handler: (event: unknown, input?: unknown) => unknown) => mocks.handlers.set(channel, handler),
removeHandler: (channel: string) => mocks.handlers.delete(channel)
}
}))
vi.mock('node:fs/promises', () => {
const stat = vi.fn(async () => ({ size: 1024 }))
return { stat, default: { stat } }
})
vi.mock('./window', () => ({ loadMainWindow: vi.fn() }))
vi.mock('./assistant/assistant-storage-upgrade', () => ({ getPendingAssistantStorageUpgrade: vi.fn() }))
vi.mock('node:worker_threads', async () => {
const { EventEmitter } = await import('node:events')
class Worker extends EventEmitter {
constructor(_path: string, readonly options: { workerData: { upgrade: unknown } }) {
super()
mocks.workers.push(this)
}
}
return { Worker, default: { Worker } }
})
beforeEach(() => {
mocks.workers.length = 0
mocks.handlers.clear()
vi.clearAllMocks()
})
it.each([false, true])('retains the original reclaimSpace=%s decision across startup retries', async (reclaimSpace) => {
const upgrade = { migrateNotes: reclaimSpace, reclaimSpace }
vi.mocked(getPendingAssistantStorageUpgrade).mockReturnValueOnce(upgrade).mockReturnValue(undefined)
const webContents = { mainFrame: { url: 'app://main' }, getURL: () => 'app://main' }
const window = { webContents, isDestroyed: () => false, setProgressBar: vi.fn() } as unknown as BrowserWindow
const event = { sender: webContents, senderFrame: webContents.mainFrame } as unknown as IpcMainInvokeEvent
const preparation = prepareAssistantStorage(window, '/test/assistant.sqlite', new AbortController().signal)
await vi.waitFor(() => expect(mocks.workers).toHaveLength(1))
mocks.workers[0]!.emit('message', {
progress: { stage: 'upgrading', processed: 0, total: 0, bytesBefore: 1024 }
})
expect(loadMainWindow).toHaveBeenCalledOnce()
expect(mocks.handlers.get(ipcChannels.storageUpgradeProgress)!(event)).toMatchObject({ stage: 'upgrading' })
mocks.workers[0]!.emit('message', { error: 'Retry this startup' })
mocks.workers[0]!.emit('exit', 0)
await vi.waitFor(() =>
expect(mocks.handlers.get(ipcChannels.storageUpgradeProgress)!(event)).toMatchObject({ stage: 'failed' })
)
mocks.handlers.get(ipcChannels.storageUpgradeAction)!(event, 'retry')
await vi.waitFor(() => expect(mocks.workers).toHaveLength(2))
expect(getPendingAssistantStorageUpgrade).toHaveBeenCalledOnce()
expect(mocks.workers.map(worker => worker.options.workerData.upgrade)).toEqual([upgrade, upgrade])
mocks.workers[1]!.emit('message', { done: true })
mocks.workers[1]!.emit('exit', 0)
await preparation
expect(mocks.handlers.size).toBe(0)
expect(window.setProgressBar).toHaveBeenLastCalledWith(-1)
})
it('does not open an upgrade page or worker for an already-current database', async () => {
vi.mocked(getPendingAssistantStorageUpgrade).mockReturnValue(undefined)
await prepareAssistantStorage({} as BrowserWindow, '/test/assistant.sqlite', new AbortController().signal)
expect(mocks.workers).toHaveLength(0)
expect(loadMainWindow).not.toHaveBeenCalled()
})