this repo has no description
at main 172 lines 5.9 kB view raw
1/** 2 * Integration tests for admin endpoints 3 */ 4 5import { describe, test, expect, beforeAll } from "bun:test"; 6import { AttoshiClient } from "../helpers/api.js"; 7import { waitForServer, BASE_URL } from "../setup.js"; 8 9// Auth token for admin endpoints 10const AUTH_TOKEN = process.env.AUTH_TOKEN || "test-auth-token"; 11 12describe("Admin Endpoints", () => { 13 const client = new AttoshiClient(); 14 15 beforeAll(async () => { 16 await waitForServer(); 17 }); 18 19 describe("POST /xrpc/cash.attoshi.initConfig", () => { 20 test("returns 401 without auth token", async () => { 21 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.initConfig`, { 22 method: "POST", 23 }); 24 expect(res.status).toBe(401); 25 }); 26 27 test("returns 401 with invalid auth token", async () => { 28 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.initConfig`, { 29 method: "POST", 30 headers: { Authorization: "Bearer invalid-token-12345" }, 31 }); 32 expect(res.status).toBe(401); 33 }); 34 35 test("returns 400 or 200 depending on config state", async () => { 36 const { status, data } = await client.initConfig(AUTH_TOKEN); 37 // Either config already exists (400) or was just created (200) 38 expect([200, 400, 401]).toContain(status); 39 if (status === 400) { 40 // Config already exists 41 expect(data.error).toBeDefined(); 42 } 43 }); 44 }); 45 46 describe("POST /xrpc/cash.attoshi.startFirehose", () => { 47 test("returns 401 without auth token", async () => { 48 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.startFirehose`, { 49 method: "POST", 50 }); 51 expect(res.status).toBe(401); 52 }); 53 54 test("returns 401 with Bearer but no token", async () => { 55 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.startFirehose`, { 56 method: "POST", 57 headers: { Authorization: "Bearer " }, 58 }); 59 expect(res.status).toBe(401); 60 }); 61 62 test("returns 401 with wrong auth scheme", async () => { 63 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.startFirehose`, { 64 method: "POST", 65 headers: { Authorization: `Basic ${AUTH_TOKEN}` }, 66 }); 67 expect(res.status).toBe(401); 68 }); 69 70 test("starts firehose with valid auth", async () => { 71 const { status, data } = await client.startFirehose(AUTH_TOKEN); 72 // 401 if token is wrong, otherwise 200 73 if (status === 200) { 74 expect(["started", "already_connected", "connecting"]).toContain( 75 data.status 76 ); 77 } else { 78 expect(status).toBe(401); 79 } 80 }); 81 }); 82 83 describe("POST /xrpc/cash.attoshi.stopFirehose", () => { 84 test("returns 401 without auth token", async () => { 85 const res = await fetch(`${BASE_URL}/xrpc/cash.attoshi.stopFirehose`, { 86 method: "POST", 87 }); 88 expect(res.status).toBe(401); 89 }); 90 91 test("stops firehose with valid auth", async () => { 92 const { status, data } = await client.stopFirehose(AUTH_TOKEN); 93 if (status === 200) { 94 expect(data.status).toBe("stopped"); 95 } else { 96 expect(status).toBe(401); 97 } 98 }); 99 }); 100 101 describe("GET /xrpc/cash.attoshi.getFirehoseStatus", () => { 102 test("returns 200 without auth requirement", async () => { 103 const { status } = await client.getFirehoseStatus(); 104 expect(status).toBe(200); 105 }); 106 107 test("returns connection status", async () => { 108 const { data } = await client.getFirehoseStatus(); 109 expect(data).toHaveProperty("connected"); 110 expect(typeof data.connected).toBe("boolean"); 111 }); 112 113 test("returns reconnect attempts", async () => { 114 const { data } = await client.getFirehoseStatus(); 115 expect(data).toHaveProperty("reconnectAttempts"); 116 expect(typeof data.reconnectAttempts).toBe("number"); 117 expect(data.reconnectAttempts).toBeGreaterThanOrEqual(0); 118 }); 119 120 test("returns entity DID", async () => { 121 const { data } = await client.getFirehoseStatus(); 122 expect(data).toHaveProperty("entityDid"); 123 expect(data.entityDid).toMatch(/^did:/); 124 }); 125 }); 126 127 describe("GET /xrpc/cash.attoshi.getStats", () => { 128 test("returns 200", async () => { 129 const { status } = await client.getStats(); 130 expect(status).toBe(200); 131 }); 132 133 test("returns UTXO count", async () => { 134 const { data } = await client.getStats(); 135 expect(data).toHaveProperty("utxoCount"); 136 expect(typeof data.utxoCount).toBe("number"); 137 expect(data.utxoCount).toBeGreaterThanOrEqual(0); 138 }); 139 140 test("returns spent count", async () => { 141 const { data } = await client.getStats(); 142 expect(data).toHaveProperty("spentCount"); 143 expect(typeof data.spentCount).toBe("number"); 144 expect(data.spentCount).toBeGreaterThanOrEqual(0); 145 }); 146 147 test("returns issuance count", async () => { 148 const { data } = await client.getStats(); 149 expect(data).toHaveProperty("issuanceCount"); 150 expect(typeof data.issuanceCount).toBe("number"); 151 expect(data.issuanceCount).toBeGreaterThanOrEqual(0); 152 }); 153 154 test("returns supply info", async () => { 155 const { data } = await client.getStats(); 156 expect(data).toHaveProperty("supply"); 157 // Note: supply uses snake_case from SQLite row 158 expect(data.supply).toHaveProperty("total_issued"); 159 expect(data.supply).toHaveProperty("burned"); 160 }); 161 162 test("stats are consistent with other endpoints", async () => { 163 const statsRes = await client.getStats(); 164 const supplyRes = await client.getSupply(); 165 166 expect(statsRes.data.issuanceCount).toBe(supplyRes.data.issuanceCount); 167 // Note: stats.supply uses snake_case, supplyRes uses camelCase 168 expect(statsRes.data.supply.total_issued).toBe(supplyRes.data.totalIssued); 169 expect(statsRes.data.supply.burned).toBe(supplyRes.data.burned); 170 }); 171 }); 172});