feat(intake-links): per-(link, project) daily-cap pure helpers
Phase 9a Task 2. Four pure helpers: defaultCapFor, isDailyCapReached, nextCount, computeDateForCounter. 7 unit tests cover the caps boundary (cap=0 means 'no submissions today'; todayCount === cap is reached), the count increment, and the local-date format. Note: project convention puts unit tests next to source (src/**/*.test.ts), not under tests/unit/. Adjusted from the plan.
This commit is contained in:
parent
eb45fb0168
commit
c4cd1e3646
2 changed files with 88 additions and 0 deletions
48
src/lib/intake-links/submissions.test.ts
Normal file
48
src/lib/intake-links/submissions.test.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import {
|
||||
computeDateForCounter,
|
||||
defaultCapFor,
|
||||
isDailyCapReached,
|
||||
nextCount,
|
||||
} from "@/lib/intake-links/submissions";
|
||||
|
||||
describe("defaultCapFor", () => {
|
||||
it("returns the row cap when set, otherwise 100", () => {
|
||||
expect(defaultCapFor(0)).toBe(100);
|
||||
expect(defaultCapFor(50)).toBe(50);
|
||||
expect(defaultCapFor(10000)).toBe(10000);
|
||||
});
|
||||
|
||||
it("treats undefined as 'use default'", () => {
|
||||
expect(defaultCapFor(undefined)).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDailyCapReached", () => {
|
||||
it("is false when the counter is below the cap", () => {
|
||||
expect(isDailyCapReached(0, 100)).toBe(false);
|
||||
expect(isDailyCapReached(99, 100)).toBe(false);
|
||||
});
|
||||
it("is true when the counter equals or exceeds the cap", () => {
|
||||
expect(isDailyCapReached(100, 100)).toBe(true);
|
||||
expect(isDailyCapReached(101, 100)).toBe(true);
|
||||
});
|
||||
it("treats cap=0 as 'no submissions today'", () => {
|
||||
expect(isDailyCapReached(0, 0)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("nextCount", () => {
|
||||
it("increments by one", () => {
|
||||
expect(nextCount(0)).toBe(1);
|
||||
expect(nextCount(7)).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computeDateForCounter", () => {
|
||||
it("formats a Date as YYYY-MM-DD in local time", () => {
|
||||
const d = new Date(2026, 8, 21, 12, 0, 0);
|
||||
expect(computeDateForCounter(d)).toBe("2026-09-21");
|
||||
});
|
||||
});
|
||||
40
src/lib/intake-links/submissions.ts
Normal file
40
src/lib/intake-links/submissions.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Per-(link, project) daily-cap helpers for the Phase 9 picker path.
|
||||
*
|
||||
* Phase 8 stored `submission_count_today` on the link row itself, which
|
||||
* silently meant "across all projects on this link". With one link per
|
||||
* client, that cap stops being meaningful — one busy project could lock
|
||||
* the rest of the client's projects out. Phase 9 moves the counter onto a
|
||||
* new `intake_link_submissions` collection, one row per
|
||||
* (link, project, date), so each project has its own daily budget.
|
||||
*
|
||||
* These helpers are pure. The PocketBase reads/writes live in
|
||||
* `src/lib/intake-links/index.ts` next to `resolveIntakeLink`.
|
||||
*/
|
||||
|
||||
export const DEFAULT_DAILY_CAP = 100;
|
||||
|
||||
/** Resolve the effective cap from the row's optional field. */
|
||||
export function defaultCapFor(rowCap: number | undefined): number {
|
||||
if (typeof rowCap === "number" && rowCap > 0) return rowCap;
|
||||
return DEFAULT_DAILY_CAP;
|
||||
}
|
||||
|
||||
/** True when today's count has reached or exceeded the cap. */
|
||||
export function isDailyCapReached(todayCount: number, cap: number): boolean {
|
||||
if (cap <= 0) return true; // explicit zero means "no submissions today"
|
||||
return todayCount >= cap;
|
||||
}
|
||||
|
||||
/** Increment. Pure. */
|
||||
export function nextCount(todayCount: number): number {
|
||||
return todayCount + 1;
|
||||
}
|
||||
|
||||
/** Local-date string in YYYY-MM-DD form. Same shape as the Phase 8 helper. */
|
||||
export function computeDateForCounter(now: Date = new Date()): string {
|
||||
const y = now.getFullYear();
|
||||
const m = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(now.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
Loading…
Reference in a new issue