← back to Sample Followup Sweep

test/sent-stamp-heartbeat.test.js

106 lines

'use strict';
// TK-12351: dead-letter heartbeat + vid-keyed pull-forward. Negative tests: each check must go RED
// on its injected fault. No FM, no network, no fs.
const assert = require('node:assert/strict');
const path = require('node:path');
const { pathToFileURL } = require('node:url');

(async () => {
  const core = await import(pathToFileURL(path.join(__dirname, '../scripts/sent-stamp-core.mjs')).href);
  const DAY = 86400e3;
  const NOW = new Date(2026, 8, 26, 9, 0, 0).getTime();
  const at = daysAgo => String(NOW - daysAgo * DAY);
  const base = { now: NOW, searchDays: 21, nearDays: 5 };

  // ── (a) heartbeat ──────────────────────────────────────────────────────────────────────────
  const msgs = [
    { id: 'fresh', to: 'a@v.com', internalDate: at(2) },
    { id: 'old-done', to: 'b@v.com', internalDate: at(19) },
    { id: 'old-stuck', to: 'Lin <samples@lincrusta.com>', internalDate: at(18) },   // 3d left
  ];
  const clean = { fresh: { stamped: 1, failed: 0 }, 'old-done': { stamped: 2, failed: 0 }, 'old-stuck': { stamped: 1, failed: 0 } };
  const hbClean = core.buildHeartbeat({ ...base, ledger: clean, msgs });
  assert.equal(hbClean.verdict, 'PASS', 'clean → PASS');
  assert.equal(hbClean.status, 'PASS');
  assert.deepEqual(hbClean.population, { sends_seen: 3, terminal: 3, non_terminal: 0, unprocessed: 0, ledger_entries: 3 });
  console.log('(a1) PASS — clean ledger → PASS, population carried beside observed');

  const stuck = { ...clean, 'old-stuck': { vids: ['LIN'], stamped: 0, failed: 0, attempts: 40, to: 'samples@lincrusta.com' } };
  const hbStuck = core.buildHeartbeat({ ...base, ledger: stuck, msgs });
  assert.equal(hbStuck.verdict, 'WARN', 'near-expiry non-terminal → WARN');
  assert.equal(hbStuck.near_expiry.length, 1);
  assert.equal(hbStuck.near_expiry[0].id, 'old-stuck');
  assert.equal(hbStuck.near_expiry[0].state, 'stamped:0');
  assert.equal(hbStuck.near_expiry[0].days_left, 3);
  assert.equal(hbStuck.near_expiry[0].sent_on, '09/08/2026');
  // a non-terminal entry with plenty of runway is NOT a dead-letter warning (no nagging on normal retries)
  const young = { ...clean, fresh: { skip: 'no vid for recipient' } };
  assert.equal(core.buildHeartbeat({ ...base, ledger: young, msgs }).verdict, 'PASS', 'non-terminal with 19d left is not near expiry');
  console.log('(a2) PASS — non-terminal within 5d of aging out → WARN; far from expiry stays PASS');

  const hbSearchErr = core.buildHeartbeat({ ...base, ledger: clean, msgs, searchErr: 'HTTP 502' });
  assert.equal(hbSearchErr.verdict, 'WARN', 'search error must never be PASS');
  assert.match(hbSearchErr.not_measured.join(), /HTTP 502/);
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, searchErr: 'truncated at 10 pages' }).verdict, 'WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: {}, msgs: [] }).verdict, 'WARN', '0 of 0 is NOT-MEASURED, not PASS');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, results: [{ id: 'x', failed: 1 }] }).verdict, 'WARN', 'FM lookup error → WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, results: [{ id: 'x', bodyFailed: true }] }).verdict, 'WARN', 'body fetch fail → WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs: [...msgs, { id: 'e', error: 'fetch-failed' }] }).verdict, 'WARN', 'metadata fail → WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, ledgerErr: 'Unexpected token' }).verdict, 'WARN', 'unreadable ledger → WARN');
  // transient-miss grace: a partial pass right after a FULL measure is not a finding; a persistent one is
  const partial = { ...base, ledger: clean, msgs, results: [{ id: 'x', bodyFailed: true }] };
  assert.equal(core.buildHeartbeat({ ...partial, lastFullMeasureAt: NOW - 60e3 }).verdict, 'PASS', 'lone transient miss inside grace');
  assert.equal(core.buildHeartbeat({ ...partial, lastFullMeasureAt: NOW - 31 * 60e3 }).verdict, 'WARN', 'no full measure for >30 min → WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, searchErr: 'HTTP 500', lastFullMeasureAt: NOW - 31 * 60e3 }).verdict, 'WARN');
  assert.equal(core.buildHeartbeat({ ...base, ledger: {}, msgs: [], lastFullMeasureAt: NOW - 60e3 }).verdict, 'WARN', 'empty population gets no grace');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, ledgerErr: 'bad', lastFullMeasureAt: NOW - 60e3 }).verdict, 'WARN', 'unreadable ledger gets no grace');
  assert.equal(core.buildHeartbeat({ ...partial, lastFullMeasureAt: NOW - 60e3, ledger: stuck }).verdict, 'WARN', 'grace never hides a near-expiry finding');
  assert.equal(core.buildHeartbeat({ ...base, ledger: clean, msgs, results: [{ id: 'x', failed: 1 }], lastFullMeasureAt: NOW - 60e3 }).verdict, 'WARN', 'FileMaker errors get no grace');
  assert.equal(hbClean.full_measure, true); assert.equal(hbClean.last_full_measure_at, new Date(NOW).toISOString());
  console.log('(a3) PASS — unmeasured (search error / truncated / empty / FM error / body / metadata / ledger) → WARN, never PASS');

  // just-aged-out unresolved entry (no longer returned by the search) → WARN; undated legacy / long-gone → no nag
  const expired = { ...clean, gone: { skip: 'no vid for recipient', sent_on: core.fmt(new Date(NOW - 23 * DAY)) } };
  const hbExp = core.buildHeartbeat({ ...base, ledger: expired, msgs });
  assert.equal(hbExp.verdict, 'WARN'); assert.equal(hbExp.recently_expired[0].id, 'gone');
  assert.equal(core.buildHeartbeat({ ...base, ledger: expired, msgs, searchErr: 'x' }).recently_expired.length, 0, 'incomplete search cannot claim expiry');
  const ancient = { ...clean, legacy: { stamped: 0 }, long: { skip: 'x', sent_on: '06/01/2026' } };
  assert.equal(core.buildHeartbeat({ ...base, ledger: ancient, msgs }).verdict, 'PASS', 'decays: undated/long-expired entries do not pin amber forever');
  console.log('(a4) PASS — aged out unresolved in last 7d → WARN; decays after');

  const hbFail = core.failHeartbeat('boom', NOW);
  assert.equal(hbFail.verdict, 'FAIL'); assert.equal(hbFail.status, 'FAIL');
  console.log('(a5) PASS — a pass that threw → FAIL');

  // skips now carry sent_on so they can be aged after leaving the search
  const rs = await core.runPass({ msgs: [{ id: 'u', to: 'x@nowhere.example', internalDate: at(4) }], ledger: {}, now: NOW, dry: false, retrySecs: 900, minAgeDays: 10, maxAgeDays: 60,
    deps: { emailVid: {}, getBody: async () => '', stamp: async () => ({ stamped: [] }) } });
  assert.equal(rs.ledger.u.sent_on, core.fmt(new Date(NOW - 4 * DAY)));

  // ── (b) vid-keyed pull-forward (Lincrusta: sample_email ≠ main_email, one vendor) ──────────
  const ev = core.buildEmailVid([{ slug: 'lincrusta', vid: 'LIN' }], { lincrusta: { sample_email: 'samples@lincrusta.com', main_email: 'info@lincrusta.com' } });
  const older = { id: 'old', to: 'Samples <samples@lincrusta.com>', internalDate: at(13) };
  const newer = { id: 'new', to: 'info@lincrusta.com', internalDate: at(2) };
  const led = { old: { vids: ['LIN'], stamped: 0, last_try: new Date(NOW - 60e3).toISOString() } };   // older is in backoff
  const order = [];
  await core.runPass({ msgs: [newer, older], ledger: led, now: NOW, dry: false, retrySecs: 900, minAgeDays: 10, maxAgeDays: 60,
    deps: { emailVid: ev, getBody: async id => id, stamp: async (v, body, w, sentOn) => { order.push(`${body}@${sentOn}`); return { stamped: [] }; } } });
  assert.deepEqual(order, [`old@${core.fmt(new Date(NOW - 13 * DAY))}`, `new@${core.fmt(new Date(NOW - 2 * DAY))}`],
    'older send to a DIFFERENT address of the same vid must run first and own the chase date');
  // unrelated vendor in backoff is NOT pulled forward
  const ev2 = core.buildEmailVid([{ slug: 'lincrusta', vid: 'LIN' }, { slug: 'k', vid: 'KRA' }], { lincrusta: { main_email: 'info@lincrusta.com' }, k: { sample_email: 'k@kravet.com' } });
  const order2 = [];
  await core.runPass({ msgs: [{ id: 'new', to: 'info@lincrusta.com', internalDate: at(2) }, { id: 'kold', to: 'k@kravet.com', internalDate: at(13) }],
    ledger: { kold: { vids: ['KRA'], stamped: 0, last_try: new Date(NOW - 60e3).toISOString() } }, now: NOW, dry: false, retrySecs: 900, minAgeDays: 10, maxAgeDays: 60,
    deps: { emailVid: ev2, getBody: async id => id, stamp: async (v, body) => { order2.push(body); return { stamped: [] }; } } });
  assert.deepEqual(order2, ['new'], 'a different vendor in backoff stays in backoff');
  // address fallback when nothing resolves: same unmapped address still pulls forward (reaches the skip path)
  const r3 = await core.runPass({ msgs: [{ id: 'n', to: 'z@x.example', internalDate: at(2) }, { id: 'o', to: 'Z <z@x.example>', internalDate: at(13) }],
    ledger: { o: { skip: 'no vid for recipient', last_try: new Date(NOW - 60e3).toISOString() } }, now: NOW, dry: false, retrySecs: 900, minAgeDays: 10, maxAgeDays: 60,
    deps: { emailVid: {}, getBody: async () => '', stamp: async () => ({ stamped: [] }) } });
  assert.equal(r3.results.find(x => x.id === 'o')?.why, 'retry-ahead-of-newer', 'address fallback keeps unmapped pull-forward');
  console.log('(b) PASS — pull-forward keyed on resolved vid (Lincrusta alias), unrelated vendor untouched, address fallback kept');

  console.log('sent-stamp-heartbeat: ALL PASS');
})().catch(e => { console.error('FAIL:', e.message); process.exit(1); });