import { test } from 'node:test'; import assert from 'node:assert/strict'; import { dismissNudge, NUDGE_GRACE_MS, NUDGE_SNOOZE_MS, nudgeFor, parseNudgeRecord, SecurityState, shouldShowNudge, } from './securityNudge'; const healthy: SecurityState = { crossSigning: true, deviceVerified: true, backupOnServer: true, backupActive: true, }; test('the #123 table: which nudge for which state', () => { // E: healthy → nothing, ever. assert.equal(nudgeFor(healthy), undefined); // B: cross-signing, this device unverified, backup on server → verify first. assert.equal(nudgeFor({ ...healthy, deviceVerified: false }), 'verify-device'); // C: cross-signing, unverified, no backup → still verify first. assert.equal( nudgeFor({ ...healthy, deviceVerified: false, backupOnServer: false }), 'verify-device', ); // D: verified, no backup → #110's set-up nudge. assert.equal(nudgeFor({ ...healthy, backupOnServer: false }), 'setup-backup'); // #110 state 2: backup exists, this device not using it. assert.equal(nudgeFor({ ...healthy, backupActive: false }), 'connect-backup'); // A: never set up cross-signing, no backup → set up (the flow does both). assert.equal( nudgeFor({ crossSigning: false, deviceVerified: false, backupOnServer: false, backupActive: false, }), 'setup-backup', ); }); test('nothing while crypto is still loading (F)', () => { assert.equal(nudgeFor({ ...healthy, deviceVerified: undefined }), undefined); assert.equal(nudgeFor({ ...healthy, backupOnServer: undefined }), undefined); assert.equal(nudgeFor({ ...healthy, backupActive: undefined }), undefined); // Unknown backup connection doesn't hide a known "no backup at all". assert.equal( nudgeFor({ ...healthy, backupOnServer: false, backupActive: undefined }), 'setup-backup', ); }); test('timing: 24 h grace, 7-day snooze, stop after 3 dismissals', () => { const t0 = 1_700_000_000_000; let record = parseNudgeRecord(null, t0); assert.equal(shouldShowNudge('verify-device', record, t0), false, 'first launch'); assert.equal(shouldShowNudge('verify-device', record, t0 + NUDGE_GRACE_MS - 1), false); let now = t0 + NUDGE_GRACE_MS; assert.equal(shouldShowNudge('verify-device', record, now), true, 'after 24 h'); for (let i = 1; i <= 3; i += 1) { record = dismissNudge('verify-device', record, now); assert.equal(shouldShowNudge('verify-device', record, now), false, `snoozed after #${i}`); now += NUDGE_SNOOZE_MS; assert.equal(shouldShowNudge('verify-device', record, now), i < 3, `after 7 days (#${i})`); } // Dismissing one nudge doesn't hide another. assert.equal(shouldShowNudge('setup-backup', record, now), true); }); test('stored record: round trip, and garbage starts a fresh grace period', () => { const t0 = 1_700_000_000_000; const r = dismissNudge('setup-backup', parseNudgeRecord(null, t0 - 5), t0); assert.deepEqual(parseNudgeRecord(JSON.stringify(r), t0 + 1), r); for (const bad of ['{', 'null', '"x"', '{"firstSeen":"yesterday"}', '{"firstSeen":1}']) { assert.deepEqual(parseNudgeRecord(bad, t0), { firstSeen: t0, dismissals: {} }, bad); } });