UNPKG

delta-sync

Version:

A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.

422 lines (421 loc) 18.3 kB
// test/adapter.ts // 适配器功能测试工具 - 用于测试任何实现了DatabaseAdapter接口的适配器 export class AdapterTester { constructor(adapter, testStoreName) { this.testStoreName = 'adapter_test'; this.testFileContent = 'Hello, this is a test file content!'; this.adapter = adapter; if (testStoreName) { this.testStoreName = testStoreName; } } /** * 运行所有测试 */ async runAllTests() { console.log('=== 开始适配器测试 ==='); const results = {}; let allSuccess = true; try { // 测试初始化 results.initialization = await this.testInitialization(); allSuccess = allSuccess && results.initialization.success; // 测试可用性 results.availability = await this.testAvailability(); allSuccess = allSuccess && results.availability.success; // 测试基本CRUD操作 results.basicCrud = await this.testBasicCrud(); allSuccess = allSuccess && results.basicCrud.success; // 测试批量操作 results.bulkOperations = await this.testBulkOperations(); allSuccess = allSuccess && results.bulkOperations.success; // 测试批量文件操作 results.batchFileOperations = await this.testBatchFileOperations(); allSuccess = allSuccess && results.batchFileOperations.success; // 测试计数功能 results.count = await this.testCount(); allSuccess = allSuccess && results.count.success; console.log('=== 适配器测试完成 ==='); if (allSuccess) { console.log('✅ 所有测试通过'); } else { console.log('❌ 部分测试失败'); } // 输出详细结果 Object.entries(results).forEach(([testName, result]) => { console.log(`${result.success ? '✅' : '❌'} ${testName}: ${result.message}`); }); return { success: allSuccess, results }; } catch (error) { console.error('测试过程中出现未捕获的错误:', error); return { success: false, results: { ...results, uncaughtError: { success: false, message: `未捕获错误: ${error instanceof Error ? error.message : String(error)}` } } }; } } /** * 测试初始化功能 */ async testInitialization() { try { console.log('测试初始化...'); await this.adapter.initSync(); return { success: true, message: '初始化成功' }; } catch (error) { console.error('初始化失败:', error); return { success: false, message: `初始化失败: ${error instanceof Error ? error.message : String(error)}` }; } } /** * 测试可用性检查 */ async testAvailability() { try { console.log('测试可用性...'); const available = await this.adapter.isAvailable(); if (available) { return { success: true, message: '适配器可用' }; } else { return { success: false, message: '适配器不可用' }; } } catch (error) { console.error('可用性检查失败:', error); return { success: false, message: `可用性检查失败: ${error instanceof Error ? error.message : String(error)}` }; } } /** * 测试基本的CRUD操作 */ async testBasicCrud() { try { console.log('测试基本CRUD操作...'); // 创建测试项目 const testItem = { _delta_id: `test_item_${Date.now()}`, _sync_status: 'pending', _store: this.testStoreName, _ver: Date.now() }; // 写入 console.log('测试写入操作...'); const writeResult = await this.adapter.putBulk(this.testStoreName, [testItem]); if (!writeResult || writeResult.length !== 1) { return { success: false, message: '写入操作失败' }; } // 读取 console.log('测试读取操作...'); const readResult = await this.adapter.readBulk(this.testStoreName, [testItem._delta_id]); if (!readResult || readResult.length !== 1 || readResult[0]._delta_id !== testItem._delta_id) { return { success: false, message: '读取操作失败' }; } // 删除 console.log('测试删除操作...'); await this.adapter.deleteBulk(this.testStoreName, [testItem._delta_id]); // 验证删除 const afterDeleteResult = await this.adapter.readBulk(this.testStoreName, [testItem._delta_id]); if (afterDeleteResult && afterDeleteResult.length > 0) { return { success: false, message: '删除操作失败' }; } return { success: true, message: '基本CRUD操作测试通过' }; } catch (error) { console.error('CRUD测试失败:', error); return { success: false, message: `CRUD测试失败: ${error instanceof Error ? error.message : String(error)}` }; } } /** * 测试批量操作 */ async testBulkOperations() { try { console.log('测试批量操作...'); // 创建多个测试项目 const testItems = Array(5).fill(0).map((_, index) => ({ _delta_id: `bulk_test_${Date.now()}_${index}`, _sync_status: 'pending', _store: this.testStoreName, _ver: Date.now(), testValue: `测试值 ${index}` })); // 批量写入 console.log('测试批量写入...'); const writeResult = await this.adapter.putBulk(this.testStoreName, testItems); if (!writeResult || writeResult.length !== testItems.length) { return { success: false, message: '批量写入失败' }; } // 批量读取 console.log('测试批量读取...'); const ids = testItems.map(item => item._delta_id); const readResult = await this.adapter.readBulk(this.testStoreName, ids); if (!readResult || readResult.length !== ids.length) { return { success: false, message: '批量读取失败' }; } // 测试分页读取 console.log('测试分页读取...'); const pageResult = await this.adapter.read(this.testStoreName, { limit: 3, offset: 0 }); if (!pageResult || !pageResult.items) { return { success: false, message: '分页读取失败' }; } // 批量删除 console.log('测试批量删除...'); await this.adapter.deleteBulk(this.testStoreName, ids); // 验证批量删除 const afterDeleteResult = await this.adapter.readBulk(this.testStoreName, ids); if (afterDeleteResult && afterDeleteResult.length > 0) { return { success: false, message: '批量删除失败' }; } return { success: true, message: '批量操作测试通过' }; } catch (error) { console.error('批量操作测试失败:', error); return { success: false, message: `批量操作测试失败: ${error instanceof Error ? error.message : String(error)}` }; } } /** * 测试计数功能 */ async testCount() { try { console.log('测试计数功能...'); // 清空测试存储以确保有干净的起点 const initialItems = await this.adapter.read(this.testStoreName); if (initialItems.items.length > 0) { await this.adapter.deleteBulk(this.testStoreName, initialItems.items.map(item => item._delta_id)); } // 验证空存储的计数 const emptyCount = await this.adapter.count(this.testStoreName); if (emptyCount !== 0) { return { success: false, message: `空存储计数错误:预期=0,实际=${emptyCount}` }; } // 插入指定数量的测试项目 const itemCount = 5; const testItems = Array(itemCount).fill(0).map((_, index) => ({ _delta_id: `count_test_${Date.now()}_${index}`, _sync_status: 'pending', _store: this.testStoreName, _ver: Date.now() })); await this.adapter.putBulk(this.testStoreName, testItems); // 验证计数结果 const count = await this.adapter.count(this.testStoreName); // 清理测试数据 await this.adapter.deleteBulk(this.testStoreName, testItems.map(item => item._delta_id)); if (count !== itemCount) { return { success: false, message: `存储计数错误:预期=${itemCount},实际=${count}` }; } return { success: true, message: '计数功能测试通过' }; } catch (error) { console.error('计数测试失败:', error); return { success: false, message: `计数测试失败: ${error instanceof Error ? error.message : String(error)}` }; } } // 测试批量文件操作 async testBatchFileOperations() { try { console.log('测试批量文件操作...'); // 准备测试文件 const totalFiles = 5; const fileContents = Array(totalFiles).fill(0).map((_, i) => `File content ${i}: ${this.testFileContent}`); const fileIds = Array(totalFiles).fill(0).map((_, i) => `batch_test_file_${Date.now()}_${i}`); const fileObjects = fileIds.map((fileId, index) => ({ fileId, content: new Blob([fileContents[index]], { type: 'text/plain' }) })); // 1. 批量保存文件 console.log('测试批量保存文件...'); const saveResults = await this.adapter.saveFiles(fileObjects); if (!saveResults || saveResults.length !== totalFiles) { return { success: false, message: `批量保存文件失败: 预期=${totalFiles},实际=${saveResults?.length || 0}` }; } // 检查每个结果的ID for (let i = 0; i < totalFiles; i++) { if (saveResults[i].id !== fileIds[i]) { return { success: false, message: `批量保存文件失败: 文件ID不匹配,预期=${fileIds[i]},实际=${saveResults[i].id}` }; } } // 2. 批量读取文件 console.log('测试批量读取文件...'); const readResults = await this.adapter.readFiles(fileIds); if (!readResults || readResults.size !== totalFiles) { return { success: false, message: `批量读取文件失败: 预期=${totalFiles},实际=${readResults?.size || 0}` }; } // 验证每个文件的内容 console.log('验证批量文件内容...'); let allContentMatch = true; let mismatchedFileId = ''; for (let i = 0; i < totalFiles; i++) { const fileId = fileIds[i]; const fileContent = readResults.get(fileId); if (!fileContent) { allContentMatch = false; mismatchedFileId = fileId; break; } // 读取 Blob 或 ArrayBuffer 的内容 let contentText = ''; if (fileContent instanceof Blob) { contentText = await fileContent.text(); } else if (fileContent instanceof ArrayBuffer) { contentText = new TextDecoder().decode(fileContent); } // 使用包含检查而不是完全匹配 if (!contentText.includes(fileContents[i].substring(0, 20))) { console.log(`文件内容不匹配: ${fileId}`); console.log(`预期包含: ${fileContents[i].substring(0, 20)}`); console.log(`实际内容: ${contentText.substring(0, 100)}`); allContentMatch = false; mismatchedFileId = fileId; break; } } if (!allContentMatch) { return { success: false, message: `批量文件内容验证失败: 文件 ${mismatchedFileId} 内容不匹配` }; } // 3. 批量删除文件 console.log('测试批量删除文件...'); const deleteResult = await this.adapter.deleteFiles(fileIds); if (!deleteResult || deleteResult.deleted.length !== totalFiles) { return { success: false, message: `批量删除文件失败: 预期删除=${totalFiles},实际删除=${deleteResult?.deleted.length || 0}` }; } // 验证文件是否已全部删除 console.log('验证批量文件删除结果...'); const verifyReadResults = await this.adapter.readFiles(fileIds); let allDeleted = true; let stillExistsFileId = ''; for (const fileId of fileIds) { const content = verifyReadResults.get(fileId); if (content !== null && content !== undefined) { allDeleted = false; stillExistsFileId = fileId; break; } } if (!allDeleted) { return { success: false, message: `批量文件删除验证失败: 文件 ${stillExistsFileId} 仍然存在` }; } // 4. 测试批量文件操作的错误处理 console.log('测试批量文件操作的错误处理...'); // 测试不存在的文件ID const nonExistentIds = ['non_existent_1', 'non_existent_2']; const nonExistentResults = await this.adapter.readFiles(nonExistentIds); let handlesNonExistentWell = true; for (const id of nonExistentIds) { if (nonExistentResults.get(id) !== null && nonExistentResults.get(id) !== undefined) { handlesNonExistentWell = false; break; } } if (!handlesNonExistentWell) { return { success: false, message: '批量读取不存在文件处理失败: 应返回null或undefined' }; } // 测试删除不存在的文件 const deleteNonExistentResult = await this.adapter.deleteFiles(nonExistentIds); if (deleteNonExistentResult.failed.length !== 2) { return { success: false, message: `批量删除不存在文件处理失败: 预期失败=2,实际失败=${deleteNonExistentResult.failed.length}` }; } // 5. 测试不同类型文件的批量处理 console.log('测试不同类型文件的批量处理...'); const mixedFiles = [ { fileId: `text_file_${Date.now()}`, content: new Blob(['Plain text content'], { type: 'text/plain' }) }, { fileId: `binary_file_${Date.now()}`, content: new Blob([new Uint8Array([10, 20, 30, 40, 50])], { type: 'application/octet-stream' }) }, { fileId: `base64_file_${Date.now()}`, content: 'data:text/plain;base64,QmF0Y2ggdGVzdGluZyBiYXNlNjQgY29udGVudA==' // "Batch testing base64 content" } ]; const mixedSaveResults = await this.adapter.saveFiles(mixedFiles); if (mixedSaveResults.length !== 3) { return { success: false, message: `混合类型文件批量保存失败: 预期=3,实际=${mixedSaveResults.length}` }; } const mixedFileIds = mixedFiles.map(file => file.fileId); const mixedReadResults = await this.adapter.readFiles(mixedFileIds); if (mixedReadResults.size !== 3) { return { success: false, message: `混合类型文件批量读取失败: 预期=3,实际=${mixedReadResults.size}` }; } // 清理混合文件 await this.adapter.deleteFiles(mixedFileIds); return { success: true, message: '批量文件操作测试通过' }; } catch (error) { console.error('批量文件操作测试失败:', error); return { success: false, message: `批量文件操作测试失败: ${error instanceof Error ? error.message : String(error)}` }; } } }