Skip to content

Commit 30bde29

Browse files
committed
refactor: share config auth test helpers
1 parent 6b940ed commit 30bde29

1 file changed

Lines changed: 117 additions & 176 deletions

File tree

src/gateway/server-methods/config.shared-auth.test.ts

Lines changed: 117 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,74 @@ const GATEWAY_CONFIG_WRITE_OPTIONS = {
8282
},
8383
};
8484

85+
function tokenAuthConfig(token: string): OpenClawConfig {
86+
return {
87+
gateway: {
88+
auth: {
89+
mode: "token",
90+
token,
91+
},
92+
},
93+
};
94+
}
95+
96+
function trustedProxyConfig(params: {
97+
trustedProxies?: string[];
98+
requiredHeaders?: string[];
99+
allowUsers?: string[];
100+
}): OpenClawConfig {
101+
return {
102+
gateway: {
103+
auth: {
104+
mode: "trusted-proxy",
105+
trustedProxy: {
106+
userHeader: "x-forwarded-user",
107+
...(params.requiredHeaders ? { requiredHeaders: params.requiredHeaders } : {}),
108+
...(params.allowUsers ? { allowUsers: params.allowUsers } : {}),
109+
},
110+
},
111+
...(params.trustedProxies ? { trustedProxies: params.trustedProxies } : {}),
112+
},
113+
};
114+
}
115+
116+
function hotReloadConfig(): OpenClawConfig {
117+
return {
118+
gateway: {
119+
reload: {
120+
mode: "hot",
121+
},
122+
},
123+
};
124+
}
125+
126+
function mockPreviousConfig(config: OpenClawConfig): void {
127+
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(config));
128+
}
129+
130+
async function runConfigPatch(
131+
raw: unknown,
132+
params: { sessionKey?: string; restartDelayMs?: number } = {},
133+
) {
134+
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
135+
method: "config.patch",
136+
params: {
137+
baseHash: "base-hash",
138+
raw: typeof raw === "string" ? raw : JSON.stringify(raw),
139+
restartDelayMs: params.restartDelayMs ?? 1_000,
140+
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
141+
},
142+
});
143+
144+
await configHandlers["config.patch"](options);
145+
await flushConfigHandlerMicrotasks();
146+
return { disconnectClientsUsingSharedGatewayAuth };
147+
}
148+
149+
function expectNoDirectRestart(): void {
150+
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
151+
}
152+
85153
afterEach(() => {
86154
vi.clearAllMocks();
87155
});
@@ -147,23 +215,8 @@ describe("config shared auth disconnects", () => {
147215
});
148216

149217
it("does not disconnect shared-auth clients for config.set auth writes without restart", async () => {
150-
const prevConfig: OpenClawConfig = {
151-
gateway: {
152-
auth: {
153-
mode: "token",
154-
token: "old-token",
155-
},
156-
},
157-
};
158-
const nextConfig: OpenClawConfig = {
159-
gateway: {
160-
auth: {
161-
mode: "token",
162-
token: "new-token",
163-
},
164-
},
165-
};
166-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
218+
const nextConfig = tokenAuthConfig("new-token");
219+
mockPreviousConfig(tokenAuthConfig("old-token"));
167220

168221
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
169222
method: "config.set",
@@ -178,226 +231,114 @@ describe("config shared auth disconnects", () => {
178231

179232
expect(writeConfigFileMock).toHaveBeenCalledWith(nextConfig, GATEWAY_CONFIG_WRITE_OPTIONS);
180233
expect(disconnectClientsUsingSharedGatewayAuth).not.toHaveBeenCalled();
181-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
234+
expectNoDirectRestart();
182235
});
183236

184237
it("lets the config reloader own hybrid-mode auth restarts", async () => {
185-
const prevConfig: OpenClawConfig = {
186-
gateway: {
187-
auth: {
188-
mode: "token",
189-
token: "old-token",
190-
},
191-
},
192-
};
193-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
238+
mockPreviousConfig(tokenAuthConfig("old-token"));
194239

195-
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
196-
method: "config.patch",
197-
params: {
198-
baseHash: "base-hash",
199-
raw: JSON.stringify({ gateway: { auth: { token: "new-token" } } }),
200-
restartDelayMs: 1_000,
201-
},
240+
const { disconnectClientsUsingSharedGatewayAuth } = await runConfigPatch({
241+
gateway: { auth: { token: "new-token" } },
202242
});
203243

204-
await configHandlers["config.patch"](options);
205-
await flushConfigHandlerMicrotasks();
206-
207-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
244+
expectNoDirectRestart();
208245
expect(disconnectClientsUsingSharedGatewayAuth).toHaveBeenCalledTimes(1);
209246
});
210247

211248
it("does not disconnect shared-auth clients when config.patch changes only inactive password auth", async () => {
212-
const prevConfig: OpenClawConfig = {
213-
gateway: {
214-
auth: {
215-
mode: "token",
216-
token: "old-token",
217-
},
218-
},
219-
};
220-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
249+
mockPreviousConfig(tokenAuthConfig("old-token"));
221250

222-
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
223-
method: "config.patch",
224-
params: {
225-
baseHash: "base-hash",
226-
raw: JSON.stringify({ gateway: { auth: { password: "new-password" } } }),
227-
restartDelayMs: 1_000,
228-
},
251+
const { disconnectClientsUsingSharedGatewayAuth } = await runConfigPatch({
252+
gateway: { auth: { password: "new-password" } },
229253
});
230254

231-
await configHandlers["config.patch"](options);
232-
await flushConfigHandlerMicrotasks();
233-
234-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
255+
expectNoDirectRestart();
235256
expect(disconnectClientsUsingSharedGatewayAuth).not.toHaveBeenCalled();
236257
});
237258

238259
it("disconnects gateway-auth clients when active trusted-proxy policy changes", async () => {
239-
const prevConfig: OpenClawConfig = {
260+
mockPreviousConfig(
261+
trustedProxyConfig({
262+
allowUsers: ["[email protected]"],
263+
trustedProxies: ["127.0.0.1"],
264+
}),
265+
);
266+
267+
const { disconnectClientsUsingSharedGatewayAuth } = await runConfigPatch({
240268
gateway: {
241269
auth: {
242-
mode: "trusted-proxy",
243270
trustedProxy: {
244271
userHeader: "x-forwarded-user",
245-
allowUsers: ["alice@example.com"],
272+
allowUsers: ["bob@example.com"],
246273
},
247274
},
248-
trustedProxies: ["127.0.0.1"],
249-
},
250-
};
251-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
252-
253-
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
254-
method: "config.patch",
255-
params: {
256-
baseHash: "base-hash",
257-
raw: JSON.stringify({
258-
gateway: {
259-
auth: {
260-
trustedProxy: {
261-
userHeader: "x-forwarded-user",
262-
allowUsers: ["[email protected]"],
263-
},
264-
},
265-
},
266-
}),
267-
restartDelayMs: 1_000,
268275
},
269276
});
270277

271-
await configHandlers["config.patch"](options);
272-
await flushConfigHandlerMicrotasks();
273-
274-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
278+
expectNoDirectRestart();
275279
expect(disconnectClientsUsingSharedGatewayAuth).toHaveBeenCalledTimes(1);
276280
});
277281

278282
it("disconnects gateway-auth clients when trusted-proxy source list changes", async () => {
279-
const prevConfig: OpenClawConfig = {
280-
gateway: {
281-
auth: {
282-
mode: "trusted-proxy",
283-
trustedProxy: {
284-
userHeader: "x-forwarded-user",
285-
},
286-
},
283+
mockPreviousConfig(
284+
trustedProxyConfig({
287285
trustedProxies: ["127.0.0.1"],
288-
},
289-
};
290-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
286+
}),
287+
);
291288

292-
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
293-
method: "config.patch",
294-
params: {
295-
baseHash: "base-hash",
296-
raw: JSON.stringify({
297-
gateway: {
298-
trustedProxies: ["10.0.0.10"],
299-
},
300-
}),
301-
restartDelayMs: 1_000,
289+
const { disconnectClientsUsingSharedGatewayAuth } = await runConfigPatch({
290+
gateway: {
291+
trustedProxies: ["10.0.0.10"],
302292
},
303293
});
304294

305-
await configHandlers["config.patch"](options);
306-
await flushConfigHandlerMicrotasks();
307-
308-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
295+
expectNoDirectRestart();
309296
expect(disconnectClientsUsingSharedGatewayAuth).toHaveBeenCalledTimes(1);
310297
});
311298

312299
it("does not disconnect gateway-auth clients when trusted-proxy lists are reordered", async () => {
313-
const prevConfig: OpenClawConfig = {
300+
mockPreviousConfig(
301+
trustedProxyConfig({
302+
requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],
303+
304+
trustedProxies: ["127.0.0.1", "10.0.0.10"],
305+
}),
306+
);
307+
308+
const { disconnectClientsUsingSharedGatewayAuth } = await runConfigPatch({
314309
gateway: {
315310
auth: {
316-
mode: "trusted-proxy",
317311
trustedProxy: {
318312
userHeader: "x-forwarded-user",
319-
requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],
320-
allowUsers: ["alice@example.com", "bob@example.com"],
313+
requiredHeaders: ["x-forwarded-host", "x-forwarded-proto"],
314+
allowUsers: ["bob@example.com", "alice@example.com"],
321315
},
322316
},
323-
trustedProxies: ["127.0.0.1", "10.0.0.10"],
324-
},
325-
};
326-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
327-
328-
const { options, disconnectClientsUsingSharedGatewayAuth } = createConfigHandlerHarness({
329-
method: "config.patch",
330-
params: {
331-
baseHash: "base-hash",
332-
raw: JSON.stringify({
333-
gateway: {
334-
auth: {
335-
trustedProxy: {
336-
userHeader: "x-forwarded-user",
337-
requiredHeaders: ["x-forwarded-host", "x-forwarded-proto"],
338-
339-
},
340-
},
341-
trustedProxies: ["10.0.0.10", "127.0.0.1"],
342-
},
343-
}),
344-
restartDelayMs: 1_000,
317+
trustedProxies: ["10.0.0.10", "127.0.0.1"],
345318
},
346319
});
347320

348-
await configHandlers["config.patch"](options);
349-
await flushConfigHandlerMicrotasks();
350-
351-
expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();
321+
expectNoDirectRestart();
352322
expect(disconnectClientsUsingSharedGatewayAuth).not.toHaveBeenCalled();
353323
});
354324

355325
it("still schedules a direct restart for hot mode when the reloader cannot apply the change", async () => {
356-
const prevConfig: OpenClawConfig = {
357-
gateway: {
358-
reload: {
359-
mode: "hot",
360-
},
361-
},
362-
};
363-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
364-
365-
const { options } = createConfigHandlerHarness({
366-
method: "config.patch",
367-
params: {
368-
baseHash: "base-hash",
369-
raw: JSON.stringify({ gateway: { port: 19001 } }),
370-
restartDelayMs: 1_000,
371-
},
372-
});
326+
mockPreviousConfig(hotReloadConfig());
373327

374-
await configHandlers["config.patch"](options);
375-
await flushConfigHandlerMicrotasks();
328+
await runConfigPatch({ gateway: { port: 19001 } });
376329

377330
expect(scheduleGatewaySigusr1RestartMock).toHaveBeenCalledTimes(1);
378331
});
379332

380333
it("does not add an agent continuation from generic control-plane sessionKey params", async () => {
381-
const prevConfig: OpenClawConfig = {
382-
gateway: {
383-
reload: {
384-
mode: "hot",
385-
},
386-
},
387-
};
388-
readConfigFileSnapshotForWriteMock.mockResolvedValue(createConfigWriteSnapshot(prevConfig));
334+
mockPreviousConfig(hotReloadConfig());
389335

390-
const { options } = createConfigHandlerHarness({
391-
method: "config.patch",
392-
params: {
393-
baseHash: "base-hash",
394-
raw: JSON.stringify({ gateway: { port: 19001 } }),
395-
restartDelayMs: 1_000,
336+
await runConfigPatch(
337+
{ gateway: { port: 19001 } },
338+
{
396339
sessionKey: "agent:main:main",
397340
},
398-
});
399-
400-
await configHandlers["config.patch"](options);
341+
);
401342

402343
const payload = restartSentinelMocks.writeRestartSentinel.mock.calls.at(-1)?.[0];
403344
expect(payload?.sessionKey).toBe("agent:main:main");

0 commit comments

Comments
 (0)