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
JavaScript
// 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)}`
};
}
}
}