import path from 'node:path'; import { setTimeout } from 'node:timers/promises'; import { removeWithRetry } from '@affine-test/kit/utils/utils'; import { v4 } from 'uuid'; import { afterAll, afterEach, beforeEach, expect, test, vi } from 'vitest'; const tmpDir = path.join(__dirname, 'tmp'); const appDataPath = path.join(tmpDir, 'app-data'); vi.doMock('@affine/electron/helper/main-rpc', () => ({ mainRPC: { getPath: async () => appDataPath, }, })); const constructorStub = vi.fn(); const destroyStub = vi.fn(); destroyStub.mockReturnValue(Promise.resolve()); function existProcess() { process.emit('beforeExit', 0); } vi.doMock('@affine/electron/helper/db/secondary-db', () => { return { SecondaryWorkspaceSQLiteDB: class { constructor(...args: any[]) { constructorStub(...args); } connectIfNeeded = () => Promise.resolve(); pull = () => Promise.resolve(); destroy = destroyStub; }, }; }); beforeEach(() => { vi.useFakeTimers({ shouldAdvanceTime: true }); }); afterEach(async () => { existProcess(); await removeWithRetry(tmpDir); vi.useRealTimers(); }); afterAll(() => { vi.doUnmock('@affine/electron/helper/main-rpc'); }); test('can get a valid WorkspaceSQLiteDB', async () => { const { ensureSQLiteDB } = await import( '@affine/electron/helper/db/ensure-db' ); const workspaceId = v4(); const db0 = await ensureSQLiteDB(workspaceId); expect(db0).toBeDefined(); expect(db0.workspaceId).toBe(workspaceId); const db1 = await ensureSQLiteDB(v4()); expect(db1).not.toBe(db0); expect(db1.workspaceId).not.toBe(db0.workspaceId); // ensure that the db is cached expect(await ensureSQLiteDB(workspaceId)).toBe(db0); }); test('db should be destroyed when app quits', async () => { const { ensureSQLiteDB } = await import( '@affine/electron/helper/db/ensure-db' ); const workspaceId = v4(); const db0 = await ensureSQLiteDB(workspaceId); const db1 = await ensureSQLiteDB(v4()); expect(db0.adapter).not.toBeNull(); expect(db1.adapter).not.toBeNull(); existProcess(); // wait the async `db.destroy()` to be called await setTimeout(100); expect(db0.adapter.db).toBeNull(); expect(db1.adapter.db).toBeNull(); }); test('db should be removed in db$Map after destroyed', async () => { const { ensureSQLiteDB, db$Map } = await import( '@affine/electron/helper/db/ensure-db' ); const workspaceId = v4(); const db = await ensureSQLiteDB(workspaceId); await db.destroy(); await setTimeout(100); expect(db$Map.has(workspaceId)).toBe(false); });