mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
🌐 chore: translate non-English comments to English in database-tests (#13771)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f0f2feb015
commit
012214205e
11 changed files with 250 additions and 250 deletions
|
|
@ -69,17 +69,17 @@ describe('ChunkModel', () => {
|
|||
expect(createdChunks[1]).toMatchObject(params[1]);
|
||||
});
|
||||
|
||||
// 测试空参数场景
|
||||
// Test empty params scenario
|
||||
it('should handle empty params array', async () => {
|
||||
const result = await chunkModel.bulkCreate([], '1');
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
// 测试事务回滚
|
||||
// Test transaction rollback
|
||||
it('should rollback transaction on error', async () => {
|
||||
const invalidParams = [
|
||||
{ text: 'Chunk 1', userId },
|
||||
{ index: 'abc', userId }, // 这会导致错误
|
||||
{ index: 'abc', userId }, // This will cause an error
|
||||
] as any;
|
||||
|
||||
await expect(chunkModel.bulkCreate(invalidParams, '1')).rejects.toThrow();
|
||||
|
|
@ -203,7 +203,7 @@ describe('ChunkModel', () => {
|
|||
expect(result[1].id).toBe(chunk2.id);
|
||||
expect(result[0].similarity).toBeGreaterThan(result[1].similarity);
|
||||
});
|
||||
// 补充无文件 ID 的搜索场景
|
||||
// Additional search scenario without file ID
|
||||
it('should perform semantic search without fileIds', async () => {
|
||||
const [chunk1, chunk2] = await serverDB
|
||||
.insert(chunks)
|
||||
|
|
@ -228,7 +228,7 @@ describe('ChunkModel', () => {
|
|||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
// 测试空结果场景
|
||||
// Test empty result scenario
|
||||
it('should return empty array when no matches found', async () => {
|
||||
const result = await chunkModel.semanticSearch({
|
||||
embedding: designThinkingQuery,
|
||||
|
|
@ -524,7 +524,7 @@ content in Table html is below:
|
|||
});
|
||||
|
||||
describe('semanticSearchForChat', () => {
|
||||
// 测试空文件 ID 列表场景
|
||||
// Test empty file ID list scenario
|
||||
it('should return empty array when fileIds is empty', async () => {
|
||||
const result = await chunkModel.semanticSearchForChat({
|
||||
embedding: designThinkingQuery,
|
||||
|
|
@ -535,7 +535,7 @@ content in Table html is below:
|
|||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
// 测试结果限制
|
||||
// Test result limit
|
||||
it('should limit results to 15 items', async () => {
|
||||
const fileId = '1';
|
||||
// Create 24 chunks
|
||||
|
|
|
|||
|
|
@ -631,7 +631,7 @@ describe('FileModel', () => {
|
|||
|
||||
describe('findByNames', () => {
|
||||
it('should find files by names', async () => {
|
||||
// 准备测试数据
|
||||
// Prepare test data
|
||||
const fileList = [
|
||||
{
|
||||
name: 'test1.txt',
|
||||
|
|
@ -658,7 +658,7 @@ describe('FileModel', () => {
|
|||
|
||||
await serverDB.insert(files).values(fileList);
|
||||
|
||||
// 测试查找文件
|
||||
// Test finding files
|
||||
const result = await fileModel.findByNames(['test1', 'test2']);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((f) => f.name)).toContain('test1.txt');
|
||||
|
|
@ -671,7 +671,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should only find files belonging to current user', async () => {
|
||||
// 准备测试数据
|
||||
// Prepare test data
|
||||
await serverDB.insert(files).values([
|
||||
{
|
||||
name: 'test1.txt',
|
||||
|
|
@ -685,7 +685,7 @@ describe('FileModel', () => {
|
|||
url: 'https://example.com/test2.txt',
|
||||
size: 200,
|
||||
fileType: 'text/plain',
|
||||
userId: 'user2', // 不同用户的文件
|
||||
userId: 'user2', // file from a different user
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
@ -697,7 +697,7 @@ describe('FileModel', () => {
|
|||
|
||||
describe('deleteGlobalFile', () => {
|
||||
it('should delete global file by hashId', async () => {
|
||||
// 准备测试数据
|
||||
// Prepare test data
|
||||
const globalFile = {
|
||||
hashId: 'test-hash',
|
||||
fileType: 'text/plain',
|
||||
|
|
@ -709,10 +709,10 @@ describe('FileModel', () => {
|
|||
|
||||
await serverDB.insert(globalFiles).values(globalFile);
|
||||
|
||||
// 执行删除操作
|
||||
// Execute delete operation
|
||||
await fileModel.deleteGlobalFile('test-hash');
|
||||
|
||||
// 验证文件已被删除
|
||||
// Verify file has been deleted
|
||||
const result = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, 'test-hash'),
|
||||
});
|
||||
|
|
@ -720,12 +720,12 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should not throw error when deleting non-existent global file', async () => {
|
||||
// 删除不存在的文件不应抛出错误
|
||||
// Deleting a non-existent file should not throw an error
|
||||
await expect(fileModel.deleteGlobalFile('non-existent-hash')).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should only delete specified global file', async () => {
|
||||
// 准备测试数据
|
||||
// Prepare test data
|
||||
const globalFiles1 = {
|
||||
hashId: 'hash1',
|
||||
fileType: 'text/plain',
|
||||
|
|
@ -743,10 +743,10 @@ describe('FileModel', () => {
|
|||
|
||||
await serverDB.insert(globalFiles).values([globalFiles1, globalFiles2]);
|
||||
|
||||
// 删除一个文件
|
||||
// Delete one file
|
||||
await fileModel.deleteGlobalFile('hash1');
|
||||
|
||||
// 验证只有指定文件被删除
|
||||
// Verify only the specified file was deleted
|
||||
const remainingFiles = await serverDB.query.globalFiles.findMany();
|
||||
expect(remainingFiles).toHaveLength(1);
|
||||
expect(remainingFiles[0].hashId).toBe('hash2');
|
||||
|
|
@ -764,22 +764,22 @@ describe('FileModel', () => {
|
|||
fileHash: 'test-hash-txn',
|
||||
};
|
||||
|
||||
// 在事务中创建文件
|
||||
// Create file in transaction
|
||||
const result = await serverDB.transaction(async (trx) => {
|
||||
const { id } = await fileModel.create(params, true, trx);
|
||||
|
||||
// 在事务内验证文件已创建
|
||||
// Verify file was created inside the transaction
|
||||
const file = await trx.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toMatchObject({ ...params, userId });
|
||||
|
||||
return { id };
|
||||
});
|
||||
|
||||
// 事务提交后,验证文件仍然存在
|
||||
// After transaction commit, verify file still exists
|
||||
const file = await serverDB.query.files.findFirst({ where: eq(files.id, result.id) });
|
||||
expect(file).toMatchObject({ ...params, userId });
|
||||
|
||||
// 验证全局文件也被创建
|
||||
// Verify global file was also created
|
||||
const globalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, params.fileHash),
|
||||
});
|
||||
|
|
@ -797,22 +797,22 @@ describe('FileModel', () => {
|
|||
|
||||
let createdFileId: string | undefined;
|
||||
|
||||
// 故意让事务失败
|
||||
// Intentionally fail the transaction
|
||||
await expect(
|
||||
serverDB.transaction(async (trx) => {
|
||||
const { id } = await fileModel.create(params, true, trx);
|
||||
createdFileId = id;
|
||||
|
||||
// 在事务内验证文件已创建
|
||||
// Verify file was created inside the transaction
|
||||
const file = await trx.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toMatchObject({ ...params, userId });
|
||||
|
||||
// 抛出错误导致事务回滚
|
||||
// Throw an error to cause transaction rollback
|
||||
throw new Error('Intentional rollback');
|
||||
}),
|
||||
).rejects.toThrow('Intentional rollback');
|
||||
|
||||
// 验证文件创建被回滚
|
||||
// Verify file creation was rolled back
|
||||
if (createdFileId) {
|
||||
const file = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, createdFileId),
|
||||
|
|
@ -820,7 +820,7 @@ describe('FileModel', () => {
|
|||
expect(file).toBeUndefined();
|
||||
}
|
||||
|
||||
// 验证全局文件创建也被回滚
|
||||
// Verify global file creation was also rolled back
|
||||
const globalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, params.fileHash),
|
||||
});
|
||||
|
|
@ -839,7 +839,7 @@ describe('FileModel', () => {
|
|||
const result = await serverDB.transaction(async (trx) => {
|
||||
const { id } = await fileModel.create(params, false, trx);
|
||||
|
||||
// 验证知识库文件关联已创建
|
||||
// Verify knowledge base file association was created
|
||||
const kbFile = await trx.query.knowledgeBaseFiles.findFirst({
|
||||
where: eq(knowledgeBaseFiles.fileId, id),
|
||||
});
|
||||
|
|
@ -848,7 +848,7 @@ describe('FileModel', () => {
|
|||
return { id };
|
||||
});
|
||||
|
||||
// 事务提交后验证
|
||||
// Verify after transaction commit
|
||||
const kbFile = await serverDB.query.knowledgeBaseFiles.findFirst({
|
||||
where: eq(knowledgeBaseFiles.fileId, result.id),
|
||||
});
|
||||
|
|
@ -862,7 +862,7 @@ describe('FileModel', () => {
|
|||
|
||||
describe('delete with transaction', () => {
|
||||
it('should delete file within provided transaction', async () => {
|
||||
// 先创建文件和全局文件
|
||||
// First create the file and global file
|
||||
await fileModel.createGlobalFile({
|
||||
hashId: 'delete-txn-hash',
|
||||
url: 'https://example.com/delete-txn.txt',
|
||||
|
|
@ -879,20 +879,20 @@ describe('FileModel', () => {
|
|||
fileHash: 'delete-txn-hash',
|
||||
});
|
||||
|
||||
// 在事务中删除文件
|
||||
// Delete file in transaction
|
||||
await serverDB.transaction(async (trx) => {
|
||||
await fileModel.delete(id, true, trx);
|
||||
|
||||
// 在事务内验证文件已删除
|
||||
// Verify file was deleted inside the transaction
|
||||
const file = await trx.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toBeUndefined();
|
||||
});
|
||||
|
||||
// 事务提交后验证文件仍然被删除
|
||||
// After transaction commit, verify file is still deleted
|
||||
const file = await serverDB.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toBeUndefined();
|
||||
|
||||
// 验证全局文件也被删除(因为没有其他引用)
|
||||
// Verify global file was also deleted (no other references)
|
||||
const globalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, 'delete-txn-hash'),
|
||||
});
|
||||
|
|
@ -900,7 +900,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should rollback file deletion when transaction fails', async () => {
|
||||
// 先创建文件和全局文件
|
||||
// First create the file and global file
|
||||
await fileModel.createGlobalFile({
|
||||
hashId: 'rollback-delete-hash',
|
||||
url: 'https://example.com/rollback-delete.txt',
|
||||
|
|
@ -917,26 +917,26 @@ describe('FileModel', () => {
|
|||
fileHash: 'rollback-delete-hash',
|
||||
});
|
||||
|
||||
// 故意让事务失败
|
||||
// Intentionally fail the transaction
|
||||
await expect(
|
||||
serverDB.transaction(async (trx) => {
|
||||
await fileModel.delete(id, true, trx);
|
||||
|
||||
// 在事务内验证文件已删除
|
||||
// Verify file was deleted inside the transaction
|
||||
const file = await trx.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toBeUndefined();
|
||||
|
||||
// 抛出错误导致事务回滚
|
||||
// Throw an error to cause transaction rollback
|
||||
throw new Error('Intentional rollback for delete');
|
||||
}),
|
||||
).rejects.toThrow('Intentional rollback for delete');
|
||||
|
||||
// 验证文件删除被回滚,文件仍然存在
|
||||
// Verify file deletion was rolled back, file still exists
|
||||
const file = await serverDB.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toBeDefined();
|
||||
expect(file?.name).toBe('rollback-delete-file.txt');
|
||||
|
||||
// 验证全局文件也被回滚,仍然存在
|
||||
// Verify global file was also rolled back, still exists
|
||||
const globalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, 'rollback-delete-hash'),
|
||||
});
|
||||
|
|
@ -944,7 +944,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should delete file but preserve global file when removeGlobalFile=false in transaction', async () => {
|
||||
// 先创建文件和全局文件
|
||||
// First create the file and global file
|
||||
await fileModel.createGlobalFile({
|
||||
hashId: 'preserve-global-hash',
|
||||
url: 'https://example.com/preserve-global.txt',
|
||||
|
|
@ -961,16 +961,16 @@ describe('FileModel', () => {
|
|||
fileHash: 'preserve-global-hash',
|
||||
});
|
||||
|
||||
// 在事务中删除文件,但不删除全局文件
|
||||
// Delete file in transaction, but keep global file
|
||||
await serverDB.transaction(async (trx) => {
|
||||
await fileModel.delete(id, false, trx);
|
||||
});
|
||||
|
||||
// 验证文件被删除
|
||||
// Verify file was deleted
|
||||
const file = await serverDB.query.files.findFirst({ where: eq(files.id, id) });
|
||||
expect(file).toBeUndefined();
|
||||
|
||||
// 验证全局文件被保留
|
||||
// Verify global file was retained
|
||||
const globalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, 'preserve-global-hash'),
|
||||
});
|
||||
|
|
@ -980,7 +980,7 @@ describe('FileModel', () => {
|
|||
|
||||
describe('mixed operations in transaction', () => {
|
||||
it('should support create and delete operations in same transaction', async () => {
|
||||
// 先创建一个要删除的文件
|
||||
// First create a file to be deleted
|
||||
await fileModel.createGlobalFile({
|
||||
hashId: 'mixed-delete-hash',
|
||||
url: 'https://example.com/mixed-delete.txt',
|
||||
|
|
@ -997,12 +997,12 @@ describe('FileModel', () => {
|
|||
fileHash: 'mixed-delete-hash',
|
||||
});
|
||||
|
||||
// 在同一个事务中删除旧文件并创建新文件
|
||||
// Delete old file and create new file in the same transaction
|
||||
const result = await serverDB.transaction(async (trx) => {
|
||||
// 删除旧文件
|
||||
// Delete old file
|
||||
await fileModel.delete(deleteFileId, true, trx);
|
||||
|
||||
// 创建新文件
|
||||
// Create new file
|
||||
const { id: newFileId } = await fileModel.create(
|
||||
{
|
||||
name: 'mixed-create-file.txt',
|
||||
|
|
@ -1018,20 +1018,20 @@ describe('FileModel', () => {
|
|||
return { newFileId };
|
||||
});
|
||||
|
||||
// 验证旧文件被删除
|
||||
// Verify old file was deleted
|
||||
const deletedFile = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, deleteFileId),
|
||||
});
|
||||
expect(deletedFile).toBeUndefined();
|
||||
|
||||
// 验证新文件被创建
|
||||
// Verify new file was created
|
||||
const newFile = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, result.newFileId),
|
||||
});
|
||||
expect(newFile).toBeDefined();
|
||||
expect(newFile?.name).toBe('mixed-create-file.txt');
|
||||
|
||||
// 验证新的全局文件被创建
|
||||
// Verify new global file was created
|
||||
const newGlobalFile = await serverDB.query.globalFiles.findFirst({
|
||||
where: eq(globalFiles.hashId, 'mixed-create-hash'),
|
||||
});
|
||||
|
|
@ -1152,7 +1152,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should delete file even when chunks deletion fails', async () => {
|
||||
// 创建测试文件
|
||||
// Create test file
|
||||
const testFile = {
|
||||
name: 'error-test-file.txt',
|
||||
url: 'https://example.com/error-test-file.txt',
|
||||
|
|
@ -1163,52 +1163,52 @@ describe('FileModel', () => {
|
|||
|
||||
const { id: fileId } = await fileModel.create(testFile, true);
|
||||
|
||||
// 创建一些测试数据来模拟chunks关联
|
||||
// Create some test data to simulate chunk associations
|
||||
const chunkId1 = '550e8400-e29b-41d4-a716-446655440001';
|
||||
const chunkId2 = '550e8400-e29b-41d4-a716-446655440002';
|
||||
|
||||
// 插入chunks
|
||||
// Insert chunks
|
||||
await serverDB.insert(chunks).values([
|
||||
{ id: chunkId1, text: 'chunk 1', userId, type: 'text' },
|
||||
{ id: chunkId2, text: 'chunk 2', userId, type: 'text' },
|
||||
]);
|
||||
|
||||
// 插入fileChunks关联
|
||||
// Insert fileChunks associations
|
||||
await serverDB.insert(fileChunks).values([
|
||||
{ fileId, chunkId: chunkId1, userId },
|
||||
{ fileId, chunkId: chunkId2, userId },
|
||||
]);
|
||||
|
||||
// 插入embeddings (1024维向量)
|
||||
// Insert embeddings (1024-dimensional vectors)
|
||||
const testEmbedding = Array.from({ length: 1024 }).fill(0.1) as number[];
|
||||
await serverDB
|
||||
.insert(embeddings)
|
||||
.values([{ chunkId: chunkId1, embeddings: testEmbedding, model: 'test-model', userId }]);
|
||||
|
||||
// 跳过 documentChunks 测试,因为需要先创建 documents 记录
|
||||
// Skip documentChunks test, requires creating documents records first
|
||||
|
||||
// 删除文件,应该会清理所有相关数据
|
||||
// Delete file, should clean up all related data
|
||||
const result = await fileModel.delete(fileId, true);
|
||||
|
||||
// 验证文件被删除
|
||||
// Verify file was deleted
|
||||
const deletedFile = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, fileId),
|
||||
});
|
||||
expect(deletedFile).toBeUndefined();
|
||||
|
||||
// 验证chunks被删除
|
||||
// Verify chunks were deleted
|
||||
const remainingChunks = await serverDB.query.chunks.findMany({
|
||||
where: inArray(chunks.id, [chunkId1, chunkId2]),
|
||||
});
|
||||
expect(remainingChunks).toHaveLength(0);
|
||||
|
||||
// 验证embeddings被删除
|
||||
// Verify embeddings were deleted
|
||||
const remainingEmbeddings = await serverDB.query.embeddings.findMany({
|
||||
where: inArray(embeddings.chunkId, [chunkId1, chunkId2]),
|
||||
});
|
||||
expect(remainingEmbeddings).toHaveLength(0);
|
||||
|
||||
// 验证fileChunks被删除
|
||||
// Verify fileChunks were deleted
|
||||
const remainingFileChunks = await serverDB.query.fileChunks.findMany({
|
||||
where: eq(fileChunks.fileId, fileId),
|
||||
});
|
||||
|
|
@ -1218,7 +1218,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should successfully delete file with all related chunks and embeddings', async () => {
|
||||
// 简化测试:只验证正常的完整删除流程(移除知识库保护后)
|
||||
// Simplified test: only verify the normal full deletion flow (after removing knowledge base protection)
|
||||
const testFile = {
|
||||
name: 'complete-deletion-test.txt',
|
||||
url: 'https://example.com/complete-deletion-test.txt',
|
||||
|
|
@ -1231,42 +1231,42 @@ describe('FileModel', () => {
|
|||
|
||||
const chunkId = '550e8400-e29b-41d4-a716-446655440003';
|
||||
|
||||
// 插入chunk
|
||||
// Insert chunk
|
||||
await serverDB
|
||||
.insert(chunks)
|
||||
.values([{ id: chunkId, text: 'complete test chunk', userId, type: 'text' }]);
|
||||
|
||||
// 插入fileChunks关联
|
||||
// Insert fileChunks associations
|
||||
await serverDB.insert(fileChunks).values([{ fileId, chunkId, userId }]);
|
||||
|
||||
// 插入embeddings
|
||||
// Insert embeddings
|
||||
const testEmbedding = Array.from({ length: 1024 }).fill(0.1) as number[];
|
||||
await serverDB
|
||||
.insert(embeddings)
|
||||
.values([{ chunkId, embeddings: testEmbedding, model: 'test-model', userId }]);
|
||||
|
||||
// 删除文件
|
||||
// Delete file
|
||||
await fileModel.delete(fileId, true);
|
||||
|
||||
// 验证文件被删除
|
||||
// Verify file was deleted
|
||||
const deletedFile = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, fileId),
|
||||
});
|
||||
expect(deletedFile).toBeUndefined();
|
||||
|
||||
// 验证chunks被删除
|
||||
// Verify chunks were deleted
|
||||
const remainingChunks = await serverDB.query.chunks.findMany({
|
||||
where: eq(chunks.id, chunkId),
|
||||
});
|
||||
expect(remainingChunks).toHaveLength(0);
|
||||
|
||||
// 验证embeddings被删除
|
||||
// Verify embeddings were deleted
|
||||
const remainingEmbeddings = await serverDB.query.embeddings.findMany({
|
||||
where: eq(embeddings.chunkId, chunkId),
|
||||
});
|
||||
expect(remainingEmbeddings).toHaveLength(0);
|
||||
|
||||
// 验证fileChunks被删除
|
||||
// Verify fileChunks were deleted
|
||||
const remainingFileChunks = await serverDB.query.fileChunks.findMany({
|
||||
where: eq(fileChunks.fileId, fileId),
|
||||
});
|
||||
|
|
@ -1274,7 +1274,7 @@ describe('FileModel', () => {
|
|||
});
|
||||
|
||||
it('should delete files that are in knowledge bases (removed protection)', async () => {
|
||||
// 测试修复后的逻辑:知识库中的文件也应该被删除
|
||||
// Test the fixed logic: files in knowledge bases should also be deleted
|
||||
const testFile = {
|
||||
name: 'knowledge-base-file.txt',
|
||||
url: 'https://example.com/knowledge-base-file.txt',
|
||||
|
|
@ -1288,47 +1288,47 @@ describe('FileModel', () => {
|
|||
|
||||
const chunkId = '550e8400-e29b-41d4-a716-446655440007';
|
||||
|
||||
// 插入chunk和关联数据
|
||||
// Insert chunk and association data
|
||||
await serverDB
|
||||
.insert(chunks)
|
||||
.values([{ id: chunkId, text: 'knowledge base chunk', userId, type: 'text' }]);
|
||||
|
||||
await serverDB.insert(fileChunks).values([{ fileId, chunkId, userId }]);
|
||||
|
||||
// 插入embeddings (1024维向量)
|
||||
// Insert embeddings (1024-dimensional vectors)
|
||||
const testEmbedding = Array.from({ length: 1024 }).fill(0.1) as number[];
|
||||
await serverDB
|
||||
.insert(embeddings)
|
||||
.values([{ chunkId, embeddings: testEmbedding, model: 'test-model', userId }]);
|
||||
|
||||
// 验证文件确实在知识库中
|
||||
// Verify file is indeed in the knowledge base
|
||||
const kbFile = await serverDB.query.knowledgeBaseFiles.findFirst({
|
||||
where: eq(knowledgeBaseFiles.fileId, fileId),
|
||||
});
|
||||
expect(kbFile).toBeDefined();
|
||||
|
||||
// 删除文件
|
||||
// Delete file
|
||||
await fileModel.delete(fileId, true);
|
||||
|
||||
// 验证知识库中的文件也被完全删除
|
||||
// Verify files in knowledge base were also completely deleted
|
||||
const deletedFile = await serverDB.query.files.findFirst({
|
||||
where: eq(files.id, fileId),
|
||||
});
|
||||
expect(deletedFile).toBeUndefined();
|
||||
|
||||
// 验证chunks被删除(这是修复的核心:之前知识库文件的chunks不会被删除)
|
||||
// Verify chunks were deleted (this is the core of the fix: previously chunks of knowledge base files would not be deleted)
|
||||
const remainingChunks = await serverDB.query.chunks.findMany({
|
||||
where: eq(chunks.id, chunkId),
|
||||
});
|
||||
expect(remainingChunks).toHaveLength(0);
|
||||
|
||||
// 验证embeddings被删除
|
||||
// Verify embeddings were deleted
|
||||
const remainingEmbeddings = await serverDB.query.embeddings.findMany({
|
||||
where: eq(embeddings.chunkId, chunkId),
|
||||
});
|
||||
expect(remainingEmbeddings).toHaveLength(0);
|
||||
|
||||
// 验证fileChunks被删除
|
||||
// Verify fileChunks were deleted
|
||||
const remainingFileChunks = await serverDB.query.fileChunks.findMany({
|
||||
where: eq(fileChunks.fileId, fileId),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@ const sessionModel = new SessionModel(serverDB, userId);
|
|||
|
||||
beforeEach(async () => {
|
||||
await serverDB.delete(users);
|
||||
// 并创建初始用户
|
||||
// and create the initial user
|
||||
await serverDB.insert(users).values({ id: userId });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// 在每个测试用例之后, 清空用户表 (应该会自动级联删除所有数据)
|
||||
// After each test case, clear the users table (should auto-cascade delete all data)
|
||||
await serverDB.delete(users);
|
||||
});
|
||||
|
||||
describe('SessionModel', () => {
|
||||
describe('query', () => {
|
||||
it('should query sessions by user ID', async () => {
|
||||
// 创建一些测试数据
|
||||
// Create some test data
|
||||
await serverDB.insert(users).values([{ id: '456' }]);
|
||||
|
||||
await serverDB.insert(sessions).values([
|
||||
|
|
@ -46,10 +46,10 @@ describe('SessionModel', () => {
|
|||
{ id: '3', userId: '456', updatedAt: new Date('2023-03-01') },
|
||||
]);
|
||||
|
||||
// 调用 query 方法
|
||||
// Call the query method
|
||||
const result = await sessionModel.query();
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].id).toBe('2');
|
||||
expect(result[1].id).toBe('1');
|
||||
|
|
@ -76,7 +76,7 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('queryWithGroups', () => {
|
||||
it('should return sessions grouped by group', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.transaction(async (trx) => {
|
||||
await trx.insert(users).values([{ id: '456' }]);
|
||||
await trx.insert(sessionGroups).values([
|
||||
|
|
@ -94,10 +94,10 @@ describe('SessionModel', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
// 调用 queryWithGroups 方法
|
||||
// Call the queryWithGroups method
|
||||
const result = await sessionModel.queryWithGroups();
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result.sessions).toHaveLength(6);
|
||||
expect(result.sessionGroups).toHaveLength(2);
|
||||
expect(result.sessionGroups[0].id).toBe('group1');
|
||||
|
|
@ -107,10 +107,10 @@ describe('SessionModel', () => {
|
|||
});
|
||||
|
||||
it('should return empty groups if no sessions', async () => {
|
||||
// 调用 queryWithGroups 方法
|
||||
// Call the queryWithGroups method
|
||||
const result = await sessionModel.queryWithGroups();
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result.sessions).toHaveLength(0);
|
||||
expect(result.sessionGroups).toHaveLength(0);
|
||||
});
|
||||
|
|
@ -236,7 +236,7 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('count', () => {
|
||||
it('should return the count of sessions for the user', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(users).values([{ id: '456' }]);
|
||||
await serverDB.insert(sessions).values([
|
||||
{ id: '1', userId },
|
||||
|
|
@ -244,22 +244,22 @@ describe('SessionModel', () => {
|
|||
{ id: '3', userId: '456' },
|
||||
]);
|
||||
|
||||
// 调用 count 方法
|
||||
// Call the count method
|
||||
const result = await sessionModel.count();
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
|
||||
it('should return 0 if no sessions exist for the user', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(users).values([{ id: '456' }]);
|
||||
await serverDB.insert(sessions).values([{ id: '3', userId: '456' }]);
|
||||
|
||||
// 调用 count 方法
|
||||
// Call the count method
|
||||
const result = await sessionModel.count();
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
|
|
@ -366,7 +366,7 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('create', () => {
|
||||
it('should create a new session', async () => {
|
||||
// 调用 create 方法
|
||||
// Call the create method
|
||||
const result = await sessionModel.create({
|
||||
type: 'agent',
|
||||
session: {
|
||||
|
|
@ -375,7 +375,7 @@ describe('SessionModel', () => {
|
|||
config: { model: 'gpt-3.5-turbo' },
|
||||
});
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
const sessionId = result.id;
|
||||
expect(sessionId).toBeDefined();
|
||||
expect(sessionId.startsWith('ssn_')).toBeTruthy();
|
||||
|
|
@ -390,7 +390,7 @@ describe('SessionModel', () => {
|
|||
});
|
||||
|
||||
it('should create a new session with custom ID', async () => {
|
||||
// 调用 create 方法,传入自定义 ID
|
||||
// Call the create method with a custom ID
|
||||
const customId = 'custom-id';
|
||||
const result = await sessionModel.create({
|
||||
type: 'agent',
|
||||
|
|
@ -399,7 +399,7 @@ describe('SessionModel', () => {
|
|||
id: customId,
|
||||
});
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result.id).toBe(customId);
|
||||
});
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('batchCreate', () => {
|
||||
it('should batch create sessions', async () => {
|
||||
// 调用 batchCreate 方法
|
||||
// Call the batchCreate method
|
||||
const sessions: NewSession[] = [
|
||||
{
|
||||
id: '1',
|
||||
|
|
@ -490,13 +490,13 @@ describe('SessionModel', () => {
|
|||
];
|
||||
const result = await sessionModel.batchCreate(sessions);
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
// pglite return affectedRows while postgres return rowCount
|
||||
expect((result as any).affectedRows || result.rowCount).toEqual(2);
|
||||
});
|
||||
|
||||
it.skip('should set group to default if group does not exist', async () => {
|
||||
// 调用 batchCreate 方法,传入不存在的 group
|
||||
// Call the batchCreate method with a non-existent group
|
||||
const sessions: NewSession[] = [
|
||||
{
|
||||
id: '1',
|
||||
|
|
@ -509,14 +509,14 @@ describe('SessionModel', () => {
|
|||
];
|
||||
const result = await sessionModel.batchCreate(sessions);
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
// expect(result[0].group).toBe('default');
|
||||
});
|
||||
});
|
||||
|
||||
describe('duplicate', () => {
|
||||
it('should duplicate a session', async () => {
|
||||
// 创建一个用户和一个 session
|
||||
// Create a user and a session
|
||||
await serverDB.transaction(async (trx) => {
|
||||
await trx
|
||||
.insert(sessions)
|
||||
|
|
@ -525,10 +525,10 @@ describe('SessionModel', () => {
|
|||
await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1', userId });
|
||||
});
|
||||
|
||||
// 调用 duplicate 方法
|
||||
// Call the duplicate method
|
||||
const result = (await sessionModel.duplicate('1', 'Duplicated Session')) as SessionItem;
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result.id).not.toBe('1');
|
||||
expect(result.userId).toBe(userId);
|
||||
expect(result.type).toBe('agent');
|
||||
|
|
@ -542,34 +542,34 @@ describe('SessionModel', () => {
|
|||
});
|
||||
|
||||
it('should return undefined if session does not exist', async () => {
|
||||
// 调用 duplicate 方法,传入不存在的 session ID
|
||||
// Call the duplicate method with a non-existent session ID
|
||||
const result = await sessionModel.duplicate('non-existent-id');
|
||||
|
||||
// 断言结果
|
||||
// Assert results
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update a session', async () => {
|
||||
// 创建一个测试 session
|
||||
// Create a test session
|
||||
const sessionId = '123';
|
||||
await serverDB.insert(sessions).values({ userId, id: sessionId, title: 'Test Session' });
|
||||
|
||||
// 调用 update 方法更新 session
|
||||
// Call the update method to update the session
|
||||
const updatedSessions = await sessionModel.update(sessionId, {
|
||||
title: 'Updated Test Session',
|
||||
description: 'This is an updated test session',
|
||||
});
|
||||
|
||||
// 断言更新后的结果
|
||||
// Assert the updated results
|
||||
expect(updatedSessions).toHaveLength(1);
|
||||
expect(updatedSessions[0].title).toBe('Updated Test Session');
|
||||
expect(updatedSessions[0].description).toBe('This is an updated test session');
|
||||
});
|
||||
|
||||
it('should not update a session if user ID does not match', async () => {
|
||||
// 创建一个测试 session,但使用不同的 user ID
|
||||
// Create a test session with a different user ID
|
||||
await serverDB.insert(users).values([{ id: '777' }]);
|
||||
|
||||
const sessionId = '123';
|
||||
|
|
@ -578,7 +578,7 @@ describe('SessionModel', () => {
|
|||
.insert(sessions)
|
||||
.values({ userId: '777', id: sessionId, title: 'Test Session' });
|
||||
|
||||
// 尝试更新这个 session,应该不会有任何更新
|
||||
// Attempt to update this session — should produce no updates
|
||||
const updatedSessions = await sessionModel.update(sessionId, {
|
||||
title: 'Updated Test Session',
|
||||
});
|
||||
|
|
@ -589,26 +589,26 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('delete', () => {
|
||||
it('should handle deleting a session with no associated messages or topics', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(sessions).values({ id: '1', userId });
|
||||
|
||||
// 调用 delete 方法
|
||||
// Call the delete method
|
||||
await sessionModel.delete('1');
|
||||
|
||||
// 断言删除结果
|
||||
// Assert deletion results
|
||||
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle concurrent deletions gracefully', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(sessions).values({ id: '1', userId });
|
||||
|
||||
// 并发调用 delete 方法
|
||||
// Concurrently call the delete method
|
||||
await Promise.all([sessionModel.delete('1'), sessionModel.delete('1')]);
|
||||
|
||||
// 断言删除结果
|
||||
// Assert deletion results
|
||||
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
|
|
@ -673,35 +673,35 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('batchDelete', () => {
|
||||
it('should handle deleting sessions with no associated messages or topics', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(sessions).values([
|
||||
{ id: '1', userId },
|
||||
{ id: '2', userId },
|
||||
]);
|
||||
|
||||
// 调用 batchDelete 方法
|
||||
// Call the batchDelete method
|
||||
await sessionModel.batchDelete(['1', '2']);
|
||||
|
||||
// 断言删除结果
|
||||
// Assert deletion results
|
||||
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle concurrent batch deletions gracefully', async () => {
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.insert(sessions).values([
|
||||
{ id: '1', userId },
|
||||
{ id: '2', userId },
|
||||
]);
|
||||
|
||||
// 并发调用 batchDelete 方法
|
||||
// Concurrently call the batchDelete method
|
||||
await Promise.all([
|
||||
sessionModel.batchDelete(['1', '2']),
|
||||
sessionModel.batchDelete(['1', '2']),
|
||||
]);
|
||||
|
||||
// 断言删除结果
|
||||
// Assert deletion results
|
||||
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
|
|
@ -1519,7 +1519,7 @@ describe('SessionModel', () => {
|
|||
|
||||
describe('findSessionsByKeywords', () => {
|
||||
it('should handle errors gracefully and return empty array', async () => {
|
||||
// 这个测试旨在覆盖 findSessionsByKeywords 中的错误处理逻辑
|
||||
// This test aims to cover the error-handling logic in findSessionsByKeywords
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
// Mock the database query to throw an error
|
||||
|
|
@ -1528,7 +1528,7 @@ describe('SessionModel', () => {
|
|||
|
||||
const result = await sessionModel.findSessionsByKeywords({ keyword: 'test' });
|
||||
|
||||
// 即使发生错误,方法也应该返回一个空数组
|
||||
// Even when an error occurs, the method should return an empty array
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result).toEqual([]);
|
||||
expect(consoleSpy).toHaveBeenCalledWith('findSessionsByKeywords error:', expect.any(Error), {
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ describe('AgentEvalBenchmarkModel', () => {
|
|||
it('should order by createdAt descending', async () => {
|
||||
const results = await benchmarkModel.query(true);
|
||||
|
||||
// 最新的应该在前面
|
||||
// The newest should come first
|
||||
// Order may vary in PGlite due to timing
|
||||
expect(results.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ describe('AgentEvalDatasetModel', () => {
|
|||
it('should order by createdAt descending', async () => {
|
||||
const results = await datasetModel.query();
|
||||
|
||||
// 最新的应该在前面
|
||||
// The newest should come first
|
||||
// Order may vary, just check we got results
|
||||
expect(results.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ describe('AiInfraRepos', () => {
|
|||
expect(merged.settings).toEqual({ searchImpl: 'params' });
|
||||
});
|
||||
|
||||
// 测试场景:用户模型 abilitie 为空(Empty),而基础模型有搜索能力和设置
|
||||
// Test scenario: user model abilities is empty (Empty) while the base model has search capability and settings
|
||||
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
||||
const providerId = 'openai';
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { search: false }, // 使用 builtin abilities
|
||||
abilities: { search: false }, // Use builtin abilities
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
},
|
||||
];
|
||||
|
|
@ -297,9 +297,9 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 使用 builtin abilities
|
||||
// Use builtin abilities
|
||||
expect(merged?.abilities?.search).toEqual(false);
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { search: true }, // 使用 builtin abilities
|
||||
abilities: { search: true }, // Use builtin abilities
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
},
|
||||
];
|
||||
|
|
@ -332,13 +332,13 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 使用 builtin abilities
|
||||
// Use builtin abilities
|
||||
expect(merged?.abilities?.search).toEqual(true);
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型有搜索能力和设置
|
||||
// Test scenario: user model has search disabled (abilities.search is undefined) while the base model has search capability and settings
|
||||
it('should retain builtin settings when user model has no abilities (empty) and builtin has settings', async () => {
|
||||
const providerId = 'openai';
|
||||
|
||||
|
|
@ -347,7 +347,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { vision: true }, // 启用 vision 能力, no search
|
||||
abilities: { vision: true }, // Enable vision ability, no search
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -368,9 +368,9 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// abilities.search 会被 merge 为 false,此处和 getEnabledAiModel 不同
|
||||
// abilities.search will be merged as false, differs from getEnabledAiModel
|
||||
expect(merged?.abilities?.search).toEqual(false);
|
||||
// 删去 builtin settings
|
||||
// Remove builtin settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { vision: true }, // 启用 vision 能力, no search
|
||||
abilities: { vision: true }, // Enable vision ability, no search
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { search: true }, // builtin abilities 会被 merge
|
||||
abilities: { search: true }, // builtin abilities will be merged
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
},
|
||||
];
|
||||
|
|
@ -403,13 +403,13 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// abilities.search 会被 merge 为 true,此处和 getEnabledAiModel 不同
|
||||
// abilities.search will be merged as true, differs from getEnabledAiModel
|
||||
expect(merged?.abilities?.search).toEqual(true);
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
// 测试:用户模型无 abilities.search(undefined),保留 builtin settings(mergeArrayById 优先用户,但用户无则 builtin)
|
||||
// Test: user model has no abilities.search (undefined), retains builtin settings (mergeArrayById prefers user, falls back to builtin when absent)
|
||||
it('should retain builtin settings when user model has no abilities.search (undefined) and builtin has settings', async () => {
|
||||
const providerId = 'openai';
|
||||
|
||||
|
|
@ -418,7 +418,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: {}, // 无 search
|
||||
abilities: {}, // no search
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -440,7 +440,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities?.search).toBeUndefined();
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
|
|
@ -452,7 +452,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: {}, // 无 search
|
||||
abilities: {}, // no search
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
// 无 settings
|
||||
// no settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -473,11 +473,11 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities?.search).toBeUndefined();
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
// 测试:用户模型有 abilities.search: true
|
||||
// Test: user model has abilities.search: true
|
||||
it('should inject defaults when user has search: true, no existing settings (builtin none)', async () => {
|
||||
const providerId = 'openai';
|
||||
|
||||
|
|
@ -486,7 +486,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { search: true }, // 用户启用
|
||||
abilities: { search: true }, // user-enabled
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -495,7 +495,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
// 无 settings
|
||||
// no settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -507,7 +507,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: true });
|
||||
// 注入 defaults
|
||||
// Inject defaults
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
||||
});
|
||||
|
||||
|
|
@ -540,11 +540,11 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: true });
|
||||
// 使用 builtin settings
|
||||
// Use builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'tool' });
|
||||
});
|
||||
|
||||
// 测试:用户模型有 abilities.search: false
|
||||
// Test: user model has abilities.search: false
|
||||
it('should remove settings when user has search: false and builtin has settings', async () => {
|
||||
const providerId = 'openai';
|
||||
|
||||
|
|
@ -553,7 +553,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { search: false }, // 用户禁用
|
||||
abilities: { search: false }, // user-disabled
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -574,7 +574,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: false });
|
||||
// 移除 search 相关,保留其他
|
||||
// Remove search-related settings, retain others
|
||||
expect(merged?.settings).toEqual({ extendParams: [] });
|
||||
});
|
||||
|
||||
|
|
@ -595,7 +595,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
// 无 settings
|
||||
// no settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -607,7 +607,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: false });
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -640,7 +640,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 应该使用用户的 settings
|
||||
// Should use user settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'user-provider' });
|
||||
});
|
||||
|
||||
|
|
@ -653,7 +653,7 @@ describe('AiInfraRepos', () => {
|
|||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { vision: true },
|
||||
// 用户未设置 settings
|
||||
// user has not set settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -673,7 +673,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 应该使用内置的 settings
|
||||
// Should use builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'tool', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
|
|
@ -686,7 +686,7 @@ describe('AiInfraRepos', () => {
|
|||
type: 'chat',
|
||||
enabled: true,
|
||||
abilities: { vision: true },
|
||||
// 用户未设置 settings
|
||||
// user has not set settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -695,7 +695,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
type: 'chat',
|
||||
enabled: true,
|
||||
// 内置也无 settings
|
||||
// builtin also has no settings
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -706,7 +706,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ describe('AiInfraRepos', () => {
|
|||
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
||||
});
|
||||
|
||||
// 测试场景:用户模型 abilitie 为空(Empty),而基础模型有搜索能力和设置
|
||||
// Test scenario: user model abilities is empty (Empty) while the base model has search capability and settings
|
||||
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
||||
const mockProviders = [
|
||||
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
||||
|
|
@ -346,7 +346,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: { search: false }, // 使用 builtin abilities
|
||||
abilities: { search: false }, // Use builtin abilities
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
};
|
||||
|
||||
|
|
@ -358,9 +358,9 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 使用 builtin abilities
|
||||
// Use builtin abilities
|
||||
expect(merged?.abilities?.search).toEqual(false);
|
||||
// 删去 builtin settings
|
||||
// Remove builtin settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -381,7 +381,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: { search: true }, // 使用 builtin abilities
|
||||
abilities: { search: true }, // Use builtin abilities
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
};
|
||||
|
||||
|
|
@ -393,13 +393,13 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 使用 builtin abilities
|
||||
// Use builtin abilities
|
||||
expect(merged?.abilities?.search).toEqual(true);
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型有搜索能力和设置
|
||||
// Test scenario: user model has search disabled (abilities.search is undefined) while the base model has search capability and settings
|
||||
it('should retain builtin settings when user model has no abilities.search (undefined) and builtin has settings', async () => {
|
||||
const mockProviders = [
|
||||
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
||||
|
|
@ -410,14 +410,14 @@ describe('AiInfraRepos', () => {
|
|||
providerId: 'openai',
|
||||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { vision: true }, // 启用 vision 能力, no search
|
||||
abilities: { vision: true }, // Enable vision ability, no search
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: { search: false }, // builtin abilities 不生效
|
||||
abilities: { search: false }, // builtin abilities have no effect
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
};
|
||||
|
||||
|
|
@ -429,9 +429,9 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// abilities.search 仍 undefined(兼容老版本)
|
||||
// abilities.search remains undefined (backward compatible)
|
||||
expect(merged?.abilities?.search).toBeUndefined();
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
|
|
@ -445,14 +445,14 @@ describe('AiInfraRepos', () => {
|
|||
providerId: 'openai',
|
||||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { vision: true }, // 启用 vision 能力, no search
|
||||
abilities: { vision: true }, // Enable vision ability, no search
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: { search: true }, // builtin abilities 不生效
|
||||
abilities: { search: true }, // builtin abilities have no effect
|
||||
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
||||
};
|
||||
|
||||
|
|
@ -464,13 +464,13 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// abilities.search 仍 undefined(兼容老版本)
|
||||
// abilities.search remains undefined (backward compatible)
|
||||
expect(merged?.abilities?.search).toBeUndefined();
|
||||
// 保留 builtin settings
|
||||
// Retain builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型也无搜索能力和设置
|
||||
// Test scenario: user model has search disabled (abilities.search is undefined) and the base model also has no search capability or settings
|
||||
it('should retain no settings when user model has no abilities.search (undefined) and builtin has no settings', async () => {
|
||||
const mockProviders = [
|
||||
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
||||
|
|
@ -481,7 +481,7 @@ describe('AiInfraRepos', () => {
|
|||
providerId: 'openai',
|
||||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: {}, // 无 search
|
||||
abilities: {}, // no search
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
|
|
@ -489,7 +489,7 @@ describe('AiInfraRepos', () => {
|
|||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: {},
|
||||
// builtin 无 settings
|
||||
// builtin has no settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -501,11 +501,11 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities?.search).toBeUndefined();
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
// 测试:用户模型有 abilities.search: true
|
||||
// Test: user model has abilities.search: true
|
||||
it('should inject defaults when user has search: true, no existing settings (builtin none)', async () => {
|
||||
const mockProviders = [
|
||||
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
||||
|
|
@ -516,7 +516,7 @@ describe('AiInfraRepos', () => {
|
|||
providerId: 'openai',
|
||||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { search: true }, // 用户启用 search
|
||||
abilities: { search: true }, // user-enabled search
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
|
|
@ -524,7 +524,7 @@ describe('AiInfraRepos', () => {
|
|||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
abilities: {},
|
||||
// 无 settings
|
||||
// no settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -536,7 +536,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: true });
|
||||
// 注入 defaults (openai: params)
|
||||
// Inject defaults (openai: params)
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
||||
});
|
||||
|
||||
|
|
@ -557,7 +557,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
settings: { searchImpl: 'tool' }, // builtin 有 settings
|
||||
settings: { searchImpl: 'tool' }, // builtin has settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -569,11 +569,11 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: true });
|
||||
// 使用 builtin settings
|
||||
// Use builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'tool' });
|
||||
});
|
||||
|
||||
// 测试:用户模型有 abilities.search: false
|
||||
// Test: user model has abilities.search: false
|
||||
it('should remove settings when user has search: false and builtin has settings', async () => {
|
||||
const mockProviders = [
|
||||
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
||||
|
|
@ -584,14 +584,14 @@ describe('AiInfraRepos', () => {
|
|||
providerId: 'openai',
|
||||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { search: false }, // 用户禁用 search
|
||||
abilities: { search: false }, // user-disabled search
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
settings: { searchImpl: 'tool', extendParams: [] }, // builtin 有 settings
|
||||
settings: { searchImpl: 'tool', extendParams: [] }, // builtin has settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -603,7 +603,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: false });
|
||||
// 移除 search 相关,保留其他
|
||||
// Remove search-related settings, retain others
|
||||
expect(merged?.settings).toEqual({ extendParams: [] });
|
||||
});
|
||||
|
||||
|
|
@ -624,7 +624,7 @@ describe('AiInfraRepos', () => {
|
|||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
// 无 settings
|
||||
// no settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -636,7 +636,7 @@ describe('AiInfraRepos', () => {
|
|||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
expect(merged?.abilities).toEqual({ search: false });
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -669,7 +669,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 应该使用用户的 settings,不是内置的
|
||||
// Should use user settings, not builtin
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'user-provider' });
|
||||
});
|
||||
|
||||
|
|
@ -684,7 +684,7 @@ describe('AiInfraRepos', () => {
|
|||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { vision: true },
|
||||
// 用户未设置 settings
|
||||
// user has not set settings
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
|
|
@ -702,7 +702,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 应该使用内置的 settings
|
||||
// Should use builtin settings
|
||||
expect(merged?.settings).toEqual({ searchImpl: 'tool', searchProvider: 'google' });
|
||||
});
|
||||
|
||||
|
|
@ -757,14 +757,14 @@ describe('AiInfraRepos', () => {
|
|||
enabled: true,
|
||||
type: 'chat',
|
||||
abilities: { vision: true },
|
||||
// 用户未设置 settings
|
||||
// user has not set settings
|
||||
};
|
||||
|
||||
const builtinModel = {
|
||||
id: 'gpt-4',
|
||||
enabled: true,
|
||||
type: 'chat' as const,
|
||||
// 内置也无 settings
|
||||
// builtin also has no settings
|
||||
};
|
||||
|
||||
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
||||
|
|
@ -775,7 +775,7 @@ describe('AiInfraRepos', () => {
|
|||
|
||||
const merged = result.find((m) => m.id === 'gpt-4');
|
||||
expect(merged).toBeDefined();
|
||||
// 无 settings
|
||||
// no settings
|
||||
expect(merged?.settings).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import { DATA_EXPORT_CONFIG, DataExporterRepos } from './index';
|
|||
|
||||
let db: LobeChatDatabase;
|
||||
|
||||
// 设置测试数据
|
||||
// Set up test data
|
||||
describe('DataExporterRepos', () => {
|
||||
// 测试数据 ID
|
||||
// Test data IDs
|
||||
const testIds = {
|
||||
userId: 'test-user-id',
|
||||
fileId: 'test-file-id',
|
||||
|
|
@ -36,7 +36,7 @@ describe('DataExporterRepos', () => {
|
|||
knowledgeBaseId: 'test-kb-id',
|
||||
};
|
||||
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
const userId: string = testIds.userId;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -45,20 +45,20 @@ describe('DataExporterRepos', () => {
|
|||
|
||||
const setupTestData = async () => {
|
||||
await db.transaction(async (trx) => {
|
||||
// 用户数据
|
||||
// User data
|
||||
await trx.insert(users).values({
|
||||
id: testIds.userId,
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
});
|
||||
|
||||
// 用户设置
|
||||
// User settings
|
||||
await trx.insert(userSettings).values({
|
||||
id: testIds.userId,
|
||||
general: { theme: 'light' },
|
||||
});
|
||||
|
||||
// 全局文件
|
||||
// Global files
|
||||
await trx.insert(globalFiles).values({
|
||||
hashId: testIds.fileHash,
|
||||
fileType: 'text/plain',
|
||||
|
|
@ -67,7 +67,7 @@ describe('DataExporterRepos', () => {
|
|||
creator: testIds.userId,
|
||||
});
|
||||
|
||||
// 文件数据
|
||||
// File data
|
||||
await trx.insert(files).values({
|
||||
id: testIds.fileId,
|
||||
userId: testIds.userId,
|
||||
|
|
@ -78,13 +78,13 @@ describe('DataExporterRepos', () => {
|
|||
url: 'https://example.com/test-file.txt',
|
||||
});
|
||||
|
||||
// 会话组
|
||||
// Session groups
|
||||
await trx.insert(sessionGroups).values({
|
||||
name: 'Test Group',
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 会话
|
||||
// Sessions
|
||||
await trx.insert(sessions).values({
|
||||
id: testIds.sessionId,
|
||||
slug: 'test-session',
|
||||
|
|
@ -92,7 +92,7 @@ describe('DataExporterRepos', () => {
|
|||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 主题
|
||||
// Topics
|
||||
await trx.insert(topics).values({
|
||||
id: testIds.topicId,
|
||||
title: 'Test Topic',
|
||||
|
|
@ -100,7 +100,7 @@ describe('DataExporterRepos', () => {
|
|||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 消息
|
||||
// Messages
|
||||
await trx.insert(messages).values({
|
||||
id: testIds.messageId,
|
||||
role: 'user',
|
||||
|
|
@ -110,42 +110,42 @@ describe('DataExporterRepos', () => {
|
|||
topicId: testIds.topicId,
|
||||
});
|
||||
|
||||
// 代理
|
||||
// Agents
|
||||
await trx.insert(agents).values({
|
||||
id: testIds.agentId,
|
||||
title: 'Test Agent',
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 代理到会话的关联
|
||||
// Agent-to-session associations
|
||||
await trx.insert(agentsToSessions).values({
|
||||
agentId: testIds.agentId,
|
||||
sessionId: testIds.sessionId,
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 文件到会话的关联
|
||||
// File-to-session associations
|
||||
await trx.insert(filesToSessions).values({
|
||||
fileId: testIds.fileId,
|
||||
sessionId: testIds.sessionId,
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 知识库
|
||||
// Knowledge bases
|
||||
await trx.insert(knowledgeBases).values({
|
||||
id: testIds.knowledgeBaseId,
|
||||
name: 'Test Knowledge Base',
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 知识库文件
|
||||
// Knowledge base files
|
||||
await trx.insert(knowledgeBaseFiles).values({
|
||||
knowledgeBaseId: testIds.knowledgeBaseId,
|
||||
fileId: testIds.fileId,
|
||||
userId: testIds.userId,
|
||||
});
|
||||
|
||||
// 代理知识库
|
||||
// Agent knowledge bases
|
||||
await trx.insert(agentsKnowledgeBases).values({
|
||||
agentId: testIds.agentId,
|
||||
knowledgeBaseId: testIds.knowledgeBaseId,
|
||||
|
|
@ -155,7 +155,7 @@ describe('DataExporterRepos', () => {
|
|||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
// 清理并插入测试数据
|
||||
// Clean up and insert test data
|
||||
await db.delete(users);
|
||||
await db.delete(globalFiles);
|
||||
await setupTestData();
|
||||
|
|
@ -170,17 +170,17 @@ describe('DataExporterRepos', () => {
|
|||
|
||||
describe('export', () => {
|
||||
it('should export all user data correctly', async () => {
|
||||
// 创建导出器实例
|
||||
// Create exporter instance
|
||||
const dataExporter = new DataExporterRepos(db, userId);
|
||||
|
||||
// 执行导出
|
||||
// Execute export
|
||||
const result = await dataExporter.export();
|
||||
|
||||
// 验证基础表导出结果
|
||||
// Verify base table export results
|
||||
// expect(result).toHaveProperty('users');
|
||||
// expect(result.users).toHaveLength(1);
|
||||
// expect(result.users[0]).toHaveProperty('id', testIds.userId);
|
||||
// expect(result.users[0]).not.toHaveProperty('userId'); // userId 字段应该被移除
|
||||
// expect(result.users[0]).not.toHaveProperty('userId'); // the userId field should be removed
|
||||
|
||||
expect(result).toHaveProperty('userSettings');
|
||||
expect(result.userSettings).toHaveLength(1);
|
||||
|
|
@ -212,7 +212,7 @@ describe('DataExporterRepos', () => {
|
|||
// expect(result.knowledgeBases).toHaveLength(1);
|
||||
// expect(result.knowledgeBases[0]).toHaveProperty('id', testIds.knowledgeBaseId);
|
||||
|
||||
// 验证关联表导出结果
|
||||
// Verify relation table export results
|
||||
// expect(result).toHaveProperty('globalFiles');
|
||||
// expect(result.globalFiles).toHaveLength(1);
|
||||
// expect(result.globalFiles[0]).toHaveProperty('hashId', testIds.fileHash);
|
||||
|
|
@ -237,18 +237,18 @@ describe('DataExporterRepos', () => {
|
|||
});
|
||||
|
||||
it('should handle empty database gracefully', async () => {
|
||||
// 清空数据库
|
||||
// Clear the database
|
||||
|
||||
await db.delete(users);
|
||||
await db.delete(globalFiles);
|
||||
|
||||
// 创建导出器实例
|
||||
// Create exporter instance
|
||||
const dataExporter = new DataExporterRepos(db, userId);
|
||||
|
||||
// 执行导出
|
||||
// Execute export
|
||||
const result = await dataExporter.export();
|
||||
|
||||
// 验证所有表都返回空数组
|
||||
// Verify all tables return empty arrays
|
||||
DATA_EXPORT_CONFIG.baseTables.forEach(({ table }) => {
|
||||
expect(result).toHaveProperty(table);
|
||||
expect(result[table]).toEqual([]);
|
||||
|
|
@ -261,17 +261,17 @@ describe('DataExporterRepos', () => {
|
|||
});
|
||||
|
||||
it('should handle database query errors', async () => {
|
||||
// 模拟查询错误
|
||||
// Simulate a query error
|
||||
// @ts-ignore
|
||||
vi.spyOn(db.query.users, 'findMany').mockRejectedValueOnce(new Error('Database error'));
|
||||
|
||||
// 创建导出器实例
|
||||
// Create exporter instance
|
||||
const dataExporter = new DataExporterRepos(db, userId);
|
||||
|
||||
// 执行导出
|
||||
// Execute export
|
||||
const result = await dataExporter.export();
|
||||
|
||||
// 验证其他表仍然被导出
|
||||
// Verify other tables are still exported
|
||||
expect(result).toHaveProperty('sessions');
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
});
|
||||
|
|
@ -329,7 +329,7 @@ describe('DataExporterRepos', () => {
|
|||
});
|
||||
|
||||
it('should export data for a different user', async () => {
|
||||
// 创建另一个用户
|
||||
// Create another user
|
||||
const anotherUserId = 'another-user-id';
|
||||
await db.transaction(async (trx) => {
|
||||
await trx.insert(users).values({
|
||||
|
|
@ -345,13 +345,13 @@ describe('DataExporterRepos', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// 创建导出器实例,使用另一个用户 ID
|
||||
// Create exporter instance using another user ID
|
||||
const dataExporter = new DataExporterRepos(db, anotherUserId);
|
||||
|
||||
// 执行导出
|
||||
// Execute export
|
||||
const result = await dataExporter.export();
|
||||
|
||||
// 验证只导出了另一个用户的数据
|
||||
// Verify only the other user's data was exported
|
||||
// expect(result).toHaveProperty('users');
|
||||
// expect(result.users).toHaveLength(1);
|
||||
// expect(result.users[0]).toHaveProperty('id', anotherUserId);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ let importer: DataImporterRepos;
|
|||
beforeEach(async () => {
|
||||
await clientDB.delete(Schema.users);
|
||||
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await clientDB.transaction(async (tx) => {
|
||||
await tx.insert(Schema.users).values({ id: userId });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ let importer: DataImporterRepos;
|
|||
beforeEach(async () => {
|
||||
await serverDB.delete(users);
|
||||
|
||||
// 创建测试数据
|
||||
// Create test data
|
||||
await serverDB.transaction(async (tx) => {
|
||||
await tx.insert(users).values({ id: userId });
|
||||
});
|
||||
|
|
@ -323,13 +323,13 @@ describe('DataImporter', () => {
|
|||
|
||||
await importer.importData(data);
|
||||
|
||||
// 验证是否为每个 session 创建了对应的 agent
|
||||
// Verify that a corresponding agent was created for each session
|
||||
const agentCount = await serverDB.query.agents.findMany({
|
||||
where: eq(agents.userId, userId),
|
||||
});
|
||||
expect(agentCount).toHaveLength(2);
|
||||
|
||||
// 验证 agent 的属性是否正确设置
|
||||
// Verify that agent attributes are correctly set
|
||||
const agent1 = await serverDB.query.agents.findFirst({
|
||||
where: eq(agents.systemRole, 'Test Agent 1'),
|
||||
});
|
||||
|
|
@ -340,7 +340,7 @@ describe('DataImporter', () => {
|
|||
});
|
||||
expect(agent2?.model).toBe('def');
|
||||
|
||||
// 验证 agentsToSessions 关联是否正确建立
|
||||
// Verify that the agentsToSessions association is correctly established
|
||||
const session1 = await serverDB.query.sessions.findFirst({
|
||||
where: eq(sessions.clientId, 'session1'),
|
||||
});
|
||||
|
|
@ -363,7 +363,7 @@ describe('DataImporter', () => {
|
|||
});
|
||||
|
||||
it('should not create duplicate agents for existing sessions', async () => {
|
||||
// 先导入一些 sessions
|
||||
// First import some sessions
|
||||
await importer.importData({
|
||||
sessions: [
|
||||
{
|
||||
|
|
@ -387,7 +387,7 @@ describe('DataImporter', () => {
|
|||
version: CURRENT_CONFIG_VERSION,
|
||||
});
|
||||
|
||||
// 再次导入相同的 sessions
|
||||
// Import the same sessions again
|
||||
await importer.importData({
|
||||
sessions: [
|
||||
{
|
||||
|
|
@ -411,7 +411,7 @@ describe('DataImporter', () => {
|
|||
version: CURRENT_CONFIG_VERSION,
|
||||
});
|
||||
|
||||
// 验证只创建了一个 agent
|
||||
// Verify that only one agent was created
|
||||
const agentCount = await serverDB.query.agents.findMany({
|
||||
where: eq(agents.userId, userId),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ describe('UserModel', () => {
|
|||
|
||||
it('should handle decrypt failure and return empty object', async () => {
|
||||
const userId = 'user-api-test-id';
|
||||
// 模拟解密失败的情况
|
||||
// Simulate decrypt failure scenario
|
||||
const invalidEncryptedData = 'invalid:-encrypted-:data';
|
||||
await serverDB.insert(users).values({ id: userId });
|
||||
await serverDB.insert(userSettings).values({
|
||||
|
|
@ -306,7 +306,7 @@ describe('UserModel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// 补充一些边界情况的测试
|
||||
// Additional edge case tests
|
||||
describe('edge cases', () => {
|
||||
describe('updatePreference', () => {
|
||||
it('should handle undefined preference', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue