import { describe, it, expect } from "vitest"; import { addMonths, periodsBetween, priceSubscriptionSpan } from "./index.js"; // Pricing a subscription SPAN against a plan. Period counting is CEIL — any started // period is a full one (hotel/parking practice). See wiki/entities/subscription.md. const iso = (s: string) => new Date(s).toISOString(); describe("periodsBetween — day", () => { it("exact multiples are not rounded up", () => { expect(periodsBetween("day", iso("2026-06-20T10:00:00Z"), iso("2026-06-23T10:00:00Z"))).toBe(3); }); it("any started day rounds up (hotel check-out mid-day)", () => { // Mon 14:00 → Wed 10:00 = 1.83 days → 2. expect(periodsBetween("day", iso("2026-06-22T14:00:00Z"), iso("2026-06-24T10:00:00Z"))).toBe(2); }); it("a sliver over zero is 1 day", () => { expect(periodsBetween("day", iso("2026-06-20T00:00:00Z"), iso("2026-06-20T00:01:00Z"))).toBe(1); }); }); describe("periodsBetween — week", () => { it("exactly two weeks", () => { expect(periodsBetween("week", iso("2026-06-01T00:00:00Z"), iso("2026-06-15T00:00:00Z"))).toBe(2); }); it("eight days rounds up to 2 weeks", () => { expect(periodsBetween("week", iso("2026-06-01T00:00:00Z"), iso("2026-06-09T00:00:00Z"))).toBe(2); }); }); describe("periodsBetween — month (whole-month walk, day-overflow clamp)", () => { it("exactly one month", () => { expect(periodsBetween("month", iso("2026-01-15T00:00:00Z"), iso("2026-02-15T00:00:00Z"))).toBe(1); }); it("just over one month rounds up to 2", () => { expect(periodsBetween("month", iso("2026-01-15T00:00:00Z"), iso("2026-02-16T00:00:00Z"))).toBe(2); }); it("Jan 31 + clamp: Jan 31 → Feb 28 is one month", () => { // addMonths clamps Jan 31 +1mo to Feb 28; a span to exactly Feb 28 = 1 month. expect(addMonths(iso("2026-01-31T00:00:00Z"), 1)).toBe(iso("2026-02-28T00:00:00Z")); expect(periodsBetween("month", iso("2026-01-31T00:00:00Z"), iso("2026-02-28T00:00:00Z"))).toBe(1); }); it("three months", () => { expect(periodsBetween("month", iso("2026-01-10T00:00:00Z"), iso("2026-04-10T00:00:00Z"))).toBe(3); }); }); describe("periodsBetween — degenerate spans", () => { it("equal/inverted spans price to 0 periods", () => { expect(periodsBetween("day", iso("2026-06-20T00:00:00Z"), iso("2026-06-20T00:00:00Z"))).toBe(0); expect(periodsBetween("day", iso("2026-06-21T00:00:00Z"), iso("2026-06-20T00:00:00Z"))).toBe(0); }); }); describe("priceSubscriptionSpan", () => { const hotelDaily = { period: "day" as const, pricePerPeriodMinor: 80000, currency: "ALL" }; it("hotel: 3 clean days × 800 ALL = 2,400 ALL", () => { const q = priceSubscriptionSpan(hotelDaily, iso("2026-06-20T12:00:00Z"), iso("2026-06-23T12:00:00Z")); expect(q).toEqual({ periods: 3, amountMinor: 240000, currency: "ALL", period: "day" }); }); it("hotel: part-day rounds up the charge", () => { const q = priceSubscriptionSpan(hotelDaily, iso("2026-06-22T14:00:00Z"), iso("2026-06-24T10:00:00Z")); expect(q.periods).toBe(2); expect(q.amountMinor).toBe(160000); }); it("monthly plan: 10,000 ALL/mo over 2 months", () => { const monthly = { period: "month" as const, pricePerPeriodMinor: 1000000, currency: "ALL" }; const q = priceSubscriptionSpan(monthly, iso("2026-01-01T00:00:00Z"), iso("2026-03-01T00:00:00Z")); expect(q).toEqual({ periods: 2, amountMinor: 2000000, currency: "ALL", period: "month" }); }); });