@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
173 lines (144 loc) • 5.29 kB
text/typescript
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ClientDBLoadingProgress, DatabaseLoadingState } from '@/types/clientDB';
import { DatabaseManager } from './db';
// Mock 所有外部依赖
vi.mock('@electric-sql/pglite', () => ({
default: vi.fn(),
IdbFs: vi.fn(),
PGlite: vi.fn(),
MemoryFS: vi.fn(),
}));
vi.mock('@electric-sql/pglite/vector', () => ({
default: vi.fn(),
vector: vi.fn(),
}));
vi.mock('drizzle-orm/pglite', () => ({
drizzle: vi.fn(() => ({
dialect: {
migrate: vi.fn().mockResolvedValue(undefined),
},
})),
}));
let manager: DatabaseManager;
let progressEvents: ClientDBLoadingProgress[] = [];
let stateChanges: DatabaseLoadingState[] = [];
let callbacks = {
onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
progressEvents.push(progress);
}),
onStateChange: vi.fn((state: DatabaseLoadingState) => {
stateChanges.push(state);
}),
};
beforeEach(() => {
vi.clearAllMocks();
progressEvents = [];
stateChanges = [];
callbacks = {
onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
progressEvents.push(progress);
}),
onStateChange: vi.fn((state: DatabaseLoadingState) => {
stateChanges.push(state);
}),
};
// @ts-expect-error
DatabaseManager['instance'] = undefined;
manager = DatabaseManager.getInstance();
});
describe('DatabaseManager', () => {
describe('Callback Handling', () => {
it('should properly track loading states', async () => {
await manager.initialize(callbacks);
// 验证状态转换顺序
expect(stateChanges).toEqual([
DatabaseLoadingState.Initializing,
DatabaseLoadingState.LoadingDependencies,
DatabaseLoadingState.LoadingWasm,
DatabaseLoadingState.Migrating,
DatabaseLoadingState.Finished,
DatabaseLoadingState.Ready,
]);
});
it('should report dependencies loading progress', async () => {
await manager.initialize(callbacks);
// 验证依赖加载进度回调
const dependencyProgress = progressEvents.filter((e) => e.phase === 'dependencies');
expect(dependencyProgress.length).toBeGreaterThan(0);
expect(dependencyProgress[dependencyProgress.length - 1]).toEqual(
expect.objectContaining({
phase: 'dependencies',
progress: 100,
costTime: expect.any(Number),
}),
);
});
it('should report WASM loading progress', async () => {
await manager.initialize(callbacks);
// 验证 WASM 加载进度回调
const wasmProgress = progressEvents.filter((e) => e.phase === 'wasm');
// expect(wasmProgress.length).toBeGreaterThan(0);
expect(wasmProgress[wasmProgress.length - 1]).toEqual(
expect.objectContaining({
phase: 'wasm',
progress: 100,
costTime: expect.any(Number),
}),
);
});
it('should handle initialization errors', async () => {
// 模拟加载失败
vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
await expect(manager.initialize(callbacks)).rejects.toThrow();
expect(stateChanges).toContain(DatabaseLoadingState.Error);
});
it('should only initialize once when called multiple times', async () => {
const firstInit = manager.initialize(callbacks);
const secondInit = manager.initialize(callbacks);
await Promise.all([firstInit, secondInit]);
// 验证回调只被调用一次
const readyStateCount = stateChanges.filter(
(state) => state === DatabaseLoadingState.Ready,
).length;
expect(readyStateCount).toBe(1);
});
});
describe('Progress Calculation', () => {
it('should report progress between 0 and 100', async () => {
await manager.initialize(callbacks);
// 验证所有进度值都在有效范围内
progressEvents.forEach((event) => {
expect(event.progress).toBeGreaterThanOrEqual(0);
expect(event.progress).toBeLessThanOrEqual(100);
});
});
it('should include timing information', async () => {
await manager.initialize(callbacks);
// 验证最终进度回调包含耗时信息
const finalProgress = progressEvents[progressEvents.length - 1];
expect(finalProgress.costTime).toBeGreaterThan(0);
});
});
describe('Error Handling', () => {
it('should handle missing callbacks gracefully', async () => {
// 测试没有提供回调的情况
await expect(manager.initialize()).resolves.toBeDefined();
});
it('should handle partial callbacks', async () => {
// 只提供部分回调
await expect(manager.initialize({ onProgress: callbacks.onProgress })).resolves.toBeDefined();
await expect(
manager.initialize({ onStateChange: callbacks.onStateChange }),
).resolves.toBeDefined();
});
});
describe('Database Access', () => {
it('should throw error when accessing database before initialization', () => {
expect(() => manager.db).toThrow('Database not initialized');
});
it('should provide access to database after initialization', async () => {
await manager.initialize();
expect(() => manager.db).not.toThrow();
});
});
});