@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.
Files changed (2) hide show
  1. package/lib/main.mjs +92 -4
  2. 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 stamp in the Raft release loop
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 stamp_session cookie value as TESTBED_TOKEN)`);
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 caseIds = args.filter((a) => !a.startsWith("--"));
190
- if (!caseIds.length) throw new Error("usage: testbed run <case-id...> [--app slug] [--ref branch] [--apk url] [--apk-run ci-run-id] [--release id]");
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.1.0",
4
- "description": "CLI for testbed \u2014 cloud phones and the acceptance stamp in the Raft release loop",
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": {