Files

115 lines
6.3 KiB
TypeScript

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)
})