mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
refactor: dedupe gateway session guards and agent test fixtures
This commit is contained in:
@@ -113,6 +113,18 @@ function captureUpdatedMainEntry() {
|
||||
return () => capturedEntry;
|
||||
}
|
||||
|
||||
function primeMainAgentRun(params?: { sessionId?: string; cfg?: Record<string, unknown> }) {
|
||||
mockMainSessionEntry(
|
||||
{ sessionId: params?.sessionId ?? "existing-session-id" },
|
||||
params?.cfg ?? {},
|
||||
);
|
||||
mocks.updateSessionStore.mockResolvedValue(undefined);
|
||||
mocks.agentCommand.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: { durationMs: 100 },
|
||||
});
|
||||
}
|
||||
|
||||
async function runMainAgent(message: string, idempotencyKey: string) {
|
||||
const respond = vi.fn();
|
||||
await invokeAgent(
|
||||
@@ -210,20 +222,7 @@ describe("gateway agent handler", () => {
|
||||
},
|
||||
};
|
||||
|
||||
mocks.loadSessionEntry.mockReturnValue({
|
||||
cfg: mocks.loadConfigReturn,
|
||||
storePath: "/tmp/sessions.json",
|
||||
entry: {
|
||||
sessionId: "existing-session-id",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
canonicalKey: "agent:main:main",
|
||||
});
|
||||
mocks.updateSessionStore.mockResolvedValue(undefined);
|
||||
mocks.agentCommand.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: { durationMs: 100 },
|
||||
});
|
||||
primeMainAgentRun({ cfg: mocks.loadConfigReturn });
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
@@ -326,20 +325,7 @@ describe("gateway agent handler", () => {
|
||||
},
|
||||
);
|
||||
|
||||
mocks.loadSessionEntry.mockReturnValue({
|
||||
cfg: {},
|
||||
storePath: "/tmp/sessions.json",
|
||||
entry: {
|
||||
sessionId: "reset-session-id",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
canonicalKey: "agent:main:main",
|
||||
});
|
||||
mocks.updateSessionStore.mockResolvedValue(undefined);
|
||||
mocks.agentCommand.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: { durationMs: 100 },
|
||||
});
|
||||
primeMainAgentRun({ sessionId: "reset-session-id" });
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
@@ -359,6 +345,58 @@ describe("gateway agent handler", () => {
|
||||
expect(call?.sessionId).toBe("reset-session-id");
|
||||
});
|
||||
|
||||
it("uses /reset suffix as the post-reset message and still injects timestamp", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-01-29T01:30:00.000Z")); // Wed Jan 28, 8:30 PM EST
|
||||
mocks.agentCommand.mockReset();
|
||||
mocks.loadConfigReturn = {
|
||||
agents: {
|
||||
defaults: {
|
||||
userTimezone: "America/New_York",
|
||||
},
|
||||
},
|
||||
};
|
||||
mocks.sessionsResetHandler.mockImplementation(
|
||||
async (opts: {
|
||||
params: { key: string; reason: string };
|
||||
respond: (ok: boolean, payload?: unknown) => void;
|
||||
}) => {
|
||||
expect(opts.params.key).toBe("agent:main:main");
|
||||
expect(opts.params.reason).toBe("reset");
|
||||
opts.respond(true, {
|
||||
ok: true,
|
||||
key: "agent:main:main",
|
||||
entry: { sessionId: "reset-session-id" },
|
||||
});
|
||||
},
|
||||
);
|
||||
mocks.sessionsResetHandler.mockClear();
|
||||
primeMainAgentRun({
|
||||
sessionId: "reset-session-id",
|
||||
cfg: mocks.loadConfigReturn,
|
||||
});
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
message: "/reset check status",
|
||||
sessionKey: "agent:main:main",
|
||||
idempotencyKey: "test-idem-reset-suffix",
|
||||
},
|
||||
{ reqId: "4b" },
|
||||
);
|
||||
|
||||
await vi.waitFor(() => expect(mocks.agentCommand).toHaveBeenCalled());
|
||||
expect(mocks.sessionsResetHandler).toHaveBeenCalledTimes(1);
|
||||
const call = mocks.agentCommand.mock.calls.at(-1)?.[0] as
|
||||
| { message?: string; sessionId?: string }
|
||||
| undefined;
|
||||
expect(call?.message).toBe("[Wed 2026-01-28 20:30 EST] check status");
|
||||
expect(call?.sessionId).toBe("reset-session-id");
|
||||
|
||||
mocks.loadConfigReturn = {};
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("rejects malformed agent session keys early in agent handler", async () => {
|
||||
mocks.agentCommand.mockClear();
|
||||
const respond = await invokeAgent(
|
||||
|
||||
@@ -156,6 +156,15 @@ async function listAgentFileNames(agentId = "main") {
|
||||
return files.map((file) => file.name);
|
||||
}
|
||||
|
||||
function expectNotFoundResponseAndNoWrite(respond: ReturnType<typeof vi.fn>) {
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({ message: expect.stringContaining("not found") }),
|
||||
);
|
||||
expect(mocks.writeConfigFile).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.fsReadFile.mockImplementation(async () => {
|
||||
throw createEnoentError();
|
||||
@@ -319,12 +328,7 @@ describe("agents.update", () => {
|
||||
});
|
||||
await promise;
|
||||
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({ message: expect.stringContaining("not found") }),
|
||||
);
|
||||
expect(mocks.writeConfigFile).not.toHaveBeenCalled();
|
||||
expectNotFoundResponseAndNoWrite(respond);
|
||||
});
|
||||
|
||||
it("ensures workspace when workspace changes", async () => {
|
||||
@@ -408,12 +412,7 @@ describe("agents.delete", () => {
|
||||
});
|
||||
await promise;
|
||||
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({ message: expect.stringContaining("not found") }),
|
||||
);
|
||||
expect(mocks.writeConfigFile).not.toHaveBeenCalled();
|
||||
expectNotFoundResponseAndNoWrite(respond);
|
||||
});
|
||||
|
||||
it("rejects invalid params (missing agentId)", async () => {
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
} from "../session-utils.js";
|
||||
import { applySessionsPatchToStore } from "../sessions-patch.js";
|
||||
import { resolveSessionKeyFromResolveParams } from "../sessions-resolve.js";
|
||||
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||
import type { GatewayClient, GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||
import { assertValidParams } from "./validation.js";
|
||||
|
||||
function requireSessionKey(key: unknown, respond: RespondFn): string | null {
|
||||
@@ -68,6 +68,26 @@ function resolveGatewaySessionTargetFromKey(key: string) {
|
||||
return { cfg, target, storePath: target.storePath };
|
||||
}
|
||||
|
||||
function rejectWebchatSessionMutation(params: {
|
||||
action: "patch" | "delete";
|
||||
client: GatewayClient | null;
|
||||
isWebchatConnect: (params: GatewayClient["connect"] | null | undefined) => boolean;
|
||||
respond: RespondFn;
|
||||
}): boolean {
|
||||
if (!params.client?.connect || !params.isWebchatConnect(params.client.connect)) {
|
||||
return false;
|
||||
}
|
||||
params.respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`webchat clients cannot ${params.action} sessions; use chat.send for session-scoped updates`,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
function migrateAndPruneSessionStoreKey(params: {
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
key: string;
|
||||
@@ -240,15 +260,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (client?.connect && isWebchatConnect(client.connect)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
"webchat clients cannot patch sessions; use chat.send for session-scoped updates",
|
||||
),
|
||||
);
|
||||
if (rejectWebchatSessionMutation({ action: "patch", client, isWebchatConnect, respond })) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -366,15 +378,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (client?.connect && isWebchatConnect(client.connect)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
"webchat clients cannot delete sessions; use chat.send for session-scoped updates",
|
||||
),
|
||||
);
|
||||
if (rejectWebchatSessionMutation({ action: "delete", client, isWebchatConnect, respond })) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user