Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions test/e2e/live/device-auth-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
}),
Expand Down
62 changes: 56 additions & 6 deletions test/e2e/live/openclaw-inference-switch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,66 @@ interface MockAnthropicProvider {
close(): Promise<void>;
}

function expectMockBaselineAuthentication(
function proveMockBaselineAuthentication(
baseline: Pick<FakeOpenAiCompatibleServer, "requests"> | undefined,
): void {
sandbox: SandboxClient,
home: string,
artifacts: { writeJson(path: string, value: unknown): Promise<string> },
): Promise<void> {
return baseline
? proveSelectedMockBaselineAuthentication(baseline, sandbox, home, artifacts)
: Promise.resolve(expect(baseline).toBeUndefined());
}

async function proveSelectedMockBaselineAuthentication(
baseline: Pick<FakeOpenAiCompatibleServer, "requests">,
sandbox: SandboxClient,
home: string,
artifacts: { writeJson(path: string, value: unknown): Promise<string> },
): Promise<void> {
// 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 {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
Loading