@tokenbuddy/tb-admin 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +280 -19
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +82 -2
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +93 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/provider.d.ts +120 -0
- package/dist/src/provider.d.ts.map +1 -0
- package/dist/src/provider.js +73 -0
- package/dist/src/provider.js.map +1 -0
- package/dist/src/seller.d.ts +104 -0
- package/dist/src/seller.d.ts.map +1 -0
- package/dist/src/seller.js +283 -0
- package/dist/src/seller.js.map +1 -0
- package/dist/src/ui-actions.d.ts +25 -0
- package/dist/src/ui-actions.d.ts.map +1 -1
- package/dist/src/ui-actions.js +81 -11
- package/dist/src/ui-actions.js.map +1 -1
- package/dist/src/ui-server.d.ts.map +1 -1
- package/dist/src/ui-server.js +15 -2
- package/dist/src/ui-server.js.map +1 -1
- package/dist/src/ui-state.d.ts +77 -2
- package/dist/src/ui-state.d.ts.map +1 -1
- package/dist/src/ui-state.js +242 -14
- package/dist/src/ui-state.js.map +1 -1
- package/dist/src/ui-static.d.ts.map +1 -1
- package/dist/src/ui-static.js +98 -20
- package/dist/src/ui-static.js.map +1 -1
- package/dist/src/vendor-client.d.ts +23 -0
- package/dist/src/vendor-client.d.ts.map +1 -0
- package/dist/src/vendor-client.js +2 -0
- package/dist/src/vendor-client.js.map +1 -0
- package/dist/src/vendor-commands.d.ts +35 -0
- package/dist/src/vendor-commands.d.ts.map +1 -0
- package/dist/src/vendor-commands.js +33 -0
- package/dist/src/vendor-commands.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +305 -31
- package/src/client.ts +118 -2
- package/src/provider.ts +150 -0
- package/src/seller.ts +362 -0
- package/src/ui-actions.ts +89 -11
- package/src/ui-server.ts +15 -1
- package/src/ui-state.ts +293 -15
- package/src/ui-static.ts +98 -20
- package/src/vendor-client.ts +23 -0
- package/src/vendor-commands.ts +65 -0
- package/tests/admin.test.ts +81 -3
- package/tests/seller.test.ts +337 -0
- package/tests/ui-state-fleet.test.ts +257 -0
- package/tests/ui-static-row.test.ts +202 -0
- package/tests/vendor-cli.test.ts +241 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import request from "supertest";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { buildAdminCli } from "../src/cli.js";
|
|
5
|
+
import { ConfigManager } from "../src/config.js";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
|
|
8
|
+
// Use the workspace-linked package. ts-jest will resolve the
|
|
9
|
+
// `.js` to the compiled `.ts` via the root moduleNameMapper.
|
|
10
|
+
import { buildApp } from "@tokenbuddy/wallet-bootstrap";
|
|
11
|
+
import type { BootstrapConfig } from "@tokenbuddy/wallet-bootstrap";
|
|
12
|
+
import { RegistryAdminClient } from "../src/client.js";
|
|
13
|
+
|
|
14
|
+
const WALLET_TEST_DIR = path.resolve(__dirname, "../../data-vendor-cli-test");
|
|
15
|
+
const TEMP_REGISTRY_PATH = path.join(WALLET_TEST_DIR, "sellers.json");
|
|
16
|
+
const TEMP_REGISTRY_DB_PATH = path.join(WALLET_TEST_DIR, "bootstrap.sqlite");
|
|
17
|
+
const TEMP_CONFIG_PATH = path.join(WALLET_TEST_DIR, "tb-registry.yaml");
|
|
18
|
+
const TEMP_ADMIN_CONF_PATH = path.join(WALLET_TEST_DIR, "admin-config.json");
|
|
19
|
+
const SUPER_ADMIN_KEY = "platform-super-admin-key-please-rotate-me-1234567890";
|
|
20
|
+
const SESSION_SECRET = "session-secret-32-chars-min-please";
|
|
21
|
+
|
|
22
|
+
const baseRegistry = {
|
|
23
|
+
version: 1,
|
|
24
|
+
defaultSeller: "platform-seed",
|
|
25
|
+
sellers: [
|
|
26
|
+
{
|
|
27
|
+
id: "platform-seed",
|
|
28
|
+
name: "Platform Seed",
|
|
29
|
+
url: "https://platform-seed.example.com",
|
|
30
|
+
status: "active",
|
|
31
|
+
models: ["seed-model"],
|
|
32
|
+
supportedProtocols: ["chat_completions"],
|
|
33
|
+
paymentMethods: ["clawtip"]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const baseConfig: BootstrapConfig = {
|
|
39
|
+
configPath: TEMP_CONFIG_PATH,
|
|
40
|
+
payTo: "pay-to-x",
|
|
41
|
+
sm4KeyBase64: "MDEyMzQ1Njc4OUFCQ0RFRg==",
|
|
42
|
+
skillSlug: "tb-registry",
|
|
43
|
+
skillId: "si-tb-registry",
|
|
44
|
+
description: "Activate",
|
|
45
|
+
resourceUrl: "https://example.com",
|
|
46
|
+
activationFeeFen: 1,
|
|
47
|
+
microsPerFen: 1000000,
|
|
48
|
+
sellerRegistryPath: TEMP_REGISTRY_PATH,
|
|
49
|
+
operatorSecret: "op-secret",
|
|
50
|
+
superAdminKey: SUPER_ADMIN_KEY,
|
|
51
|
+
superAdminLabel: "ops",
|
|
52
|
+
sessionSecret: SESSION_SECRET
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function buildServer() {
|
|
56
|
+
fs.mkdirSync(WALLET_TEST_DIR, { recursive: true });
|
|
57
|
+
fs.writeFileSync(TEMP_REGISTRY_PATH, JSON.stringify(baseRegistry), "utf8");
|
|
58
|
+
for (const f of [TEMP_REGISTRY_DB_PATH, `${TEMP_REGISTRY_DB_PATH}-wal`, `${TEMP_REGISTRY_DB_PATH}-shm`]) {
|
|
59
|
+
try { fs.rmSync(f, { force: true }); } catch { /* ignore */ }
|
|
60
|
+
}
|
|
61
|
+
return buildApp(baseConfig);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function createVendorToken(app: ReturnType<typeof buildServer>, name: string): Promise<string> {
|
|
65
|
+
const response = await request(app)
|
|
66
|
+
.post("/platform/vendors")
|
|
67
|
+
.set("X-Registry-Admin-Key", SUPER_ADMIN_KEY)
|
|
68
|
+
.send({ name });
|
|
69
|
+
expect(response.status).toBe(200);
|
|
70
|
+
return response.body.token;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runCli(args: string[], env: NodeJS.ProcessEnv): Promise<{ stdout: string; stderr: string; status: number }> {
|
|
74
|
+
// We invoke the CLI as a child process so the `program` state does
|
|
75
|
+
// not leak between tests. The package's `dist/` may not exist in
|
|
76
|
+
// worktrees, so we run from TypeScript source via `tsx` if
|
|
77
|
+
// available; fall back to requiring the source.
|
|
78
|
+
const cliEntry = path.resolve(__dirname, "../src/cli.ts");
|
|
79
|
+
// Try `npx tsx` first, then `node --import tsx`, then fallback
|
|
80
|
+
// to `node -r ts-node/register`.
|
|
81
|
+
let cmd: string;
|
|
82
|
+
let finalArgs: string[];
|
|
83
|
+
try {
|
|
84
|
+
execSync("which tsx", { stdio: "ignore" });
|
|
85
|
+
cmd = "npx";
|
|
86
|
+
finalArgs = ["tsx", cliEntry, ...args];
|
|
87
|
+
} catch {
|
|
88
|
+
cmd = "node";
|
|
89
|
+
finalArgs = ["--import", "tsx", cliEntry, ...args];
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const stdout = execSync(`${cmd} ${finalArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
93
|
+
env: { ...process.env, ...env },
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
encoding: "utf8"
|
|
96
|
+
});
|
|
97
|
+
return { stdout, stderr: "", status: 0 };
|
|
98
|
+
} catch (err: any) {
|
|
99
|
+
return {
|
|
100
|
+
stdout: err.stdout?.toString() || "",
|
|
101
|
+
stderr: err.stderr?.toString() || err.message,
|
|
102
|
+
status: err.status || 1
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe("Vendor CLI integration (Step 5)", () => {
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
fs.mkdirSync(WALLET_TEST_DIR, { recursive: true });
|
|
110
|
+
for (const f of [TEMP_REGISTRY_PATH, TEMP_REGISTRY_DB_PATH, `${TEMP_REGISTRY_DB_PATH}-wal`, `${TEMP_REGISTRY_DB_PATH}-shm`, TEMP_CONFIG_PATH, TEMP_ADMIN_CONF_PATH]) {
|
|
111
|
+
try { fs.rmSync(f, { force: true }); } catch { /* ignore */ }
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterAll(() => {
|
|
116
|
+
fs.rmSync(WALLET_TEST_DIR, { recursive: true, force: true });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("buildAdminCli still constructs without errors after the registry redesign", () => {
|
|
120
|
+
const config = new ConfigManager(TEMP_ADMIN_CONF_PATH);
|
|
121
|
+
const cli = buildAdminCli(config);
|
|
122
|
+
expect(cli.commands.find((c: any) => c.name() === "vendor-bootstrap")).toBeDefined();
|
|
123
|
+
// The legacy `bootstrap` command is still there for backward compat.
|
|
124
|
+
expect(cli.commands.find((c: any) => c.name() === "bootstrap")).toBeDefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("RegistryVendorClient can authenticate against a live wallet-bootstrap app and stage a seller", async () => {
|
|
128
|
+
const app = buildServer();
|
|
129
|
+
const token = await createVendorToken(app, "Acme");
|
|
130
|
+
// Direct HTTP call to the vendor API as the CLI would do.
|
|
131
|
+
const stage = await request(app)
|
|
132
|
+
.post("/platform/sellers/stage")
|
|
133
|
+
.set("Authorization", `Bearer ${token}`)
|
|
134
|
+
.send({ seller: {
|
|
135
|
+
id: "acme-seller-1",
|
|
136
|
+
name: "Acme Seller 1",
|
|
137
|
+
url: "https://acme.example.com",
|
|
138
|
+
status: "active",
|
|
139
|
+
models: ["m1"],
|
|
140
|
+
supportedProtocols: ["chat_completions"],
|
|
141
|
+
paymentMethods: ["clawtip"]
|
|
142
|
+
} });
|
|
143
|
+
expect(stage.status).toBe(200);
|
|
144
|
+
expect(stage.body.pendingSeller.id).toBe("acme-seller-1");
|
|
145
|
+
expect(stage.body.pendingSeller.status).toBe("staged");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("RegistryVendorClient listPendingSellers only returns this vendor's rows", async () => {
|
|
149
|
+
const app = buildServer();
|
|
150
|
+
const tokenA = await createVendorToken(app, "VendorA");
|
|
151
|
+
const tokenB = await createVendorToken(app, "VendorB");
|
|
152
|
+
await request(app)
|
|
153
|
+
.post("/platform/sellers/stage")
|
|
154
|
+
.set("Authorization", `Bearer ${tokenA}`)
|
|
155
|
+
.send({ seller: {
|
|
156
|
+
id: "va-1", name: "VA1", url: "https://va1.example.com", status: "active",
|
|
157
|
+
models: ["m1"], supportedProtocols: ["chat_completions"], paymentMethods: ["clawtip"]
|
|
158
|
+
} });
|
|
159
|
+
await request(app)
|
|
160
|
+
.post("/platform/sellers/stage")
|
|
161
|
+
.set("Authorization", `Bearer ${tokenB}`)
|
|
162
|
+
.send({ seller: {
|
|
163
|
+
id: "vb-1", name: "VB1", url: "https://vb1.example.com", status: "active",
|
|
164
|
+
models: ["m1"], supportedProtocols: ["chat_completions"], paymentMethods: ["clawtip"]
|
|
165
|
+
} });
|
|
166
|
+
const aList = await request(app)
|
|
167
|
+
.get("/platform/sellers/pending?status=staged")
|
|
168
|
+
.set("Authorization", `Bearer ${tokenA}`);
|
|
169
|
+
const bList = await request(app)
|
|
170
|
+
.get("/platform/sellers/pending?status=staged")
|
|
171
|
+
.set("Authorization", `Bearer ${tokenB}`);
|
|
172
|
+
expect(aList.body.pendingSellers).toHaveLength(1);
|
|
173
|
+
expect(aList.body.pendingSellers[0].id).toBe("va-1");
|
|
174
|
+
expect(bList.body.pendingSellers).toHaveLength(1);
|
|
175
|
+
expect(bList.body.pendingSellers[0].id).toBe("vb-1");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("RegistryVendorClient submitRelease -> listReleaseRequests round-trips", async () => {
|
|
179
|
+
const app = buildServer();
|
|
180
|
+
const token = await createVendorToken(app, "VendorC");
|
|
181
|
+
await request(app)
|
|
182
|
+
.post("/platform/sellers/stage")
|
|
183
|
+
.set("Authorization", `Bearer ${token}`)
|
|
184
|
+
.send({ seller: {
|
|
185
|
+
id: "vc-1", name: "VC1", url: "https://vc1.example.com", status: "active",
|
|
186
|
+
models: ["m1"], supportedProtocols: ["chat_completions"], paymentMethods: ["clawtip"]
|
|
187
|
+
} });
|
|
188
|
+
const submit = await request(app)
|
|
189
|
+
.post("/platform/release-requests")
|
|
190
|
+
.set("Authorization", `Bearer ${token}`)
|
|
191
|
+
.send({ stagedSellerIds: ["vc-1"], note: "test" });
|
|
192
|
+
expect(submit.status).toBe(200);
|
|
193
|
+
const list = await request(app)
|
|
194
|
+
.get("/platform/release-requests?limit=10")
|
|
195
|
+
.set("Authorization", `Bearer ${token}`);
|
|
196
|
+
expect(list.body.releaseRequests).toHaveLength(1);
|
|
197
|
+
expect(list.body.releaseRequests[0].id).toBe(submit.body.releaseRequest.id);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("RegistryAdminClient uses canonical admin API routes", async () => {
|
|
201
|
+
const app = buildServer();
|
|
202
|
+
const server = app.listen(0);
|
|
203
|
+
const address = server.address();
|
|
204
|
+
if (!address || typeof address !== "object") {
|
|
205
|
+
throw new Error("test server did not bind a TCP port");
|
|
206
|
+
}
|
|
207
|
+
const client = new RegistryAdminClient(`http://127.0.0.1:${address.port}`, SUPER_ADMIN_KEY);
|
|
208
|
+
try {
|
|
209
|
+
const vendor = await client.createVendor("AdminClientVendor");
|
|
210
|
+
expect(vendor.token).toBeTruthy();
|
|
211
|
+
|
|
212
|
+
const vendors = await client.listVendors();
|
|
213
|
+
expect(vendors.vendors.length).toBeGreaterThan(0);
|
|
214
|
+
|
|
215
|
+
const versions = await client.listVersions();
|
|
216
|
+
expect(versions.versions.length).toBeGreaterThan(0);
|
|
217
|
+
|
|
218
|
+
const token = vendor.token;
|
|
219
|
+
await request(app)
|
|
220
|
+
.post("/platform/sellers/stage")
|
|
221
|
+
.set("Authorization", `Bearer ${token}`)
|
|
222
|
+
.send({ seller: {
|
|
223
|
+
id: "admin-client-force-1",
|
|
224
|
+
name: "Admin Client Force",
|
|
225
|
+
url: "https://admin-client-force.example.com",
|
|
226
|
+
status: "active",
|
|
227
|
+
models: ["m1"],
|
|
228
|
+
supportedProtocols: ["chat_completions"],
|
|
229
|
+
paymentMethods: ["clawtip"]
|
|
230
|
+
} });
|
|
231
|
+
const submit = await request(app)
|
|
232
|
+
.post("/platform/release-requests")
|
|
233
|
+
.set("Authorization", `Bearer ${token}`)
|
|
234
|
+
.send({ stagedSellerIds: ["admin-client-force-1"] });
|
|
235
|
+
const forced = await client.forcePublish(submit.body.releaseRequest.id);
|
|
236
|
+
expect(forced.releaseRequest).toMatchObject({ status: "published" });
|
|
237
|
+
} finally {
|
|
238
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|