Files
bitvid/issues/todo-test-coverage-gaps.md
T
Claude 75136cb613 test: add comprehensive payment system tests (lnurl, platformFee, zapReceipts, zapRequests)
Add 4 new test files covering previously untested payment/zap logic:

- lnurl.test.mjs: bech32 encode/decode roundtrip, resolveLightningAddress
  (email, lnurl, URL formats), fetchPayServiceData (success, HTTP errors,
  invalid JSON, error status), validateInvoiceAmount (boundaries, invalid
  inputs), requestInvoice (comment truncation, amount formats, error paths)

- platform-fee.test.mjs: parsePercentValue (numbers, bigints, strings with
  %, fractions, edge cases), clampPercent (boundaries, NaN, non-finite),
  getDefaultPlatformFeePercent, resolvePlatformFeePercent (override vs
  fallback behavior)

- zap-receipt-validator.test.mjs: computeZapRequestHash determinism,
  extractDescriptionHashFromBolt11, validateZapReceipt covering all failure
  modes (missing request, invalid JSON, no relays, missing invoice,
  hash mismatch, amount mismatch, wrong pubkey, invalid signature,
  no receipt found) plus success path and override injection

- zap-requests.test.mjs: normalizeRelayList (dedup, filtering, empty),
  resolveLnurlTag (bech32 vs email addresses), buildZapRequestPayload
  (minimal and full params, timestamp handling), signZapRequest (signer
  validation)

Also updates todo-test-coverage-gaps.md with a prioritized, detailed plan
covering all remaining untested areas (P0 through P3).

https://claude.ai/code/session_01QDsPRh4f31ZpDYqS1kETFV
2026-02-05 14:50:41 +00:00

324 lines
15 KiB
Markdown

# TODO: Improve Test Coverage for Untested Areas
**Source:** Test coverage analysis (Feb 2026)
**Context:**
Several areas of the codebase lack unit test coverage. This document tracks the remaining gaps after the initial test coverage expansion. Items are ordered by risk/impact.
---
## P0 — Financial Correctness (js/payments/)
These files handle real money (Lightning/zap payments). Bugs here directly affect user funds. **No existing tests except zapSplit, zapSharedState, and nwcClient.**
### [x] `js/payments/lnurl.js` — LNURL protocol + bech32 encoding
**Exports:** `encodeLnurlBech32`, `resolveLightningAddress`, `fetchPayServiceData`, `validateInvoiceAmount`, `requestInvoice`
**Testing helpers via `__TESTING__`:** `bech32Encode`, `bech32Decode`, `decodeLnurlBech32`, `convertWords`, `createChecksum`
**Dependencies:** TextEncoder/TextDecoder, fetch API
**Test cases needed:**
- bech32 encode/decode roundtrip for various URLs
- `resolveLightningAddress`: lnurl-encoded input, email format (`user@domain`), HTTPS URL, invalid/empty input, mixed-case rejection
- `fetchPayServiceData`: successful fetch, HTTP errors (404/500), invalid JSON, missing callback, error status in response body, invalid metadata JSON gracefully skipped
- `validateInvoiceAmount`: within limits, below min, above max, zero/negative, non-numeric
- `requestInvoice`: valid retrieval, comment truncation based on `commentAllowed`, both `amountSats` and `amountMsats`, missing invoice in response, error response
### [x] `js/payments/zapReceiptValidator.js` — Validates zap receipts from relays
**Exports:** `validateZapReceipt(context, overrides)`
**Returns:** `{status: "passed"|"failed"|"skipped", reason, event, checkedRelays}`
**Dependencies:** nostr-tools (SimplePool, event validation), bech32, sha256, bytesToHex
**Test cases needed:**
- Missing/invalid zap request JSON → status:"skipped"
- Valid zap request but no relays specified → status:"skipped"
- Invoice description hash extraction and SHA256 matching
- Amount validation (invoice amount vs expected share, with tolerance)
- Receipt found with matching pubkey and valid signature → status:"passed"
- Receipt found with wrong pubkey → status:"failed"
- No receipt found on relays → status:"failed"
- Override injection for `createPool`, `listEvents`, `decodeDescriptionHash`, `getAmountFromBolt11`
- BOLT11 parsing: valid with description hash tag "h", without it, truncated/invalid
### [x] `js/payments/platformFee.js` — Fee percentage parsing and clamping
**Exports:** `parsePercentValue`, `clampPercent`, `getDefaultPlatformFeePercent`, `resolvePlatformFeePercent`
**Dependencies:** `../config.js` (PLATFORM_FEE_PERCENT)
**Test cases needed:**
- `parsePercentValue`: integers, floats, bigints, string percentages ("50%"), string fractions ("10/100"), division by zero, empty strings, whitespace
- `clampPercent`: 0, 50, 100, >100, <0, NaN, non-numeric → 0
- `getDefaultPlatformFeePercent`: valid config, invalid config → 0
- `resolvePlatformFeePercent`: with override (uses override), without (uses default), null/undefined → default
### [x] `js/payments/zapRequests.js` — Zap request event creation and publishing
**Exports:** `resolveZapRecipient`, `buildZapRequestPayload`, `signZapRequest`, `publishZapRequest`, `createAndPublishZapRequest`
**Testing helpers via `__TESTING__`:** `normalizeRelayList`, `resolveLnurlTag`
**Dependencies:** lnurl.js, nostrEventSchemas, nostrPublish, signRequestQueue, RELAY_URLS
**Test cases needed:**
- `buildZapRequestPayload`: all params, minimal params, created_at override, additionalTags passthrough → verify kind:9734 event structure
- `signZapRequest`: valid signer, timeout scenario
- `publishZapRequest`: relay acceptance, relay rejection → throws
- `createAndPublishZapRequest`: full flow, recipient doesn't support Nostr zaps, invoice fetch failure (non-fatal)
- `normalizeRelayList`: deduplication, empty list
### [ ] `js/payments/zapNotifications.js` — UI notifications for zap events
**Exports:** `showLoginRequiredToZapNotification`, `syncNotificationPortalVisibility`
**Dependencies:** DOM APIs, applicationContext, zapMessages
**Test cases needed:**
- With app context (showStatus available)
- Fallback to showError
- Without app context (creates DOM portal elements)
- Auto-hide timing (configurable `autoHideMs`)
- Portal visibility sync based on banner state
- Multiple calls don't duplicate elements
- Accessibility attributes (aria-live, aria-label)
### [ ] `js/payments/platformAddress.js` — Platform Lightning address from admin profile
**Exports:** `getPlatformLightningAddress`, `__resetPlatformAddressCache`
**Dependencies:** config (PLATFORM_LUD16_OVERRIDE, ADMIN_SUPER_NPUB), nostrClient, nostr-tools (nip19)
**Test cases needed:**
- PLATFORM_LUD16_OVERRIDE set → returns immediately
- No override: fetches admin kind:0 profile, extracts lud16 or lud06
- Cache TTL (10 min): fresh → returns cached, expired → re-fetches
- forceRefresh bypasses cache
- Missing ADMIN_SUPER_NPUB → null
- Relay failures → null (logged)
- nostr-tools unavailable → fallback handling
---
## P0 — Identity/Feed-Critical Utilities
### [ ] `js/utils/hex.js` — Hex normalization for Nostr pubkeys
**Exports:** `normalizeHexString`, `normalizeHexId`, `normalizeHexPubkey`
**Dependencies:** None (pure utility)
**Why P0:** Incorrect hex normalization breaks identity lookups silently.
**Test cases needed:**
- Valid 64-char hex → lowercase trimmed
- Mixed case → normalized
- Whitespace → trimmed
- Non-string inputs → graceful handling
- Empty string, null, undefined
### [ ] `js/utils/hashtagNormalization.js` — Hashtag canonicalization
**Exports:** `normalizeHashtag`, `formatHashtag`
**Dependencies:** None (pure utility)
**Why P0:** Affects feed filtering and content matching across the app.
**Test cases needed:**
- Strip `#` prefix, handle `##` double prefix
- Trim whitespace before and after
- Lowercase conversion
- Empty-after-strip detection
- `formatHashtag` adds `#` prefix back
- Roundtrip: normalizeHashtag → formatHashtag
---
## P1 — Upload Reliability (js/storage/)
### [ ] `js/storage/s3-multipart.js` — Multipart upload chunking
**Why:** Multipart upload bugs are extremely hard to debug in production. Only `dmDb.js` is tested in this directory.
**Test cases needed:**
- Chunk splitting at correct sizes
- Part numbering sequence
- Upload completion/abort flows
- Progress tracking
- Error recovery for individual part failures
### [ ] `js/storage/s3-client.js` — S3-compatible client
**Test cases needed:**
- Request signing
- Bucket operations
- Error response parsing
- SDK loading fallback
### [ ] `js/storage/r2-mgmt.js` — R2 bucket management
### [ ] `js/storage/r2-s3.js` — R2/S3 integration layer
### [ ] `js/services/s3UploadService.js` — Upload orchestration and progress tracking
---
## P1 — Content Safety (js/feedEngine/)
### [ ] `js/feedEngine/kidsAudienceFilterStage.js` — Kids mode content filtering
**Exports:** `createKidsAudienceFilterStage()` → async filter function
**Dependencies:** nostrService, `toSet` utility
**Why P1:** Safety-critical — wrong classification shows inappropriate content to children.
**Test cases needed:**
- Removes non-kids, NSFW, blacklisted, content-warning-flagged videos
- Warning normalization: `trim().toLowerCase().replace(/[\s_]+/g, "-")`
- Array/string/Set warning inputs; comma/semicolon/pipe delimiters
- Context fallback: runtime → config → fallback defaults
- Empty video lists, null video objects
### [ ] `js/feedEngine/kidsScoring.js` — Kids content scoring (0-1 clamped)
**Exports:** `createKidsScorerStage()` → async scoring function
**Dependencies:** `normalizeHashtag`, `toSet`, `isPlainObject`
**Key factors:** age-appropriateness (0.35), educational (0.25), author trust (0.15), popularity (0.1), freshness (0.1), safety multiplier (0.6 weight)
**Test cases needed:**
- Individual factor calculations at boundaries
- Clamping edge cases (NaN, Infinity → 0 or 1)
- Exponential freshness decay with various half-lives
- Safety penalty stacking (trusted reports + muted)
- Weight override resolution order: options → runtime → config
- Metadata injection correctness (kidsScore, components)
### [ ] `js/feedEngine/sorters.js` — Feed sorting strategies
**Exports:** `createChronologicalSorter()`, `createExploreDiversitySorter()`, `createKidsScoreSorter()`
**Test cases needed:**
- Chronological: timestamp resolution path priority (hook → rootCreatedAt → nip71Source → created_at), trustedMuted items sunk to end
- MMR diversity: cosine similarity on zero vectors, NaN handling, lambda=0 (pure diversity) vs lambda=1 (pure relevance)
- Kids score: desc score → desc timestamp → video ID tiebreak
---
## P1 — DM UI Components (js/ui/dm/)
**Why:** 14 files, zero tests. Growing feature area with complex state management.
### [ ] `js/ui/dm/MessageBubble.js` — Individual message rendering
### [ ] `js/ui/dm/Composer.js` — Message composition UI
### [ ] `js/ui/dm/DMPrivacySettings.js` — Privacy configuration
### [ ] `js/ui/dm/MessageThread.js` — Message thread display
### [ ] `js/ui/dm/NotificationCenter.js` — Notification handling
### [ ] `js/ui/dm/ConversationList.js` — Conversation list rendering
### [ ] `js/ui/dm/ZapInterface.js` — In-chat zap sending
### [ ] `js/ui/dm/ZapReceiptList.js` — Zap receipt display
### [ ] `js/ui/dm/ContactRow.js` — Contact list item
### [ ] `js/ui/dm/Avatar.js` — Profile avatar display
### [ ] `js/ui/dm/AppShell.js` — DM app shell layout
### [ ] `js/ui/dm/DayDivider.js` — Date separator between messages
### [ ] `js/ui/dm/index.js` — Module entry point
---
## P2 — Playback Failure Modes
### [ ] `js/services/playbackService.js` — Additional failure/fallback path tests
**Existing tests:** playbackService.test.mjs (3 variants: base, forcedSource, order)
**What's missing:** The URL→magnet fallback chain is documented as the most fragile path.
**Test cases to add:**
- No video element → error path
- No playable source (no URL, bad magnet)
- Probe HEAD timeout → triggers torrent fallback
- Playback stall (8s default watchdog) → triggers fallback
- Unsupported BTIH magnet format
- Autoplay blocked (NotAllowedError) → wait for user play → restore watchdog
- Race conditions: request signature matching, overlapping session cleanup
- effectiveTimeout=0 when forcedSource set
- Watchdog cleanup before new session
---
## P2 — View Managers
### [ ] `js/forYouView.js` — "For You" feed with empty state lifecycle
**Exports:** `initForYouView()`, `ForYouView` class
**Test focus:** MutationObserver lifecycle, empty state injection/removal, action button delegation
### [ ] `js/exploreView.js` — Explore feed
**Test focus:** Same empty state management pattern as forYouView
### [ ] `js/searchView.js` — Search results with profile/video dual search
**Exports:** `initSearchView()` + internal helpers
**Test focus:** Token cancellation for stale searches, JSON.parse failures, filter state persistence, relay search fallback to local
### [ ] `js/hashView.js` — Hash-based view routing
**Exports:** `getHashViewName()`, `setHashView(viewName, options)`
**Test focus:** Regex extraction, URL param manipulation, modal/v param preservation
### [ ] `js/kidsView.js` — Kids-only feed
**Test focus:** Same pattern as exploreView
---
## P2 — UI Modal Components
### [ ] `js/ui/ModalManager.js` — Modal lifecycle management
### [ ] `js/ui/components/DeleteModal.js` — Delete confirmation (destructive operation)
### [ ] `js/ui/components/UploadModal.js` — Upload flow (destructive operation)
### [ ] `js/ui/components/EditModal.js` — Edit modal
### [ ] `js/ui/components/SearchFilterModal.js` — Search filter UI
### [ ] `js/ui/components/EventDetailsModal.js` — Event details display
### [ ] `js/ui/initEditModal.js` — Edit modal initialization
### [ ] `js/ui/initDeleteModal.js` — Delete modal initialization
---
## P2 — Utilities (js/utils/)
### [ ] `js/utils/logger.js` — Logger with dev/user channels
**Exports:** `logger`, `devLogger`, `userLogger`, `isCompatibleLogger`
**Test focus:** Console fallback chain, force flag parsing, dev mode gating, noop fallback for missing console
### [ ] `js/utils/profileBatchFetcher.js` — Batch profile fetching
**Exports:** `batchFetchProfilesFromRelays`
**Test focus:** Pubkey validation (HEX64), cache hit bypass, newest-by-timestamp dedup, malformed JSON handling, relay timeouts
### [ ] `js/utils/domUtils.js` — DOM helpers
### [ ] `js/utils/storage.js` — localStorage abstraction
### [ ] `js/utils/storagePointer.js` — Storage pointer helpers
### [ ] `js/utils/videoTimestamps.js` — Timestamp parsing
### [ ] `js/utils/videoPointer.js` — Video pointer utilities
### [ ] `js/utils/torrentHash.js` — Torrent hash utilities
### [ ] `js/utils/linkPreviewSettings.js` — Link preview config
### [ ] `js/utils/profileMedia.js` — Profile media handling
### [ ] `js/utils/qrcode.js` — QR code generation
---
## P3 — Infrastructure Improvements
### [ ] Add coverage reporting
**Details:** Add `--experimental-test-coverage` to Node.js test runner (available since Node 20) or integrate `c8`. This gives concrete numbers and trend tracking for CI.
### [ ] Worker test harness
**Details:** Web Worker code (`dmDecryptWorker.js`, `nip04Worker.js`) can't be tested in JSDOM. Consider `vitest` with `@vitest/web-worker` or a lightweight worker polyfill.
### [ ] Fix flaky E2E tests
**Details:** `KNOWN_ISSUES.md` documents broken E2E tests: modal timeouts, missing elements, undefined functions. Fix these to restore CI confidence.
---
## P3 — Low Priority / Hard to Test
### Nostr Workers (need worker harness)
- [ ] `js/nostr/dmDecryptWorker.js`
- [ ] `js/nostr/dmDecryptWorkerClient.js`
- [ ] `js/nostr/nip04Worker.js`
- [ ] `js/nostr/nip04WorkerClient.js`
### Diagnostic/Debug Files
- [ ] `js/nostr/countDiagnostics.js`
- [ ] `js/nostr/maxListenerDiagnostics.js`
### Remaining UI Components
- [ ] `js/ui/watchHistoryController.js`
- [ ] `js/ui/profileIdentityController.js`
- [ ] `js/ui/ambientBackground.js`
- [ ] `js/ui/components/FeedInfoPopover.js`
- [ ] `js/ui/components/EmbedVideoModal.js`
- [ ] `js/ui/components/RevertModal.js`
- [ ] `js/ui/components/ShareNostrModal.js`
---
## E2E Test Gaps
Only 6 E2E tests exist. Consider adding:
- [ ] Full upload flow (video file → published note)
- [ ] Video playback fallback scenarios (URL → magnet)
- [ ] Authentication flows (NIP-07, NIP-46 connection)
- [ ] Relay switching behavior under failure conditions
- [ ] Watch history persistence and sync
- [ ] DM conversation flow
---
## Notes
- When adding tests, follow existing patterns in `tests/` directory
- Use `node:test` and `node:assert/strict` for unit tests
- Use Playwright for E2E tests
- Mock external dependencies (nostr-tools, relays) appropriately
- Run `npm run test:unit` to verify new tests pass
- Files exposing `__TESTING__` objects provide internal helpers for white-box testing
- Payment files are pure-ish logic (no DOM) — good candidates for straightforward unit tests