aboutsummaryrefslogtreecommitdiffstats
path: root/test/preload.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-05 16:44:47 -0800
committerAdam Soltys <[email protected]>2026-02-06 09:00:10 -0800
commit4e9c0c5198c7884cfd97cb4495f6156632e3124a (patch)
treece8144806d7f88a7c3084eca3fd9669f13df54af /test/preload.ts
parent635ca5ad96c791bdd229f50c138b4f3292ae29ad (diff)
downloadcoinos-server-4e9c0c5198c7884cfd97cb4495f6156632e3124a.tar.gz
coinos-server-4e9c0c5198c7884cfd97cb4495f6156632e3124a.zip
payment forwarding
Diffstat (limited to 'test/preload.ts')
-rw-r--r--test/preload.ts262
1 files changed, 262 insertions, 0 deletions
diff --git a/test/preload.ts b/test/preload.ts
new file mode 100644
index 00000000..0704561a
--- /dev/null
+++ b/test/preload.ts
@@ -0,0 +1,262 @@
+import { mock } from "bun:test";
+
+// This file is loaded before test files via bunfig.toml [test].preload
+// Mocks must be set up here to intercept transitive imports
+
+let kvStore: Record<string, string> = {};
+let listStore: Record<string, string[]> = {};
+let setStore: Record<string, Set<string>> = {};
+
+globalThis.__testStore = { kvStore, listStore, setStore };
+
+const makeMulti = () => {
+ const ops: Array<() => Promise<any>> = [];
+ 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) => { 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 () => [],
+ type: async () => "none",
+ },
+ 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: "[email protected]",
+ 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 () => ({})),
+ sendinvoice: 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: () => {}, sendHeartbeat: () => {} }));
+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,
+}));
+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: () => 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: () => {} }),
+ };
+});