92 lines
5.7 KiB
TypeScript
92 lines
5.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "bun:test";
|
|
|
|
import type { CaseRunRecord } from "../../internal/harnessrl/contracts.ts";
|
|
import { CaseRunTransportError, createApiCaseRunTransport, createLocalCaseRunTransport } from "../../internal/harnessrl/transport.ts";
|
|
import { runHwlabCli } from "../src/hwlab-cli-lib.ts";
|
|
|
|
const record: CaseRunRecord = {
|
|
runId: "run-2617", caseId: "case-2617", workflowId: "harnessrl-caserun-run-2617", workflowRunId: "workflow-run-2617",
|
|
status: "queued", stage: "queued", terminal: false, caseRepo: "/cases", runDir: "/state/run-2617", runtimeApiUrl: "local://harnessrl",
|
|
result: null, error: null, blocker: null, createdAt: "2026-07-17T00:00:00.000Z", updatedAt: "2026-07-17T00:00:00.000Z"
|
|
};
|
|
|
|
test("L0 local adapter executes the shared CaseRun command contract without HTTP", async () => {
|
|
const calls: string[] = [];
|
|
const transport = createLocalCaseRunTransport({ service: {
|
|
async submit() { calls.push("start"); return record; },
|
|
async getRun() { calls.push("status"); return record; },
|
|
async events() { calls.push("events"); return { ...record, events: [] }; },
|
|
async aggregate() { calls.push("aggregate"); return { ok: true, runId: record.runId, status: record.status }; },
|
|
async cancel() { calls.push("cancel"); return { ...record, status: "cancel_requested", stage: "cancel_requested" }; }
|
|
}});
|
|
|
|
const started = await transport.execute({ kind: "start", caseId: record.caseId, runId: record.runId });
|
|
assert.equal(started.runId, record.runId);
|
|
assert.equal(started.workflowId, record.workflowId);
|
|
await transport.execute({ kind: "status", runId: record.runId });
|
|
await transport.execute({ kind: "events", runId: record.runId });
|
|
await transport.execute({ kind: "aggregate", runId: record.runId });
|
|
await transport.execute({ kind: "cancel", runId: record.runId });
|
|
assert.deepEqual(calls, ["start", "status", "events", "aggregate", "cancel"]);
|
|
});
|
|
|
|
test("CLI defaults to local and requires explicit over-api for base-url", async () => {
|
|
const local = createLocalCaseRunTransport({ service: {
|
|
async submit() { return record; }, async getRun() { return record; }, async events() { return { ...record, events: [] }; },
|
|
async aggregate() { return { ok: true, runId: record.runId }; }, async cancel() { return record; }
|
|
}});
|
|
const result = await runHwlabCli(["case", "run", "start", record.caseId, "--run-id", record.runId], { caseRunTransportFactory: () => local });
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.transport, "local");
|
|
assert.equal(result.payload.service, "native-harnessrl-application-service");
|
|
assert.equal(result.payload.baseUrl, null);
|
|
assert.equal(result.payload.route, null);
|
|
assert.equal(result.payload.method, null);
|
|
assert.equal(result.payload.workflowRunId, record.workflowRunId);
|
|
|
|
const rejected = await runHwlabCli(["case", "run", "status", record.runId, "--base-url", "http://api.test"]);
|
|
assert.equal(rejected.exitCode, 1);
|
|
assert.equal(rejected.payload.error.code, "caserun_over_api_required");
|
|
});
|
|
|
|
test("API adapter uses only the explicit CaseRun origin and preserves typed errors", async () => {
|
|
const requests: Array<{ url: string; method: string; authorization: string | null }> = [];
|
|
const transport = createApiCaseRunTransport({ baseUrl: "http://api.test", apiKey: "hwl_live_test", fetchImpl: async (input, init) => {
|
|
requests.push({ url: String(input), method: String(init?.method), authorization: new Headers(init?.headers).get("authorization") });
|
|
return Response.json({ ok: true, ...record });
|
|
}});
|
|
const response = await transport.execute({ kind: "events", runId: record.runId });
|
|
assert.equal(response.workflowId, record.workflowId);
|
|
assert.deepEqual(requests, [{ url: `http://api.test/v1/caserun/runs/${record.runId}/events`, method: "GET", authorization: "Bearer hwl_live_test" }]);
|
|
|
|
const failed = createApiCaseRunTransport({ baseUrl: "http://api.test", fetchImpl: async () => Response.json({ ok: false, error: { code: "caserun_run_not_found", message: "missing", details: { runId: record.runId } } }, { status: 404 }) });
|
|
await assert.rejects(failed.execute({ kind: "status", runId: record.runId }), (error: any) => error instanceof CaseRunTransportError && error.code === "caserun_run_not_found");
|
|
});
|
|
|
|
test("API adapter times out through its real AbortSignal path with typed route details", async () => {
|
|
const transport = createApiCaseRunTransport({ baseUrl: "http://api.test", timeoutMs: 5, fetchImpl: async (_input, init) => {
|
|
await new Promise((_resolve, reject) => init?.signal?.addEventListener("abort", () => reject(new DOMException("aborted", "AbortError")), { once: true }));
|
|
throw new Error("unreachable");
|
|
}});
|
|
await assert.rejects(transport.execute({ kind: "status", runId: record.runId }), (error: any) => {
|
|
assert.equal(error instanceof CaseRunTransportError, true);
|
|
assert.equal(error.code, "caserun_api_timeout");
|
|
assert.deepEqual(error.details, { timeoutMs: 5, baseUrl: "http://api.test", route: `/v1/caserun/runs/${record.runId}`, method: "GET" });
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("over-api CLI discloses bounded origin route and method without credentials", async () => {
|
|
const result = await runHwlabCli(["case", "run", "events", record.runId, "--over-api", "--base-url", "http://api.test", "--timeout-ms", "25"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_secret" },
|
|
fetchImpl: async () => Response.json({ ok: true, ...record, events: [] })
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.transport, "api");
|
|
assert.equal(result.payload.baseUrl, "http://api.test");
|
|
assert.equal(result.payload.route, `/v1/caserun/runs/${record.runId}/events`);
|
|
assert.equal(result.payload.method, "GET");
|
|
assert.equal(JSON.stringify(result.payload).includes("hwl_live_secret"), false);
|
|
});
|