Files
bitvid/TEST_INTEGRITY.md
T
thePR0M3TH3ANandClaude Opus 4.8 380de0922a docs: update zap-audit plan, pre-launch TODO, and test-integrity notes
Record this session's fixes (delete/zombie, NIP-71 mirror teardown,
watch-history bucketing, the zap chain, the test-runner quarantine work)
and the test spec-corrections.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-23 14:53:51 -04:00

19 KiB

Test Integrity Notes

Machine-readable record of test-expectation changes made under the Spec Correction Protocol (see CLAUDE.md → Dark Factory rules). Each entry justifies why a previous expectation was wrong relative to intended behavior and confirms the replacement is equally strict (or stricter).


test_integrity_note:
  date: 2026-06-14
  file: tests/app-batch-fetch-profiles.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-profiles-batched
      given: "Multiple relays configured (one fast, one failing) and uncached, valid pubkeys to fetch"
      when: "batchFetchProfiles runs after profile fetches were migrated to the L1 SubscriptionManager"
      then: "Profiles are fetched in ONE batched query across all relays (not one query per relay); the fast relay's newest profile still renders, cached profiles hydrate immediately, and a failing relay does not break the result"
  observable_outcomes:
    - "exactly one pool.list call, targeting all configured relays"
    - "query authors = only uncached/valid pubkeys"
    - "DOM updates: cached profile first, then newest fast-relay profile"
    - "cache set with the newest (Fast) profile only"
  determinism_controls:
    - "mock pool.list with deterministic per-relay responses; fixed createdAt ordering"
  anti_cheat_rationale:
    prevents:
      - "over-mocking internal logic"
      - "hard-coded return value"
    note: "Only the per-relay CALL-COUNT assertion changed (2 -> 1), because the
      refactor intentionally batches the fetch. All behavioral assertions
      (correct/newest profile rendered, cached hydration, failing relay
      tolerated) are unchanged, and a new assertion verifies the single batched
      query targets all relays — so coverage is equal or stronger."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-14
  files:
    - tests/relay-subscribe-cap.test.mjs
    - tests/user-blocks.test.mjs
    - tests/subscriptions-manager.test.mjs
    - tests/nostr/client.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-relay-reads-bounded
      given: "A user's NIP-65 read set (in prod often ~20 relays, most dead) and a
        cold login that must decrypt encrypted lists (blocks, subs, hashtags) via
        the nip-07 extension"
      when: "Reads/subscriptions are built via capReadRelays — the user's own
        relays first, with RESERVED_DEFAULT_RELAY_SLOTS reserved for reliable
        default aggregators, bounded to MAX_SUBSCRIBE_RELAYS=8; writes stay
        uncapped"
      then: "Every configured relay is still queried when the user's set is small
        (data lives on the user's own relays); the distinct read fan-out never
        exceeds 8 (so a large dead list can't storm the pool and starve nip-07
        decryption); at least the reserved reliable defaults remain reachable even
        when the user supplied none; write reach is unchanged and defaults are NOT
        injected into writes"
  observable_outcomes:
    - "read/subscribe relay set length <= 8"
    - "writeRelays == full configured set; no default injected into writes"
    - ">= RESERVED_DEFAULT_RELAY_SLOTS reliable defaults present in the read set"
    - "user-blocks: every configured relay queried; fast-then-full background
      ordering preserved; block list hydrates from the background relay payload"
    - "subscriptions-manager: every configured relay queried even when one
      rejects; only the newest event decrypted"
    - "nostr/client: same events arriving from multiple seeded relays still
      dedupe to the expected set; relay fan-out bounded"
  determinism_controls:
    - "deterministic per-relay mock pool.list responses; fixed created_at ordering"
    - "capReadRelays is test-aware: the reserved core resolves to the test-relay
      override under test, keeping harnesses isolated from prod default URLs"
    - "tests set nostrClient.readRelays to the user's relays to mirror the
      logged-in state applyRelayPreferences establishes in prod (the fetcher ranks
      candidates by read preference)"
  anti_cheat_rationale:
    prevents:
      - "hard-coded return value"
      - "over-mocking internal logic"
      - "retry/sleep-based flake masking"
    note: "Only incidental EXACT relay-count assertions were loosened to BOUNDS
      (<=8) because the storm fix intentionally adds a small reliable-default
      backstop to the read set. Every behavioral assertion (correct/newest event
      decrypted, block hydration from the right relay, dedup correctness, per-relay
      coverage, writes uncapped) is unchanged or strengthened — coverage is equal
      or stronger, and new assertions pin the reserved-defaults liveness guarantee."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: "Exact-count assertions became bounded-count
      assertions, but this reflects intended new behavior (reliable-default
      backstop) rather than weakening a real correctness check; coverage of the
      actual security/decrypt behavior is preserved."

test_integrity_note:
  date: 2026-06-23
  file: tests/nip71-mirror-flags.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-mirror-delete-unconditional
      given: "A video that was shared to NIP-71 on one device/session, then deleted from another device (or after the per-video opt-in flag — browser-local localStorage — was cleared)"
      when: "resolveDeleteSync runs during delete with FEATURE_NIP71_MIRROR on but the local enabled flag false/absent"
      then: "The mirror teardown (NIP-09 + empty tombstone, idempotent) is still attempted, so the NIP-71 mirror is not orphaned on other apps; the feature flag still fully gates it"
  observable_outcomes:
    - "resolveDeleteSync({featureOn:true, enabled:false|undefined}) -> {action:'unshare'}"
    - "resolveDeleteSync({featureOn:false, ...}) -> {action:'none'} (feature flag still gates)"
  determinism_controls:
    - "pure function; explicit input matrix"
  anti_cheat_rationale:
    prevents:
      - "hard-coded return value"
    note: "The previous expectation (enabled:false -> none) encoded the bug: it
      gated teardown on a browser-local flag, orphaning the mirror across devices.
      The corrected spec is STRICTER — teardown fires whenever the feature is on —
      and adds the enabled:undefined case. The feature-flag gate assertion is
      retained, so no real gate was weakened."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-23
  file: tests/nostr-delete-flow.test.mjs
  change_type: ["spec_correction", "refactor_tests"]
  scenarios:
    - id: SCN-delete-surfaces-relay-failures
      given: "A logged-in delete where one write relay rejects the kind-5 deletion"
      when: "deleteAllVersions publishes to the WRITE set via getDeletePublishRelays() (the afb6200b relay-scope fix)"
      then: "The delete summary surfaces the failing relay"
    - id: SCN-buffered-feed-tombstone-guard
      given: "A delete event seeds a tombstone, then an OLDER non-deleted event for the same root arrives on the live stream"
      when: "events flow through the real VideoEventBuffer (the path subscribeVideos builds via the SubscriptionManager), not the removed direct pool.sub() injection"
      then: "The older event is tombstone-guarded (marked deleted, kept out of activeMap) and never surfaces as a LIVE video; a deletion event surfaces marked deleted:true so the UI removes the card"
  observable_outcomes:
    - "deleteSummary.summary.failed identifies wss://relay.fail (writeRelays now configured so the injected failure is exercised)"
    - "no callback video with the deleted/older event id and deleted:false"
    - "client.activeMap cleared for the root; tombstone advanced; stored event marked deleted"
  determinism_controls:
    - "videoEventVerifier virtualizes the off-thread signature worker (all events valid)"
    - "buffer.scheduleFlush(true) awaited for synchronous commit"
    - "real NostrClient + real VideoEventBuffer (no mocked tombstone guard)"
  anti_cheat_rationale:
    prevents:
      - "over-mocking internal logic"
      - "snapshot rubber-stamping"
    note: "Two corrections. (1) The file used bare assert + top-level await, so the
      unit runner (which requires a node:test import) SILENTLY EXCLUDED it — it had
      rotted and was failing on main unnoticed. Converted to node:test so it runs
      in CI. (2) The relay-failure test set only client.relays, but deletes moved to
      the write set (getDeletePublishRelays); it now sets writeRelays so the
      injected failure is actually exercised — the assertion was previously passing
      vacuously. (3) The subscription assertions drove the REMOVED pool.sub() path
      and expected 'no callback at all' for deleted events; the current
      VideoEventBuffer intentionally surfaces deleted events marked deleted:true so
      the UI can remove the card. Rewritten to drive the real buffer + real client
      and assert the stricter, accurate invariant (never surfaces as LIVE; guarded
      and removed from activeMap). Coverage is stronger: the buffer's own unit test
      mocks the client's tombstone guard, whereas this exercises the real guard."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-23
  file: tests/watch-history.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-watch-history-partial-acceptance-durable
      given: "A logged-in user snapshots watch history to 3 write relays where 2 accept and 1 rejects (single attempt, no escalation)"
      when: "watchHistoryService.snapshot runs after the 565a9618 fix (a snapshot is durable as soon as ANY relay accepts; reads take the newest event per d-tag)"
      then: "The snapshot SUCCEEDS — it does not throw, the result is ok and not retryable, no republish is scheduled, the month result is flagged partial with the accepting/rejecting relays captured, and the pointer queue is emptied (the pointer was durably published)"
    - id: SCN-watch-history-logged-in-not-session
      given: "A logged-in user (nostrClient.pubkey set) records a watch and the entry is published and re-loaded"
      when: "publishView resolves the owning actor as the logged-in pubkey and sets the session flag only when nobody is logged in"
      then: "The round-tripped entry is NOT flagged session=true (it is the logged-in user's history, not a local-only session entry); the queue files under the logged-in pubkey so getQueuedPointers(actor) sees it"
  observable_outcomes:
    - "snapshot() resolves (does not throw); result.ok === true; result.retryable === false"
    - "scheduleWatchHistoryRepublish NOT called"
    - "result.results[0].partial === true; relayStatus.pointer shows relay.one/two accepted, relay.three rejected"
    - "getQueuedPointers(actor).length === 0 after a successful snapshot"
    - "resolvedItems[0].session !== true for a logged-in user"
  determinism_controls:
    - "poolHarness resolver with a fixed accept/reject plan per relay url"
    - "session crypto + recordVideoView virtualized; stable relay set"
    - "logged-in pubkey aligned with the queried actor (matches the convention used by the other tests in the file)"
  anti_cheat_rationale:
    prevents:
      - "snapshot rubber-stamping"
      - "hard-coded return value"
    note: "Three corrections in this file, all matching shipped behavior. (1) The
      partial-acceptance test asserted the INVERSE of intended behavior — that
      snapshot() THROWS on partial acceptance and retries until EVERY relay accepts —
      encoding exactly the all-relays false-failure bug 565a9618 fixed. Rewritten to
      the success path (no throw, partial flag, per-relay status, queue drained, no
      spurious republish). (2) testWatchHistoryServiceIntegration set the logged-in
      pubkey to a value DIFFERENT from the actor it queried; since watch history now
      files under the logged-in pubkey, the queue assertions looked at the wrong key.
      Aligned pubkey to the queried actor (the convention every other test uses) — a
      setup fix, no assertion weakened. (3) The same test asserted session===true for
      a logged-in watch, which is the pre-fix bug (logged-in watches were wrongly
      flagged session by comparing against the view actor); corrected to session!==true
      per the current rule that session marks only logged-out, local-only entries.
      With these the full file is green and was REMOVED from the run-unit-tests
      QUARANTINE."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-23
  files:
    - tests/zap-shared-state.test.mjs
    - tests/video-modal-zap.test.mjs
  change_type: ["spec_correction", "refactor_tests"]
  scenarios:
    - id: SCN-platform-fee-unparseable-override
      given: "An unparseable platform-fee override value (e.g. 'not-a-number') with PLATFORM_FEE_PERCENT configured > 0"
      when: "resolvePlatformFeePercent runs"
      then: "It falls back to the configured platform default (getDefaultPlatformFeePercent), NOT to 0 — a junk override cannot silently disable/bypass the platform fee"
    - id: SCN-zap-completion-state-resets-then-completes
      given: "A zap whose receipts validate to a warning (awaiting validated receipt)"
      when: "sendZap runs"
      then: "The modal completion state is reset to false at the start of the send and set true at the terminal path, so two completion-state changes are recorded and the end state is true"
    - id: SCN-zap-receipts-render-into-controller-element
      given: "VideoModal.renderZapReceipts called with one validated (or one failed) receipt"
      when: "rendering delegates to the inner zapController"
      then: "Exactly one entry is rendered into the controller's modalZapReceipts element"
  observable_outcomes:
    - "resolvePlatformFeePercent('not-a-number') === getDefaultPlatformFeePercent() (> 0)"
    - "completedStates grows by 2 (false then true) on a completing send; end state true"
    - "controller.modalZapReceipts.children.length === 1 per rendered receipt"
  determinism_controls:
    - "pure fee function with explicit inputs; configured PLATFORM_FEE_PERCENT read via getDefaultPlatformFeePercent (not hard-coded)"
    - "modal stub records setZapCompleted/resetZapForm calls; DummyElement DOM"
  anti_cheat_rationale:
    prevents:
      - "hard-coded return value"
      - "over-mocking internal logic"
    note: "Three corrections, all to match shipped behavior. (1) The fee-fallback test
      asserted 0; the function correctly falls back to the configured platform default
      so a garbage override cannot zero out (bypass) the fee — corrected to assert
      against getDefaultPlatformFeePercent and that the result is > 0 (STRICTER: pins
      the no-bypass guarantee). (2) The completion-count test asserted +1; sendZap
      legitimately resets completed=false at the start of every send and sets true at
      the terminal path (+2), with the end-state==true assertion retained as the real
      invariant. (3) The receipt-render tests set modalZapReceipts on the outer
      VideoModal, but rendering was refactored to delegate to the inner zapController
      which owns that element — set/assert on the controller's element (a harness fix,
      no assertion weakened). NOTE: these accompany three PRODUCTION bug fixes found
      via the same tests — NWC getPublicKey hex->bytes (connection was broken), the
      in-flight 'Sending…' status mis-toned as warning, and the success path not using
      the holistic resetZapForm."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-23
  file: tests/video-modal-zap.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-zap-paid-but-receipt-unconfirmed-is-success
      given: "A zap share whose PAYMENT succeeded (NWC returned a preimage) but whose kind-9735 zap receipt could not be validated on the advertised relays"
      when: "sendZap finishes"
      then: "It is reported as a SUCCESS (Sent N sats) with a soft note that the on-relay receipt couldn't be confirmed — NOT as a warning/error. The form resets and the attempt is marked completed."
  observable_outcomes:
    - "last zap status tone === 'success' (was 'warning')"
    - "status message matches /sent 750 sats/i and /couldn't confirm the zap receipt/i (was /awaiting validated zap receipt/i)"
    - "receipts rendered in partial mode; form reset; completedStates +2 ending true"
  determinism_controls:
    - "injected splitAndZap returns a paid receipt (status:success, payment.preimage) with validation.status:'failed'"
  anti_cheat_rationale:
    prevents:
      - "snapshot rubber-stamping"
      - "hard-coded return value"
    note: "The previous expectation asserted the BUG: a paid share whose 9735 receipt
      wasn't found was surfaced as a 'warning' error ('Awaiting validated zap receipt')
      and routed through notifyError — telling users a SUCCESSFUL zap had failed
      (confirmed live: the platform fee landed but the UI showed a red error). The NWC
      payment preimage is proof the invoice was paid; the 9735 receipt is supplementary
      and is commonly not seen by a static client (relay coverage/timing). Corrected to
      assert success-with-a-soft-note. Genuine PAYMENT failures (status !== 'success')
      still go through the failure/retry path, unchanged."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""

test_integrity_note:
  date: 2026-06-23
  file: tests/zap-split.test.mjs
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-zap-receipt-found-by-indexed-tags
      given: "A published kind-9735 zap receipt for a video zap (the zap request carries #p recipient + #e event tags)"
      when: "validateZapReceipt queries relays for the receipt"
      then: "It filters by the reliably-indexed #e + #p tags (not #bolt11, which most relays don't index), then confirms via author pubkey + bolt11 + description hash"
  observable_outcomes:
    - "the relay query filter has filters[0]['#e'] === [eventId] and ['#p'] === [recipient]"
    - "filters[0]['#bolt11'] is absent"
    - "a matching receipt still validates as status:'passed'"
  determinism_controls:
    - "mock SimplePool.list captures the filter; fixed zap request tags + test bolt11 with matching description hash"
  anti_cheat_rationale:
    prevents:
      - "snapshot rubber-stamping"
      - "over-mocking internal logic"
    note: "The previous test asserted filters[0]['#bolt11'][0] === invoice — encoding the
      bug. Relays generally do not index arbitrary tags like bolt11, so that query found
      nothing and a successfully-paid zap was reported as having no receipt. Corrected to
      assert the reliably-indexed #e/#p anchors (added an #e tag to the request to model a
      real video zap). The receipt-matching assertions (passed status, correct event,
      checked relays) are unchanged/retained — coverage is stronger, not weaker."
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""