test(gateway): cover provider status/logout RPCs
This commit is contained in:
@@ -6,6 +6,7 @@ import path from "node:path";
|
|||||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
import { WebSocket } from "ws";
|
import { WebSocket } from "ws";
|
||||||
import { agentCommand } from "../commands/agent.js";
|
import { agentCommand } from "../commands/agent.js";
|
||||||
|
import { readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
|
||||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||||
import { GatewayLockError } from "../infra/gateway-lock.js";
|
import { GatewayLockError } from "../infra/gateway-lock.js";
|
||||||
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
|
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
|
||||||
@@ -129,23 +130,90 @@ vi.mock("../config/sessions.js", async () => {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
vi.mock("../config/config.js", () => ({
|
vi.mock("../config/config.js", () => {
|
||||||
loadConfig: () => ({
|
const resolveConfigPath = () =>
|
||||||
inbound: {
|
path.join(os.homedir(), ".clawdis", "clawdis.json");
|
||||||
allowFrom: testAllowFrom,
|
|
||||||
workspace: path.join(os.tmpdir(), "clawd-gateway-test"),
|
const readConfigFileSnapshot = async () => {
|
||||||
agent: { provider: "anthropic", model: "claude-opus-4-5" },
|
const configPath = resolveConfigPath();
|
||||||
session: { mainKey: "main", store: testSessionStorePath },
|
try {
|
||||||
|
await fs.access(configPath);
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
path: configPath,
|
||||||
|
exists: false,
|
||||||
|
raw: null,
|
||||||
|
parsed: {},
|
||||||
|
valid: true,
|
||||||
|
config: {},
|
||||||
|
issues: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(configPath, "utf-8");
|
||||||
|
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
path: configPath,
|
||||||
|
exists: true,
|
||||||
|
raw,
|
||||||
|
parsed,
|
||||||
|
valid: true,
|
||||||
|
config: parsed,
|
||||||
|
issues: [],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
path: configPath,
|
||||||
|
exists: true,
|
||||||
|
raw: null,
|
||||||
|
parsed: {},
|
||||||
|
valid: false,
|
||||||
|
config: {},
|
||||||
|
issues: [{ path: "", message: `read failed: ${String(err)}` }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeConfigFile = async (cfg: Record<string, unknown>) => {
|
||||||
|
const configPath = resolveConfigPath();
|
||||||
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||||
|
const raw = JSON.stringify(cfg, null, 2).trimEnd().concat("\n");
|
||||||
|
await fs.writeFile(configPath, raw, "utf-8");
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
CONFIG_PATH_CLAWDIS: resolveConfigPath(),
|
||||||
|
loadConfig: () => ({
|
||||||
|
inbound: {
|
||||||
|
allowFrom: testAllowFrom,
|
||||||
|
workspace: path.join(os.tmpdir(), "clawd-gateway-test"),
|
||||||
|
agent: { provider: "anthropic", model: "claude-opus-4-5" },
|
||||||
|
session: { mainKey: "main", store: testSessionStorePath },
|
||||||
|
},
|
||||||
|
gateway: testGatewayBind ? { bind: testGatewayBind } : undefined,
|
||||||
|
cron: (() => {
|
||||||
|
const cron: Record<string, unknown> = {};
|
||||||
|
if (typeof testCronEnabled === "boolean") cron.enabled = testCronEnabled;
|
||||||
|
if (typeof testCronStorePath === "string") cron.store = testCronStorePath;
|
||||||
|
return Object.keys(cron).length > 0 ? cron : undefined;
|
||||||
|
})(),
|
||||||
|
}),
|
||||||
|
parseConfigJson5: (raw: string) => {
|
||||||
|
try {
|
||||||
|
return { ok: true, parsed: JSON.parse(raw) as unknown };
|
||||||
|
} catch (err) {
|
||||||
|
return { ok: false, error: String(err) };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
gateway: testGatewayBind ? { bind: testGatewayBind } : undefined,
|
validateConfigObject: (parsed: unknown) => ({
|
||||||
cron: (() => {
|
ok: true,
|
||||||
const cron: Record<string, unknown> = {};
|
config: parsed as Record<string, unknown>,
|
||||||
if (typeof testCronEnabled === "boolean") cron.enabled = testCronEnabled;
|
issues: [],
|
||||||
if (typeof testCronStorePath === "string") cron.store = testCronStorePath;
|
}),
|
||||||
return Object.keys(cron).length > 0 ? cron : undefined;
|
readConfigFileSnapshot,
|
||||||
})(),
|
writeConfigFile,
|
||||||
}),
|
};
|
||||||
}));
|
});
|
||||||
|
|
||||||
vi.mock("../commands/health.js", () => ({
|
vi.mock("../commands/health.js", () => ({
|
||||||
getHealthSnapshot: vi.fn().mockResolvedValue({ ok: true, stub: true }),
|
getHealthSnapshot: vi.fn().mockResolvedValue({ ok: true, stub: true }),
|
||||||
@@ -1890,6 +1958,81 @@ describe("gateway server", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test("providers.status returns snapshot without probe", async () => {
|
||||||
|
const prevToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
const { server, ws } = await startServerWithClient();
|
||||||
|
await connectOk(ws);
|
||||||
|
|
||||||
|
const res = await rpcReq<{
|
||||||
|
whatsapp?: { linked?: boolean };
|
||||||
|
telegram?: {
|
||||||
|
configured?: boolean;
|
||||||
|
tokenSource?: string;
|
||||||
|
probe?: unknown;
|
||||||
|
lastProbeAt?: unknown;
|
||||||
|
};
|
||||||
|
}>(ws, "providers.status", { probe: false, timeoutMs: 2000 });
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(res.payload?.whatsapp).toBeTruthy();
|
||||||
|
expect(res.payload?.telegram?.configured).toBe(false);
|
||||||
|
expect(res.payload?.telegram?.tokenSource).toBe("none");
|
||||||
|
expect(res.payload?.telegram?.probe).toBeUndefined();
|
||||||
|
expect(res.payload?.telegram?.lastProbeAt).toBeNull();
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
await server.close();
|
||||||
|
if (prevToken === undefined) {
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
} else {
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = prevToken;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("web.logout reports no session when missing", async () => {
|
||||||
|
const { server, ws } = await startServerWithClient();
|
||||||
|
await connectOk(ws);
|
||||||
|
|
||||||
|
const res = await rpcReq<{ cleared?: boolean }>(ws, "web.logout");
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(res.payload?.cleared).toBe(false);
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("telegram.logout clears bot token from config", async () => {
|
||||||
|
const prevToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
await writeConfigFile({
|
||||||
|
telegram: { botToken: "123:abc", requireMention: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { server, ws } = await startServerWithClient();
|
||||||
|
await connectOk(ws);
|
||||||
|
|
||||||
|
const res = await rpcReq<{ cleared?: boolean; envToken?: boolean }>(
|
||||||
|
ws,
|
||||||
|
"telegram.logout",
|
||||||
|
);
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(res.payload?.cleared).toBe(true);
|
||||||
|
expect(res.payload?.envToken).toBe(false);
|
||||||
|
|
||||||
|
const snap = await readConfigFileSnapshot();
|
||||||
|
expect(snap.valid).toBe(true);
|
||||||
|
expect(snap.config?.telegram?.botToken).toBeUndefined();
|
||||||
|
expect(snap.config?.telegram?.requireMention).toBe(false);
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
await server.close();
|
||||||
|
if (prevToken === undefined) {
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
} else {
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = prevToken;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"presence events carry seq + stateVersion",
|
"presence events carry seq + stateVersion",
|
||||||
{ timeout: 8000 },
|
{ timeout: 8000 },
|
||||||
|
|||||||
Reference in New Issue
Block a user