From ad7b09b43323d0ffc0a8ddabb43e4838a184c697 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 24 Feb 2026 13:18:42 -0800 Subject: formatting --- test/forward.test.ts | 211 +++++++++-- test/integration/ark-reconcile.test.ts | 20 +- test/integration/bolt12.test.ts | 35 +- test/onchain.test.ts | 128 ++++--- test/preload.ts | 639 ++++++++++++++++++--------------- 5 files changed, 623 insertions(+), 410 deletions(-) (limited to 'test') diff --git a/test/forward.test.ts b/test/forward.test.ts index 692c5b1e..cae988b7 100644 --- a/test/forward.test.ts +++ b/test/forward.test.ts @@ -111,9 +111,13 @@ const resetAll = () => { const makeRes = () => { let sent: any; return { - send: (data: any) => { sent = data; }, + send: (data: any) => { + sent = data; + }, code: (code: number) => ({ - send: (data: any) => { sent = data; }, + send: (data: any) => { + sent = data; + }, }), getSent: () => sent, }; @@ -129,7 +133,13 @@ describe("completePayment", () => { test("forwards payment and debit stays in custodial", async () => { const user = makeUser(); const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 }); - const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "ark", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); @@ -141,7 +151,13 @@ describe("completePayment", () => { test("forwards and debit stays in custodial when aid matches user", async () => { const user = makeUser(); const inv = makeInvoice({ aid: custodialUid, forward: bolt11 }); - const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "ark", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); @@ -152,9 +168,17 @@ describe("completePayment", () => { test("handles forward failure gracefully", async () => { const user = makeUser(); const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 }); - const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "ark", + uid: custodialUid, + created: Date.now(), + }; - mockLnXpay.mockImplementationOnce(async () => { throw new Error("route not found"); }); + mockLnXpay.mockImplementationOnce(async () => { + throw new Error("route not found"); + }); const w = await completePayment(inv, p, user); @@ -166,7 +190,13 @@ describe("completePayment", () => { test("skips forward when already forwarded", async () => { const user = makeUser(); const inv = makeInvoice({ aid: arkAccountId, forward: bolt11, forwarded: true }); - const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "ark", + uid: custodialUid, + created: Date.now(), + }; const w = await completePayment(inv, p, user); @@ -177,7 +207,13 @@ describe("completePayment", () => { test("skips forward when payment not confirmed", async () => { const user = makeUser(); const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 }); - const p = { amount: 1000, confirmed: false, type: "ark", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: false, + type: "ark", + uid: custodialUid, + created: Date.now(), + }; const w = await completePayment(inv, p, user); @@ -251,7 +287,9 @@ describe("arkReceive", () => { const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 }); seedInvoice(inv); - mockLnXpay.mockImplementationOnce(async () => { throw new Error("no route"); }); + mockLnXpay.mockImplementationOnce(async () => { + throw new Error("no route"); + }); const req = { body: { iid: inv.id, amount: 1000, hash: "ark-txid-777" }, @@ -353,14 +391,15 @@ describe("arkSync", () => { // Pre-store a known tx hash and its sync lock store().kvStore[`arksync:${arkAccountId}:known-hash`] = "1"; store().kvStore[`payment:${arkAccountId}:known-hash`] = JSON.stringify("existing-id"); - store().kvStore["payment:existing-id"] = JSON.stringify({ id: "existing-id", confirmed: false }); + store().kvStore["payment:existing-id"] = JSON.stringify({ + id: "existing-id", + confirmed: false, + }); const req = { body: { aid: arkAccountId, - transactions: [ - { hash: "known-hash", amount: -1000, settled: true, createdAt: Date.now() }, - ], + transactions: [{ hash: "known-hash", amount: -1000, settled: true, createdAt: Date.now() }], }, user: makeUser(), }; @@ -385,8 +424,30 @@ describe("arkSync reconciliation", () => { test("creates expired debit when payment sum exceeds wallet balance", async () => { // Pre-seed two received payments totaling 7128 - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() - 2000 }; - const pay2 = { id: "pay-2", aid: arkAccountId, amount: 2128, hash: "h2", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() - 1000 }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 5000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now() - 2000, + }; + const pay2 = { + id: "pay-2", + aid: arkAccountId, + amount: 2128, + hash: "h2", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now() - 1000, + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:pay-2`] = JSON.stringify(pay2); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); @@ -421,7 +482,18 @@ describe("arkSync reconciliation", () => { test("does not reconcile when sum matches balance", async () => { // Pre-seed a payment of 5000 - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 5000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); store().listStore[`${arkAccountId}:payments`] = ["pay-1"]; @@ -445,7 +517,18 @@ describe("arkSync reconciliation", () => { }); test("does not reconcile when balance is not provided (backward compat)", async () => { - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 5000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); store().listStore[`${arkAccountId}:payments`] = ["pay-1"]; @@ -468,7 +551,18 @@ describe("arkSync reconciliation", () => { test("is idempotent — second sync does not create duplicate", async () => { // Seed payment sum = 5000, balance = 0 - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 5000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); store().listStore[`${arkAccountId}:payments`] = ["pay-1"]; @@ -495,8 +589,30 @@ describe("arkSync reconciliation", () => { test("reconciles partial expiry", async () => { // 10000 received, 3000 sent, sum = 7000. Actual balance = 2000 (5000 expired) - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 10000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; - const pay2 = { id: "pay-2", aid: arkAccountId, amount: -3000, hash: "h2", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 10000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; + const pay2 = { + id: "pay-2", + aid: arkAccountId, + amount: -3000, + hash: "h2", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:pay-2`] = JSON.stringify(pay2); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); @@ -519,7 +635,18 @@ describe("arkSync reconciliation", () => { test("does not reconcile when sum is less than balance", async () => { // Sum = 3000, balance = 5000 (e.g., recovered VTXOs already synced) - const pay1 = { id: "pay-1", aid: arkAccountId, amount: 3000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() }; + const pay1 = { + id: "pay-1", + aid: arkAccountId, + amount: 3000, + hash: "h1", + confirmed: true, + type: "ark", + uid: custodialUid, + rate: 50000, + currency: "USD", + created: Date.now(), + }; store().kvStore[`payment:pay-1`] = JSON.stringify(pay1); store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1"); store().listStore[`${arkAccountId}:payments`] = ["pay-1"]; @@ -568,7 +695,13 @@ describe("BOLT12 autowithdraw", () => { seedUser(user); const inv = makeInvoice({ type: "lightning", amount: 1000 }); - const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "lightning", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); @@ -602,7 +735,13 @@ describe("BOLT12 autowithdraw", () => { const user = makeUser(); const inv = makeInvoice({ forward: bolt12Offer }); - const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "lightning", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); @@ -621,7 +760,13 @@ describe("BOLT12 autowithdraw", () => { seedUser(user); const inv = makeInvoice({ type: "lightning", amount: 1000 }); - const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "lightning", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); @@ -642,10 +787,12 @@ describe("BOLT12 autowithdraw", () => { // Simulate a route with 50 sat routing fee (getroutes response format) mockLnGetroutes.mockImplementation(async () => ({ - routes: [{ - amount_msat: balance * 1000, - path: [{ amount_msat: (balance + routingFee) * 1000 }], - }], + routes: [ + { + amount_msat: balance * 1000, + path: [{ amount_msat: (balance + routingFee) * 1000 }], + }, + ], })); mockLnFetchinvoice.mockImplementation(async () => ({ @@ -661,7 +808,13 @@ describe("BOLT12 autowithdraw", () => { seedUser(user); const inv = makeInvoice({ type: "lightning", amount: 1000 }); - const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() }; + const p = { + amount: 1000, + confirmed: true, + type: "lightning", + uid: custodialUid, + created: Date.now(), + }; await completePayment(inv, p, user); diff --git a/test/integration/ark-reconcile.test.ts b/test/integration/ark-reconcile.test.ts index 5e2061e6..84825475 100644 --- a/test/integration/ark-reconcile.test.ts +++ b/test/integration/ark-reconcile.test.ts @@ -126,9 +126,7 @@ describe("ark sync reconciliation", () => { method: "POST", body: JSON.stringify({ aid: arkAccountId, - transactions: [ - { arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }, - ], + transactions: [{ arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }], balance: 13000, }), }); @@ -138,9 +136,7 @@ describe("ark sync reconciliation", () => { method: "POST", body: JSON.stringify({ aid: arkAccountId, - transactions: [ - { arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }, - ], + transactions: [{ arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }], balance: 13000, }), }); @@ -192,9 +188,7 @@ describe("ark sync reconciliation", () => { method: "POST", body: JSON.stringify({ aid: arkAccountId, - transactions: [ - { arkTxid: hash, amount: 5000, settled: true, createdAt: Date.now() }, - ], + transactions: [{ arkTxid: hash, amount: 5000, settled: true, createdAt: Date.now() }], balance: 5000, }), }); @@ -204,9 +198,7 @@ describe("ark sync reconciliation", () => { // Sum is now 5000 (13000 - 13000 + 5000), matches balance=5000 // No reconciliation needed - expect( - result.payments.filter((p: any) => p.memo === "expired"), - ).toHaveLength(0); + expect(result.payments.filter((p: any) => p.memo === "expired")).toHaveLength(0); }); test("partial expiry creates correct debit", async () => { @@ -240,9 +232,7 @@ describe("ark sync reconciliation", () => { method: "POST", body: JSON.stringify({ aid: arkAccountId, - transactions: [ - { arkTxid: hash, amount: 2000, settled: true, createdAt: Date.now() }, - ], + transactions: [{ arkTxid: hash, amount: 2000, settled: true, createdAt: Date.now() }], balance: 4000, // previous 2000 + new 2000 }), }); diff --git a/test/integration/bolt12.test.ts b/test/integration/bolt12.test.ts index 06929c6c..24aa53e0 100644 --- a/test/integration/bolt12.test.ts +++ b/test/integration/bolt12.test.ts @@ -24,9 +24,7 @@ const exec = async (cmd: string): Promise => { const clExec = async (container: string, ...args: string[]): Promise => { const escaped = args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" "); - const result = await exec( - `docker exec ${container} lightning-cli ${escaped}`, - ); + const result = await exec(`docker exec ${container} lightning-cli ${escaped}`); try { return JSON.parse(result); } catch { @@ -35,18 +33,11 @@ const clExec = async (container: string, ...args: string[]): Promise => { }; const mine = async (n: number) => { - const addr = await exec( - "docker exec bc bitcoin-cli -regtest -rpcwallet=coinos getnewaddress", - ); - await exec( - `docker exec bc bitcoin-cli -regtest generatetoaddress ${n} ${addr}`, - ); + const addr = await exec("docker exec bc bitcoin-cli -regtest -rpcwallet=coinos getnewaddress"); + await exec(`docker exec bc bitcoin-cli -regtest generatetoaddress ${n} ${addr}`); }; -const waitFor = async ( - fn: () => Promise, - timeout = 30000, -): Promise => { +const waitFor = async (fn: () => Promise, timeout = 30000): Promise => { const start = Date.now(); let lastError: any; while (Date.now() - start < timeout) { @@ -185,11 +176,7 @@ describe("BOLT12 autowithdraw", () => { // Send 100k sats from funder to withdrawer (triggers autowithdraw) const sendAmount = 100_000; - const sendResult = await sendInternal( - funderToken, - withdrawerName, - sendAmount, - ); + const sendResult = await sendInternal(funderToken, withdrawerName, sendAmount); expect(sendResult.amount).toBe(-sendAmount); // Wait for autowithdraw to complete — withdrawer balance should drop near 0 @@ -210,9 +197,7 @@ describe("BOLT12 autowithdraw", () => { // Check payment records on withdrawer const payments = await getPayments(withdrawerToken); - const withdrawal = payments.payments?.find( - (p: any) => p.amount < 0 && p.type === "lightning", - ); + const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning"); expect(withdrawal).toBeTruthy(); expect(withdrawal.fee).toBeGreaterThanOrEqual(0); }, 30000); @@ -259,9 +244,7 @@ describe("BOLT12 autowithdraw", () => { // Check payment record has routing fee const payments = await getPayments(withdrawerToken); - const withdrawal = payments.payments?.find( - (p: any) => p.amount < 0 && p.type === "lightning", - ); + const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning"); expect(withdrawal).toBeTruthy(); // Multi-hop should have non-zero routing fee expect(withdrawal.fee).toBeGreaterThan(0); @@ -290,9 +273,7 @@ describe("BOLT12 autowithdraw", () => { // Get the withdrawal payment record const payments = await getPayments(withdrawerToken); - const withdrawal = payments.payments?.find( - (p: any) => p.amount < 0 && p.type === "lightning", - ); + const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning"); expect(withdrawal).toBeTruthy(); // For a direct peer, actual routing fee should be 0 or very small diff --git a/test/onchain.test.ts b/test/onchain.test.ts index eae77544..e1ebe608 100644 --- a/test/onchain.test.ts +++ b/test/onchain.test.ts @@ -102,9 +102,13 @@ const resetAll = () => { const makeRes = () => { let sent: any; return { - send: (data: any) => { sent = data; }, + send: (data: any) => { + sent = data; + }, code: (code: number) => ({ - send: (data: any) => { sent = data; }, + send: (data: any) => { + sent = data; + }, }), getSent: () => sent, }; @@ -339,13 +343,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 0, - details: [{ - address: lqAddress, - amount: 0.0005, - asset: "test-asset", - category: "receive", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.0005, + asset: "test-asset", + category: "receive", + vout: 0, + }, + ], }), }; @@ -384,13 +390,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 2, - details: [{ - address: lqAddress, - amount: 0.0005, - asset: "test-asset", - category: "receive", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.0005, + asset: "test-asset", + category: "receive", + vout: 0, + }, + ], }), }; @@ -418,13 +426,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 1, - details: [{ - address: lqAddress, - amount: 0.0005, - asset: "test-asset", - category: "send", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.0005, + asset: "test-asset", + category: "send", + vout: 0, + }, + ], }), }; @@ -442,13 +452,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 0, - details: [{ - address: lqAddress, - amount: 0.001, - asset: "wrong-asset-id", - category: "receive", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.001, + asset: "wrong-asset-id", + category: "receive", + vout: 0, + }, + ], }), }; @@ -471,13 +483,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 0, - details: [{ - address: lqAddress, - amount: 0.0005, - asset: "test-asset", - category: "receive", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.0005, + asset: "test-asset", + category: "receive", + vout: 0, + }, + ], }), }; @@ -508,13 +522,15 @@ describe("confirm (liquid)", () => { store().rpcOverride = { getTransaction: async () => ({ confirmations: 3, - details: [{ - address: lqAddress, - amount: 0.0005, - asset: "test-asset", - category: "receive", - vout: 0, - }], + details: [ + { + address: lqAddress, + amount: 0.0005, + asset: "test-asset", + category: "receive", + vout: 0, + }, + ], }), }; @@ -542,11 +558,13 @@ describe("catchUp", () => { store().esploraOverride = { addressTxs: { - [address]: [{ - txid: "missed-tx-001", - vout: [{ scriptpubkey_address: address, value: 40000 }], - status: { confirmed: false }, - }], + [address]: [ + { + txid: "missed-tx-001", + vout: [{ scriptpubkey_address: address, value: 40000 }], + status: { confirmed: false }, + }, + ], }, }; @@ -569,11 +587,13 @@ describe("catchUp", () => { store().esploraOverride = { addressTxs: { - [address]: [{ - txid: "missed-tx-002", - vout: [{ scriptpubkey_address: address, value: 60000 }], - status: { confirmed: true }, - }], + [address]: [ + { + txid: "missed-tx-002", + vout: [{ scriptpubkey_address: address, value: 60000 }], + status: { confirmed: true }, + }, + ], }, }; diff --git a/test/preload.ts b/test/preload.ts index a3d3cbb9..01629d97 100644 --- a/test/preload.ts +++ b/test/preload.ts @@ -7,307 +7,376 @@ if (process.env.INTEGRATION) { // Skip all mocking — use real services globalThis.__testStore = { kvStore: {}, listStore: {}, setStore: {} }; } else { + let kvStore: Record = {}; + let listStore: Record = {}; + let setStore: Record> = {}; -let kvStore: Record = {}; -let listStore: Record = {}; -let setStore: Record> = {}; + globalThis.__testStore = { kvStore, listStore, setStore }; -globalThis.__testStore = { kvStore, listStore, setStore }; - -const makeMulti = () => { - const ops: Array<() => Promise> = []; - const chain: any = { - set: (k: string, v: string) => { - ops.push(async () => { globalThis.__testStore.kvStore[k] = v; }); - return chain; - }, - lPush: (k: string, v: string) => { - ops.push(async () => { - const ls = globalThis.__testStore.listStore; - if (!ls[k]) ls[k] = []; - ls[k].unshift(v); - }); - return chain; - }, - del: (k: string) => { - ops.push(async () => { delete globalThis.__testStore.kvStore[k]; }); - return chain; - }, - lRem: (k: string, _count: number, v: string) => { - ops.push(async () => { - const ls = globalThis.__testStore.listStore; - if (ls[k]) { - const idx = ls[k].indexOf(v); - if (idx >= 0) ls[k].splice(idx, 1); - } - }); - return chain; - }, - sRem: (k: string, v: string) => { - ops.push(async () => globalThis.__testStore.setStore[k]?.delete(v)); - return chain; - }, - exec: async () => { - for (const op of ops) await op(); - return []; - }, - }; - return chain; -}; - -mock.module("$lib/db", () => { - const kv = () => globalThis.__testStore.kvStore; - const ls = () => globalThis.__testStore.listStore; - const ss = () => globalThis.__testStore.setStore; - const g = async (k: string) => { - const v = kv()[k] ?? null; - if (v === null) return null; - try { return JSON.parse(v); } catch { return v; } - }; - const s = (k: string, v: any) => { - if (k === "user:null" || k === "user:undefined") throw new Error("null user"); - kv()[k] = JSON.stringify(v); - }; - const gf = async (k: string) => { - const v = kv()[k] ?? null; - if (v === null) return null; - try { return JSON.parse(v); } catch { return v; } - }; - return { - db: { - get: async (k: string) => kv()[k] ?? null, - set: async (k: string, v: string, opts?: any) => { - if (opts?.NX && kv()[k] !== undefined) return null; - kv()[k] = v; - return "OK"; + const makeMulti = () => { + const ops: Array<() => Promise> = []; + const chain: any = { + set: (k: string, v: string) => { + ops.push(async () => { + globalThis.__testStore.kvStore[k] = v; + }); + return chain; }, - del: async (k: string) => { delete kv()[k]; return 1; }, - exists: async (k: string) => (kv()[k] !== undefined ? 1 : 0), - lPush: async (k: string, v: string) => { - if (!ls()[k]) ls()[k] = []; - ls()[k].unshift(v); - return ls()[k].length; + lPush: (k: string, v: string) => { + ops.push(async () => { + const ls = globalThis.__testStore.listStore; + if (!ls[k]) ls[k] = []; + ls[k].unshift(v); + }); + return chain; }, - lRem: async (k: string, _count: number, v: string) => { - if (!ls()[k]) return 0; - const idx = ls()[k].indexOf(v); - if (idx >= 0) { ls()[k].splice(idx, 1); return 1; } - return 0; + del: (k: string) => { + ops.push(async () => { + delete globalThis.__testStore.kvStore[k]; + }); + return chain; }, - lRange: async (k: string, start: number, end: number) => { - if (!ls()[k]) return []; - if (end === -1) return ls()[k].slice(start); - return ls()[k].slice(start, end + 1); + lRem: (k: string, _count: number, v: string) => { + ops.push(async () => { + const ls = globalThis.__testStore.listStore; + if (ls[k]) { + const idx = ls[k].indexOf(v); + if (idx >= 0) ls[k].splice(idx, 1); + } + }); + return chain; }, - lPos: async (k: string, v: string) => { - if (!ls()[k]) return null; - const idx = ls()[k].indexOf(v); - return idx >= 0 ? idx : null; + sRem: (k: string, v: string) => { + ops.push(async () => globalThis.__testStore.setStore[k]?.delete(v)); + return chain; }, - sAdd: async (k: string, ...vals: string[]) => { - if (!ss()[k]) ss()[k] = new Set(); - for (const v of vals) ss()[k].add(v); - return vals.length; + exec: async () => { + for (const op of ops) await op(); + return []; }, - sRem: async (k: string, v: string) => ss()[k]?.delete(v) ? 1 : 0, - sIsMember: async (k: string, v: string) => ss()[k]?.has(v) ?? false, - sMembers: async (k: string) => (ss()[k] ? [...ss()[k]] : []), - incrBy: async (k: string, n: number) => { - const v = Number.parseInt(kv()[k] || "0") + n; - kv()[k] = String(v); + }; + return chain; + }; + + mock.module("$lib/db", () => { + const kv = () => globalThis.__testStore.kvStore; + const ls = () => globalThis.__testStore.listStore; + const ss = () => globalThis.__testStore.setStore; + const g = async (k: string) => { + const v = kv()[k] ?? null; + if (v === null) return null; + try { + return JSON.parse(v); + } catch { return v; + } + }; + const s = (k: string, v: any) => { + if (k === "user:null" || k === "user:undefined") throw new Error("null user"); + kv()[k] = JSON.stringify(v); + }; + const gf = async (k: string) => { + const v = kv()[k] ?? null; + if (v === null) return null; + try { + return JSON.parse(v); + } catch { + return v; + } + }; + return { + db: { + get: async (k: string) => kv()[k] ?? null, + set: async (k: string, v: string, opts?: any) => { + if (opts?.NX && kv()[k] !== undefined) return null; + kv()[k] = v; + return "OK"; + }, + del: async (k: string) => { + delete kv()[k]; + return 1; + }, + exists: async (k: string) => (kv()[k] !== undefined ? 1 : 0), + lPush: async (k: string, v: string) => { + if (!ls()[k]) ls()[k] = []; + ls()[k].unshift(v); + return ls()[k].length; + }, + lRem: async (k: string, _count: number, v: string) => { + if (!ls()[k]) return 0; + const idx = ls()[k].indexOf(v); + if (idx >= 0) { + ls()[k].splice(idx, 1); + return 1; + } + return 0; + }, + lRange: async (k: string, start: number, end: number) => { + if (!ls()[k]) return []; + if (end === -1) return ls()[k].slice(start); + return ls()[k].slice(start, end + 1); + }, + lPos: async (k: string, v: string) => { + if (!ls()[k]) return null; + const idx = ls()[k].indexOf(v); + return idx >= 0 ? idx : null; + }, + sAdd: async (k: string, ...vals: string[]) => { + if (!ss()[k]) ss()[k] = new Set(); + for (const v of vals) ss()[k].add(v); + return vals.length; + }, + sRem: async (k: string, v: string) => (ss()[k]?.delete(v) ? 1 : 0), + sIsMember: async (k: string, v: string) => ss()[k]?.has(v) ?? false, + sMembers: async (k: string) => (ss()[k] ? [...ss()[k]] : []), + incrBy: async (k: string, n: number) => { + const v = Number.parseInt(kv()[k] || "0") + n; + kv()[k] = String(v); + return v; + }, + watch: async () => {}, + multi: makeMulti, + expire: async () => 1, + setNX: async (k: string, v: string) => { + if (kv()[k]) return false; + kv()[k] = v; + return true; + }, + zScore: async () => null, + zAdd: async () => 1, + zCard: async () => 0, + zRemRangeByRank: async () => 0, + keys: async (pattern: string) => { + const ss = globalThis.__testStore.setStore; + const prefix = pattern.replace("*", ""); + return Object.keys(ss).filter((k) => k.startsWith(prefix)); + }, + type: async (k: string) => { + if (globalThis.__testStore.setStore[k]) return "set"; + if (globalThis.__testStore.listStore[k]) return "list"; + if (globalThis.__testStore.kvStore[k] !== undefined) return "string"; + return "none"; + }, }, - watch: async () => {}, - multi: makeMulti, - expire: async () => 1, - setNX: async (k: string, v: string) => { - if (kv()[k]) return false; - kv()[k] = v; - return true; - }, - zScore: async () => null, - zAdd: async () => 1, - zCard: async () => 0, - zRemRangeByRank: async () => 0, - keys: async (pattern: string) => { - const ss = globalThis.__testStore.setStore; - const prefix = pattern.replace("*", ""); - return Object.keys(ss).filter((k) => k.startsWith(prefix)); + g, + s, + gf, + ga: async () => null, + archive: { lRange: async () => [] }, + }; + }); + + mock.module("$config", () => ({ + default: { + bitcoin: { + host: "localhost", + wallet: "test", + user: "u", + password: "p", + network: "regtest", + port: 18443, }, - type: async (k: string) => { - if (globalThis.__testStore.setStore[k]) return "set"; - if (globalThis.__testStore.listStore[k]) return "list"; - if (globalThis.__testStore.kvStore[k] !== undefined) return "string"; - return "none"; + liquid: { + host: "localhost", + wallet: "test", + user: "u", + password: "p", + btc: "test-asset", + port: 7040, }, + lightning: "/dev/null", + fee: { bitcoin: 0.004, liquid: 0.001, lightning: 0.001 }, + adminpass: "test", + ark: { arkPrivateKey: "0000", arkServerUrl: "http://localhost" }, + nostr: "ws://localhost:7777", + tigerbeetle: { cluster_id: 0n, replica_addresses: ["localhost:3000"] }, + vapid: { pk: "test", sk: "test" }, + support: "test@test.com", + txWebhookSecret: "test", }, - g, s, gf, - ga: async () => null, - archive: { lRange: async () => [] }, - }; -}); - -mock.module("$config", () => ({ - default: { - bitcoin: { host: "localhost", wallet: "test", user: "u", password: "p", network: "regtest", port: 18443 }, - liquid: { host: "localhost", wallet: "test", user: "u", password: "p", btc: "test-asset", port: 7040 }, - lightning: "/dev/null", - fee: { bitcoin: 0.004, liquid: 0.001, lightning: 0.001 }, - adminpass: "test", - ark: { arkPrivateKey: "0000", arkServerUrl: "http://localhost" }, - nostr: "ws://localhost:7777", - tigerbeetle: { cluster_id: 0n, replica_addresses: ["localhost:3000"] }, - vapid: { pk: "test", sk: "test" }, - support: "test@test.com", - txWebhookSecret: "test", - }, -})); - -mock.module("$lib/tb", () => ({ - getBalance: mock(async () => 10_000_000), - getCredit: mock(async () => 0), - tbDebit: mock(async () => 0), - tbCredit: mock(async () => undefined), - tbRefund: mock(async () => undefined), - tbReverse: mock(async () => undefined), - tbConfirm: mock(async () => undefined), - tbSetBalance: mock(async () => undefined), - tbSetPending: mock(async () => undefined), - fundDebit: mock(async () => ({ err: null })), - initTigerBeetle: mock(async () => {}), -})); + })); -mock.module("$lib/ln", () => ({ - default: { - decode: mock(async () => ({ type: "bolt11", amount_msat: 1_000_000, payee: "test-payee" })), - listpeerchannels: mock(async () => ({ channels: [] })), - listpays: mock(async () => ({ pays: [] })), - xpay: mock(async () => ({ amount_sent_msat: 1_000_000, payment_preimage: "preimage-abc" })), - getinfo: mock(async () => ({ id: "our-node-id" })), - listinvoices: mock(async () => ({ invoices: [] })), - listfunds: mock(async () => ({ channels: [] })), - keysend: mock(async () => ({})), - fetchinvoice: mock(async () => ({})), - getroutes: mock(async () => ({ routes: [] })), - sendinvoice: mock(async () => ({})), - }, -})); + mock.module("$lib/tb", () => ({ + getBalance: mock(async () => 10_000_000), + getCredit: mock(async () => 0), + tbDebit: mock(async () => 0), + tbCredit: mock(async () => undefined), + tbRefund: mock(async () => undefined), + tbReverse: mock(async () => undefined), + tbConfirm: mock(async () => undefined), + tbSetBalance: mock(async () => undefined), + tbSetPending: mock(async () => undefined), + fundDebit: mock(async () => ({ err: null })), + initTigerBeetle: mock(async () => {}), + })); -mock.module("$lib/logging", () => ({ l: () => {}, warn: mock(() => {}), err: () => {} })); -mock.module("$lib/notifications", () => ({ notify: () => {}, nwcNotify: () => {} })); -mock.module("$lib/webhooks", () => ({ callWebhook: mock(() => {}) })); -mock.module("$lib/sockets", () => ({ emit: mock(() => {}), sendHeartbeat: () => {} })); -mock.module("$lib/esplora", () => ({ - btcNetwork: { bech32: "bcrt", pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef }, - hdVersions: { private: 0x04358394, public: 0x043587cf }, - getAddressTxs: mock(async (address: string) => { - const txs = globalThis.__testStore.esploraOverride?.addressTxs?.[address]; - return txs || []; - }), - getTxStatus: mock(async (txid: string) => { - const status = globalThis.__testStore.esploraOverride?.txStatus?.[txid]; - return status || { confirmed: false }; - }), - getAddressUtxos: mock(async () => []), - getUtxos: mock(async () => []), - getTxHex: mock(async () => ""), - getTx: mock(async () => ({})), - broadcastTx: mock(async () => ({})), - getFeeEstimates: mock(async () => ({})), - deriveAddress: mock(() => ({ address: "bcrt1qmock" })), - deriveAddresses: mock(() => []), - parseDescriptor: mock(() => ({})), - findLastUsedIndex: mock(async () => -1), -})); -mock.module("$lib/square", () => ({ squarePayment: () => {} })); -mock.module("$lib/nostr", () => ({ - handleZap: async () => {}, publish: async () => {}, - serverPubkey: "m", serverPubkey2: "m", serverSecret: "m", serverSecret2: "m", -})); -mock.module("$lib/mqtt", () => ({ default: { publish: () => {} } })); -mock.module("$lib/ark", () => ({ - getArkAddress: async () => "ark-addr", sendArk: async () => "ark-txid", getArkBalance: async () => 0, verifyArkVtxo: async () => true, -})); -mock.module("$lib/ecash", () => ({ request: async () => ({}) })); -mock.module("$lib/lightning", () => ({ - replay: async () => ({}), fixBolt12: () => {}, listenForLightning: () => {}, -})); -mock.module("$lib/mail", () => ({ mail: async () => {}, templates: {} })); -mock.module("$lib/auth", () => ({ - requirePin: async () => {}, auth: (_r: any, _s: any, n: any) => n(), optional: (_r: any, _s: any, n: any) => n(), admin: (_r: any, _s: any, n: any) => n(), -})); -mock.module("$lib/invoices", () => ({ - generate: mock(async ({ invoice, user }: any) => ({ - id: "gen-inv", hash: "gen-hash", uid: user?.id, received: 0, pending: 0, ...invoice, - })), -})); -mock.module("$lib/api", () => ({ default: { bitcoin: "http://localhost", liquid: "http://localhost" } })); -mock.module("$lib/store", () => ({ default: { rates: { USD: 50000 } } })); -mock.module("@coinos/rpc", () => ({ - default: () => new Proxy({}, { - get: (_target, prop) => async (...args: any[]) => { - const override = globalThis.__testStore.rpcOverride; - if (override && typeof override[prop] === "function") { - return override[prop](...args); - } - return {}; + mock.module("$lib/ln", () => ({ + default: { + decode: mock(async () => ({ type: "bolt11", amount_msat: 1_000_000, payee: "test-payee" })), + listpeerchannels: mock(async () => ({ channels: [] })), + listpays: mock(async () => ({ pays: [] })), + xpay: mock(async () => ({ amount_sent_msat: 1_000_000, payment_preimage: "preimage-abc" })), + getinfo: mock(async () => ({ id: "our-node-id" })), + listinvoices: mock(async () => ({ invoices: [] })), + listfunds: mock(async () => ({ channels: [] })), + keysend: mock(async () => ({})), + fetchinvoice: mock(async () => ({})), + getroutes: mock(async () => ({ routes: [] })), + sendinvoice: mock(async () => ({})), }, - }), -})); + })); -mock.module("$lib/utils", () => { - const SATS = 100_000_000; - const kv = () => globalThis.__testStore.kvStore; - return { - SATS, - btc: (n: number) => Number.parseFloat((n / SATS).toFixed(8)), - fail: (msg: string) => { throw new Error(msg); }, - fmt: (n: number) => String(n), - formatReceipt: () => {}, - getInvoice: async (hash: string) => { - const raw = kv()[`invoice:${hash}`] ?? null; - if (!raw) return null; - let iid; - try { iid = JSON.parse(raw); } catch { iid = raw; } - if (iid?.id) iid = iid.id; - else if (iid?.hash) iid = iid.hash; - const raw2 = kv()[`invoice:${iid}`] ?? null; - if (!raw2) return null; - try { return JSON.parse(raw2); } catch { return raw2; } - }, - getPayment: async (id: string) => { - const raw = kv()[`payment:${id}`]; - if (!raw) return null; - try { - const parsed = JSON.parse(raw); - if (typeof parsed === "string") { - const p = kv()[`payment:${parsed}`]; - return p ? JSON.parse(p) : null; - } - return parsed; - } catch { return null; } - }, - getUser: async (username: string) => { - const raw = kv()[`user:${username}`]; - return raw ? JSON.parse(raw) : null; - }, - getAccount: async () => null, - link: (id: string) => `http://test/${id}`, - sats: (n: number) => Math.round(n * SATS), - sleep: async () => {}, - t: () => ({ insufficientFunds: "Insufficient funds" }), - bail: (res: any, msg: string) => res.code(500).send(msg), - bip21: () => "", - fields: [], - nada: () => {}, - fiat: (n: number, r: number) => (n * r) / SATS, - f: (s: any) => String(s), - pick: (O: any, K: string[]) => K.reduce((o: any, k: string) => ((o[k] = O[k]), o), {}), - prod: false, - uniq: (a: any[], k: any) => [...new Map(a.map((x: any) => [k(x), x])).values()], - wait: async () => {}, - time: () => ({ start: () => {}, end: () => {} }), - }; -}); + mock.module("$lib/logging", () => ({ l: () => {}, warn: mock(() => {}), err: () => {} })); + mock.module("$lib/notifications", () => ({ notify: () => {}, nwcNotify: () => {} })); + mock.module("$lib/webhooks", () => ({ callWebhook: mock(() => {}) })); + mock.module("$lib/sockets", () => ({ emit: mock(() => {}), sendHeartbeat: () => {} })); + mock.module("$lib/esplora", () => ({ + btcNetwork: { bech32: "bcrt", pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef }, + hdVersions: { private: 0x04358394, public: 0x043587cf }, + getAddressTxs: mock(async (address: string) => { + const txs = globalThis.__testStore.esploraOverride?.addressTxs?.[address]; + return txs || []; + }), + getTxStatus: mock(async (txid: string) => { + const status = globalThis.__testStore.esploraOverride?.txStatus?.[txid]; + return status || { confirmed: false }; + }), + getAddressUtxos: mock(async () => []), + getUtxos: mock(async () => []), + getTxHex: mock(async () => ""), + getTx: mock(async () => ({})), + broadcastTx: mock(async () => ({})), + getFeeEstimates: mock(async () => ({})), + deriveAddress: mock(() => ({ address: "bcrt1qmock" })), + deriveAddresses: mock(() => []), + parseDescriptor: mock(() => ({})), + findLastUsedIndex: mock(async () => -1), + })); + mock.module("$lib/square", () => ({ squarePayment: () => {} })); + mock.module("$lib/nostr", () => ({ + handleZap: async () => {}, + publish: async () => {}, + serverPubkey: "m", + serverPubkey2: "m", + serverSecret: "m", + serverSecret2: "m", + })); + mock.module("$lib/mqtt", () => ({ default: { publish: () => {} } })); + mock.module("$lib/ark", () => ({ + getArkAddress: async () => "ark-addr", + sendArk: async () => "ark-txid", + getArkBalance: async () => 0, + verifyArkVtxo: async () => true, + })); + mock.module("$lib/ecash", () => ({ request: async () => ({}) })); + mock.module("$lib/lightning", () => ({ + replay: async () => ({}), + fixBolt12: () => {}, + listenForLightning: () => {}, + })); + mock.module("$lib/mail", () => ({ mail: async () => {}, templates: {} })); + mock.module("$lib/auth", () => ({ + requirePin: async () => {}, + auth: (_r: any, _s: any, n: any) => n(), + optional: (_r: any, _s: any, n: any) => n(), + admin: (_r: any, _s: any, n: any) => n(), + })); + mock.module("$lib/invoices", () => ({ + generate: mock(async ({ invoice, user }: any) => ({ + id: "gen-inv", + hash: "gen-hash", + uid: user?.id, + received: 0, + pending: 0, + ...invoice, + })), + })); + mock.module("$lib/api", () => ({ + default: { bitcoin: "http://localhost", liquid: "http://localhost" }, + })); + mock.module("$lib/store", () => ({ default: { rates: { USD: 50000 } } })); + mock.module("@coinos/rpc", () => ({ + default: () => + new Proxy( + {}, + { + get: + (_target, prop) => + async (...args: any[]) => { + const override = globalThis.__testStore.rpcOverride; + if (override && typeof override[prop] === "function") { + return override[prop](...args); + } + return {}; + }, + }, + ), + })); + mock.module("$lib/utils", () => { + const SATS = 100_000_000; + const kv = () => globalThis.__testStore.kvStore; + return { + SATS, + btc: (n: number) => Number.parseFloat((n / SATS).toFixed(8)), + fail: (msg: string) => { + throw new Error(msg); + }, + fmt: (n: number) => String(n), + formatReceipt: () => {}, + getInvoice: async (hash: string) => { + const raw = kv()[`invoice:${hash}`] ?? null; + if (!raw) return null; + let iid; + try { + iid = JSON.parse(raw); + } catch { + iid = raw; + } + if (iid?.id) iid = iid.id; + else if (iid?.hash) iid = iid.hash; + const raw2 = kv()[`invoice:${iid}`] ?? null; + if (!raw2) return null; + try { + return JSON.parse(raw2); + } catch { + return raw2; + } + }, + getPayment: async (id: string) => { + const raw = kv()[`payment:${id}`]; + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + if (typeof parsed === "string") { + const p = kv()[`payment:${parsed}`]; + return p ? JSON.parse(p) : null; + } + return parsed; + } catch { + return null; + } + }, + getUser: async (username: string) => { + const raw = kv()[`user:${username}`]; + return raw ? JSON.parse(raw) : null; + }, + getAccount: async () => null, + link: (id: string) => `http://test/${id}`, + sats: (n: number) => Math.round(n * SATS), + sleep: async () => {}, + t: () => ({ insufficientFunds: "Insufficient funds" }), + bail: (res: any, msg: string) => res.code(500).send(msg), + bip21: () => "", + fields: [], + nada: () => {}, + fiat: (n: number, r: number) => (n * r) / SATS, + f: (s: any) => String(s), + pick: (O: any, K: string[]) => K.reduce((o: any, k: string) => ((o[k] = O[k]), o), {}), + prod: false, + uniq: (a: any[], k: any) => [...new Map(a.map((x: any) => [k(x), x])).values()], + wait: async () => {}, + time: () => ({ start: () => {}, end: () => {} }), + }; + }); } // end INTEGRATION skip -- cgit v1.2.3