diff --git a/e2e/nwc-faucet.test.ts b/e2e/nwc-faucet.test.ts index 4e3c2d5..aee5341 100644 --- a/e2e/nwc-faucet.test.ts +++ b/e2e/nwc-faucet.test.ts @@ -13,7 +13,7 @@ describe("NWC faucet integration", () => { test( "creates wallet via faucet and balance is 10_000 sats", async () => { - const { nwcUrl } = await createTestWallet(EXPECTED_BALANCE_SATS, 3); + const { nwcUrl } = await createTestWallet(EXPECTED_BALANCE_SATS); const nwc = new NWCClient({ nostrWalletConnectUrl: nwcUrl }); try { diff --git a/e2e/nwc-list-transactions-after-payment.test.ts b/e2e/nwc-list-transactions-after-payment.test.ts new file mode 100644 index 0000000..6213ac9 --- /dev/null +++ b/e2e/nwc-list-transactions-after-payment.test.ts @@ -0,0 +1,59 @@ +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); + sender = await createTestWallet(BALANCE_SATS); + }, 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, + ); +}); diff --git a/e2e/nwc-lookup-invoice.test.ts b/e2e/nwc-lookup-invoice.test.ts new file mode 100644 index 0000000..b937ca7 --- /dev/null +++ b/e2e/nwc-lookup-invoice.test.ts @@ -0,0 +1,47 @@ +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); + sender = await createTestWallet(BALANCE_SATS); + }, 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, + ); +}); diff --git a/e2e/nwc-pay-invoice-insufficient-funds.test.ts b/e2e/nwc-pay-invoice-insufficient-funds.test.ts new file mode 100644 index 0000000..58984a1 --- /dev/null +++ b/e2e/nwc-pay-invoice-insufficient-funds.test.ts @@ -0,0 +1,50 @@ +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); + sender = await createTestWallet(SENDER_BALANCE_SATS); + }, 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", + }); + + const payInvoicePromise = senderClient.payInvoice({ + invoice: invoiceResult.invoice, + }); + await expect(payInvoicePromise).rejects.toBeInstanceOf(Nip47WalletError); + await expect(payInvoicePromise).rejects.toMatchObject({ + code: "INSUFFICIENT_BALANCE", + }); + } finally { + receiverClient.close(); + senderClient.close(); + } + }, + 60_000, + ); +}); diff --git a/e2e/nwc-pay-invoice.test.ts b/e2e/nwc-pay-invoice.test.ts new file mode 100644 index 0000000..06e91c9 --- /dev/null +++ b/e2e/nwc-pay-invoice.test.ts @@ -0,0 +1,46 @@ +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); + sender = await createTestWallet(BALANCE_SATS); + }, 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, + ); +}); diff --git a/e2e/nwc-pay-keysend.test.ts b/e2e/nwc-pay-keysend.test.ts new file mode 100644 index 0000000..3be55c8 --- /dev/null +++ b/e2e/nwc-pay-keysend.test.ts @@ -0,0 +1,43 @@ +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); + sender = await createTestWallet(BALANCE_SATS); + }, 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 receiverInfoResult = await receiverClient.getInfo(); + expect(receiverInfoResult.pubkey).toBeDefined(); + + const keysendResult = await senderClient.payKeysend({ + amount: AMOUNT_MSATS, + pubkey: receiverInfoResult.pubkey, + }); + expect(keysendResult.preimage).toBeDefined(); + expect(typeof keysendResult.preimage).toBe("string"); + } finally { + receiverClient.close(); + senderClient.close(); + } + }, + 60_000, + ); +}); diff --git a/jest.e2e.config.ts b/jest.e2e.config.ts index 6b2a603..35afca4 100644 --- a/jest.e2e.config.ts +++ b/jest.e2e.config.ts @@ -4,4 +4,5 @@ export default { testEnvironment: 'node', testMatch: ['/e2e/**/*.test.ts'], testPathIgnorePatterns: ['/node_modules/', '/e2e/browser'], + maxWorkers: 1, // Run e2e tests serially to avoid faucet rate limiting };