test: verify database migration and scheduled operations

This commit is contained in:
陈煜 committed 2026-10-02 17:56:44 +08:00
1 parent 86c6daf695
commit 932a864ed6
5 files changed
+96 -5

No files matched your search

+1 -1
View File
@@ -151,7 +151,7 @@ test('borrow/lend/collect/repay pair balances atomically and survive concurrency
200,
);
const backup = (await call(a.token, '/backup')).data;
assert.equal(JSON.parse(packBackup(backup)['manifest.json']).version, 6);
assert.equal(JSON.parse(packBackup(backup)['manifest.json']).version, 7);
assert.equal(backup.transfers.length, 5);
assert.ok(backup.transfers.some((p: any) => p.operation === 'lend' && p.fee === '-2'));
assert.equal(
+81 -2
View File
@@ -129,8 +129,62 @@ test('real MySQL: groups, scheduled atomic execution, calendar, privacy and ZIP
assert.equal(day.expense, '5.50');
assert.equal(day.income, '0.00');
assert.equal((await call('/calendar/day?date=' + today(), a.cookie)).data.items.length, 4);
const transferPlan = await call('/schedules', a.cookie, 'POST', {
...input,
name: 'Scheduled transfer',
operation: 'transfer',
targetId,
amount: '3',
received: '3',
});
assert.equal(transferPlan.status, 201);
const transferRace = await Promise.all([
call('/schedules/run', a.cookie, 'POST', {}),
call('/schedules/run', a.cookie, 'POST', {}),
]);
assert.equal(
transferRace.reduce((sum, v) => sum + v.data.executed, 0),
1,
);
const afterTransfer = (await call('/positions', a.cookie)).data;
assert.equal(afterTransfer.find((p: any) => p.id === sourceId).amount, '-23.5');
assert.equal(afterTransfer.find((p: any) => p.id === targetId).amount, '28');
assert.equal((await call('/calendar/day?date=' + today(), a.cookie)).data.items.length, 4);
const failedPlan = await call('/schedules', a.cookie, 'POST', {
...input,
name: 'Archived account',
});
assert.equal(failedPlan.status, 201);
const beforeFailure = (await call('/schedules', a.cookie)).data.find(
(p: any) => p.id === failedPlan.data.id,
);
await call('/positions/' + sourceId, a.cookie, 'PATCH', {
name: 'Daily',
category: 'bank',
groupName: '日常',
archived: true,
});
const failure = await call('/schedules/run', a.cookie, 'POST', {});
assert.equal(failure.data.executed, 0);
assert.equal(failure.data.errors.length, 1);
assert.equal(
(await call('/schedules', a.cookie)).data.find((p: any) => p.id === failedPlan.data.id)
.nextAt,
beforeFailure.nextAt,
);
assert.equal(
(await call('/positions', a.cookie)).data.find((p: any) => p.id === sourceId).amount,
'-23.5',
);
await call('/positions/' + sourceId, a.cookie, 'PATCH', {
name: 'Daily',
category: 'bank',
groupName: '日常',
archived: false,
});
assert.equal((await call('/schedules/' + failedPlan.data.id, a.cookie, 'DELETE')).status, 200);
const backup = (await call('/backup', a.cookie)).data;
assert.equal(backup.schedules.length, 2);
assert.equal(backup.schedules.length, 3);
assert.equal(
(await call('/backup/import', b.cookie, 'POST', { confirmed: true, backup })).status,
201,
@@ -141,7 +195,32 @@ test('real MySQL: groups, scheduled atomic execution, calendar, privacy and ZIP
['日常', '理财'].sort(),
);
const restored = (await call('/backup', b.cookie)).data;
assert.equal(restored.schedules.filter((p: any) => p.completed).length, 1);
assert.equal(restored.schedules.filter((p: any) => p.completed).length, 2);
await db.schedule.createMany({
data: Array.from({ length: 21 }, (_, i) => ({
id: randomUUID(),
userId: a.id,
name: 'Batch ' + i,
operation: 'expense',
sourceId,
amount: '1',
received: '0',
nextAt: new Date(Date.now() - 60000),
intervalDays: 0,
enabled: true,
notes: '',
})),
});
const firstBatch = await call('/schedules/run', a.cookie, 'POST', {});
assert.equal(firstBatch.data.executed, 20);
assert.equal(firstBatch.data.hasMore, true);
const secondBatch = await call('/schedules/run', a.cookie, 'POST', {});
assert.equal(secondBatch.data.executed, 1);
assert.equal(secondBatch.data.hasMore, false);
assert.equal(
(await call('/positions', a.cookie)).data.find((p: any) => p.id === sourceId).amount,
'-44.5',
);
await call('/positions/' + sourceId, a.cookie, 'PATCH', {
name: 'Daily',
category: 'bank',
+1 -1
View File
@@ -55,4 +55,4 @@
## 2026-10-02 账户分组、计划与日历
本轮 28 项单元测试、类型检查和生产构建通过。浏览器模拟数据验证与实际数据库未验证部分详见 [本轮验收](update-2026-10-02.md)。连接数据库发生 PROTOCOL_CONNECTION_LOST,迁移、图库初始化及真实 MySQL 并发集成仍需在可连接环境执行;前文并发余额不足的旧断言已随允许透支需求调整。
本轮 28 项单元测试、类型检查和生产构建通过。浏览器模拟数据验证与实际数据库未验证部分详见 [本轮验收](update-2026-10-02.md)。首轮连接数据库发生 PROTOCOL_CONNECTION_LOST;2026-10-02 17:54 补充验收已完成迁移、31 个图库图标初始化及全部 9 项真实 MySQL 集成检查。前文并发余额不足的旧断言已随允许透支需求调整。
+11 -1
View File
@@ -49,4 +49,14 @@ pnpm test:integration
- `pnpm test` 28 项通过:透支/负转入余额、债务超额还款拒绝、请求去重、金额范围、债务符号/币种、轨迹裁剪、计划完成/重复/失败推进、日历口径/小数汇总/缺汇率、ZIP v7 分组计划往返及旧版兼容。
- 浏览器使用明确的模拟数据检查分组弹窗切换、转账选择和月历默认今天/日期切换/上下布局及红色支出,未操作真实用户财务数据。
- 新增 `test/update-integration.test.ts`,覆盖真实 MySQL 中计划并发执行一次、透支、补记、分组恢复、隐藏账户与日历隔离,纳入 `pnpm test:integration`。
- 本环境尝试连接现有数据库时发生 `PROTOCOL_CONNECTION_LOST`,因此本轮未应用数据库迁移、未向实际数据库导入新图标、未运行真实数据库集成测试。上述命令需在数据库可连接后完成;历史验收文档的旧测试结果不代表本轮已通过。
- 首轮连接数据库发生 `PROTOCOL_CONNECTION_LOST` 时,数据库验收暂未执行;该阻塞已在下面的补充验收中解决。
## 数据库恢复后的补充验收(2026-10-02 17:54,香港时间)
- 已确认 MySQL 连接恢复并通过 SELECT 1;增量应用 `20261002000400_groups_schedules` 成功,`pnpm db:status` 确认全部 12 个迁移完成,schema 最新。
- 已生成 Prisma 客户端并编译后端,运行图库初始化;数据库检查确认 31 个固定内置图标 ID 均有图片数据且为共享图标。
- 在独立端口 3101 启动当前工作树后端,`pnpm test:integration` 9 项全部通过。测试使用随机验收账号,测试结束按对应 ID/用户名清理自己的数据,未使用真实用户账号执行记账或清空。
- 新增专项进一步验证:定时支出/转账并发只记一次、透支后的精确余额、归档账户执行失败保留到期时间和余额、21 项到期计划按 20 + 1 分批处理、内部转账不计日历收支、ZIP 分组/计划状态恢复及隐藏账户隔离。
- 修复债务集成测试中仍断言 manifest v6 的旧预期,当前应为 v7。连续重跑触发临时服务的正常登录限流后,重启本次临时测试服务再执行,未修改限流规则。
- 本轮 `pnpm test` 28 项通过,`pnpm typecheck` 通过;后端编译通过,前端生产构建在此前实现验收已通过,本轮未修改前端代码。
- 上一轮遗留的数据库迁移、图标初始化及真实 MySQL 集成验收现已完成。浏览器布局验收仍沿用此前模拟数据结果,没有新增真实浏览器端到端验收。
+2
View File
@@ -23,3 +23,5 @@
-
验证与部署边界见 [更新说明](docs/update-2026-10-02.md)。
数据库验收完成:2026-10-02 17:54(香港时间)。12 个迁移状态最新,31 个内置图标已初始化;28 项单元测试、9 项真实数据库集成检查通过。