refactor(test): reuse withGatewayServer in auth/http suites

This commit is contained in:
Peter Steinberger
2026-02-15 23:06:34 +00:00
parent 99909f7bc7
commit 8ba16a894f
2 changed files with 140 additions and 129 deletions

View File

@@ -2,7 +2,13 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { HISTORY_CONTEXT_MARKER } from "../auto-reply/reply/history.js";
import { CURRENT_MESSAGE_MARKER } from "../auto-reply/reply/mentions.js";
import { emitAgentEvent } from "../infra/agent-events.js";
import { agentCommand, getFreePort, installGatewayTestHooks, testState } from "./test-helpers.js";
import {
agentCommand,
getFreePort,
installGatewayTestHooks,
testState,
withGatewayServer,
} from "./test-helpers.js";
installGatewayTestHooks({ scope: "suite" });
@@ -345,46 +351,46 @@ describe("OpenAI-compatible HTTP API (e2e)", () => {
});
it("returns 429 for repeated failed auth when gateway.auth.rateLimit is configured", async () => {
const { startGatewayServer } = await import("./server.js");
testState.gatewayAuth = {
mode: "token",
token: "secret",
rateLimit: { maxAttempts: 1, windowMs: 60_000, lockoutMs: 60_000, exemptLoopback: false },
// oxlint-disable-next-line typescript/no-explicit-any
} as any;
const port = await getFreePort();
const server = await startGatewayServer(port, {
host: "127.0.0.1",
controlUiEnabled: false,
openAiChatCompletionsEnabled: true,
});
try {
const headers = {
"content-type": "application/json",
authorization: "Bearer wrong",
};
const body = {
model: "openclaw",
messages: [{ role: "user", content: "hi" }],
};
await withGatewayServer(
async ({ port }) => {
const headers = {
"content-type": "application/json",
authorization: "Bearer wrong",
};
const body = {
model: "openclaw",
messages: [{ role: "user", content: "hi" }],
};
const first = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
expect(first.status).toBe(401);
const first = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
expect(first.status).toBe(401);
const second = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
expect(second.status).toBe(429);
expect(second.headers.get("retry-after")).toBeTruthy();
} finally {
await server.close({ reason: "rate-limit auth test done" });
}
const second = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
expect(second.status).toBe(429);
expect(second.headers.get("retry-after")).toBeTruthy();
},
{
serverOptions: {
host: "127.0.0.1",
controlUiEnabled: false,
openAiChatCompletionsEnabled: true,
},
},
);
});
it("streams SSE chunks when stream=true", async () => {

View File

@@ -14,6 +14,7 @@ import {
startServerWithClient,
testTailscaleWhois,
testState,
withGatewayServer,
} from "./test-helpers.js";
installGatewayTestHooks({ scope: "suite" });
@@ -596,62 +597,64 @@ describe("gateway server auth/connect", () => {
} as any);
const prevToken = process.env.OPENCLAW_GATEWAY_TOKEN;
process.env.OPENCLAW_GATEWAY_TOKEN = "secret";
const port = await getFreePort();
const server = await startGatewayServer(port);
const ws = new WebSocket(`ws://127.0.0.1:${port}`, {
headers: {
origin: "https://localhost",
"x-forwarded-for": "203.0.113.10",
},
});
const challengePromise = onceMessage<{ payload?: unknown }>(
ws,
(o) => o.type === "event" && o.event === "connect.challenge",
);
await new Promise<void>((resolve) => ws.once("open", resolve));
const challenge = await challengePromise;
const nonce = (challenge.payload as { nonce?: unknown } | undefined)?.nonce;
expect(typeof nonce).toBe("string");
const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload } =
await import("../infra/device-identity.js");
const identity = loadOrCreateDeviceIdentity();
const scopes = ["operator.admin", "operator.approvals", "operator.pairing"];
const signedAtMs = Date.now();
const payload = buildDeviceAuthPayload({
deviceId: identity.deviceId,
clientId: GATEWAY_CLIENT_NAMES.CONTROL_UI,
clientMode: GATEWAY_CLIENT_MODES.WEBCHAT,
role: "operator",
scopes,
signedAtMs,
token: "secret",
nonce: String(nonce),
});
const device = {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
signature: signDevicePayload(identity.privateKeyPem, payload),
signedAt: signedAtMs,
nonce: String(nonce),
};
const res = await connectReq(ws, {
token: "secret",
scopes,
device,
client: {
id: GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: "1.0.0",
platform: "web",
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
},
});
expect(res.ok).toBe(true);
ws.close();
await server.close();
if (prevToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
try {
await withGatewayServer(async ({ port }) => {
const ws = new WebSocket(`ws://127.0.0.1:${port}`, {
headers: {
origin: "https://localhost",
"x-forwarded-for": "203.0.113.10",
},
});
const challengePromise = onceMessage<{ payload?: unknown }>(
ws,
(o) => o.type === "event" && o.event === "connect.challenge",
);
await new Promise<void>((resolve) => ws.once("open", resolve));
const challenge = await challengePromise;
const nonce = (challenge.payload as { nonce?: unknown } | undefined)?.nonce;
expect(typeof nonce).toBe("string");
const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload } =
await import("../infra/device-identity.js");
const identity = loadOrCreateDeviceIdentity();
const scopes = ["operator.admin", "operator.approvals", "operator.pairing"];
const signedAtMs = Date.now();
const payload = buildDeviceAuthPayload({
deviceId: identity.deviceId,
clientId: GATEWAY_CLIENT_NAMES.CONTROL_UI,
clientMode: GATEWAY_CLIENT_MODES.WEBCHAT,
role: "operator",
scopes,
signedAtMs,
token: "secret",
nonce: String(nonce),
});
const device = {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
signature: signDevicePayload(identity.privateKeyPem, payload),
signedAt: signedAtMs,
nonce: String(nonce),
};
const res = await connectReq(ws, {
token: "secret",
scopes,
device,
client: {
id: GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: "1.0.0",
platform: "web",
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
},
});
expect(res.ok).toBe(true);
ws.close();
});
} finally {
if (prevToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
}
}
});
@@ -660,46 +663,48 @@ describe("gateway server auth/connect", () => {
testState.gatewayAuth = { mode: "token", token: "secret" };
const prevToken = process.env.OPENCLAW_GATEWAY_TOKEN;
process.env.OPENCLAW_GATEWAY_TOKEN = "secret";
const port = await getFreePort();
const server = await startGatewayServer(port);
const ws = await openWs(port, { origin: originForPort(port) });
const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload } =
await import("../infra/device-identity.js");
const identity = loadOrCreateDeviceIdentity();
const signedAtMs = Date.now() - 60 * 60 * 1000;
const payload = buildDeviceAuthPayload({
deviceId: identity.deviceId,
clientId: GATEWAY_CLIENT_NAMES.CONTROL_UI,
clientMode: GATEWAY_CLIENT_MODES.WEBCHAT,
role: "operator",
scopes: [],
signedAtMs,
token: "secret",
});
const device = {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
signature: signDevicePayload(identity.privateKeyPem, payload),
signedAt: signedAtMs,
};
const res = await connectReq(ws, {
token: "secret",
device,
client: {
id: GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: "1.0.0",
platform: "web",
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
},
});
expect(res.ok).toBe(true);
expect((res.payload as { auth?: unknown } | undefined)?.auth).toBeUndefined();
ws.close();
await server.close();
if (prevToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
try {
await withGatewayServer(async ({ port }) => {
const ws = await openWs(port, { origin: originForPort(port) });
const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload } =
await import("../infra/device-identity.js");
const identity = loadOrCreateDeviceIdentity();
const signedAtMs = Date.now() - 60 * 60 * 1000;
const payload = buildDeviceAuthPayload({
deviceId: identity.deviceId,
clientId: GATEWAY_CLIENT_NAMES.CONTROL_UI,
clientMode: GATEWAY_CLIENT_MODES.WEBCHAT,
role: "operator",
scopes: [],
signedAtMs,
token: "secret",
});
const device = {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
signature: signDevicePayload(identity.privateKeyPem, payload),
signedAt: signedAtMs,
};
const res = await connectReq(ws, {
token: "secret",
device,
client: {
id: GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: "1.0.0",
platform: "web",
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
},
});
expect(res.ok).toBe(true);
expect((res.payload as { auth?: unknown } | undefined)?.auth).toBeUndefined();
ws.close();
});
} finally {
if (prevToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
}
}
});