diff --git a/test/e2e/live/device-auth-health.test.ts b/test/e2e/live/device-auth-health.test.ts index ea5d4ece380..2b0871f31fe 100644 --- a/test/e2e/live/device-auth-health.test.ts +++ b/test/e2e/live/device-auth-health.test.ts @@ -9,7 +9,7 @@ */ import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts"; -import { resultText } from "../fixtures/clients/index.ts"; +import { resultText, shellQuote } from "../fixtures/clients/index.ts"; import { trustedSandboxShellScript } from "../fixtures/clients/sandbox.ts"; import { expect, test } from "../fixtures/e2e-test.ts"; import { startFakeOpenAiCompatibleServer } from "../fixtures/fake-openai-compatible.ts"; @@ -74,7 +74,7 @@ test("device auth health probes treat 401 as live instead of offline (#2342)", { dashboardPort: DASHBOARD_PORT, contracts: [ "onboard succeeds with device auth enabled", - "onboard authenticates to the fixture inference endpoint", + "the onboarded inference route authenticates to the fixture endpoint", "/health is reachable from inside the sandbox", "the authenticated dashboard root may return 401 without being treated as offline", "nemoclaw status reports the gateway as live, not Health Offline", @@ -107,9 +107,51 @@ test("device auth health probes treat 401 as live instead of offline (#2342)", { progress.phase("onboard device-auth OpenClaw sandbox"); const install = await installDeviceAuthSandbox(host, inferenceConfig, installLog); expect(install.exitCode, resultText(install)).toBe(0); - expect(inference.requests()).toContainEqual( + + // Ignore incidental onboarding traffic. The fixture appends its ledger row + // before responding, so this awaited POST is the publication barrier for the + // requests sliced from this offset. + const authenticatedRequestOffset = inference.requests().length; + const authenticatedProbe = await sandbox.execShell( + SANDBOX_NAME, + trustedSandboxShellScript( + `curl -fsS --max-time 60 https://inference.local/v1/chat/completions -H 'Content-Type: application/json' --data ${shellQuote( + JSON.stringify({ + model: INFERENCE_MODEL, + messages: [{ role: "user", content: "reply with OK" }], + max_tokens: 8, + }), + )} >/dev/null`, + ), + { + artifactName: "phase-1-explicit-authenticated-inference-post", + env: commandEnv(), + timeoutMs: 90_000, + }, + ); + const authenticatedRequests = inference.requests().slice(authenticatedRequestOffset); + const authenticatedArtifact = "phase-1-explicit-authenticated-inference-requests.json"; + const authenticatedPhase = "onboard device-auth OpenClaw sandbox"; + const authenticatedRequestEvidence = authenticatedRequests + .slice(0, 20) + .map(({ auth, method, model, path }) => ({ auth, method, model, path })); + await artifacts.writeJson(authenticatedArtifact, { + phase: authenticatedPhase, + requestCount: authenticatedRequests.length, + requests: authenticatedRequestEvidence, + truncated: authenticatedRequests.length > authenticatedRequestEvidence.length, + }); + expect( + authenticatedProbe.exitCode, + `${authenticatedPhase}: explicit authenticated inference failed; see ${authenticatedArtifact}`, + ).toBe(0); + expect( + authenticatedRequests, + `${authenticatedPhase}: explicit verification probe did not reach the authenticated fixture; see ${authenticatedArtifact}`, + ).toContainEqual( expect.objectContaining({ auth: "ok", + method: "POST", model: INFERENCE_MODEL, path: "/v1/chat/completions", }), diff --git a/test/e2e/live/openclaw-inference-switch.test.ts b/test/e2e/live/openclaw-inference-switch.test.ts index 66a604468fb..01510128cf1 100644 --- a/test/e2e/live/openclaw-inference-switch.test.ts +++ b/test/e2e/live/openclaw-inference-switch.test.ts @@ -136,17 +136,66 @@ interface MockAnthropicProvider { close(): Promise; } -function expectMockBaselineAuthentication( +function proveMockBaselineAuthentication( baseline: Pick | undefined, -): void { + sandbox: SandboxClient, + home: string, + artifacts: { writeJson(path: string, value: unknown): Promise }, +): Promise { + return baseline + ? proveSelectedMockBaselineAuthentication(baseline, sandbox, home, artifacts) + : Promise.resolve(expect(baseline).toBeUndefined()); +} + +async function proveSelectedMockBaselineAuthentication( + baseline: Pick, + sandbox: SandboxClient, + home: string, + artifacts: { writeJson(path: string, value: unknown): Promise }, +): Promise { + // Ignore incidental onboarding traffic. The fixture appends its ledger row + // before responding, so this awaited POST is the publication barrier for the + // requests sliced from this offset. + const requestOffset = baseline.requests().length; + const payload = { + model: MOCK_BASELINE_MODEL, + messages: [{ role: "user", content: "reply with OK" }], + max_tokens: 8, + }; + const probe = await sandboxShell( + sandbox, + home, + `curl -fsS --max-time 60 https://inference.local/v1/chat/completions -H 'Content-Type: application/json' --data ${shellQuote(JSON.stringify(payload))} >/dev/null`, + { + artifactName: "baseline-explicit-authenticated-inference-post", + timeoutMs: 90_000, + }, + ); + const requests = baseline.requests().slice(requestOffset); + const artifactName = "baseline-explicit-authenticated-inference-requests.json"; + const phase = "install and onboard baseline OpenClaw"; + const requestEvidence = requests + .slice(0, 20) + .map(({ auth, method, model, path }) => ({ auth, method, model, path })); + await artifacts.writeJson(artifactName, { + phase, + requestCount: requests.length, + requests: requestEvidence, + truncated: requests.length > requestEvidence.length, + }); + expect(probe.exitCode, `${phase}: explicit baseline inference failed; see ${artifactName}`).toBe( + 0, + ); const expectedRequest = expect.objectContaining({ auth: "ok", + method: "POST", model: MOCK_BASELINE_MODEL, path: "/v1/chat/completions", }); - baseline - ? expect(baseline.requests()).toContainEqual(expectedRequest) - : expect(baseline).toBeUndefined(); + expect( + requests, + `${phase}: explicit verification probe did not reach the authenticated fixture; see ${artifactName}`, + ).toContainEqual(expectedRequest); } function stripAnsi(value: string): string { @@ -895,6 +944,7 @@ test("openclaw-inference-switch: switches route and preserves live OpenClaw beha contracts: [ "Docker is running and an authenticated compatible baseline endpoint is staged", "install.sh --non-interactive onboards an OpenClaw sandbox", + "when selected, the mock baseline route completes one explicit authenticated fixture request", "nemoclaw inference set switches the running sandbox route", "OpenClaw gateway is supervisor-restarted only when the inference API family changes", "OpenShell route points at the switched provider/model", @@ -1008,7 +1058,7 @@ test("openclaw-inference-switch: switches route and preserves live OpenClaw beha skip("NVIDIA endpoint validation was unavailable/rate-limited during onboarding"); } expect(install.exitCode, installText).toBe(0); - expectMockBaselineAuthentication(baselineProvider); + await proveMockBaselineAuthentication(baselineProvider, sandbox, home, artifacts); progress.phase("prepare the switched provider and endpoint"); const publicProvider = publicApiKey