import 'fake-indexeddb/auto' import { bindDatabase, WorklogDB } from '../src/db/schema' const BASE = 'http://127.0.0.1:3780' let pass = 0 let fail = 0 function check(name: string, ok: boolean, detail?: string): void { if (ok) { pass++; console.log(' PASS ' + name) } else { fail++; console.log(' FAIL ' + name + (detail ? ' -> ' + detail : '')) } } async function main() { // 云端注册(清空服务端数据后全新注册) await fetch(BASE + '/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'twodev', password: 'dev123456' }) }).catch(() => { /* 已存在则忽略 */ }) const engine = await import('../src/sync/engine') // ===== 设备 A:创建项目、工作日志、日志条目、设置 ===== bindDatabase(new WorklogDB('dev_a')) const a = (await import('../src/db/schema')).db await a.project.bulkAdd([{ name: '项目A', archived: 0, created_at: '2025-01-01T00:00:00' }]) await a.work_log.bulkAdd([{ date: '2025-01-02', created_at: '2025-01-02T00:00:00' }]) await a.setting.bulkAdd([{ key: 'work_start', value: '09:00' }]) await a.table('log_item').bulkAdd([{ work_log_id: 1, content: 'A 设备的日志', project_id: 1, category: 'dev', minutes: 60, is_milestone: 1, needs_followup: 0, takeaway: null, remark: null, sort_order: 0, created_at: '2025-01-02T10:00:00' }]) const rA = await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) console.log('设备A 推送:', JSON.stringify({ p: rA.pushed, e: rA.errors })) // ===== 设备 B:全新空库,拉取设备 A 数据 ===== bindDatabase(new WorklogDB('dev_b')) const b = (await import('../src/db/schema')).db const rB1 = await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) console.log('设备B 拉取:', JSON.stringify({ p: rB1.pushed, e: rB1.errors })) const bProjects = await b.project.toArray() const bWorkLogs = await b.work_log.toArray() const bItems = await b.log_item.toArray() const bSettings = await b.setting.toArray() check('B 拉到项目', bProjects.length === 1 && bProjects[0].name === '项目A') check('B 拉到工作日志', bWorkLogs.length === 1 && bWorkLogs[0].date === '2025-01-02') check('B 拉到日志条目', bItems.length === 1 && bItems[0].content === 'A 设备的日志') check('B 外键重映射(log_item.work_log_id 指向本地 work_log)', bItems[0].work_log_id === bWorkLogs[0].id) check('B 外键重映射(log_item.project_id 指向本地 project)', bItems[0].project_id === bProjects[0].id) check('B 拉到设置', bSettings.some(s => s.key === 'work_start' && s.value === '09:00')) // ===== 设备 B:打卡(创建当日考勤与工作日志)+ 新日志条目,推送 ===== await b.attendance.bulkAdd([{ date: '2025-01-02', clock_in_at: '2025-01-02T09:00:00', clock_out_at: null, kind: 'normal', note: null, created_at: '2025-01-02T09:00:00' }]) await b.table('log_item').add({ work_log_id: bWorkLogs[0].id, content: 'B 设备的日志', project_id: null, category: 'dev', minutes: null, is_milestone: 0, needs_followup: 1, takeaway: null, remark: null, sort_order: 0, created_at: '2025-01-02T11:00:00' }) const rB2 = await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) console.log('设备B 二次推送:', JSON.stringify({ p: rB2.pushed, e: rB2.errors })) // ===== 设备 A:拉取 B 的数据(考勤 + 新日志条目,含自然键去重场景)===== bindDatabase(new WorklogDB('dev_a')) const a2 = (await import('../src/db/schema')).db const rA2 = await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) console.log('设备A 拉取:', JSON.stringify({ p: rA2.pushed, e: rA2.errors })) const aAtt = await a2.attendance.toArray() const aItems = await a2.log_item.toArray() const aWorkLogs = await a2.work_log.toArray() check('A 拉到 B 的考勤', aAtt.length === 1 && aAtt[0].date === '2025-01-02') check('A 拉到 B 的日志条目', aItems.some(i => i.content === 'B 设备的日志')) check('A 无重复工作日志(自然键去重)', aWorkLogs.length === 1) check('A 的日志条目仍然可见(未因去重丢失)', aItems.length >= 2) // ===== 冲突场景:两端为同一天各自创建工作日志后合并 ===== bindDatabase(new WorklogDB('dev_b')) const b2 = (await import('../src/db/schema')).db await b2.work_log.bulkPut([{ date: '2025-01-03', created_at: '2025-01-03T00:00:00', uuid: 'b-day3', updated_at: '2025-01-03T08:00:00' }]) await b2.table('log_item').add({ work_log_id: 2, content: 'B 的 01-03 记录', project_id: null, category: 'dev', minutes: null, is_milestone: 0, needs_followup: 0, takeaway: null, remark: null, sort_order: 0, created_at: '2025-01-03T08:00:00', uuid: 'item-b-day3', updated_at: '2025-01-03T08:00:00' }) await b2.table('log_item').add({ work_log_id: 999, content: 'B 的 01-03 记录(重映射目标)', project_id: null, category: 'dev', minutes: null, is_milestone: 0, needs_followup: 0, takeaway: null, remark: null, sort_order: 0, created_at: '2025-01-03T08:30:00', uuid: 'item-b-day3b', updated_at: '2025-01-03T08:30:00', wuuid: 'b-day3' }) await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) bindDatabase(new WorklogDB('dev_a')) const a3 = (await import('../src/db/schema')).db await a3.work_log.bulkAdd([{ date: '2025-01-03', created_at: '2025-01-03T07:00:00', uuid: 'a-day3', updated_at: '2025-01-03T07:00:00' }]) await engine.runSync({ baseUrl: BASE, username: 'twodev', password: 'dev123456' }) const a3Logs = await a3.work_log.toArray() const day3 = a3Logs.filter(l => l.date === '2025-01-03') check('冲突合并后同日仅一条工作日志', day3.length === 1) console.log(' DEBUG day3:', JSON.stringify(day3)) const a3Items = await a3.log_item.toArray() const day3Items = a3Items.filter(i => i.content.indexOf('01-03') >= 0) console.log(' DEBUG day3Items:', JSON.stringify(day3Items)) check('冲突合并后 B 的 01-03 条目保留且外键正确', day3Items.length === 2 && day3Items.every(i => i.work_log_id === day3[0].id)) console.log('==== 结果: PASS ' + pass + ' / FAIL ' + fail + ' ====') process.exit(fail > 0 ? 1 : 0) } main().catch(e => { console.error('FAILED:', (e && (e.stack || e.message)) || e) process.exit(1) })