Skip to content

Comments

test: add NWC faucet integration test#535

Open
DSanich wants to merge 5 commits intogetAlby:masterfrom
DSanich:feat/jest-nwc-faucet-test
Open

test: add NWC faucet integration test#535
DSanich wants to merge 5 commits intogetAlby:masterfrom
DSanich:feat/jest-nwc-faucet-test

Conversation

@DSanich
Copy link
Contributor

@DSanich DSanich commented Feb 18, 2026

Summary

Adds a single Jest integration test that creates a wallet via the NWC faucet and verifies the balance is 10_000 sats. Per reviewer feedback, this PR focuses on the test setup for easier review.

Changes

  • src/test/helpers.tscreateTestWallet(balanceSats) helper using https://faucet.nwc.dev (inspired by Alby CLI tests)
  • src/test/nwc-faucet.test.ts — Integration test: create wallet → get balance via NWCClient.getBalance() → assert 10_000 sats
  • .gitignore — Add test-results and playwright-report

How to run

yarn test src/test/nwc-faucet.test.ts

Summary by CodeRabbit

  • Tests

    • Added end-to-end integration tests for the NWC faucet: wallet provisioning, balance verification, and teardown; included a new test helper to create faucet wallets.
  • Chores

    • Added a dedicated e2e Jest config and test:e2e script; updated unit test ignores to skip e2e runs.
    • Expanded .gitignore to exclude e2e test outputs and reports.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end testing for the NWC faucet: new E2E test and helper with retry/backoff and lud16 parsing, a dedicated Jest E2E config, updates to main Jest config and npm scripts, and expanded .gitignore entries for test artifacts. No exported/public library APIs changed.

Changes

Cohort / File(s) Summary
Git & CI artifacts
/.gitignore
Added test-results and playwright-report to ignore test artifacts.
NPM scripts & formatting
package.json
Included e2e paths in formatting scripts and added a test:e2e script.
Jest Configurations
jest.config.ts, jest.e2e.config.ts
Added testPathIgnorePatterns to main Jest config; added jest.e2e.config.ts preset (ts-jest) targeting <rootDir>/e2e/**/*.test.ts.
E2E Tests & Helpers
e2e/helpers.ts, e2e/nwc-faucet.test.ts
New TestWallet interface and createTestWallet(balanceSats = 10000, retries = 5) implementing POST-to-faucet with retry/backoff (2s,4s,6s,...), parsing returned NWC URL to extract lud16/lightning address; new integration test creates a wallet, instantiates NWCClient against the returned URL, asserts balance equals 10,000 sats (10_000_000 msats) and closes the client (60s timeout), marks as network-dependent.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant TestRunner as Test Runner
participant Helper as e2e/helpers.ts
participant Faucet as NWC Faucet (HTTP)
participant Client as NWCClient
participant Node as NWC Node / Wallet

TestRunner->>Helper: createTestWallet(balanceSats)
Helper->>Faucet: POST /?balance=10000
Faucet-->>Helper: 200 OK (body/redirect with NWC URL containing lud16)
Helper->>Helper: parse NWC URL -> extract lud16 -> lightningAddress
Helper-->>TestRunner: return TestWallet { nwcUrl, lightningAddress }
TestRunner->>Client: new NWCClient(nwcUrl)
TestRunner->>Client: getBalance()
Client->>Node: request balance
Node-->>Client: balance (msats)
Client-->>TestRunner: balance result
TestRunner->>Client: close()

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped to the faucet, hopeful and bright,
POSTed for sats in the soft morning light,
Retries danced twice, then four, then six,
Parsed lud16, fetched balances in ticks,
Tests now hum — small hops, big delight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'test: add NWC faucet integration test' directly and clearly summarizes the main change: adding an integration test for the NWC faucet.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DSanich
Copy link
Contributor Author

DSanich commented Feb 18, 2026

@rolznz Hi!

#533 (comment) - As we discussed here, I added one Jest test. I look forward to your review 🙏

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
.gitignore (1)

3-4: playwright-report entry seems unrelated to this Jest-only PR.

If Playwright isn't used anywhere in the repo, this entry is dead weight. If it's anticipating future Playwright adoption, a comment would help.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 3 - 4, The .gitignore contains an entry
"playwright-report" that appears unrelated to this Jest-only PR; either remove
the "playwright-report" line to avoid dead-weight ignores or keep it but add a
clarifying comment above it indicating it's reserved for future Playwright
artifacts (e.g., "reserved for Playwright reports if/when Playwright is
adopted"); ensure you only modify the "playwright-report" entry (leave
"test-results" as-is) and commit the change so the intent is clear to reviewers.
src/test/helpers.ts (2)

23-25: Add an AbortController signal to the fetch call.

Without a signal, the underlying TCP connection persists even after Jest's 30_000 ms timeout fires, which can delay teardown or interfere with subsequent test runs.

♻️ Proposed fix
+      const controller = new AbortController();
+      const fetchTimeout = setTimeout(() => controller.abort(), 25_000);
       const response = await fetch(`${FAUCET_URL}?balance=${balanceSats}`, {
         method: "POST",
+        signal: controller.signal,
       });
+      clearTimeout(fetchTimeout);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/helpers.ts` around lines 23 - 25, The fetch call that posts to
`${FAUCET_URL}?balance=${balanceSats}` (the const response = await fetch(...) in
helpers.ts) needs an AbortController signal so the request is cancelled on test
timeout; create an AbortController, start a timer (e.g., via setTimeout) that
calls controller.abort() after Jest's timeout threshold, pass controller.signal
into the fetch options, and clear the timer once the fetch resolves/rejects so
the TCP connection is properly torn down.

27-30: Duplicated backoff logic and an unreachable final throw.

Two separate issues in the retry loop:

  1. The delay-and-continue pattern (lines 27–30 and 43–46) is identical in both branches — extract to a shared helper or consolidate into a single finally-style check after the try/catch.
  2. The throw at line 50 is unreachable: in the last iteration the throw at line 31 is raised inside try, immediately caught at line 42, and re-thrown at line 47, so the for loop never completes normally.
♻️ Proposed refactor
 export async function createTestWallet(
   balanceSats = 10_000,
   retries = 5,
 ): Promise<TestWallet> {
+  let lastError: unknown;
   for (let i = 0; i < retries; i++) {
     try {
       const response = await fetch(`${FAUCET_URL}?balance=${balanceSats}`, {
         method: "POST",
       });
       if (!response.ok) {
         throw new Error(`Faucet request failed: ${response.status}`);
       }
       const nwcUrl = (await response.text()).trim();
       const lud16Match = nwcUrl.match(/lud16=([^&\s]+)/);
       if (!lud16Match) {
         throw new Error("No lud16 in NWC URL");
       }
       return {
         nwcUrl,
         lightningAddress: decodeURIComponent(lud16Match[1]),
       };
     } catch (error) {
+      lastError = error;
       if (i < retries - 1) {
         await new Promise((r) => setTimeout(r, 2000 * (i + 1)));
-        continue;
       }
-      throw error;
     }
   }
-  throw new Error("Failed to create test wallet");
+  throw lastError ?? new Error("Failed to create test wallet");
 }

Also applies to: 43-46, 50-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/helpers.ts` around lines 27 - 30, Consolidate the duplicate backoff
logic by extracting the setTimeout-based delay into a shared helper (e.g.,
sleep(ms)) and replace both duplicated await new Promise((r) => setTimeout(...))
occurrences with await sleep(2000 * (i + 1)); also fix the unreachable throw by
removing the throw that never executes and instead either rethrow the caught
error when i === retries - 1 inside the catch or capture the last error (e.g.,
lastError) and after the for loop throw lastError so that the final error is
actually propagated; update the retry loop that uses variables retries and i to
use this single-place delay and proper final throw behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.gitignore:
- Around line 3-4: The .gitignore contains an entry "playwright-report" that
appears unrelated to this Jest-only PR; either remove the "playwright-report"
line to avoid dead-weight ignores or keep it but add a clarifying comment above
it indicating it's reserved for future Playwright artifacts (e.g., "reserved for
Playwright reports if/when Playwright is adopted"); ensure you only modify the
"playwright-report" entry (leave "test-results" as-is) and commit the change so
the intent is clear to reviewers.

In `@src/test/helpers.ts`:
- Around line 23-25: The fetch call that posts to
`${FAUCET_URL}?balance=${balanceSats}` (the const response = await fetch(...) in
helpers.ts) needs an AbortController signal so the request is cancelled on test
timeout; create an AbortController, start a timer (e.g., via setTimeout) that
calls controller.abort() after Jest's timeout threshold, pass controller.signal
into the fetch options, and clear the timer once the fetch resolves/rejects so
the TCP connection is properly torn down.
- Around line 27-30: Consolidate the duplicate backoff logic by extracting the
setTimeout-based delay into a shared helper (e.g., sleep(ms)) and replace both
duplicated await new Promise((r) => setTimeout(...)) occurrences with await
sleep(2000 * (i + 1)); also fix the unreachable throw by removing the throw that
never executes and instead either rethrow the caught error when i === retries -
1 inside the catch or capture the last error (e.g., lastError) and after the for
loop throw lastError so that the final error is actually propagated; update the
retry loop that uses variables retries and i to use this single-place delay and
proper final throw behavior.

@rolznz
Copy link
Contributor

rolznz commented Feb 19, 2026

@DSanich thanks for the PR! these should somehow be differentiated from our non-E2E tests. How can we do that?

@DSanich
Copy link
Contributor Author

DSanich commented Feb 19, 2026

@rolznz Hello! Glad to help. My suggestion is to rename the script and create a separate directory for e2e tests. I did this in the commit 2cc0e44. Look at this please 🙏

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
e2e/helpers.ts (1)

23-25: No timeout on fetch — a stalled connection will hang until Jest's test-level timeout fires, blocking all retry attempts.

Add an AbortController signal so each attempt has a bounded duration:

♻️ Suggested fix
+      const controller = new AbortController();
+      const fetchTimeout = setTimeout(() => controller.abort(), 5000);
       const response = await fetch(`${FAUCET_URL}?balance=${balanceSats}`, {
         method: "POST",
+        signal: controller.signal,
       });
+      clearTimeout(fetchTimeout);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers.ts` around lines 23 - 25, The fetch call that posts to
`${FAUCET_URL}?balance=${balanceSats}` has no timeout and can hang; modify the
helper so each attempt creates an AbortController, set a per-attempt timeout
(e.g., setTimeout that calls controller.abort()) and pass controller.signal into
fetch, and clear the timeout after fetch completes so stalled connections are
aborted and retries can proceed; update the code that uses the response variable
to handle AbortError appropriately.
package.json (1)

51-51: lint:js does not cover e2e/.

format and format:fix were extended to include e2e/**/*.(js|ts) (lines 54–55), but lint:js still targets only src. E2E files will be formatted but never statically analysed.

♻️ Suggested fix
-    "lint:js": "eslint src --ext .js,.ts --max-warnings 0",
+    "lint:js": "eslint src e2e --ext .js,.ts --max-warnings 0",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 51, The "lint:js" npm script currently only lints src
files; update the "lint:js" script (script name lint:js) to include the e2e
directory as well (same extensions as format/format:fix) so E2E files are
statically analysed—modify the command to target both src and e2e (or use a glob
like e2e/**/*.{js,ts}) while keeping --ext .js,.ts and --max-warnings 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/helpers.ts`:
- Around line 33-48: The parse error "No lud16 in NWC URL" is being swallowed by
the surrounding retry loop because the throw inside the try is caught by the
catch that retries; update the logic in the function that reads nwcUrl (use
symbols nwcUrl, lud16Match, lightningAddress and the retries loop) so that only
transient I/O/errors trigger a retry: after reading nwcUrl and computing
lud16Match, if lud16Match is falsy immediately throw the parse error (or
rethrow) outside the retry catch path (e.g., validate and throw before the
try/retry block or detect parse errors in the catch and rethrow without
retrying) so parse/logic errors do not incur retry delays.

In `@e2e/nwc-faucet.test.ts`:
- Around line 19-23: The test currently calls nwc.getBalance() and then
nwc.close(), but if NWCClient.getBalance() rejects the nwc.close() call is
skipped and sockets remain open; wrap the call to nwc.getBalance() in a
try/finally (using the existing nwc variable and NWCClient.close()) so that
nwc.close() is invoked in the finally block regardless of success or failure,
ensuring WebSocket connections are always closed even on errors.
- Around line 14-26: The Jest test timeout (30_000) is too small for
createTestWallet's linear backoff (retries default = 5) plus network round-trips
and NWCClient.getBalance's replyTimeout; update the test to either increase the
Jest timeout (e.g., to cover worst-case backoff + replyTimeout) or call
createTestWallet with a reduced retries/ceiling (e.g., retries = 3) so the total
backoff (2+4s) fits inside 30_000; adjust the call site in the test that invokes
createTestWallet and/or the test's third-argument timeout value to implement the
chosen option and rerun the test.

---

Nitpick comments:
In `@e2e/helpers.ts`:
- Around line 23-25: The fetch call that posts to
`${FAUCET_URL}?balance=${balanceSats}` has no timeout and can hang; modify the
helper so each attempt creates an AbortController, set a per-attempt timeout
(e.g., setTimeout that calls controller.abort()) and pass controller.signal into
fetch, and clear the timeout after fetch completes so stalled connections are
aborted and retries can proceed; update the code that uses the response variable
to handle AbortError appropriately.

In `@package.json`:
- Line 51: The "lint:js" npm script currently only lints src files; update the
"lint:js" script (script name lint:js) to include the e2e directory as well
(same extensions as format/format:fix) so E2E files are statically
analysed—modify the command to target both src and e2e (or use a glob like
e2e/**/*.{js,ts}) while keeping --ext .js,.ts and --max-warnings 0.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@e2e/nwc-faucet.test.ts`:
- Around line 14-28: No changes needed — the test was updated to increase the
timeout to 60_000 and call createTestWallet(EXPECTED_BALANCE_SATS, 3) to handle
retries, and the NWCClient instance is properly closed in a finally block via
nwc.close() after calling nwc.getBalance(), so leave the test as-is.

@DSanich DSanich force-pushed the feat/jest-nwc-faucet-test branch from f8c77bd to 7e7f034 Compare February 20, 2026 10:53
@DSanich
Copy link
Contributor Author

DSanich commented Feb 20, 2026

@rolznz I rebased this pull request to reflect our merge #534

preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '/e2e/browser/'],
testPathIgnorePatterns: ['/node_modules/', '/e2e/'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks wrong, isn't it? @DSanich

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see there's a new config for e2e

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['<rootDir>/e2e/**/*.test.ts'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this work? the browser folder is in there and that should be done by playwright?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants