this repo has no description
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});