delta-sync
Version:
A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.
180 lines (179 loc) • 7.75 kB
JavaScript
// tests/coordinator.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { SyncView } from '../core/types';
import { MemoryAdapter } from '../core/adapters';
import { Coordinator } from '../core/Coordinator';
describe('Coordinator Tests', () => {
let coordinator;
let memoryAdapter;
beforeEach(() => {
memoryAdapter = new MemoryAdapter();
coordinator = new Coordinator(memoryAdapter);
});
describe('Initialization', () => {
it('should initialize correctly', async () => {
await coordinator.initSync?.();
const view = await coordinator.getCurrentView();
expect(view).toBeInstanceOf(SyncView);
});
});
describe('Data Operations', () => {
const TEST_STORE = 'test_store';
it('should handle basic CRUD operations', async () => {
const testData = [
{ id: 'test1', content: 'test content 1' },
{ id: 'test2', content: 'test content 2' }
];
// Test Write
const savedData = await coordinator.putBulk(TEST_STORE, testData);
expect(savedData).toHaveLength(testData.length);
// Test Read
const readData = await coordinator.readBulk(TEST_STORE, [testData[0].id]);
expect(readData[0]).toBeDefined();
expect(readData[0].id).toBe(testData[0].id);
// Test Delete
await coordinator.deleteBulk(TEST_STORE, [testData[0].id]);
const afterDelete = await coordinator.readBulk(TEST_STORE, [testData[0].id]);
expect(afterDelete).toHaveLength(0);
});
});
describe('Query Operations', () => {
const QUERY_STORE = 'query_test';
it('should handle queries with pagination', async () => {
// Clean up test data
const currentView = await coordinator.getCurrentView();
await coordinator.deleteBulk(QUERY_STORE, currentView.getByStore(QUERY_STORE).map(item => item.id));
// Insert test data
const testItems = Array.from({ length: 10 }, (_, i) => ({
id: `query-test-${i}`,
content: `content ${i}`,
timestamp: Date.now() + i
}));
await coordinator.putBulk(QUERY_STORE, testItems);
// Test basic query
const result = await coordinator.query(QUERY_STORE, { limit: 5 });
expect(result.items).toHaveLength(5);
// Test pagination
const pageResult = await coordinator.query(QUERY_STORE, {
offset: 3,
limit: 3
});
expect(pageResult.items).toHaveLength(3);
});
});
describe('Concurrency', () => {
it('should handle concurrent operations', async () => {
const operations = Array(10).fill(null).map((_, i) => coordinator.putBulk('concurrent_test', [{
id: `concurrent-${i}`,
content: `concurrent content ${i}`
}]));
await expect(Promise.all(operations)).resolves.toBeDefined();
});
});
describe('Large Dataset', () => {
it('should handle large datasets', async () => {
const TOTAL_ITEMS = 1000;
const largeData = Array.from({ length: TOTAL_ITEMS }, (_, i) => ({
id: `large-${i}`,
content: `large content ${i}`
}));
// 写入数据
await coordinator.putBulk('large_test', largeData);
// 使用分页获取所有数据
let allItems = [];
let offset = 0;
const PAGE_SIZE = 200;
while (true) {
const result = await coordinator.query('large_test', {
limit: PAGE_SIZE,
offset: offset
});
allItems.push(...result.items);
if (!result.hasMore)
break;
offset += PAGE_SIZE;
}
// 验证总数
expect(allItems).toHaveLength(TOTAL_ITEMS);
// 验证数据完整性
allItems.sort((a, b) => {
const aNum = parseInt(a.id.split('-')[1]);
const bNum = parseInt(b.id.split('-')[1]);
return aNum - bNum;
});
for (let i = 0; i < TOTAL_ITEMS; i++) {
expect(allItems[i].id).toBe(`large-${i}`);
expect(allItems[i].content).toBe(`large content ${i}`);
}
});
it('should handle pagination correctly', async () => {
const TOTAL_ITEMS = 200;
const PAGE_SIZE = 50;
// 先清理已存在的数据
const currentView = await coordinator.getCurrentView();
const existingIds = currentView.getByStore('large_test').map(item => item.id);
if (existingIds.length > 0) {
await coordinator.deleteBulk('large_test', existingIds);
}
// 准备测试数据
const testData = Array.from({ length: TOTAL_ITEMS }, (_, i) => ({
id: `page-test-${i}`,
content: `content ${i}`
}));
// 写入测试数据
await coordinator.putBulk('large_test', testData);
// 测试第一页
const result1 = await coordinator.query('large_test', {
limit: PAGE_SIZE,
offset: 0
});
expect(result1.items).toHaveLength(PAGE_SIZE);
expect(result1.hasMore).toBe(true);
// 测试第二页
const result2 = await coordinator.query('large_test', {
limit: PAGE_SIZE,
offset: PAGE_SIZE
});
expect(result2.items).toHaveLength(PAGE_SIZE);
expect(result2.hasMore).toBe(true);
// 验证数据连续性
const allIds = new Set([
...result1.items.map(item => item.id),
...result2.items.map(item => item.id)
]);
expect(allIds.size).toBe(PAGE_SIZE * 2); // 确保没有重复
// 验证顺序
const firstPageLastId = parseInt(result1.items[PAGE_SIZE - 1].id.split('-')[2]);
const secondPageFirstId = parseInt(result2.items[0].id.split('-')[2]);
expect(secondPageFirstId).toBe(firstPageLastId + 1);
});
});
describe('Edge Cases', () => {
it('should handle empty array', async () => {
await expect(coordinator.putBulk('edge_test', [])).resolves.toHaveLength(0);
});
it('should handle special characters in id', async () => {
await expect(coordinator.putBulk('edge_test', [{
id: 'special-!@#$%^&*()',
content: '!@#$%^&*()'
}])).resolves.toBeDefined();
});
it('should handle large objects', async () => {
const largeObject = {
id: 'large-object',
content: 'x'.repeat(1000000)
};
await expect(coordinator.putBulk('edge_test', [largeObject])).resolves.toBeDefined();
});
});
describe('View Management', () => {
it('should manage sync view correctly', async () => {
const testData = { id: 'view-test', content: 'view test' };
await coordinator.putBulk('view_test', [testData]);
const view = await coordinator.getCurrentView();
const viewItem = view.get('view_test', 'view-test');
expect(viewItem).toBeDefined();
expect(viewItem?.id).toBe('view-test');
});
});
});