Files
SubsTracker/tests/data/reminders.test.js
wangwangit b9de410f6f feat(observability): 三类可观测性仓库 + 迁移扩展
新增三个 KV 仓库(v3 通知/调度可观测性数据底座):

1. src/data/reminders.repo.js
   - reminder_rules:{subId} 数组,支持 CRUD + 智能预设(4 条:到期前 7/3/1/当天)
   - legacyFieldToRule 把 v2 的 reminderUnit/reminderValue 单点提醒
     转成 1 条等价 before_expiry 规则
   - normalizeRule 修复非法字段,repeatInterval 仅 after_expiry 类型保留

2. src/data/notification-logs.repo.js
   - notify_log:{ymdh}:{subId}:{ruleId}:{channel}:{rand}, TTL 30 天
   - writeLog / query(subId/channel/status/since/until/limit 过滤)
   - 时间倒序,KV.list 拉全后排序

3. src/data/scheduler-logs.repo.js
   - sched_log:{isoUtc}, TTL 30 天
   - writeLog / getRecent(按时间倒序)

迁移扩展:MIGRATION_STEPS 追加两个 step
   - reminder_rules_v3:为现有订阅生成默认提醒规则
   - scheduler_logs_v3:v2 的 scheduler_status_history 转换到新表

调试页新增链接:/debug?export=sched_logs&limit=50 直接下载 JSON

测试:
- tests/data/reminders.test.js (13)
- tests/data/notification-logs.test.js (8)
- tests/data/scheduler-logs.test.js (4)
- 共 85 条测试全绿;lint 干净

Refs Task 4 of refactor/v3-product-grade plan.
2026-05-24 18:03:01 +08:00

101 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
/**
* 提醒规则仓库单元测试
*/
import { describe, it, expect, beforeEach } from 'vitest';
// @ts-ignore
import { env } from 'cloudflare:test';
import * as repo from '../../src/data/reminders.repo.js';
async function clearKv() {
const list = await env.SUBSCRIPTIONS_KV.list();
await Promise.all(list.keys.map((k) => env.SUBSCRIPTIONS_KV.delete(k.name)));
}
beforeEach(clearKv);
describe('reminders.repo', () => {
it('listForSubscription 空返回空数组', async () => {
expect(await repo.listForSubscription(env, 's1')).toEqual([]);
});
it('addRule + listForSubscription', async () => {
const r = await repo.addRule(env, 's1', { type: 'before_expiry', value: 7, unit: 'days' });
expect(r.id).toBeTruthy();
expect(r.value).toBe(7);
const list = await repo.listForSubscription(env, 's1');
expect(list).toHaveLength(1);
expect(list[0].id).toBe(r.id);
});
it('updateRule 合并 patch', async () => {
const r = await repo.addRule(env, 's1', { type: 'before_expiry', value: 7, unit: 'days' });
const upd = await repo.updateRule(env, 's1', r.id, { value: 3, isEnabled: false });
expect(upd?.value).toBe(3);
expect(upd?.isEnabled).toBe(false);
const list = await repo.listForSubscription(env, 's1');
expect(list[0].value).toBe(3);
});
it('deleteRule 不存在返回 false', async () => {
expect(await repo.deleteRule(env, 's1', 'nope')).toBe(false);
});
it('deleteRule 存在返回 true', async () => {
const r = await repo.addRule(env, 's1', { type: 'on_expiry', value: 0, unit: 'days' });
expect(await repo.deleteRule(env, 's1', r.id)).toBe(true);
expect(await repo.listForSubscription(env, 's1')).toEqual([]);
});
it('clearForSubscription 清空整个 key', async () => {
await repo.addRule(env, 's1', { type: 'before_expiry', value: 7, unit: 'days' });
await repo.clearForSubscription(env, 's1');
expect(await repo.listForSubscription(env, 's1')).toEqual([]);
});
it('defaultPresetRules 返回 4 条预设7/3/1/当天)', () => {
const rules = repo.defaultPresetRules();
expect(rules).toHaveLength(4);
expect(rules.map((r) => `${r.type}:${r.value}${r.unit[0]}`)).toEqual([
'before_expiry:7d',
'before_expiry:3d',
'before_expiry:1d',
'on_expiry:0d'
]);
rules.forEach((r) => expect(r.isEnabled).toBe(true));
});
it('legacyFieldToRuleday 单位 7 → before_expiry/7/days', () => {
const r = repo.legacyFieldToRule({ reminderUnit: 'day', reminderValue: 7 });
expect(r).toMatchObject({ type: 'before_expiry', value: 7, unit: 'days' });
});
it('legacyFieldToRulehour 单位', () => {
const r = repo.legacyFieldToRule({ reminderUnit: 'hour', reminderValue: 12 });
expect(r).toMatchObject({ type: 'before_expiry', value: 12, unit: 'hours' });
});
it('legacyFieldToRulevalue=0 → on_expiry', () => {
const r = repo.legacyFieldToRule({ reminderUnit: 'day', reminderValue: 0 });
expect(r.type).toBe('on_expiry');
});
it('legacyFieldToRule缺失值兜底 7 天', () => {
const r = repo.legacyFieldToRule({});
expect(r).toMatchObject({ type: 'before_expiry', value: 7, unit: 'days' });
});
it('normalizeRule非法 type 兜底为 before_expiry', () => {
const r = repo.normalizeRule({ type: 'lol', value: 5, unit: 'days' });
expect(r.type).toBe('before_expiry');
});
it('normalizeRulerepeatInterval 仅 after_expiry 保留', () => {
const a = repo.normalizeRule({ type: 'before_expiry', value: 1, unit: 'days', repeatInterval: 24 });
expect(a.repeatInterval).toBeNull();
const b = repo.normalizeRule({ type: 'after_expiry', value: 0, unit: 'days', repeatInterval: 24 });
expect(b.repeatInterval).toBe(24);
});
});