Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/nwc-faucet.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { createTestWallet } from "./helpers";

Expand Down
60 changes: 60 additions & 0 deletions e2e/nwc-list-transactions-after-payment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { createTestWallet } from "./helpers";

/**
* E2E test for list_transactions after a successful invoice payment.
* Requires network access.
*/
describe("NWC list_transactions after pay_invoice", () => {
const AMOUNT_MSATS = 100_000; // 100 sats
const BALANCE_SATS = 10_000;

let sender: { nwcUrl: string };
let receiver: { nwcUrl: string };

beforeAll(async () => {
receiver = await createTestWallet(BALANCE_SATS, 3);
sender = await createTestWallet(BALANCE_SATS, 3);
}, 60_000);

test(
"returns an outgoing settled transaction for the paid invoice",
async () => {
const receiverClient = new NWCClient({
nostrWalletConnectUrl: receiver.nwcUrl,
});
const senderClient = new NWCClient({ nostrWalletConnectUrl: sender.nwcUrl });

try {
const invoiceResult = await receiverClient.makeInvoice({
amount: AMOUNT_MSATS,
description: "E2E list_transactions after payment",
});
expect(invoiceResult.invoice).toBeDefined();

await senderClient.payInvoice({ invoice: invoiceResult.invoice });

const listResult = await senderClient.listTransactions({
limit: 20,
type: "outgoing",
});

expect(Array.isArray(listResult.transactions)).toBe(true);

const matchingTx = listResult.transactions.find(
(tx) => tx.invoice === invoiceResult.invoice,
);

expect(matchingTx).toBeDefined();
expect(matchingTx?.type).toBe("outgoing");
expect(matchingTx?.state).toBe("settled");
expect(matchingTx?.amount).toBe(AMOUNT_MSATS);
} finally {
receiverClient.close();
senderClient.close();
}
},
60_000,
);
});
48 changes: 48 additions & 0 deletions e2e/nwc-lookup-invoice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { createTestWallet } from "./helpers";

/**
* E2E test for lookup_invoice using the NWC faucet.
* Requires network access.
*/
describe("NWC lookup_invoice", () => {
const AMOUNT_MSATS = 100_000; // 100 sats
const BALANCE_SATS = 10_000;

let sender: { nwcUrl: string };
let receiver: { nwcUrl: string };

beforeAll(async () => {
receiver = await createTestWallet(BALANCE_SATS, 3);
sender = await createTestWallet(BALANCE_SATS, 3);
}, 60_000);

test(
"finds paid invoice by invoice string",
async () => {
const receiverClient = new NWCClient({ nostrWalletConnectUrl: receiver.nwcUrl });
const senderClient = new NWCClient({ nostrWalletConnectUrl: sender.nwcUrl });

try {
const invoiceResult = await receiverClient.makeInvoice({
amount: AMOUNT_MSATS,
description: "E2E lookup test",
});
expect(invoiceResult.invoice).toBeDefined();

await senderClient.payInvoice({ invoice: invoiceResult.invoice });

const lookupResult = await receiverClient.lookupInvoice({
invoice: invoiceResult.invoice,
});
expect(lookupResult.payment_hash).toBeDefined();
expect(lookupResult.invoice).toBe(invoiceResult.invoice);
} finally {
receiverClient.close();
senderClient.close();
}
},
60_000,
);
});
47 changes: 47 additions & 0 deletions e2e/nwc-pay-invoice-insufficient-funds.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { Nip47WalletError } from "../src/nwc/types";
import { createTestWallet } from "./helpers";

/**
* E2E negative test for pay_invoice using the NWC faucet.
* Requires network access.
*/
describe("NWC pay_invoice insufficient funds", () => {
const INVOICE_AMOUNT_MSATS = 1_000_000; // 1_000 sats
const RECEIVER_BALANCE_SATS = 10_000;
const SENDER_BALANCE_SATS = 50;

let sender: { nwcUrl: string };
let receiver: { nwcUrl: string };

beforeAll(async () => {
receiver = await createTestWallet(RECEIVER_BALANCE_SATS, 3);
sender = await createTestWallet(SENDER_BALANCE_SATS, 3);
}, 60_000);

test(
"fails with wallet error when invoice amount exceeds sender balance",
async () => {
const receiverClient = new NWCClient({
nostrWalletConnectUrl: receiver.nwcUrl,
});
const senderClient = new NWCClient({ nostrWalletConnectUrl: sender.nwcUrl });

try {
const invoiceResult = await receiverClient.makeInvoice({
amount: INVOICE_AMOUNT_MSATS,
description: "E2E insufficient funds test",
});

await expect(
senderClient.payInvoice({ invoice: invoiceResult.invoice }),
).rejects.toBeInstanceOf(Nip47WalletError);
Comment on lines +37 to +39
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Assert the specific insufficient-funds error code, not only the error class.

Line 37-39 currently passes for any Nip47WalletError, so it does not fully validate the intended INSUFFICIENT_BALANCE path.

🎯 Suggested assertion tightening
-        await expect(
-          senderClient.payInvoice({ invoice: invoiceResult.invoice }),
-        ).rejects.toBeInstanceOf(Nip47WalletError);
+        await expect(
+          senderClient.payInvoice({ invoice: invoiceResult.invoice }),
+        ).rejects.toMatchObject(
+          expect.objectContaining({
+            // Use the exact field your Nip47WalletError exposes (e.g. `code` or `error.code`)
+            code: "INSUFFICIENT_BALANCE",
+          }),
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/nwc-pay-invoice-insufficient-funds.test.ts` around lines 37 - 39, The
test currently only asserts the thrown type; tighten it by also asserting the
specific insufficient-funds error code: when calling senderClient.payInvoice({
invoice: invoiceResult.invoice }) update the assertion to check both the error
class (Nip47WalletError) and that the thrown error contains the
INSUFFICIENT_BALANCE code (e.g. await expect(...).rejects.toMatchObject({ code:
'INSUFFICIENT_BALANCE' }) or combine with .toBeInstanceOf(Nip47WalletError));
reference senderClient.payInvoice, Nip47WalletError and the INSUFFICIENT_BALANCE
error code in your assertion.

} finally {
receiverClient.close();
senderClient.close();
}
},
60_000,
);
});
47 changes: 47 additions & 0 deletions e2e/nwc-pay-invoice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { createTestWallet } from "./helpers";

/**
* E2E test for make_invoice and pay_invoice using the NWC faucet.
* Requires network access.
*/
describe("NWC make_invoice and pay_invoice", () => {
const AMOUNT_MSATS = 100_000; // 100 sats
const BALANCE_SATS = 10_000;

let sender: { nwcUrl: string };
let receiver: { nwcUrl: string };

beforeAll(async () => {
receiver = await createTestWallet(BALANCE_SATS, 3);
sender = await createTestWallet(BALANCE_SATS, 3);
}, 60_000);

test(
"receiver creates invoice, sender pays it",
async () => {
const receiverClient = new NWCClient({ nostrWalletConnectUrl: receiver.nwcUrl });
const senderClient = new NWCClient({ nostrWalletConnectUrl: sender.nwcUrl });

try {
const invoiceResult = await receiverClient.makeInvoice({
amount: AMOUNT_MSATS,
description: "E2E test invoice",
});
expect(invoiceResult.invoice).toBeDefined();
expect(invoiceResult.invoice).toMatch(/^ln/);

const payResult = await senderClient.payInvoice({
invoice: invoiceResult.invoice,
});
expect(payResult.preimage).toBeDefined();
expect(typeof payResult.preimage).toBe("string");
} finally {
receiverClient.close();
senderClient.close();
}
},
60_000,
);
});
44 changes: 44 additions & 0 deletions e2e/nwc-pay-keysend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import "websocket-polyfill";
import { NWCClient } from "../src/nwc/NWCClient";
import { createTestWallet } from "./helpers";

/**
* E2E test for pay_keysend using the NWC faucet.
* Requires network access.
*/
describe("NWC pay_keysend", () => {
const AMOUNT_MSATS = 100_000; // 100 sats
const BALANCE_SATS = 10_000;

let sender: { nwcUrl: string };
let receiver: { nwcUrl: string };

beforeAll(async () => {
receiver = await createTestWallet(BALANCE_SATS, 3);
sender = await createTestWallet(BALANCE_SATS, 3);
}, 60_000);

test(
"sends keysend payment to receiver pubkey",
async () => {
const receiverClient = new NWCClient({ nostrWalletConnectUrl: receiver.nwcUrl });
const senderClient = new NWCClient({ nostrWalletConnectUrl: sender.nwcUrl });

try {
const infoResult = await receiverClient.getInfo();
expect(infoResult.pubkey).toBeDefined();

const keysendResult = await senderClient.payKeysend({
amount: AMOUNT_MSATS,
pubkey: infoResult.pubkey,
});
expect(keysendResult.preimage).toBeDefined();
expect(typeof keysendResult.preimage).toBe("string");
} finally {
receiverClient.close();
senderClient.close();
}
},
60_000,
);
});
1 change: 1 addition & 0 deletions jest.e2e.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default {
testEnvironment: 'node',
testMatch: ['<rootDir>/e2e/**/*.test.ts'],
testPathIgnorePatterns: ['/node_modules/', '/e2e/browser'],
maxWorkers: 1, // Run e2e tests serially to avoid faucet rate limiting
};
Loading