

















@@ -0,0 +1,165 @@
1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, describe, expect, it } from "vitest";
5+import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js";
6+import { clearSessionStoreCacheForTest } from "../config/sessions/store.js";
7+import type { OpenClawConfig } from "../config/types.openclaw.js";
8+import { captureEnv } from "../test-utils/env.js";
9+import { ADMIN_SCOPE, APPROVALS_SCOPE } from "./method-scopes.js";
10+import { withOperatorApprovalsGatewayClient } from "./operator-approvals-client.js";
11+import { startGatewayServer } from "./server.js";
12+import {
13+connectGatewayClient,
14+disconnectGatewayClient,
15+getFreeGatewayPort,
16+} from "./test-helpers.e2e.js";
17+18+const TEST_ENV_KEYS = [
19+"HOME",
20+"OPENCLAW_STATE_DIR",
21+"OPENCLAW_CONFIG_PATH",
22+"OPENCLAW_GATEWAY_URL",
23+"OPENCLAW_GATEWAY_TOKEN",
24+"OPENCLAW_GATEWAY_PASSWORD",
25+"OPENCLAW_GATEWAY_PORT",
26+];
27+28+type Cleanup = () => Promise<void> | void;
29+30+async function requestExecApproval(params: {
31+requester: Awaited<ReturnType<typeof connectGatewayClient>>;
32+id: string;
33+}): Promise<void> {
34+await expect(
35+params.requester.request("exec.approval.request", {
36+id: params.id,
37+command: "printf smoke",
38+cwd: "/tmp",
39+host: "local",
40+ask: "always",
41+twoPhase: true,
42+timeoutMs: 60_000,
43+}),
44+).resolves.toMatchObject({
45+status: "accepted",
46+id: params.id,
47+});
48+}
49+50+describe("operator approval gateway client runtime token source", () => {
51+const cleanup: Cleanup[] = [];
52+53+afterEach(async () => {
54+for (const step of cleanup.splice(0).toReversed()) {
55+await step();
56+}
57+clearRuntimeConfigSnapshot();
58+clearConfigCache();
59+clearSessionStoreCacheForTest();
60+});
61+62+it("uses runtime authority only for generated local gateway URLs", async () => {
63+const envSnapshot = captureEnv(TEST_ENV_KEYS);
64+cleanup.push(() => envSnapshot.restore());
65+delete process.env.OPENCLAW_CONFIG_PATH;
66+delete process.env.OPENCLAW_GATEWAY_URL;
67+delete process.env.OPENCLAW_GATEWAY_TOKEN;
68+delete process.env.OPENCLAW_GATEWAY_PASSWORD;
69+70+const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-approval-client-e2e-"));
71+cleanup.push(() => fs.rm(tempHome, { recursive: true, force: true, maxRetries: 5 }));
72+73+const stateDir = path.join(tempHome, ".openclaw");
74+await fs.mkdir(stateDir, { recursive: true });
75+process.env.HOME = tempHome;
76+process.env.OPENCLAW_STATE_DIR = stateDir;
77+78+const port = await getFreeGatewayPort();
79+const token = "approval-client-e2e-token";
80+const url = `ws://127.0.0.1:${port}`;
81+process.env.OPENCLAW_GATEWAY_PORT = String(port);
82+83+const server = await startGatewayServer(port, {
84+bind: "loopback",
85+auth: { mode: "token", token },
86+controlUiEnabled: false,
87+deferStartupSidecars: true,
88+});
89+cleanup.push(() => server.close());
90+91+const admin = await connectGatewayClient({
92+ url,
93+ token,
94+clientDisplayName: "approval admin",
95+scopes: [ADMIN_SCOPE],
96+timeoutMs: 60_000,
97+});
98+cleanup.push(() => disconnectGatewayClient(admin));
99+100+const requester = await connectGatewayClient({
101+ url,
102+ token,
103+clientDisplayName: "approval requester",
104+scopes: [APPROVALS_SCOPE],
105+timeoutMs: 60_000,
106+});
107+cleanup.push(() => disconnectGatewayClient(requester));
108+109+const localConfig = {
110+gateway: {
111+ port,
112+auth: { mode: "token", token },
113+},
114+} satisfies OpenClawConfig;
115+116+await requestExecApproval({ requester, id: "local-source-approval" });
117+await withOperatorApprovalsGatewayClient(
118+{
119+config: localConfig,
120+clientDisplayName: "local source approval resolver",
121+},
122+async (client) => {
123+await client.request(
124+"exec.approval.resolve",
125+{ id: "local-source-approval", decision: "allow-once" },
126+{ timeoutMs: 10_000 },
127+);
128+},
129+);
130+131+const remoteLoopbackConfig = {
132+gateway: {
133+mode: "remote",
134+remote: { url },
135+auth: { mode: "token", token },
136+},
137+} satisfies OpenClawConfig;
138+139+await requestExecApproval({ requester, id: "remote-loopback-approval" });
140+await expect(
141+withOperatorApprovalsGatewayClient(
142+{
143+config: remoteLoopbackConfig,
144+clientDisplayName: "remote loopback approval resolver",
145+},
146+async (client) => {
147+await client.request(
148+"exec.approval.resolve",
149+{ id: "remote-loopback-approval", decision: "allow-once" },
150+{ timeoutMs: 10_000 },
151+);
152+},
153+),
154+).rejects.toMatchObject({
155+gatewayCode: "INVALID_REQUEST",
156+details: { reason: "APPROVAL_NOT_FOUND" },
157+});
158+159+await admin.request(
160+"exec.approval.resolve",
161+{ id: "remote-loopback-approval", decision: "deny" },
162+{ timeoutMs: 10_000 },
163+);
164+}, 120_000);
165+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。