// 「哪一天」的定义 —— 未成年人模式每日限额的地基(《拟人化互动办法》第十四条「使用时长限制」)。 // // ⚠ 这组测试存在的理由:原实现用 now.getFullYear()/getMonth()/getDate() 读【进程本地时区】, // 并在注释里写「部署时服务器 TZ 应为 Asia/Shanghai」—— 但注释不是保证。共享 ECS 若是 UTC, // 「每日 60 分钟」会在北京时间早上 8 点重置而不是午夜:限额名不副实, // 没有报错、没有告警、没有任何人会发现。合规义务不能挂在一条没人签字、没人复查的部署约定上。 // // ⚠ 别用 `TZ=UTC node --test` 这种方式测:Windows 上 TZ 环境变量对 Node 不生效, // 三个时区会全部返回同一个值 —— 那不是「代码锁死了时区」,那是【测试本身是坏的】。 // (我们真的被这个绿色骗过一次。) // 下面的断言不依赖进程 TZ:给定一个【确切的 UTC 时刻】,断言它在北京时间的日历日 —— // 服务器在哪个时区,这个答案都必须一样。 import test from 'node:test'; import assert from 'node:assert/strict'; import { dayKey, lastDayKeys, DAY_TZ } from '../src/lib/day.js'; test('切日时区恒为 Asia/Shanghai', () => { assert.equal(DAY_TZ, 'Asia/Shanghai'); }); test('★ 跨日的那一刻:18:30Z 在北京已是次日凌晨 2:30 —— 必须算作次日', () => { // 这一条是整组的核心。若 dayKey 读的是服务器本地时区: // UTC 服务器 → '2026-07-16'(错,北京已经是 17 号了) // 纽约服务器 → '2026-07-16'(错) // 只有显式锁 Asia/Shanghai 才恒为 '2026-07-17'。 assert.equal(dayKey(new Date('2026-07-16T18:30:00Z')), '2026-07-17'); }); test('★ 北京的午夜前后:15:59Z 是当天,16:00Z 就跨日了', () => { // 北京 = UTC+8,故 UTC 16:00 整 = 北京次日 00:00。 assert.equal(dayKey(new Date('2026-07-16T15:59:59Z')), '2026-07-16'); // 北京 23:59:59 assert.equal(dayKey(new Date('2026-07-16T16:00:00Z')), '2026-07-17'); // 北京 次日 00:00 // ↑ 这两条把「每日限额到底几点重置」钉死在北京的午夜。 // 若哪天有人把 DAY_TZ 改了、或把 Intl 换回 getDate(),这两条会立刻红。 }); test('UTC 白天的时刻不该被误判', () => { assert.equal(dayKey(new Date('2026-07-16T04:00:00Z')), '2026-07-16'); // 北京 12:00 assert.equal(dayKey(new Date('2026-01-01T00:00:00Z')), '2026-01-01'); // 北京 08:00,跨年也不许错 }); test('跨年:12/31 的 16:00Z 在北京已是元旦', () => { assert.equal(dayKey(new Date('2025-12-31T16:00:00Z')), '2026-01-01'); }); test('格式恒为 YYYY-MM-DD,月日补零', () => { assert.match(dayKey(new Date('2026-03-05T04:00:00Z')), /^\d{4}-\d{2}-\d{2}$/); assert.equal(dayKey(new Date('2026-03-05T04:00:00Z')), '2026-03-05'); }); test('lastDayKeys:从旧到新,末位是今天,且同样按北京时间切', () => { // 起点选北京时间 7/17 02:30(= 7/16 18:30Z)—— 若这里退回按服务器本地日历日构造, // 末位会变成 '2026-07-16',这条会红。 const keys = lastDayKeys(3, new Date('2026-07-16T18:30:00Z')); assert.deepEqual(keys, ['2026-07-15', '2026-07-16', '2026-07-17']); assert.equal(keys[keys.length - 1], dayKey(new Date('2026-07-16T18:30:00Z')), '末位必须是今天'); }); test('lastDayKeys:跨月不许错', () => { assert.deepEqual(lastDayKeys(3, new Date('2026-03-01T04:00:00Z')), ['2026-02-27', '2026-02-28', '2026-03-01']); }); test('lastDayKeys(1) 只给今天', () => { assert.deepEqual(lastDayKeys(1, new Date('2026-07-16T04:00:00Z')), ['2026-07-16']); }); test('记账与查账必须是同一个「今天」', () => { // minor.js 按 dayKey 记账、guardian.js 按 lastDayKeys 查账。两者口径若分叉, // 家长看到的「今天用了多久」会和后端真正拿来拦人的数字对不上 —— 而这种错不报任何错, // 只会让家长觉得「这功能在骗我」。这条断言把两者钉在一起。 const now = new Date('2026-07-16T18:30:00Z'); const last = lastDayKeys(7, now); assert.equal(last[last.length - 1], dayKey(now)); });