import { describe, expect, it } from "@suaveplan/testing/runner"; import type { Clock } from "@suaveplan/types"; import { TimeEncodingType } from "../types/license.js"; import { checkExpiration, dayNumberToDate, getDayNumber } from "./time.js"; describe("time helpers", () => { it("NONE is never expired", { timeout: 5000 }, () => { const result = checkExpiration(TimeEncodingType.NONE, 0); expect(result.isExpired).toBe(false); expect(result.expirationDate).toBeUndefined(); }); it("ABSOLUTE_DAYS expires after its day number", { timeout: 5000 }, () => { const day = 20000; // 2024-10-04ish const refAfter = new Date((day + 1) * 86_400_000); const refBefore = new Date((day - 1) * 86_400_000); const after = checkExpiration( TimeEncodingType.ABSOLUTE_DAYS, day, refAfter ); const before = checkExpiration( TimeEncodingType.ABSOLUTE_DAYS, day, refBefore ); expect(after.isExpired).toBe(true); expect(after.expirationDate?.getTime()).toBe(day * 86_400_000); expect(before.isExpired).toBe(false); }); it("RELATIVE_DAYS uses creationDate baseline", { timeout: 5000 }, () => { const creation = new Date("2026-01-01T00:00:00Z"); const refAfter = new Date("2026-01-15T00:00:01Z"); // 14 days + 1s const refBefore = new Date("2026-01-10T00:00:00Z"); const after = checkExpiration( TimeEncodingType.RELATIVE_DAYS, 14, refAfter, creation ); const before = checkExpiration( TimeEncodingType.RELATIVE_DAYS, 14, refBefore, creation ); expect(after.isExpired).toBe(true); expect(before.isExpired).toBe(false); }); it("RELATIVE_DAYS falls back to referenceDate when no creationDate", { timeout: 5000, }, () => { const ref = new Date("2026-01-01T00:00:00Z"); const r = checkExpiration(TimeEncodingType.RELATIVE_DAYS, 5, ref); expect(r.isExpired).toBe(false); expect(r.expirationDate?.getTime()).toBe( ref.getTime() + 5 * 86_400_000 ); }); it("ABSOLUTE_TIMESTAMP expires after its second", { timeout: 5000 }, () => { const ts = 1_700_000_000; // a moment in 2023 const before = checkExpiration( TimeEncodingType.ABSOLUTE_TIMESTAMP, ts, new Date((ts - 10) * 1000) ); const after = checkExpiration( TimeEncodingType.ABSOLUTE_TIMESTAMP, ts, new Date((ts + 10) * 1000) ); expect(before.isExpired).toBe(false); expect(after.isExpired).toBe(true); }); it("uses injected Clock when no referenceDate supplied", { timeout: 5000, }, () => { const fixed = 1_000_000_000_000; // 2001-09-09T01:46:40Z const clock: Clock = () => fixed; // ABSOLUTE_DAYS at day 11574 (which equals fixed/MS_PER_DAY=11574) const day = Math.floor(fixed / 86_400_000); const exactNow = checkExpiration( TimeEncodingType.ABSOLUTE_DAYS, day - 1, undefined, undefined, clock ); expect(exactNow.isExpired).toBe(true); }); it("default unknown encoding falls through to non-expired", { timeout: 5000, }, () => { const r = checkExpiration(99 as TimeEncodingType, 0); expect(r.isExpired).toBe(false); }); it("getDayNumber uses provided date", { timeout: 5000 }, () => { expect(getDayNumber(new Date(0))).toBe(0); expect(getDayNumber(new Date(86_400_000))).toBe(1); }); it("getDayNumber uses injected clock when no date", { timeout: 5000, }, () => { const clock: Clock = () => 5 * 86_400_000; expect(getDayNumber(undefined, clock)).toBe(5); }); it("dayNumberToDate inverts getDayNumber", { timeout: 5000 }, () => { expect(dayNumberToDate(10).getTime()).toBe(10 * 86_400_000); }); });