// Contract for the review-wait liveness policy — the ISO-8601 timeout validation handed to the // process's timer catch, and the Copilot re-request nudge cooldown. Both are env-driven, so a // blank/malformed operator value must fall back to a sane default rather than deploy an // uninterpretable timer or a runaway nudge interval. Run with `node --test`. import { test } from "node:test"; import { assertEquals } from "#test-assert"; import { clampNudgeMinutes, DEFAULT_REVIEW_NUDGE_MINUTES, DEFAULT_REVIEW_WAIT_TIMEOUT, isoDurationToMs, isReviewStale, MAX_REVIEW_NUDGE_MINUTES, reviewWaitTimeout, } from "./reviewWait.ts"; test("DEFAULT_REVIEW_WAIT_TIMEOUT: pins the user-visible default to PT30M", () => { // A direct regression guard on the literal default — the fallback-based assertions below compare // against DEFAULT_REVIEW_WAIT_TIMEOUT itself, so they would still pass if it were accidentally // reverted to PT20M. This anchors the intended value so that change is caught. assertEquals(DEFAULT_REVIEW_WAIT_TIMEOUT, "PT30M"); }); test("reviewWaitTimeout: blank / absent / malformed → default", () => { assertEquals(reviewWaitTimeout(undefined), DEFAULT_REVIEW_WAIT_TIMEOUT); assertEquals(reviewWaitTimeout(""), DEFAULT_REVIEW_WAIT_TIMEOUT); assertEquals(reviewWaitTimeout(" "), DEFAULT_REVIEW_WAIT_TIMEOUT); assertEquals(reviewWaitTimeout("20m"), DEFAULT_REVIEW_WAIT_TIMEOUT); // missing leading P/T assertEquals(reviewWaitTimeout("P"), DEFAULT_REVIEW_WAIT_TIMEOUT); // no component assertEquals(reviewWaitTimeout("PT"), DEFAULT_REVIEW_WAIT_TIMEOUT); // T with no time component assertEquals(reviewWaitTimeout("garbage"), DEFAULT_REVIEW_WAIT_TIMEOUT); }); test("reviewWaitTimeout: a valid ISO-8601 duration is honoured and upper-cased", () => { assertEquals(reviewWaitTimeout("PT30M"), "PT30M"); assertEquals(reviewWaitTimeout("pt30m"), "PT30M"); assertEquals(reviewWaitTimeout(" PT1H30M "), "PT1H30M"); assertEquals(reviewWaitTimeout("PT45S"), "PT45S"); assertEquals(reviewWaitTimeout("P1D"), "P1D"); assertEquals(reviewWaitTimeout("P1DT2H"), "P1DT2H"); }); test("reviewWaitTimeout: a custom fallback is used when the value is invalid", () => { assertEquals(reviewWaitTimeout("nope", "PT10M"), "PT10M"); assertEquals(reviewWaitTimeout(undefined, "PT10M"), "PT10M"); }); test("isoDurationToMs: converts each component and sums them", () => { assertEquals(isoDurationToMs("PT1S", "PT0S"), 1000); assertEquals(isoDurationToMs("PT45S", "PT0S"), 45_000); assertEquals(isoDurationToMs("PT30M", "PT0S"), 1_800_000); assertEquals(isoDurationToMs("PT2H", "PT0S"), 7_200_000); assertEquals(isoDurationToMs("PT1H30M", "PT0S"), 5_400_000); assertEquals(isoDurationToMs("P1D", "PT0S"), 86_400_000); assertEquals(isoDurationToMs("P1DT2H", "PT0S"), 93_600_000); assertEquals(isoDurationToMs("P1W", "PT0S"), 604_800_000); }); test("isoDurationToMs: blank / absent / malformed → the (parsed) default", () => { assertEquals(isoDurationToMs(undefined, "PT30M"), 1_800_000); assertEquals(isoDurationToMs("", "PT30M"), 1_800_000); assertEquals(isoDurationToMs("20m", "PT5M"), 300_000); // missing leading P → default assertEquals(isoDurationToMs("garbage", "PT5M"), 300_000); }); test("clampNudgeMinutes: blank / absent / non-numeric → fallback", () => { assertEquals(clampNudgeMinutes(""), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes(" "), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes(undefined), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes(null), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes("abc"), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes(NaN), DEFAULT_REVIEW_NUDGE_MINUTES); }); test("clampNudgeMinutes: a valid positive value (string or number) is honoured", () => { assertEquals(clampNudgeMinutes("10"), 10); assertEquals(clampNudgeMinutes(15), 15); assertEquals(clampNudgeMinutes(" 7 "), 7); }); test("clampNudgeMinutes: fractional values are truncated", () => { assertEquals(clampNudgeMinutes("5.9"), 5); assertEquals(clampNudgeMinutes(3.2), 3); }); test("clampNudgeMinutes: zero and negatives fall back (a 0 cooldown would hammer the API)", () => { assertEquals(clampNudgeMinutes(0), DEFAULT_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes("-4"), DEFAULT_REVIEW_NUDGE_MINUTES); }); test("clampNudgeMinutes: above the ceiling is clamped, not rejected", () => { assertEquals(clampNudgeMinutes(100_000), MAX_REVIEW_NUDGE_MINUTES); assertEquals(clampNudgeMinutes(String(MAX_REVIEW_NUDGE_MINUTES + 1)), MAX_REVIEW_NUDGE_MINUTES); }); test("clampNudgeMinutes: an oversized fallback is itself clamped to the ceiling", () => { assertEquals(clampNudgeMinutes("", MAX_REVIEW_NUDGE_MINUTES + 50), MAX_REVIEW_NUDGE_MINUTES); }); // ── isReviewStale (issue #799) ─────────────────────────────────────────────── test("isReviewStale: differing review commit_id and HEAD sha is stale", () => { assertEquals(isReviewStale("aaa1111", "bbb2222"), true); }); test("isReviewStale: matching review commit_id and HEAD sha is NOT stale", () => { assertEquals(isReviewStale("aaa1111", "aaa1111"), false); }); test("isReviewStale: surrounding whitespace is ignored in the comparison", () => { assertEquals(isReviewStale(" aaa1111 ", "aaa1111"), false); assertEquals(isReviewStale("aaa1111", " bbb2222 "), true); }); test("isReviewStale: a missing review commit_id fails safe to NOT stale", () => { assertEquals(isReviewStale(null, "bbb2222"), false); assertEquals(isReviewStale(undefined, "bbb2222"), false); assertEquals(isReviewStale("", "bbb2222"), false); assertEquals(isReviewStale(" ", "bbb2222"), false); }); test("isReviewStale: a missing HEAD sha fails safe to NOT stale", () => { assertEquals(isReviewStale("aaa1111", null), false); assertEquals(isReviewStale("aaa1111", undefined), false); assertEquals(isReviewStale("aaa1111", ""), false); }); test("isReviewStale: both missing is NOT stale", () => { assertEquals(isReviewStale(null, null), false); });