import { test } from "node:test"; import assert from "node:assert/strict"; import { computeSafeForMs, makeNativeLease, makeWebviewLease, isValidLease, LEASE_FALLBACK_SAFE_MS, LEASE_SAFETY_MARGIN_MS, LEASE_MAX_SAFE_MS, } from "../../src/lib/preview/lease.js"; import { redactUrl, parseSignedExpiryMs, isAllowedMediaUrl } from "../../src/lib/preview/redact.js"; const SRC = Object.freeze({ provider: "deezer", providerTrackId: "1", matchConfidence: "native" }); const NOW = 1_784_760_000_000; const MIN = 60_000; // ---------------------------------------------------------- safeForMs ----- test("valid exp + Date: provider-clock lifetime minus margin", () => { const safe = computeSafeForMs({ expEpochMs: NOW + 14 * MIN, dateHeaderMs: NOW, localNowMs: NOW }); assert.equal(safe, 14 * MIN - LEASE_SAFETY_MARGIN_MS); }); test("local clock AHEAD of provider: the shorter local view wins", () => { const safe = computeSafeForMs({ expEpochMs: NOW + 14 * MIN, dateHeaderMs: NOW, localNowMs: NOW + 10 * MIN, // local thinks only 4 min remain }); assert.equal(safe, 4 * MIN - LEASE_SAFETY_MARGIN_MS); }); test("local clock BEHIND provider: local view cannot extend the lease", () => { const safe = computeSafeForMs({ expEpochMs: NOW + 14 * MIN, dateHeaderMs: NOW, localNowMs: NOW - 60 * MIN, // local view claims 74 min }); assert.equal(safe, 14 * MIN - LEASE_SAFETY_MARGIN_MS); }); test("missing exp → short capped fallback", () => { assert.equal(computeSafeForMs({ expEpochMs: null, dateHeaderMs: NOW, localNowMs: NOW }), LEASE_FALLBACK_SAFE_MS); }); test("malformed exp → fallback", () => { assert.equal(computeSafeForMs({ expEpochMs: NaN, dateHeaderMs: NOW, localNowMs: NOW }), LEASE_FALLBACK_SAFE_MS); }); test("contradictory: exp before Date → fallback", () => { assert.equal( computeSafeForMs({ expEpochMs: NOW - MIN, dateHeaderMs: NOW, localNowMs: NOW }), LEASE_FALLBACK_SAFE_MS ); }); test("contradictory: absurd multi-hour lifetime → fallback", () => { assert.equal( computeSafeForMs({ expEpochMs: NOW + 6 * 60 * MIN, dateHeaderMs: NOW, localNowMs: NOW }), LEASE_FALLBACK_SAFE_MS ); }); test("missing Date but sane local view → local view minus margin, capped", () => { const safe = computeSafeForMs({ expEpochMs: NOW + 14 * MIN, dateHeaderMs: null, localNowMs: NOW }); assert.equal(safe, 14 * MIN - LEASE_SAFETY_MARGIN_MS); const capped = computeSafeForMs({ expEpochMs: NOW + 90 * MIN, dateHeaderMs: null, localNowMs: NOW }); assert.equal(capped, LEASE_MAX_SAFE_MS - LEASE_SAFETY_MARGIN_MS); }); test("already expired with no Date → fallback (refresh immediately-ish, never negative)", () => { assert.equal( computeSafeForMs({ expEpochMs: NOW - MIN, dateHeaderMs: null, localNowMs: NOW }), LEASE_FALLBACK_SAFE_MS ); }); // ------------------------------------------------------ union validity ---- test("discriminated union: native carries exactly an opaque id", () => { const lease = makeNativeLease({ source: SRC, opaqueLeaseId: "op-1", safeForMs: 1000 }); assert.ok(isValidLease(lease)); assert.equal(lease.url, undefined); assert.ok(!isValidLease({ ...lease, url: "https://x" })); }); test("discriminated union: webview carries exactly a url", () => { const lease = makeWebviewLease({ source: SRC, url: "https://cdnt-preview.dzcdn.net/a.mp3", safeForMs: 1000 }); assert.ok(isValidLease(lease)); assert.equal(lease.opaqueLeaseId, undefined); assert.ok(!isValidLease({ ...lease, opaqueLeaseId: "x" })); }); test("constructors refuse the wrong locator", () => { assert.throws(() => makeNativeLease({ source: SRC, safeForMs: 1 })); assert.throws(() => makeWebviewLease({ source: SRC, safeForMs: 1 })); }); // ------------------------------------------------------------ redaction --- test("redactUrl yields host + hash + exp, never the url", () => { const url = "https://cdnt-preview.dzcdn.net/api/x.mp3?hdnea=exp=1784761537~acl=/x*~hmac=deadbeef"; const r = redactUrl(url); assert.equal(r.host, "cdnt-preview.dzcdn.net"); assert.equal(r.expMs, 1784761537000); assert.match(r.hash, /^[0-9a-f]{8}$/); assert.ok(!JSON.stringify(r).includes("hmac")); }); test("parseSignedExpiryMs handles bare exp params and garbage", () => { assert.equal(parseSignedExpiryMs("https://h/x?exp=1784761537"), 1784761537000); assert.equal(parseSignedExpiryMs("https://h/x"), null); assert.equal(parseSignedExpiryMs(null), null); }); test("host policy: https + exact allowlist, redirects re-checked by the same gate", () => { assert.ok(isAllowedMediaUrl("https://cdnt-preview.dzcdn.net/a.mp3")); assert.ok(!isAllowedMediaUrl("http://cdnt-preview.dzcdn.net/a.mp3")); // scheme assert.ok(!isAllowedMediaUrl("https://evil.dzcdn.net/a.mp3")); // host assert.ok(!isAllowedMediaUrl("https://cdnt-preview.dzcdn.net.evil.com/a.mp3")); // suffix trick assert.ok(!isAllowedMediaUrl("not a url")); });