mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
aba9e8150c
* 👷 build: upgrade vitest to 5.0.0 Bump vitest and @vitest/coverage-v8 to 5.0.0 and migrate the configs for Vitest 4/5: drop the removed coverage.all option and replace poolOptions.forks.singleFork with maxWorkers/isolate. * 👷 build: shim jest-dom matcher types for vitest 5 Vitest 5 exposes Assertion<R, T> and no longer reads the jest.Matchers augmentation that @testing-library/jest-dom ships, so its matchers vanish from expect(). Re-declare them on the Vitest interface, and replace the removed describe.sequential with { concurrent: false }. * 👷 build: migrate the test suite to vitest 5 Vitest 5 (and @vitest/coverage-v8) replaces the esbuild dep optimizer with Rolldown, removed environmentMatchGlobs/coverage.all/poolOptions, forwards `new` to mock implementations, requires top-level vi.mock, and no longer resets automocks in restoreAllMocks. Migrate the root config to app/server projects with Rolldown optimizer options, shim the jest-dom matchers on Assertion<R, T>, and update the affected tests (constructor mocks, hoisted mocks, awaited assertions, explicit mock types, per-test mock defaults). * 👷 build: make the openapi knowledge-base FileService mock constructable tests/ was outside the per-area sweep; vitest 5 forwards `new` to the mock implementation, so the arrow implementation threw "is not a constructor". * 👷 build: point the CI blob-report artifacts at the vitest 5 output dir Vitest 5 writes blob reports to .vitest/blob instead of .vitest-reports, so the sharded jobs uploaded nothing and the merge jobs failed with ENOENT. Track the new directory in .gitignore too. * 👷 build: refresh the desktop lockfile for vitest 5 apps/desktop/pnpm-lock.yaml is committed and the desktop build action installs with --frozen-lockfile, so the vitest/override change has to be reflected in it. * 👷 build: keep the pre-vitest-4 coverage exclusions explicit Vitest 4+ ships an empty coverageConfigDefaults.exclude, so configs that spread it silently lost the default exclusions (node_modules, dist, *.config.*) and counted more files. Inline the previous list. * 🎨 style: format the migrated vitest configs * 👷 build: keep canary's new class mocks constructable under vitest 5 exploreGraph/dispatchAuthorization/scopedDevices (canary #19375, #19352) mock classes with arrow implementations; vitest 5 constructs the implementation when the mock is newed, so the arrows throw 'is not a constructor'.
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { LobeChatDatabase } from '@/database/type';
|
|
import { FileService } from '@/server/services/file';
|
|
|
|
import { KnowledgeBaseService } from '../../packages/openapi/src/services/knowledge-base.service';
|
|
|
|
vi.mock('@/server/services/file');
|
|
|
|
describe('KnowledgeBaseService.deleteKnowledgeBase', () => {
|
|
let db: LobeChatDatabase;
|
|
let deleteFilesSpy: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
db = {
|
|
query: {
|
|
knowledgeBases: {
|
|
findFirst: vi.fn().mockResolvedValue({ id: 'kb-1', userId: 'user-1' }),
|
|
},
|
|
},
|
|
} as unknown as LobeChatDatabase;
|
|
|
|
deleteFilesSpy = vi.fn().mockResolvedValue(undefined);
|
|
vi.mocked(FileService).mockImplementation(function () {
|
|
return { deleteFiles: deleteFilesSpy } as any;
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
const createService = () => {
|
|
const service = new KnowledgeBaseService(db, 'user-1');
|
|
|
|
vi.spyOn(service as any, 'log').mockImplementation(() => {});
|
|
vi.spyOn(service as any, 'resolveOperationPermission').mockResolvedValue({
|
|
isPermitted: true,
|
|
message: '',
|
|
});
|
|
|
|
return service;
|
|
};
|
|
|
|
it('should always delete exclusive files together with the knowledge base', async () => {
|
|
const service = createService();
|
|
const deleteWithFilesSpy = vi.fn().mockResolvedValue({
|
|
deletedFiles: [],
|
|
});
|
|
|
|
Reflect.set(service, 'knowledgeBaseModel', {
|
|
deleteWithFiles: deleteWithFilesSpy,
|
|
});
|
|
|
|
await expect(service.deleteKnowledgeBase('kb-1')).resolves.toEqual({
|
|
message: 'Knowledge base deleted successfully',
|
|
success: true,
|
|
});
|
|
|
|
expect(deleteWithFilesSpy).toHaveBeenCalledWith('kb-1');
|
|
});
|
|
|
|
it('should delete external files when deleted knowledge-base files have URLs', async () => {
|
|
const service = createService();
|
|
|
|
Reflect.set(service, 'knowledgeBaseModel', {
|
|
deleteWithFiles: vi.fn().mockResolvedValue({
|
|
deletedFiles: [
|
|
{ id: 'file-1', url: 'https://example.com/a.pdf' },
|
|
{ id: 'file-2', url: null },
|
|
{ id: 'file-3', url: 'https://example.com/b.pdf' },
|
|
],
|
|
}),
|
|
});
|
|
|
|
await service.deleteKnowledgeBase('kb-1');
|
|
|
|
expect(deleteFilesSpy).toHaveBeenCalledWith([
|
|
'https://example.com/a.pdf',
|
|
'https://example.com/b.pdf',
|
|
]);
|
|
});
|
|
|
|
it('should skip external file deletion when deleted files have no URLs', async () => {
|
|
const service = createService();
|
|
|
|
Reflect.set(service, 'knowledgeBaseModel', {
|
|
deleteWithFiles: vi.fn().mockResolvedValue({
|
|
deletedFiles: [{ id: 'file-1', url: null }],
|
|
}),
|
|
});
|
|
|
|
await service.deleteKnowledgeBase('kb-1');
|
|
|
|
expect(deleteFilesSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|