@botiverse/testbed-cli 0.1.0 → 0.3.0
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/lib/main.mjs +92 -4
- package/package.json +2 -2
package/lib/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveAuth } from "./auth.mjs";
|
|
|
6
6
|
import { makeClient, ApiError } from "./client.mjs";
|
|
7
7
|
import { runAgentLogin } from "./agent-login.mjs";
|
|
8
8
|
|
|
9
|
-
export const USAGE = `testbed — cloud phones + the acceptance
|
|
9
|
+
export const USAGE = `testbed — cloud phones + the acceptance testbed in the Raft release loop
|
|
10
10
|
|
|
11
11
|
testbed login agent: PKCE login via raft (no browser); human: how to set TESTBED_TOKEN
|
|
12
12
|
testbed logout agent: forget the stored session
|
|
@@ -21,8 +21,18 @@ export const USAGE = `testbed — cloud phones + the acceptance stamp in the Raf
|
|
|
21
21
|
testbed repair-adb <lease-id>
|
|
22
22
|
testbed diagnose-adb <lease-id> [--pubkey-sha256 HEX]
|
|
23
23
|
|
|
24
|
+
testbed verify start [--ttl MIN] [--instance ID] [--request-id ID] [--app-ref LABEL] [--apk-key fast/<id> --apk-sha256 HEX]
|
|
25
|
+
open a human verification session (takes one pool device)
|
|
26
|
+
testbed verify show <session-id>
|
|
27
|
+
testbed verify renew <session-id> [--minutes MIN]
|
|
28
|
+
testbed verify end <session-id>
|
|
29
|
+
testbed verify accounts <session-id> QA accounts the verifier may log in with
|
|
30
|
+
testbed verify login <session-id> --account <account-id>
|
|
31
|
+
testbed verify reinstall <session-id> --apk-key fast/<id> --apk-sha256 HEX
|
|
32
|
+
|
|
24
33
|
testbed cases acceptance case library
|
|
25
34
|
testbed run <case-id...> [--app --ref --apk --apk-run --release --by]
|
|
35
|
+
testbed run --all [...] the whole case table in one run (release smoke)
|
|
26
36
|
testbed runs
|
|
27
37
|
testbed show <run-id>
|
|
28
38
|
|
|
@@ -84,7 +94,7 @@ export async function main(argv, opts) {
|
|
|
84
94
|
case "login": {
|
|
85
95
|
if (auth.mode !== "agent") {
|
|
86
96
|
out("Not a managed Raft agent. Humans and CI use TESTBED_TOKEN:");
|
|
87
|
-
out(` browser: ${auth.apiBase}/login (then reuse the
|
|
97
|
+
out(` browser: ${auth.apiBase}/login (then reuse the testbed_session cookie value as TESTBED_TOKEN)`);
|
|
88
98
|
out(" CI: a deploy token issued by the testbed owner");
|
|
89
99
|
return exit(0);
|
|
90
100
|
}
|
|
@@ -174,6 +184,77 @@ export async function main(argv, opts) {
|
|
|
174
184
|
emit(d, () => out(JSON.stringify(d, null, 2)));
|
|
175
185
|
return exit(0);
|
|
176
186
|
}
|
|
187
|
+
case "verify": {
|
|
188
|
+
const sub = args.shift();
|
|
189
|
+
const vs = (id) => `/verify-sessions/${encodeURIComponent(id)}`;
|
|
190
|
+
const fmt = (t) => (t ? new Date(t).toISOString().slice(0, 16) : "-");
|
|
191
|
+
const describe = (sess) => {
|
|
192
|
+
out(`session ${sess.id} — ${sess.state}${sess.instance_id ? ` on ${sess.instance_id}` : ""}, expires ${fmt(sess.expires_at)}`);
|
|
193
|
+
out(`${auth.apiBase.replace(/\/api$/, "")}/verify/${sess.id}`);
|
|
194
|
+
if (sess.app_ref) out(`app: ${sess.app_ref}`);
|
|
195
|
+
if (sess.qa_account_label) out(`qa account: ${sess.qa_account_label}`);
|
|
196
|
+
};
|
|
197
|
+
switch (sub) {
|
|
198
|
+
case "start": {
|
|
199
|
+
const ttl = takeFlag(args, "ttl"); const instance = takeFlag(args, "instance"); const requestId = takeFlag(args, "request-id");
|
|
200
|
+
const appRef = takeFlag(args, "app-ref"); const apkKey = takeFlag(args, "apk-key"); const apkSha = takeFlag(args, "apk-sha256");
|
|
201
|
+
if ((apkKey === undefined) !== (apkSha === undefined)) throw new Error("usage: --apk-key and --apk-sha256 go together");
|
|
202
|
+
const d = await api("POST", "/verify-sessions", {
|
|
203
|
+
ttl_minutes: ttl !== undefined ? Number(ttl) : undefined, instance_id: instance, request_id: requestId,
|
|
204
|
+
app_ref: appRef, app_oss_key: apkKey, app_sha256: apkSha,
|
|
205
|
+
});
|
|
206
|
+
emit(d, () => describe(d.session));
|
|
207
|
+
return exit(0);
|
|
208
|
+
}
|
|
209
|
+
case "show": {
|
|
210
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify show <session-id>");
|
|
211
|
+
const d = await api("GET", vs(id));
|
|
212
|
+
emit(d, () => describe(d.session));
|
|
213
|
+
return exit(0);
|
|
214
|
+
}
|
|
215
|
+
case "renew": {
|
|
216
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify renew <session-id> [--minutes MIN]");
|
|
217
|
+
const minutes = takeFlag(args, "minutes");
|
|
218
|
+
const d = await api("POST", `${vs(id)}/renew`, minutes !== undefined ? { minutes: Number(minutes) } : {});
|
|
219
|
+
emit(d, () => out(`session ${id} now until ${new Date(d.expires_at).toISOString()} (lease until ${new Date(d.lease_expires_at).toISOString()})`));
|
|
220
|
+
return exit(0);
|
|
221
|
+
}
|
|
222
|
+
case "end": {
|
|
223
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify end <session-id>");
|
|
224
|
+
const d = await api("POST", `${vs(id)}/end`, {});
|
|
225
|
+
emit(d, () => out(d.already_terminal ? `session ${id} was already over` : `ended session ${id}; device reclaim continues asynchronously`));
|
|
226
|
+
return exit(0);
|
|
227
|
+
}
|
|
228
|
+
case "accounts": {
|
|
229
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify accounts <session-id>");
|
|
230
|
+
const d = await api("GET", `${vs(id)}/qa-accounts`);
|
|
231
|
+
emit(d, () => {
|
|
232
|
+
table(d.accounts.map((a) => ({
|
|
233
|
+
account: a.id, label: a.label, email: a.login_email ?? "-", env: a.environment ?? "-",
|
|
234
|
+
state: a.in_use ? "in use" : a.cooldown_until && a.cooldown_until > Date.now() ? "cooldown" : "free",
|
|
235
|
+
})), ["account", "label", "email", "env", "state"], out);
|
|
236
|
+
if (d.current) out(`\ncurrently logged in: ${d.current.account_label ?? d.current.account_id}`);
|
|
237
|
+
});
|
|
238
|
+
return exit(0);
|
|
239
|
+
}
|
|
240
|
+
case "login": {
|
|
241
|
+
const id = args[0]; const account = takeFlag(args, "account");
|
|
242
|
+
if (!id || !account) throw new Error("usage: testbed verify login <session-id> --account <account-id>");
|
|
243
|
+
const d = await api("POST", `${vs(id)}/login-qa`, { account_id: account });
|
|
244
|
+
emit(d, () => out(`logged ${d.account_label ?? account} into session ${id}`));
|
|
245
|
+
return exit(0);
|
|
246
|
+
}
|
|
247
|
+
case "reinstall": {
|
|
248
|
+
const id = args[0]; const apkKey = takeFlag(args, "apk-key"); const apkSha = takeFlag(args, "apk-sha256");
|
|
249
|
+
if (!id || !apkKey || !apkSha) throw new Error("usage: testbed verify reinstall <session-id> --apk-key fast/<id> --apk-sha256 HEX");
|
|
250
|
+
const d = await api("POST", `${vs(id)}/reinstall`, { app_oss_key: apkKey, app_sha256: apkSha });
|
|
251
|
+
emit(d, () => out(`reinstalled on session ${id}: ${d.status ?? "ok"}`));
|
|
252
|
+
return exit(0);
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
throw new Error("usage: testbed verify <start|show|renew|end|accounts|login|reinstall> …");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
177
258
|
case "cases": {
|
|
178
259
|
const d = await api("GET", "/cases");
|
|
179
260
|
emit(d, () => table(d.cases.map((c) => ({ id: c.id, tier: c.tier, rev: c.revision, title: c.title })), ["id", "tier", "rev", "title"], out));
|
|
@@ -186,8 +267,15 @@ export async function main(argv, opts) {
|
|
|
186
267
|
const apkRun = takeFlag(args, "apk-run");
|
|
187
268
|
const release = takeFlag(args, "release");
|
|
188
269
|
const by = takeFlag(args, "by", env.USER || "testbed-cli");
|
|
189
|
-
const
|
|
190
|
-
|
|
270
|
+
const all = takeSwitch(args, "all");
|
|
271
|
+
let caseIds = args.filter((a) => !a.startsWith("--"));
|
|
272
|
+
if (all) {
|
|
273
|
+
if (caseIds.length) throw new Error("usage: --all takes no case ids");
|
|
274
|
+
const cat = await api("GET", "/cases");
|
|
275
|
+
caseIds = cat.cases.map((c) => c.id);
|
|
276
|
+
if (!caseIds.length) throw new Error("the case table is empty");
|
|
277
|
+
}
|
|
278
|
+
if (!caseIds.length) throw new Error("usage: testbed run <case-id...> | --all [--app slug] [--ref branch] [--apk url] [--apk-run ci-run-id] [--release id] [--by name]");
|
|
191
279
|
const d = await api("POST", "/runs", { app_slug: app, case_ids: caseIds, apk_ref: apk, ref, apk_run_id: apkRun, hands_release_id: release, requested_by: by });
|
|
192
280
|
emit(d, () => { out(`run ${d.run_id} — ${d.cases} case(s), ${d.dispatched} dispatched`); out(`${auth.apiBase}/runs/${d.run_id}`); });
|
|
193
281
|
return exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botiverse/testbed-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for testbed \u2014 cloud phones and the acceptance
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI for testbed \u2014 cloud phones and the acceptance testbed in the Raft release loop",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|