aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-15 21:53:52 -0700
committerAdam Soltys <[email protected]>2026-03-15 21:53:52 -0700
commitddfe2e3279706f6476364cecfe23427dcabcfd3e (patch)
treef37a2ea3d8e904214c7d8b695c3d87dca7c56466 /test
parentfddfe10f84908c058e239b93985dfe02ba37df04 (diff)
downloadcoinos-server-ddfe2e3279706f6476364cecfe23427dcabcfd3e.tar.gz
coinos-server-ddfe2e3279706f6476364cecfe23427dcabcfd3e.zip
tests
Diffstat (limited to 'test')
-rw-r--r--test/api.test.ts126
-rw-r--r--test/forward.test.ts223
-rw-r--r--test/integration/ark-reconcile.test.ts9
-rw-r--r--test/onchain.test.ts86
-rw-r--r--test/preload.ts93
5 files changed, 377 insertions, 160 deletions
diff --git a/test/api.test.ts b/test/api.test.ts
new file mode 100644
index 00000000..92e506f3
--- /dev/null
+++ b/test/api.test.ts
@@ -0,0 +1,126 @@
+import { describe, test, expect, beforeAll } from "bun:test";
+import app from "$lib/app";
+import { auth, optional } from "$lib/auth";
+import users from "$routes/users";
+import info from "$routes/info";
+import invoices from "$routes/invoices";
+
+// Register routes (subset of index.ts — no side effects, no Bun.serve)
+app.get("/health", info.health);
+app.post("/register", users.create);
+app.post("/login", users.login);
+app.get("/me", auth, users.me);
+app.get("/users/:key", users.get);
+app.post("/invoice", optional, invoices.create);
+app.get("/invoice/:id", invoices.get);
+
+const post = (path: string, body: any) =>
+ app.request(path, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ });
+
+// Use unique username to avoid collisions with other test files
+const USERNAME = `apiuser${Date.now()}`;
+const PASSWORD = "testpass123";
+
+beforeAll(() => {
+ // Clear any leftover state from other test files
+ const store = globalThis.__testStore;
+ for (const k of Object.keys(store.kvStore)) delete store.kvStore[k];
+ for (const k of Object.keys(store.listStore)) delete store.listStore[k];
+ for (const k of Object.keys(store.setStore)) delete store.setStore[k];
+});
+
+describe("Health", () => {
+ test("GET /health returns 200", async () => {
+ const res = await app.request("/health");
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.healthy).toBe(true);
+ });
+});
+
+describe("Registration", () => {
+ test("creates user with valid credentials", async () => {
+ const res = await post("/register", {
+ user: { username: USERNAME, password: PASSWORD },
+ });
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.token).toBeDefined();
+ expect(body.username).toBe(USERNAME);
+ });
+
+ test("rejects duplicate username", async () => {
+ const res = await post("/register", {
+ user: { username: USERNAME, password: PASSWORD },
+ });
+ expect(res.status).toBe(500);
+ });
+
+ test("rejects missing user object", async () => {
+ const res = await post("/register", { password: PASSWORD });
+ expect(res.status).toBe(500);
+ });
+
+ test("rejects invalid username", async () => {
+ const res = await post("/register", {
+ user: { username: "ab!@#", password: PASSWORD },
+ });
+ expect(res.status).toBe(500);
+ });
+});
+
+describe("Login", () => {
+ test("succeeds with valid credentials", async () => {
+ const res = await post("/login", {
+ username: USERNAME,
+ password: PASSWORD,
+ });
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.token).toBeDefined();
+ expect(body.user).toBeDefined();
+ });
+
+ test("fails with wrong password", async () => {
+ const res = await post("/login", {
+ username: USERNAME,
+ password: "wrongpass",
+ });
+ expect(res.status).toBe(401);
+ });
+
+ test("fails with nonexistent user", async () => {
+ const res = await post("/login", {
+ username: "noone",
+ password: PASSWORD,
+ });
+ expect(res.status).toBe(401);
+ });
+});
+
+describe("Authenticated endpoints", () => {
+ test("GET /me returns user data", async () => {
+ const res = await app.request("/me");
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.username).toBeDefined();
+ });
+});
+
+describe("User lookup", () => {
+ test("GET /users/:key finds registered user", async () => {
+ const res = await app.request(`/users/${USERNAME}`);
+ expect(res.status).toBe(200);
+ });
+});
+
+describe("Routing", () => {
+ test("unknown route returns 404", async () => {
+ const res = await app.request("/nonexistent");
+ expect(res.status).toBe(404);
+ });
+});
diff --git a/test/forward.test.ts b/test/forward.test.ts
index 4c538153..7354d17c 100644
--- a/test/forward.test.ts
+++ b/test/forward.test.ts
@@ -109,19 +109,30 @@ const resetAll = () => {
seedUser(makeUser());
};
-const makeRes = () => {
+const makeContext = (opts: { body?: any; user?: any } = {}) => {
let sent: any;
- return {
- send: (data: any) => {
+ const ctx: any = {
+ req: {
+ json: async () => opts.body,
+ header: () => undefined,
+ path: "/test",
+ method: "POST",
+ query: () => ({}),
+ raw: { clone: () => ({ json: async () => opts.body }) },
+ },
+ get: (key: string) => {
+ if (key === "user") return opts.user;
+ return undefined;
+ },
+ set: () => {},
+ json: (data: any, _status?: number) => {
sent = data;
+ return new Response(JSON.stringify(data), { status: _status || 200 });
},
- code: (_code: number) => ({
- send: (data: any) => {
- sent = data;
- },
- }),
+ env: {},
getSent: () => sent,
};
+ return ctx;
};
// =====================================================================
@@ -234,14 +245,13 @@ describe("arkReceive", () => {
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 });
seedInvoice(inv);
- const req = {
+ const ctx = makeContext({
body: { iid: inv.id, amount: 1000, hash: "ark-txid-999" },
user: makeUser(),
- };
- const res = makeRes();
+ });
- await routes.arkReceive(req as any, res as any);
- const sent = (res as any).getSent();
+ await routes.arkReceive(ctx);
+ const sent = ctx.getSent();
// Response is the custodial credit record
expect(sent).toBeTruthy();
@@ -265,14 +275,13 @@ describe("arkReceive", () => {
const inv = makeInvoice({ aid: arkAccountId });
seedInvoice(inv);
- const req = {
+ const ctx = makeContext({
body: { iid: inv.id, amount: 1000, hash: "ark-txid-888" },
user: makeUser(),
- };
- const res = makeRes();
+ });
- await routes.arkReceive(req as any, res as any);
- const sent = (res as any).getSent();
+ await routes.arkReceive(ctx);
+ const sent = ctx.getSent();
expect(sent).toBeTruthy();
expect(sent.amount).toBe(1000);
@@ -292,14 +301,13 @@ describe("arkReceive", () => {
throw new Error("no route");
});
- const req = {
+ const ctx = makeContext({
body: { iid: inv.id, amount: 1000, hash: "ark-txid-777" },
user: makeUser(),
- };
- const res = makeRes();
+ });
- await routes.arkReceive(req as any, res as any);
- const sent = (res as any).getSent();
+ await routes.arkReceive(ctx);
+ const sent = ctx.getSent();
// Credit still lands in custodial even though forward failed
expect(sent).toBeTruthy();
@@ -314,15 +322,14 @@ describe("arkReceive", () => {
const inv = makeInvoice();
seedInvoice(inv);
- const req = {
+ const ctx = makeContext({
body: { iid: inv.id, amount: 0, hash: "ark-txid-000" },
user: makeUser(),
- };
- const res = makeRes();
+ });
- await routes.arkReceive(req as any, res as any);
+ await routes.arkReceive(ctx);
// Should error - check that send wasn't called with a valid payment
- const sent = (res as any).getSent();
+ const sent = ctx.getSent();
expect(sent).toBeDefined();
});
@@ -330,14 +337,13 @@ describe("arkReceive", () => {
const inv = makeInvoice({ uid: "other-user-id" });
seedInvoice(inv);
- const req = {
+ const ctx = makeContext({
body: { iid: inv.id, amount: 1000, hash: "ark-txid-666" },
user: makeUser(),
- };
- const res = makeRes();
+ });
- await routes.arkReceive(req as any, res as any);
- const sent = (res as any).getSent();
+ await routes.arkReceive(ctx);
+ const sent = ctx.getSent();
expect(sent).toBeDefined();
});
});
@@ -350,7 +356,7 @@ describe("arkSync", () => {
beforeEach(resetAll);
test("records received VTXOs (positive amounts)", async () => {
- const req = {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [
@@ -358,19 +364,17 @@ describe("arkSync", () => {
],
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(1);
expect(sent.received).toBe(9000);
expect(store().listStore[`${arkAccountId}:payments`] ?? []).toHaveLength(1);
});
- test("records sent transactions (negative amounts)", async () => {
- const req = {
+ test("skips sent transactions (negative amounts)", async () => {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [
@@ -378,14 +382,12 @@ describe("arkSync", () => {
],
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
- expect(sent.synced).toBe(1);
- expect((store().listStore[`${arkAccountId}:payments`] ?? []).length).toBe(1);
+ // Negative amounts are skipped — balance reconciliation handles losses
+ expect(sent.synced).toBe(0);
});
test("deduplicates known transactions", async () => {
@@ -397,17 +399,15 @@ describe("arkSync", () => {
confirmed: false,
});
- const req = {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [{ hash: "known-hash", amount: -1000, settled: true, createdAt: Date.now() }],
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(0);
// Existing record should be updated to confirmed
@@ -425,6 +425,8 @@ describe("arkSync reconciliation", () => {
test("creates expired debit when payment sum exceeds wallet balance", async () => {
// Pre-seed two received payments totaling 7128
+ // Timestamps must be >120s old to pass hasRecentPayments guard
+ const oldTime = Date.now() - 200_000;
const pay1 = {
id: "pay-1",
aid: arkAccountId,
@@ -435,7 +437,7 @@ describe("arkSync reconciliation", () => {
uid: custodialUid,
rate: 50000,
currency: "USD",
- created: Date.now() - 2000,
+ created: oldTime,
};
const pay2 = {
id: "pay-2",
@@ -447,7 +449,7 @@ describe("arkSync reconciliation", () => {
uid: custodialUid,
rate: 50000,
currency: "USD",
- created: Date.now() - 1000,
+ created: oldTime,
};
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:pay-2`] = JSON.stringify(pay2);
@@ -456,18 +458,16 @@ describe("arkSync reconciliation", () => {
store().listStore[`${arkAccountId}:payments`] = ["pay-2", "pay-1"];
// Sync with no new transactions but balance=0 (VTXOs expired)
- const req = {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [],
balance: 0,
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(1);
expect(sent.payments).toHaveLength(1);
@@ -499,18 +499,16 @@ describe("arkSync reconciliation", () => {
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
- const req = {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [],
balance: 5000,
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(0);
expect(sent.payments).toHaveLength(0);
@@ -534,17 +532,15 @@ describe("arkSync reconciliation", () => {
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
- const req = {
+ const ctx = makeContext({
body: {
aid: arkAccountId,
transactions: [],
},
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(0);
expect(sent.payments).toHaveLength(0);
@@ -552,6 +548,7 @@ describe("arkSync reconciliation", () => {
test("is idempotent — second sync does not create duplicate", async () => {
// Seed payment sum = 5000, balance = 0
+ const oldTime = Date.now() - 200_000;
const pay1 = {
id: "pay-1",
aid: arkAccountId,
@@ -562,34 +559,37 @@ describe("arkSync reconciliation", () => {
uid: custodialUid,
rate: 50000,
currency: "USD",
- created: Date.now(),
+ created: oldTime,
};
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
- const req = {
+ const ctx1 = makeContext({
body: { aid: arkAccountId, transactions: [], balance: 0 },
user: makeUser(),
- };
+ });
// First sync — should create reconciliation
- const res1 = makeRes();
- await routes.arkSync(req as any, res1 as any);
- const sent1 = (res1 as any).getSent();
+ await routes.arkSync(ctx1);
+ const sent1 = ctx1.getSent();
expect(sent1.synced).toBe(1);
expect(sent1.payments[0].amount).toBe(-5000);
// Second sync — sum is now 0, matches balance, no new reconciliation
- const res2 = makeRes();
- await routes.arkSync(req as any, res2 as any);
- const sent2 = (res2 as any).getSent();
+ const ctx2 = makeContext({
+ body: { aid: arkAccountId, transactions: [], balance: 0 },
+ user: makeUser(),
+ });
+ await routes.arkSync(ctx2);
+ const sent2 = ctx2.getSent();
expect(sent2.synced).toBe(0);
expect(sent2.payments).toHaveLength(0);
});
test("reconciles partial expiry", async () => {
// 10000 received, 3000 sent, sum = 7000. Actual balance = 2000 (5000 expired)
+ const oldTime = Date.now() - 200_000;
const pay1 = {
id: "pay-1",
aid: arkAccountId,
@@ -600,7 +600,7 @@ describe("arkSync reconciliation", () => {
uid: custodialUid,
rate: 50000,
currency: "USD",
- created: Date.now(),
+ created: oldTime,
};
const pay2 = {
id: "pay-2",
@@ -612,7 +612,7 @@ describe("arkSync reconciliation", () => {
uid: custodialUid,
rate: 50000,
currency: "USD",
- created: Date.now(),
+ created: oldTime,
};
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:pay-2`] = JSON.stringify(pay2);
@@ -620,14 +620,12 @@ describe("arkSync reconciliation", () => {
store().kvStore[`payment:${arkAccountId}:h2`] = JSON.stringify("pay-2");
store().listStore[`${arkAccountId}:payments`] = ["pay-2", "pay-1"];
- const req = {
+ const ctx = makeContext({
body: { aid: arkAccountId, transactions: [], balance: 2000 },
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(1);
expect(sent.payments[0].amount).toBe(-5000);
@@ -652,14 +650,12 @@ describe("arkSync reconciliation", () => {
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
- const req = {
+ const ctx = makeContext({
body: { aid: arkAccountId, transactions: [], balance: 5000 },
user: makeUser(),
- };
- const res = makeRes();
-
- await routes.arkSync(req as any, res as any);
- const sent = (res as any).getSent();
+ });
+ await routes.arkSync(ctx);
+ const sent = ctx.getSent();
expect(sent.synced).toBe(0);
expect(sent.payments).toHaveLength(0);
@@ -687,13 +683,17 @@ describe("BOLT12 autowithdraw", () => {
: { type: "bolt12 invoice", invoice_amount_msat: 980_000, invoice_node_id: "bolt12-payee" },
);
- const user = makeUser({
+ const user = makeUser();
+ seedUser(user);
+
+ const awAccount = {
+ id: "aw-account",
autowithdraw: true,
threshold: 500,
reserve: 0,
destination: bolt12Offer,
- });
- seedUser(user);
+ };
+ store().kvStore[`account:${awAccount.id}`] = JSON.stringify(awAccount);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
const p = {
@@ -701,6 +701,7 @@ describe("BOLT12 autowithdraw", () => {
confirmed: true,
type: "lightning",
uid: custodialUid,
+ aid: awAccount.id,
created: Date.now(),
};
@@ -752,13 +753,17 @@ describe("BOLT12 autowithdraw", () => {
});
test("regular ln invoice is NOT passed through fetchinvoice", async () => {
- const user = makeUser({
+ const user = makeUser();
+ seedUser(user);
+
+ const awAccount = {
+ id: "aw-account",
autowithdraw: true,
threshold: 500,
reserve: 0,
destination: bolt11,
- });
- seedUser(user);
+ };
+ store().kvStore[`account:${awAccount.id}`] = JSON.stringify(awAccount);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
const p = {
@@ -766,6 +771,7 @@ describe("BOLT12 autowithdraw", () => {
confirmed: true,
type: "lightning",
uid: custodialUid,
+ aid: awAccount.id,
created: Date.now(),
};
@@ -800,13 +806,17 @@ describe("BOLT12 autowithdraw", () => {
invoice: bolt12Invoice,
}));
- const user = makeUser({
+ const user = makeUser();
+ seedUser(user);
+
+ const awAccount = {
+ id: "aw-account",
autowithdraw: true,
threshold: 500,
reserve: 0,
destination: bolt12Offer,
- });
- seedUser(user);
+ };
+ store().kvStore[`account:${awAccount.id}`] = JSON.stringify(awAccount);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
const p = {
@@ -814,6 +824,7 @@ describe("BOLT12 autowithdraw", () => {
confirmed: true,
type: "lightning",
uid: custodialUid,
+ aid: awAccount.id,
created: Date.now(),
};
diff --git a/test/integration/ark-reconcile.test.ts b/test/integration/ark-reconcile.test.ts
index 342d02a6..c223149d 100644
--- a/test/integration/ark-reconcile.test.ts
+++ b/test/integration/ark-reconcile.test.ts
@@ -83,6 +83,7 @@ beforeAll(async () => {
describe("ark sync reconciliation", () => {
test("syncs new transactions and creates payment records", async () => {
const hash = `test-recv-${Date.now()}`;
+ const oldTime = Date.now() - 200_000;
const result = await api("/ark/sync", token, {
method: "POST",
body: JSON.stringify({
@@ -93,7 +94,7 @@ describe("ark sync reconciliation", () => {
commitmentTxid: null,
amount: 10000,
settled: true,
- createdAt: Date.now(),
+ createdAt: oldTime,
},
],
balance: 10000,
@@ -115,7 +116,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() - 200_000 }],
balance: 13000,
}),
});
@@ -125,7 +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() - 200_000 }],
balance: 13000,
}),
});
@@ -177,7 +178,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() - 200_000 }],
balance: 5000,
}),
});
diff --git a/test/onchain.test.ts b/test/onchain.test.ts
index 9850bba3..884bcbc9 100644
--- a/test/onchain.test.ts
+++ b/test/onchain.test.ts
@@ -98,19 +98,30 @@ const resetAll = () => {
seedUser(makeUser());
};
-const makeRes = () => {
+const makeContext = (opts: { body?: any; user?: any } = {}) => {
let sent: any;
- return {
- send: (data: any) => {
+ const ctx: any = {
+ req: {
+ json: async () => opts.body,
+ header: () => undefined,
+ path: "/test",
+ method: "POST",
+ query: () => ({}),
+ raw: { clone: () => ({ json: async () => opts.body }) },
+ },
+ get: (key: string) => {
+ if (key === "user") return opts.user;
+ return undefined;
+ },
+ set: () => {},
+ json: (data: any, _status?: number) => {
sent = data;
+ return new Response(JSON.stringify(data), { status: _status || 200 });
},
- code: (_code: number) => ({
- send: (data: any) => {
- sent = data;
- },
- }),
+ env: {},
getSent: () => sent,
};
+ return ctx;
};
// =====================================================================
@@ -325,13 +336,12 @@ describe("confirm (liquid)", () => {
beforeEach(resetAll);
test("ignores non-liquid types", async () => {
- const req = {
+ const ctx = makeContext({
body: { txid: "btctx100", wallet: "test", type: PaymentType.bitcoin },
- };
- const res = makeRes();
+ });
- await routes.confirm(req as any, res as any);
- expect((res as any).getSent()).toEqual({});
+ await routes.confirm(ctx);
+ expect(ctx.getSent()).toEqual({});
expect(mockTbCredit).not.toHaveBeenCalled();
});
@@ -354,13 +364,11 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx001", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
- expect((res as any).getSent()).toEqual({});
+ });
+ await routes.confirm(ctx);
+ expect(ctx.getSent()).toEqual({});
// Payment should be created via credit()
const pid = JSON.parse(store().kvStore[`payment:lqtx001:0`]);
@@ -401,12 +409,10 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx002", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
+ });
+ await routes.confirm(ctx);
// Payment confirmed
const p = JSON.parse(store().kvStore[`payment:${pendingPayment.id}`]);
@@ -437,12 +443,10 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx003", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
+ });
+ await routes.confirm(ctx);
expect(mockTbCredit).not.toHaveBeenCalled();
expect(mockTbConfirm).not.toHaveBeenCalled();
});
@@ -463,12 +467,10 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx004", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
+ });
+ await routes.confirm(ctx);
expect(mockTbCredit).not.toHaveBeenCalled();
});
@@ -494,12 +496,10 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx005", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
+ });
+ await routes.confirm(ctx);
expect(mockTbCredit).not.toHaveBeenCalled();
});
@@ -533,12 +533,10 @@ describe("confirm (liquid)", () => {
}),
};
- const req = {
+ const ctx = makeContext({
body: { txid: "lqtx006", wallet: "test", type: PaymentType.liquid },
- };
- const res = makeRes();
-
- await routes.confirm(req as any, res as any);
+ });
+ await routes.confirm(ctx);
expect(mockTbConfirm).not.toHaveBeenCalled();
});
});
diff --git a/test/preload.ts b/test/preload.ts
index 01629d97..690512b2 100644
--- a/test/preload.ts
+++ b/test/preload.ts
@@ -1,3 +1,5 @@
+process.env.URL ||= "http://localhost:3119";
+
import { mock } from "bun:test";
// This file is loaded before test files via bunfig.toml [test].preload
@@ -142,6 +144,17 @@ if (process.env.INTEGRATION) {
kv()[k] = v;
return true;
},
+ incr: async (k: string) => {
+ const v = Number.parseInt(kv()[k] || "0") + 1;
+ kv()[k] = String(v);
+ return v;
+ },
+ ttl: async (k: string) => (kv()[k] !== undefined ? 300 : -2),
+ decrBy: async (k: string, n: number) => {
+ const v = Number.parseInt(kv()[k] || "0") - n;
+ kv()[k] = String(v);
+ return v;
+ },
zScore: async () => null,
zAdd: async () => 1,
zCard: async () => 0,
@@ -162,6 +175,8 @@ if (process.env.INTEGRATION) {
s,
gf,
ga: async () => null,
+ gfAll: async () => ({}),
+ scan: async function* (_pattern: string) {},
archive: { lRange: async () => [] },
};
});
@@ -191,6 +206,13 @@ if (process.env.INTEGRATION) {
nostr: "ws://localhost:7777",
tigerbeetle: { cluster_id: 0n, replica_addresses: ["localhost:3000"] },
vapid: { pk: "test", sk: "test" },
+ jwt: "test-jwt-secret",
+ publicRelay: "ws://localhost:7777",
+ relays: ["ws://localhost:7777"],
+ hostname: "localhost",
+ admin: "admin",
+ recaptcha: "",
+ ipregistry: undefined,
support: "[email protected]",
txWebhookSecret: "test",
},
@@ -208,9 +230,18 @@ if (process.env.INTEGRATION) {
tbSetPending: mock(async () => undefined),
fundDebit: mock(async () => ({ err: null })),
initTigerBeetle: mock(async () => {}),
+ createBalanceAccount: mock(async () => {}),
+ createCreditAccounts: mock(async () => {}),
+ getPending: mock(async () => 0),
+ getFundBalance: mock(async () => 0),
+ tbFundCredit: mock(async () => {}),
+ tbFundDebit: mock(async () => ({ err: null })),
}));
mock.module("$lib/ln", () => ({
+ LightningUnavailableError: class LightningUnavailableError extends Error {
+ constructor(msg: string) { super(msg); this.name = "LightningUnavailableError"; }
+ },
default: {
decode: mock(async () => ({ type: "bolt11", amount_msat: 1_000_000, payee: "test-payee" })),
listpeerchannels: mock(async () => ({ channels: [] })),
@@ -226,6 +257,13 @@ if (process.env.INTEGRATION) {
},
}));
+ mock.module("pino", () => {
+ const noop = () => {};
+ const logger = { info: noop, warn: noop, error: noop, debug: noop, trace: noop, fatal: noop };
+ const pino: any = () => logger;
+ pino.destination = () => ({});
+ return { default: pino };
+ });
mock.module("$lib/logging", () => ({ l: () => {}, warn: mock(() => {}), err: () => {} }));
mock.module("$lib/notifications", () => ({ notify: () => {}, nwcNotify: () => {} }));
mock.module("$lib/webhooks", () => ({ callWebhook: mock(() => {}) }));
@@ -256,6 +294,8 @@ if (process.env.INTEGRATION) {
mock.module("$lib/nostr", () => ({
handleZap: async () => {},
publish: async () => {},
+ getNostrUser: async () => null,
+ getProfile: async () => null,
serverPubkey: "m",
serverPubkey2: "m",
serverSecret: "m",
@@ -268,6 +308,10 @@ if (process.env.INTEGRATION) {
getArkBalance: async () => 0,
verifyArkVtxo: async () => true,
}));
+ mock.module("$lib/bip353", () => ({
+ setupBip353: async () => {},
+ teardownBip353: async () => {},
+ }));
mock.module("$lib/ecash", () => ({ request: async () => ({}) }));
mock.module("$lib/lightning", () => ({
replay: async () => ({}),
@@ -277,9 +321,37 @@ if (process.env.INTEGRATION) {
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(),
+ auth: async (c: any, next: any) => {
+ c.set("user", {
+ id: "00000000-0000-0000-0000-000000000001",
+ username: "authuser",
+ currency: "USD",
+ currencies: ["USD", "CAD"],
+ fiat: false,
+ });
+ await next();
+ },
+ optional: async (c: any, next: any) => {
+ c.set("user", {
+ id: "00000000-0000-0000-0000-000000000001",
+ username: "authuser",
+ currency: "USD",
+ currencies: ["USD", "CAD"],
+ fiat: false,
+ });
+ await next();
+ },
+ admin: async (c: any, next: any) => {
+ c.set("user", {
+ id: "00000000-0000-0000-0000-000000000001",
+ username: "authuser",
+ admin: true,
+ currency: "USD",
+ currencies: ["USD", "CAD"],
+ fiat: false,
+ });
+ await next();
+ },
}));
mock.module("$lib/invoices", () => ({
generate: mock(async ({ invoice, user }: any) => ({
@@ -358,15 +430,24 @@ if (process.env.INTEGRATION) {
}
},
getUser: async (username: string) => {
- const raw = kv()[`user:${username}`];
- return raw ? JSON.parse(raw) : null;
+ const k = username?.replace(/\s/g, "").toLowerCase();
+ const raw = kv()[`user:${k}`];
+ if (raw == null) return null;
+ let user: any;
+ try { user = JSON.parse(raw); } catch { user = raw; }
+ if (typeof user === "string") {
+ const raw2 = kv()[`user:${user}`];
+ if (raw2 == null) return null;
+ try { user = JSON.parse(raw2); } catch { user = raw2; }
+ }
+ return user;
},
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),
+ bail: (c: any, msg: string) => c.json(msg, 500),
bip21: () => "",
fields: [],
nada: () => {},