@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.
214 lines (175 loc) • 5.73 kB
text/typescript
import { TRPCError } from '@trpc/server';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fileRouter } from '@/server/routers/lambda/file';
import { AsyncTaskStatus } from '@/types/asyncTask';
// Patch: Use actual router context middleware to inject the correct models/services
function createCallerWithCtx(partialCtx: any = {}) {
// All mocks are spies
const fileModel = {
checkHash: vi.fn().mockResolvedValue({ isExist: true }),
create: vi.fn().mockResolvedValue({ id: 'test-id' }),
findById: vi.fn().mockResolvedValue(undefined),
query: vi.fn().mockResolvedValue([]),
delete: vi.fn().mockResolvedValue(undefined),
deleteMany: vi.fn().mockResolvedValue([]),
clear: vi.fn().mockResolvedValue({} as any),
};
const fileService = {
getFullFileUrl: vi.fn().mockResolvedValue('full-url'),
deleteFile: vi.fn().mockResolvedValue(undefined),
deleteFiles: vi.fn().mockResolvedValue(undefined),
};
const chunkModel = {
countByFileIds: vi.fn().mockResolvedValue([{ id: 'test-id', count: 5 }]),
countByFileId: vi.fn().mockResolvedValue(5),
};
const asyncTaskModel = {
findByIds: vi.fn().mockResolvedValue([
{
id: 'test-task-id',
status: AsyncTaskStatus.Success,
},
]),
findById: vi.fn(),
delete: vi.fn(),
};
const ctx = {
serverDB: {} as any,
userId: 'test-user',
asyncTaskModel,
chunkModel,
fileModel,
fileService,
...partialCtx,
};
return { ctx, caller: fileRouter.createCaller(ctx) };
}
vi.mock('@/config/db', () => ({
serverDBEnv: {
REMOVE_GLOBAL_FILE: false,
},
}));
vi.mock('@/database/models/asyncTask', () => ({
AsyncTaskModel: vi.fn(() => ({
findById: vi.fn(),
findByIds: vi.fn(),
delete: vi.fn(),
})),
}));
vi.mock('@/database/models/chunk', () => ({
ChunkModel: vi.fn(() => ({
countByFileId: vi.fn(),
countByFileIds: vi.fn(),
})),
}));
vi.mock('@/database/models/file', () => ({
FileModel: vi.fn(() => ({
checkHash: vi.fn(),
create: vi.fn(),
delete: vi.fn(),
deleteMany: vi.fn(),
findById: vi.fn(),
query: vi.fn(),
clear: vi.fn(),
})),
}));
vi.mock('@/server/services/file', () => ({
FileService: vi.fn(() => ({
getFullFileUrl: vi.fn(),
deleteFile: vi.fn(),
deleteFiles: vi.fn(),
})),
}));
describe('fileRouter', () => {
let ctx: any;
let caller: any;
let mockFile: any;
beforeEach(() => {
vi.clearAllMocks();
mockFile = {
id: 'test-id',
name: 'test.txt',
url: 'test-url',
createdAt: new Date(),
updatedAt: new Date(),
accessedAt: new Date(),
userId: 'test-user',
size: 100,
fileType: 'text',
metadata: {},
fileHash: null,
clientId: null,
chunkTaskId: null,
embeddingTaskId: null,
};
// Use actual context with default mocks
({ ctx, caller } = createCallerWithCtx());
});
describe('checkFileHash', () => {
it('should handle when fileModel.checkHash returns undefined', async () => {
ctx.fileModel.checkHash.mockResolvedValue(undefined);
await expect(caller.checkFileHash({ hash: 'test-hash' })).resolves.toBeUndefined();
});
});
describe('createFile', () => {
it('should throw if fileModel.checkHash returns undefined', async () => {
ctx.fileModel.checkHash.mockResolvedValue(undefined);
await expect(
caller.createFile({
hash: 'test-hash',
fileType: 'text',
name: 'test.txt',
size: 100,
url: 'test-url',
metadata: {},
}),
).rejects.toThrow();
});
});
describe('findById', () => {
it('should throw error when file not found', async () => {
ctx.fileModel.findById.mockResolvedValue(null);
await expect(caller.findById({ id: 'invalid-id' })).rejects.toThrow(TRPCError);
});
});
describe('getFileItemById', () => {
it('should throw error when file not found', async () => {
ctx.fileModel.findById.mockResolvedValue(null);
await expect(caller.getFileItemById({ id: 'invalid-id' })).rejects.toThrow(TRPCError);
});
});
describe('getFiles', () => {
it('should handle fileModel.query returning undefined', async () => {
ctx.fileModel.query.mockResolvedValue(undefined);
await expect(caller.getFiles({})).rejects.toThrow();
});
});
describe('removeFile', () => {
it('should do nothing when file not found', async () => {
ctx.fileModel.delete.mockResolvedValue(null);
await caller.removeFile({ id: 'invalid-id' });
expect(ctx.fileService.deleteFile).not.toHaveBeenCalled();
});
});
describe('removeFiles', () => {
it('should do nothing when no files found', async () => {
ctx.fileModel.deleteMany.mockResolvedValue([]);
await caller.removeFiles({ ids: ['invalid-1', 'invalid-2'] });
expect(ctx.fileService.deleteFiles).not.toHaveBeenCalled();
});
});
describe('removeFileAsyncTask', () => {
it('should do nothing when file not found', async () => {
ctx.fileModel.findById.mockResolvedValue(null);
await caller.removeFileAsyncTask({ id: 'test-id', type: 'chunk' });
expect(ctx.asyncTaskModel.delete).not.toHaveBeenCalled();
});
it('should do nothing when task id is missing', async () => {
ctx.fileModel.findById.mockResolvedValue(mockFile);
await caller.removeFileAsyncTask({ id: 'test-id', type: 'embedding' });
expect(ctx.asyncTaskModel.delete).not.toHaveBeenCalled();
await caller.removeFileAsyncTask({ id: 'test-id', type: 'chunk' });
expect(ctx.asyncTaskModel.delete).not.toHaveBeenCalled();
});
});
});