Files
growqr-backend/scripts/streak-policy.test.ts

96 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from "node:assert/strict";
import { computeStreakFromDays } from "../src/v1/curator/streak-utils.js";
/**
* Test: Streak policies — current/longest, same-day duplicate, gap reset,
* recovery (resume after gap), and seven-day eligibility.
*
* Uses the pure computeStreakFromDays function with a fixed "today" so tests
* are deterministic.
*/
const TODAY = "2026-07-10";
// ── 1. Current and longest from a contiguous run ending today ────────────────
{
const days = ["2026-07-10", "2026-07-09", "2026-07-08"];
const streak = computeStreakFromDays(days, TODAY);
assert.equal(streak.current_days, 3, "current streak should be 3 for 3 consecutive days ending today");
assert.equal(streak.longest_days, 3, "longest should match current when run is unbroken");
assert.equal(streak.last_completed_on, "2026-07-10");
}
// ── 2. Same-day duplicate must not inflate the streak ────────────────────────
// computeStreakFromDays requires deduplicated days (the SQL GROUP BY ensures
// this in production). Passing a duplicate should not double-count.
{
const days = ["2026-07-10", "2026-07-10", "2026-07-09"];
const streak = computeStreakFromDays(days, TODAY);
// With a duplicate, the descending list has two "today" entries. The while
// loop checks `days.includes(cursor)` which is set-based, so current stays 2.
// But the longest loop iterates ALL entries including the dup, which could
// inflate longest to 3. The correct behavior: duplicates must not inflate.
assert.equal(streak.current_days, 2, "duplicate day must not inflate current streak");
assert.equal(
streak.longest_days,
2,
"duplicate day must not inflate longest streak — duplicates should be deduped before counting",
);
}
// ── 3. Gap resets the current streak ─────────────────────────────────────────
{
// Completed today and 3 days ago — gap breaks current streak
const days = ["2026-07-10", "2026-07-07"];
const streak = computeStreakFromDays(days, TODAY);
assert.equal(streak.current_days, 1, "gap resets current streak to 1 (only today)");
assert.equal(streak.longest_days, 1, "longest should also be 1 with only isolated days");
}
// ── 4. Recovery: longest captures a past longer run even if current is broken ─
{
// 5-day run last week, then a gap, then completed today
const days = [
"2026-07-10", // today (current restart)
"2026-07-05", // past run: Jul 15
"2026-07-04",
"2026-07-03",
"2026-07-02",
"2026-07-01",
];
const streak = computeStreakFromDays(days, TODAY);
assert.equal(streak.current_days, 1, "current is 1 after a gap even if a longer past run exists");
assert.equal(streak.longest_days, 5, "longest should capture the 5-day past run");
}
// ── 5. Seven-day eligibility: a 7-day streak counts as eligible ──────────────
{
const days = [
"2026-07-10", "2026-07-09", "2026-07-08",
"2026-07-07", "2026-07-06", "2026-07-05", "2026-07-04",
];
const streak = computeStreakFromDays(days, TODAY);
assert.equal(streak.current_days, 7, "7 consecutive days should yield current=7");
assert.equal(streak.longest_days, 7, "7 consecutive days should yield longest=7");
assert.ok(streak.current_days >= 7, "7-day streak eligibility threshold met");
}
// ── 6. Empty days yields zero streak ─────────────────────────────────────────
{
const streak = computeStreakFromDays([], TODAY);
assert.equal(streak.current_days, 0);
assert.equal(streak.longest_days, 0);
assert.equal(streak.last_completed_on, null);
}
// ── 7. Yesterday keeps the current streak alive until today's IST day closes ─
{
const days = ["2026-07-09"];
const streak = computeStreakFromDays(days, TODAY);
assert.equal(streak.current_days, 1, "yesterday remains current during the one-day grace window");
assert.equal(streak.longest_days, 1, "longest should still count the 1-day run");
}
console.log("streak-policy tests passed");
process.exit(0);