usage-aggregator.test.ts66 lines · main
1/**
2 * Hour-boundary tests for the usage aggregator. The DB-touching path
3 * (aggregateUsageForCompletedHour) is exercised by the post-deploy
4 * smoke; this file pins the pure boundary math + the conflict-update
5 * shape so a future tweak surfaces in CI.
6 */
7
8import { describe, expect, test } from 'bun:test';
9
10import { currentHourStart } from './usage-aggregator.js';
11
12describe('currentHourStart', () => {
13 test('snaps to the first millisecond of the containing UTC hour', () => {
14 // 14:23:45.678 UTC → 14:00:00.000 UTC
15 const mid = new Date('2026-05-11T14:23:45.678Z');
16 const out = currentHourStart(mid);
17 expect(out.toISOString()).toBe('2026-05-11T14:00:00.000Z');
18 });
19
20 test('the first millisecond of an hour stays on that hour', () => {
21 // 14:00:00.000 UTC → 14:00:00.000 UTC (idempotent on a boundary)
22 const exact = new Date('2026-05-11T14:00:00.000Z');
23 expect(currentHourStart(exact).toISOString()).toBe('2026-05-11T14:00:00.000Z');
24 });
25
26 test('the last millisecond of an hour stays in that hour', () => {
27 // 14:59:59.999 UTC → 14:00:00.000 UTC (not rolled forward)
28 const last = new Date('2026-05-11T14:59:59.999Z');
29 expect(currentHourStart(last).toISOString()).toBe('2026-05-11T14:00:00.000Z');
30 });
31
32 test('midnight UTC handles correctly', () => {
33 const midnight = new Date('2026-05-11T00:00:00.000Z');
34 expect(currentHourStart(midnight).toISOString()).toBe('2026-05-11T00:00:00.000Z');
35 });
36
37 test('crosses month boundary correctly', () => {
38 // 23:59:59.999 on the last day of April → 23:00 same day
39 const lastMin = new Date('2026-04-30T23:59:59.999Z');
40 expect(currentHourStart(lastMin).toISOString()).toBe('2026-04-30T23:00:00.000Z');
41 });
42});
43
44describe('completed-hour window', () => {
45 // The aggregator rolls up the hour that JUST ENDED — [start, end)
46 // where end == currentHourStart(now). This pins that the window is
47 // exactly one hour and ends at a clean boundary.
48 function completedHourWindow(now: Date): { start: Date; end: Date } {
49 const end = currentHourStart(now);
50 const start = new Date(end.getTime() - 60 * 60 * 1000);
51 return { start, end };
52 }
53
54 test('rolls up the previous full hour', () => {
55 const now = new Date('2026-05-11T14:05:00.000Z');
56 const { start, end } = completedHourWindow(now);
57 expect(start.toISOString()).toBe('2026-05-11T13:00:00.000Z');
58 expect(end.toISOString()).toBe('2026-05-11T14:00:00.000Z');
59 });
60
61 test('window is exactly 60 minutes wide', () => {
62 const now = new Date('2026-05-11T03:30:00.000Z');
63 const { start, end } = completedHourWindow(now);
64 expect(end.getTime() - start.getTime()).toBe(60 * 60 * 1000);
65 });
66});