
























@@ -1,18 +1,63 @@
1+import fs from "node:fs";
2+import path from "node:path";
13import { describe, expect, it } from "vitest";
24import { installGatewayTestHooks, testState, withGatewayServer } from "./test-helpers.js";
3546installGatewayTestHooks();
5768const { callGateway } = await import("./call.js");
79const { probeGateway } = await import("./probe.js");
10+const { storeDeviceAuthToken } = await import("../infra/device-auth-store.js");
11+const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem } =
12+await import("../infra/device-identity.js");
13+const { approveDevicePairing, requestDevicePairing } = await import("../infra/device-pairing.js");
14+15+function requireGatewayToken(): string {
16+const token =
17+typeof (testState.gatewayAuth as { token?: unknown } | undefined)?.token === "string"
18+ ? ((testState.gatewayAuth as { token?: string }).token ?? "")
19+ : "";
20+expect(token).toBeTruthy();
21+return token;
22+}
23+24+function statePath(...parts: string[]): string {
25+const stateDir = process.env.OPENCLAW_STATE_DIR;
26+expect(stateDir).toBeTruthy();
27+return path.join(stateDir ?? "", ...parts);
28+}
29+30+async function seedCachedOperatorToken(scopes: string[]): Promise<void> {
31+const identity = loadOrCreateDeviceIdentity();
32+const pairing = await requestDevicePairing({
33+deviceId: identity.deviceId,
34+publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
35+displayName: "vitest probe",
36+platform: process.platform,
37+clientId: "test",
38+clientMode: "probe",
39+role: "operator",
40+ scopes,
41+silent: true,
42+});
43+const approved = await approveDevicePairing(pairing.request.requestId, {
44+callerScopes: scopes,
45+});
46+expect(approved?.status).toBe("approved");
47+const token =
48+approved?.status === "approved" ? (approved.device.tokens?.operator?.token ?? "") : "";
49+expect(token).toBeTruthy();
50+storeDeviceAuthToken({
51+deviceId: identity.deviceId,
52+role: "operator",
53+ token,
54+ scopes,
55+});
56+}
857958describe("probeGateway auth integration", () => {
1059it("keeps direct local authenticated status RPCs device-bound", async () => {
11-const token =
12-typeof (testState.gatewayAuth as { token?: unknown } | undefined)?.token === "string"
13- ? ((testState.gatewayAuth as { token?: string }).token ?? "")
14- : "";
15-expect(token).toBeTruthy();
60+const token = requireGatewayToken();
16611762await withGatewayServer(async ({ port }) => {
1863const status = await callGateway({
@@ -26,12 +71,30 @@ describe("probeGateway auth integration", () => {
2671});
2772});
287329-it("keeps detail RPCs available for local authenticated probes", async () => {
30-const token =
31-typeof (testState.gatewayAuth as { token?: unknown } | undefined)?.token === "string"
32- ? ((testState.gatewayAuth as { token?: string }).token ?? "")
33- : "";
34-expect(token).toBeTruthy();
74+it("keeps first-time local authenticated probes non-mutating", async () => {
75+const token = requireGatewayToken();
76+77+await withGatewayServer(async ({ port }) => {
78+const result = await probeGateway({
79+url: `ws://127.0.0.1:${port}`,
80+auth: { token },
81+timeoutMs: 5_000,
82+});
83+84+expect(result.ok).toBe(false);
85+expect(result.health).toBeNull();
86+expect(result.status).toBeNull();
87+expect(result.configSnapshot).toBeNull();
88+expect(result.auth.capability).toBe("connected_no_operator_scope");
89+expect(fs.existsSync(statePath("devices", "paired.json"))).toBe(false);
90+expect(fs.existsSync(statePath("devices", "pending.json"))).toBe(false);
91+expect(fs.existsSync(statePath("identity", "device-auth.json"))).toBe(false);
92+});
93+});
94+95+it("keeps detail RPCs available for local authenticated probes with cached device auth", async () => {
96+const token = requireGatewayToken();
97+await seedCachedOperatorToken(["operator.read"]);
35983699await withGatewayServer(async ({ port }) => {
37100const result = await probeGateway({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。