tests/billing-period-stats.unit.test.ts
Rama main · réplica de lectura del repositorio original.
import { describe, expect, it } from 'vitest'
import { billingPeriodStatsTestUtils } from '../supabase/functions/_backend/triggers/cron_email.ts'
// The SQL cycle helper itself (billing_period_completed_cycle) is covered by
// supabase/tests/63_test_billing_period_stats_email.sql; here we only test the
// TypeScript mapping from a half-open cycle to the inclusive metrics range.
const { toDateOnlyUtc, addDaysToDateOnly, billingPeriodMetricsRange } = billingPeriodStatsTestUtils
const DAY_MS = 24 * 60 * 60 * 1000
const iso = (date: Date) => date.toISOString().slice(0, 10)
function clampedAnniversary(year: number, monthIndex: number, dom: number) {
return new Date(Date.UTC(year, monthIndex, Math.min(dom, new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate())))
}
describe('billing period stats date helpers', () => {
it.concurrent('extracts calendar date from timestamptz without shifting days', () => {
expect(toDateOnlyUtc('2026-06-15T13:54:45+00:00')).toBe('2026-06-15')
expect(toDateOnlyUtc('2026-06-15 00:00:00+00')).toBe('2026-06-15')
expect(toDateOnlyUtc('2026-07-15')).toBe('2026-07-15')
})
it.concurrent('shifts date-only values in UTC', () => {
expect(addDaysToDateOnly('2026-07-15', -1)).toBe('2026-07-14')
expect(addDaysToDateOnly('2026-03-01', -1)).toBe('2026-02-28')
})
it.concurrent('maps half-open cycle bounds to inclusive metrics end', () => {
expect(billingPeriodMetricsRange('2026-06-15T00:00:00+00:00', '2026-07-15T00:00:00+00:00')).toEqual({
periodStart: '2026-06-15',
periodEndExclusive: '2026-07-15',
metricsEndInclusive: '2026-07-14',
})
})
it.concurrent.each([
[31, '2025-12-31', '2026-01-31'],
[31, '2026-01-31', '2026-02-28'],
[31, '2026-02-28', '2026-03-31'],
[31, '2024-01-31', '2024-02-29'],
[31, '2024-02-29', '2024-03-31'],
[30, '2026-01-30', '2026-02-28'],
[29, '2025-01-29', '2025-02-28'],
[1, '2026-02-01', '2026-03-01'],
])('maps the DOM %i month-end cycle %s..%s to metrics ending the day before', (_dom, start, end) => {
const range = billingPeriodMetricsRange(`${start}T00:00:00.000Z`, `${end}T00:00:00.000Z`)
expect(range.periodStart).toBe(start)
expect(range.periodEndExclusive).toBe(end)
expect(range.metricsEndInclusive).toBe(addDaysToDateOnly(end, -1))
})
it.concurrent('covers exactly every day of each clamped monthly cycle for every DOM over three years', () => {
const bad: string[] = []
for (let dom = 1; dom <= 31; dom++) {
for (let month = 0; month < 36; month++) {
const start = clampedAnniversary(2024, month - 1, dom)
const end = clampedAnniversary(2024, month, dom)
const range = billingPeriodMetricsRange(start.toISOString(), end.toISOString())
const halfOpenDays = Math.round((end.getTime() - start.getTime()) / DAY_MS)
const inclusiveDays = Math.round((Date.parse(range.metricsEndInclusive) - Date.parse(range.periodStart)) / DAY_MS) + 1
if (range.periodStart !== iso(start) || range.periodEndExclusive !== iso(end) || inclusiveDays !== halfOpenDays || halfOpenDays < 28 || halfOpenDays > 31)
bad.push(`dom=${dom} ${iso(start)}..${iso(end)} ${JSON.stringify(range)}`)
}
}
expect(bad.slice(0, 20)).toEqual([])
})
})