WFDASH-WEBAPP/src/lib/server/rep-performance-service.test.ts

245 lines
7.8 KiB
TypeScript
Raw Normal View History

import { describe, it, expect } from "vitest";
import {
buildBuckets,
periodDays,
periodLabel,
resolvePeriod,
metricsTotal,
type RepMetrics,
} from "./rep-performance-service";
/**
* These cover the pure date maths behind the rep-performance report. Every
* case here is a bug that actually shipped once:
* - bucket labels rendered "01 Sept – 31 Aug" (double off-by-one),
* - single-day buckets ended at midnight so a 05:46 record fell outside
* its own bucket and the whole report read zero,
* - a Jul–Sep range collapsed its label to "July 2026".
*/
describe("resolvePeriod", () => {
const anchor = "2026-09-15"; // a Tuesday
it("defaults to the current month when nothing is supplied", () => {
const p = resolvePeriod({ now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.preset).toBe("month");
expect(p.from).toBe("2026-09-01");
expect(p.to).toBe("2026-09-30");
expect(p.granularity).toBe("week");
});
it("day preset covers exactly that day", () => {
const p = resolvePeriod({ preset: "day", anchor, now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.from).toBe("2026-09-15");
expect(p.to).toBe("2026-09-15");
expect(p.granularity).toBe("day");
});
it("week preset is Monday-anchored", () => {
const p = resolvePeriod({ preset: "week", anchor, now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.from).toBe("2026-09-14");
expect(p.to).toBe("2026-09-20");
expect(p.granularity).toBe("day");
});
it("month preset ignores the day of the anchor", () => {
const p = resolvePeriod({ preset: "month", anchor, now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.from).toBe("2026-09-01");
expect(p.to).toBe("2026-09-30");
});
it("quarter preset snaps to the containing quarter", () => {
const p = resolvePeriod({ preset: "quarter", anchor, now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.from).toBe("2026-07-01");
expect(p.to).toBe("2026-09-30");
expect(p.granularity).toBe("month");
});
it("year preset is the whole calendar year", () => {
const p = resolvePeriod({ preset: "year", anchor, now: new Date("2026-09-28T10:00:00.000Z") });
expect(p.from).toBe("2026-01-01");
expect(p.to).toBe("2026-12-31");
});
it("custom preset honours from/to", () => {
const p = resolvePeriod({
preset: "custom",
from: "2026-08-01",
to: "2026-08-31",
now: new Date("2026-09-28T10:00:00.000Z"),
});
expect(p.from).toBe("2026-08-01");
expect(p.to).toBe("2026-08-31");
expect(p.granularity).toBe("week");
});
it("custom with a reversed range is normalised rather than rejected", () => {
const p = resolvePeriod({
preset: "custom",
from: "2026-08-31",
to: "2026-08-01",
now: new Date("2026-09-28T10:00:00.000Z"),
});
expect(p.from).toBe("2026-08-31");
expect(p.to).toBe("2026-08-31");
});
it("falls back to the anchor month when custom is half-specified", () => {
const p = resolvePeriod({
preset: "custom",
from: "2026-08-01",
now: new Date("2026-09-28T10:00:00.000Z"),
});
expect(p.from).toBe("2026-09-01");
expect(p.to).toBe("2026-09-30");
});
it("rejects junk rather than throwing", () => {
const p = resolvePeriod({
preset: "nonsense" as never,
from: "not-a-date",
to: "2026-13-45",
granularity: "hourly" as never,
now: new Date("2026-09-28T10:00:00.000Z"),
});
expect(p.preset).toBe("month");
expect(p.granularity).toBe("week");
expect(p.from).toBe("2026-09-01");
});
it("explicit granularity always wins over the preset default", () => {
const p = resolvePeriod({ preset: "month", granularity: "day", anchor });
expect(p.granularity).toBe("day");
});
it("clamps an absurd custom range instead of scanning years of rows", () => {
const p = resolvePeriod({
preset: "custom",
from: "2000-01-01",
to: "2026-09-30",
now: new Date("2026-09-28T10:00:00.000Z"),
});
expect(periodDays(p)).toBe(401); // capped at 400 days + the inclusive end
});
});
describe("buildBuckets", () => {
const september = { preset: "month" as const, from: "2026-09-01", to: "2026-09-30" };
it("weekly buckets are contiguous, exhaustive and start at the window start", () => {
const buckets = buildBuckets({ ...september, granularity: "week" });
expect(buckets[0].start).toBe("2026-09-01");
expect(buckets.at(-1)!.end).toBe("2026-09-30");
for (let i = 1; i < buckets.length; i++) {
const prev = new Date(`${buckets[i - 1].end}T00:00:00.000Z`).getTime();
const next = new Date(`${buckets[i].start}T00:00:00.000Z`).getTime();
expect(next - prev).toBe(86_400_000); // no gap, no overlap
}
});
it("bucket labels never end before they start", () => {
// Regression: labels used to render "01 Sept – 31 Aug".
for (const granularity of ["day", "week", "month"] as const) {
for (const b of buildBuckets({ ...september, granularity })) {
expect(b.start <= b.end).toBe(true);
}
}
});
it("monthly fold of a month range is a single bucket", () => {
const buckets = buildBuckets({ ...september, granularity: "month" });
expect(buckets).toHaveLength(1);
expect(buckets[0]).toMatchObject({ start: "2026-09-01", end: "2026-09-30" });
});
it("daily fold yields one bucket per day in the window", () => {
const buckets = buildBuckets({ ...september, granularity: "day" });
expect(buckets).toHaveLength(30);
expect(buckets[0]).toMatchObject({ start: "2026-09-01", end: "2026-09-01" });
expect(buckets.at(-1)).toMatchObject({ start: "2026-09-30", end: "2026-09-30" });
});
it("handles a single-day period", () => {
const buckets = buildBuckets({
preset: "day",
from: "2026-09-15",
to: "2026-09-15",
granularity: "day",
});
expect(buckets).toHaveLength(1);
});
it("returns nothing for unparseable dates", () => {
expect(
buildBuckets({ preset: "custom", from: "nope", to: "also-nope", granularity: "day" }),
).toEqual([]);
});
});
describe("periodLabel", () => {
it("names a whole month", () => {
expect(
periodLabel({ preset: "month", from: "2026-09-01", to: "2026-09-30", granularity: "week" }),
).toBe("September 2026");
});
it("names a single day", () => {
expect(
periodLabel({ preset: "day", from: "2026-09-15", to: "2026-09-15", granularity: "day" }),
).toBe("15 September 2026");
});
it("names a whole week", () => {
expect(
periodLabel({ preset: "week", from: "2026-09-14", to: "2026-09-20", granularity: "day" }),
).toBe("Week of 14 September 2026");
});
it("does not collapse a multi-month range to its first month", () => {
// Regression: a Jul–Sep range used to report "July 2026".
const label = periodLabel({
preset: "custom",
from: "2026-07-01",
to: "2026-09-30",
granularity: "month",
});
expect(label).not.toBe("July 2026");
expect(label).toContain("1 July");
expect(label).toContain("30 September 2026");
});
it("spells out an arbitrary range", () => {
const label = periodLabel({
preset: "custom",
from: "2026-09-03",
to: "2026-09-19",
granularity: "week",
});
expect(label).toBe("3 September – 19 September 2026");
});
});
describe("metricsTotal", () => {
it("sums every metric", () => {
const m: RepMetrics = {
quotesLoaded: 1,
quotesCompleted: 2,
ordersPlaced: 3,
ordersReceived: 4,
deliveryNotesRaised: 5,
};
expect(metricsTotal(m)).toBe(15);
});
it("is zero for an all-zero row", () => {
expect(
metricsTotal({
quotesLoaded: 0,
quotesCompleted: 0,
ordersPlaced: 0,
ordersReceived: 0,
deliveryNotesRaised: 0,
}),
).toBe(0);
});
});