test/shared/env.test.ts
Rama main · réplica de lectura del repositorio original.
import process from "node:process";
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import { spy } from "@std/testing/mock";
import {
getEnv,
getReadOnlyCutoffIso,
getRenewalUrl,
isInWarningWindow,
isReadOnly,
isReadOnlyFromCutoff,
isReadOnlyWarning,
isUpdateInProgress,
parseWarnDays,
requireEnv,
} from "#shared/env.ts";
import { describeWithEnv } from "#test-utils/db.ts";
const KEY = "TEST_ENV_VAR_FOR_ENV_SPEC";
const DAY_MS = 86_400_000;
const isoFromNow = (offsetMs: number): string =>
new Date(Date.now() + offsetMs).toISOString();
describeWithEnv(
"env",
{
env: {
[KEY]: undefined,
READ_ONLY_FROM: undefined,
READ_ONLY_WARN_DAYS: undefined,
RENEWAL_URL: undefined,
UPDATE_IN_PROGRESS: undefined,
},
},
() => {
describe("getEnv", () => {
test("returns the value set in the environment", () => {
process.env[KEY] = "hello";
expect(getEnv(KEY)).toBe("hello");
});
test("returns undefined when the variable is not set", () => {
expect(getEnv(KEY)).toBeUndefined();
});
test("returns an empty string when the variable is set to empty", () => {
process.env[KEY] = "";
expect(getEnv(KEY)).toBe("");
});
});
describe("requireEnv", () => {
test("returns the value when the variable is set", () => {
process.env[KEY] = "required_value";
expect(requireEnv(KEY)).toBe("required_value");
});
test("throws an error that names the missing key", () => {
expect(() => requireEnv(KEY)).toThrow(KEY);
});
});
describe("isReadOnly", () => {
test("is false when READ_ONLY_FROM is unset", () => {
expect(isReadOnly()).toBe(false);
});
test("is true when READ_ONLY_FROM is in the past", () => {
process.env.READ_ONLY_FROM = isoFromNow(-DAY_MS);
expect(isReadOnly()).toBe(true);
});
test("is false when READ_ONLY_FROM is in the future", () => {
process.env.READ_ONLY_FROM = isoFromNow(DAY_MS * 30);
expect(isReadOnly()).toBe(false);
});
test("is false when READ_ONLY_FROM is malformed (fail open)", async () => {
process.env.READ_ONLY_FROM = "not-a-date";
const errorSpy = spy(console, "error");
try {
expect(isReadOnly()).toBe(false);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(errorSpy.calls.length).toBe(1);
} finally {
errorSpy.restore();
}
});
});
describe("isUpdateInProgress", () => {
test("is false when UPDATE_IN_PROGRESS is unset", () => {
expect(isUpdateInProgress()).toBe(false);
});
test("is false when UPDATE_IN_PROGRESS is empty (a lifted flag)", () => {
process.env.UPDATE_IN_PROGRESS = "";
expect(isUpdateInProgress()).toBe(false);
});
test("is true when UPDATE_IN_PROGRESS is set", () => {
process.env.UPDATE_IN_PROGRESS = "1";
expect(isUpdateInProgress()).toBe(true);
});
test("puts the site into read-only mode", () => {
process.env.UPDATE_IN_PROGRESS = "1";
expect(isReadOnly()).toBe(true);
});
test("overrides a future READ_ONLY_FROM cutoff", () => {
process.env.UPDATE_IN_PROGRESS = "1";
process.env.READ_ONLY_FROM = isoFromNow(DAY_MS * 30);
expect(isReadOnly()).toBe(true);
});
test("does not touch the READ_ONLY_FROM behaviour when unset", () => {
process.env.READ_ONLY_FROM = isoFromNow(-DAY_MS);
expect(isReadOnly()).toBe(true);
delete process.env.READ_ONLY_FROM;
expect(isReadOnly()).toBe(false);
});
});
describe("isReadOnlyWarning", () => {
test("is true when inside warning window", () => {
process.env.READ_ONLY_FROM = isoFromNow(DAY_MS * 7);
process.env.READ_ONLY_WARN_DAYS = "14";
expect(isReadOnlyWarning()).toBe(true);
});
test("is false when outside warning window", () => {
process.env.READ_ONLY_FROM = isoFromNow(DAY_MS * 30);
process.env.READ_ONLY_WARN_DAYS = "14";
expect(isReadOnlyWarning()).toBe(false);
});
test("is false when already read-only", () => {
process.env.READ_ONLY_FROM = isoFromNow(-DAY_MS);
expect(isReadOnlyWarning()).toBe(false);
});
test("is false when READ_ONLY_FROM is unset", () => {
delete process.env.READ_ONLY_FROM;
expect(isReadOnlyWarning()).toBe(false);
});
test("is false when READ_ONLY_FROM is in the past", () => {
process.env.READ_ONLY_FROM = isoFromNow(-DAY_MS);
expect(isReadOnlyWarning()).toBe(false);
});
});
describe("getReadOnlyCutoffIso", () => {
test("returns the ISO string when READ_ONLY_FROM is set", () => {
const cutoff = isoFromNow(DAY_MS * 10);
process.env.READ_ONLY_FROM = cutoff;
expect(getReadOnlyCutoffIso()).toBe(cutoff);
});
test("returns null when READ_ONLY_FROM is unset", () => {
delete process.env.READ_ONLY_FROM;
expect(getReadOnlyCutoffIso()).toBeNull();
});
test("returns null when READ_ONLY_FROM is malformed", () => {
process.env.READ_ONLY_FROM = "garbage";
expect(getReadOnlyCutoffIso()).toBeNull();
});
});
describe("getRenewalUrl", () => {
test("returns the URL when RENEWAL_URL is set", () => {
process.env.RENEWAL_URL = "https://example.com/renew/?t=abc";
expect(getRenewalUrl()).toBe("https://example.com/renew/?t=abc");
});
test("returns null when RENEWAL_URL is unset", () => {
delete process.env.RENEWAL_URL;
expect(getRenewalUrl()).toBeNull();
});
});
},
);
describe("parseWarnDays", () => {
test("returns 14 when undefined", () => {
expect(parseWarnDays(undefined)).toBe(14);
});
test("returns 14 when empty string", () => {
expect(parseWarnDays("")).toBe(14);
});
test("returns the parsed value for valid input", () => {
expect(parseWarnDays("7")).toBe(7);
});
test("returns 14 for zero", () => {
expect(parseWarnDays("0")).toBe(14);
});
test("returns 14 for negative", () => {
expect(parseWarnDays("-3")).toBe(14);
});
test("returns 14 for non-numeric", () => {
expect(parseWarnDays("abc")).toBe(14);
});
test("respects custom default", () => {
expect(parseWarnDays(undefined, 30)).toBe(30);
});
});
describe("isReadOnlyFromCutoff", () => {
test("returns true when now >= cutoff", () => {
const now = Date.parse("2026-06-01T00:00:00Z");
expect(isReadOnlyFromCutoff(now, "2026-06-01T00:00:00Z")).toBe(true);
});
test("returns true when now > cutoff", () => {
const now = Date.parse("2026-07-01T00:00:00Z");
expect(isReadOnlyFromCutoff(now, "2026-06-01T00:00:00Z")).toBe(true);
});
test("returns false when now < cutoff", () => {
const now = Date.parse("2026-05-01T00:00:00Z");
expect(isReadOnlyFromCutoff(now, "2026-06-01T00:00:00Z")).toBe(false);
});
test("returns false for unparseable cutoff", () => {
expect(isReadOnlyFromCutoff(Date.now(), "not-a-date")).toBe(false);
});
});
describe("isInWarningWindow", () => {
const cutoff = "2026-07-01T00:00:00Z";
const cutoffMs = Date.parse(cutoff);
test("returns true when inside warning window", () => {
const now = cutoffMs - 7 * DAY_MS;
expect(isInWarningWindow(now, cutoff, 14)).toBe(true);
});
test("returns false when exactly at cutoff", () => {
expect(isInWarningWindow(cutoffMs, cutoff, 14)).toBe(false);
});
test("returns false when outside warning window", () => {
const now = cutoffMs - 15 * DAY_MS;
expect(isInWarningWindow(now, cutoff, 14)).toBe(false);
});
test("returns false when now >= cutoff (already expired)", () => {
const now = cutoffMs + DAY_MS;
expect(isInWarningWindow(now, cutoff, 14)).toBe(false);
});
test("returns true at the exact boundary of warning window", () => {
const now = cutoffMs - 14 * DAY_MS;
expect(isInWarningWindow(now, cutoff, 14)).toBe(true);
});
test("returns false for unparseable cutoff", () => {
expect(isInWarningWindow(Date.now(), "garbage", 14)).toBe(false);
});
});