mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 08:47:40 +00:00
test: streamline auto-reply and tts suites
This commit is contained in:
@@ -165,26 +165,21 @@ describe("handleCommands gating", () => {
|
||||
expect(result.reply?.text).toContain("elevated is not available");
|
||||
});
|
||||
|
||||
it("blocks /config when disabled", async () => {
|
||||
it("blocks /config and /debug when disabled", async () => {
|
||||
const cfg = {
|
||||
commands: { config: false, debug: false, text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/config show", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("/config is disabled");
|
||||
});
|
||||
|
||||
it("blocks /debug when disabled", async () => {
|
||||
const cfg = {
|
||||
commands: { config: false, debug: false, text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/debug show", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("/debug is disabled");
|
||||
const cases = [
|
||||
{ commandBody: "/config show", expectedText: "/config is disabled" },
|
||||
{ commandBody: "/debug show", expectedText: "/debug is disabled" },
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
const params = buildParams(testCase.commandBody, cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain(testCase.expectedText);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not enable gated commands from inherited command flags", async () => {
|
||||
@@ -266,50 +261,29 @@ describe("/approve command", () => {
|
||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows gateway clients with approvals scope", async () => {
|
||||
it("allows gateway clients with approvals or admin scopes", async () => {
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/approve abc allow-once", cfg, {
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
GatewayClientScopes: ["operator.approvals"],
|
||||
});
|
||||
const scopeCases = [["operator.approvals"], ["operator.admin"]];
|
||||
for (const scopes of scopeCases) {
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
const params = buildParams("/approve abc allow-once", cfg, {
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
GatewayClientScopes: scopes,
|
||||
});
|
||||
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Exec approval allow-once submitted");
|
||||
expect(callGatewayMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: "exec.approval.resolve",
|
||||
params: { id: "abc", decision: "allow-once" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("allows gateway clients with admin scope", async () => {
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/approve abc allow-once", cfg, {
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
GatewayClientScopes: ["operator.admin"],
|
||||
});
|
||||
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Exec approval allow-once submitted");
|
||||
expect(callGatewayMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: "exec.approval.resolve",
|
||||
params: { id: "abc", decision: "allow-once" },
|
||||
}),
|
||||
);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Exec approval allow-once submitted");
|
||||
expect(callGatewayMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
method: "exec.approval.resolve",
|
||||
params: { id: "abc", decision: "allow-once" },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -420,67 +394,76 @@ describe("buildCommandsPaginationKeyboard", () => {
|
||||
});
|
||||
|
||||
describe("parseConfigCommand", () => {
|
||||
it("parses show/unset", () => {
|
||||
expect(parseConfigCommand("/config")).toEqual({ action: "show" });
|
||||
expect(parseConfigCommand("/config show")).toEqual({
|
||||
action: "show",
|
||||
path: undefined,
|
||||
});
|
||||
expect(parseConfigCommand("/config show foo.bar")).toEqual({
|
||||
action: "show",
|
||||
path: "foo.bar",
|
||||
});
|
||||
expect(parseConfigCommand("/config get foo.bar")).toEqual({
|
||||
action: "show",
|
||||
path: "foo.bar",
|
||||
});
|
||||
expect(parseConfigCommand("/config unset foo.bar")).toEqual({
|
||||
action: "unset",
|
||||
path: "foo.bar",
|
||||
});
|
||||
});
|
||||
it("parses config/debug command actions and JSON payloads", () => {
|
||||
const cases: Array<{
|
||||
parse: (input: string) => unknown;
|
||||
input: string;
|
||||
expected: unknown;
|
||||
}> = [
|
||||
{ parse: parseConfigCommand, input: "/config", expected: { action: "show" } },
|
||||
{
|
||||
parse: parseConfigCommand,
|
||||
input: "/config show",
|
||||
expected: { action: "show", path: undefined },
|
||||
},
|
||||
{
|
||||
parse: parseConfigCommand,
|
||||
input: "/config show foo.bar",
|
||||
expected: { action: "show", path: "foo.bar" },
|
||||
},
|
||||
{
|
||||
parse: parseConfigCommand,
|
||||
input: "/config get foo.bar",
|
||||
expected: { action: "show", path: "foo.bar" },
|
||||
},
|
||||
{
|
||||
parse: parseConfigCommand,
|
||||
input: "/config unset foo.bar",
|
||||
expected: { action: "unset", path: "foo.bar" },
|
||||
},
|
||||
{
|
||||
parse: parseConfigCommand,
|
||||
input: '/config set foo={"a":1}',
|
||||
expected: { action: "set", path: "foo", value: { a: 1 } },
|
||||
},
|
||||
{ parse: parseDebugCommand, input: "/debug", expected: { action: "show" } },
|
||||
{ parse: parseDebugCommand, input: "/debug show", expected: { action: "show" } },
|
||||
{ parse: parseDebugCommand, input: "/debug reset", expected: { action: "reset" } },
|
||||
{
|
||||
parse: parseDebugCommand,
|
||||
input: "/debug unset foo.bar",
|
||||
expected: { action: "unset", path: "foo.bar" },
|
||||
},
|
||||
{
|
||||
parse: parseDebugCommand,
|
||||
input: '/debug set foo={"a":1}',
|
||||
expected: { action: "set", path: "foo", value: { a: 1 } },
|
||||
},
|
||||
];
|
||||
|
||||
it("parses set with JSON", () => {
|
||||
const cmd = parseConfigCommand('/config set foo={"a":1}');
|
||||
expect(cmd).toEqual({ action: "set", path: "foo", value: { a: 1 } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseDebugCommand", () => {
|
||||
it("parses show/reset", () => {
|
||||
expect(parseDebugCommand("/debug")).toEqual({ action: "show" });
|
||||
expect(parseDebugCommand("/debug show")).toEqual({ action: "show" });
|
||||
expect(parseDebugCommand("/debug reset")).toEqual({ action: "reset" });
|
||||
});
|
||||
|
||||
it("parses set with JSON", () => {
|
||||
const cmd = parseDebugCommand('/debug set foo={"a":1}');
|
||||
expect(cmd).toEqual({ action: "set", path: "foo", value: { a: 1 } });
|
||||
});
|
||||
|
||||
it("parses unset", () => {
|
||||
const cmd = parseDebugCommand("/debug unset foo.bar");
|
||||
expect(cmd).toEqual({ action: "unset", path: "foo.bar" });
|
||||
for (const testCase of cases) {
|
||||
expect(testCase.parse(testCase.input)).toEqual(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractMessageText", () => {
|
||||
it("preserves user text that looks like tool call markers", () => {
|
||||
const message = {
|
||||
role: "user",
|
||||
content: "Here [Tool Call: foo (ID: 1)] ok",
|
||||
};
|
||||
const result = extractMessageText(message);
|
||||
expect(result?.text).toContain("[Tool Call: foo (ID: 1)]");
|
||||
});
|
||||
it("preserves user markers and sanitizes assistant markers", () => {
|
||||
const cases = [
|
||||
{
|
||||
message: { role: "user", content: "Here [Tool Call: foo (ID: 1)] ok" },
|
||||
expectedText: "Here [Tool Call: foo (ID: 1)] ok",
|
||||
},
|
||||
{
|
||||
message: { role: "assistant", content: "Here [Tool Call: foo (ID: 1)] ok" },
|
||||
expectedText: "Here ok",
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("sanitizes assistant tool call markers", () => {
|
||||
const message = {
|
||||
role: "assistant",
|
||||
content: "Here [Tool Call: foo (ID: 1)] ok",
|
||||
};
|
||||
const result = extractMessageText(message);
|
||||
expect(result?.text).toBe("Here ok");
|
||||
for (const testCase of cases) {
|
||||
const result = extractMessageText(testCase.message);
|
||||
expect(result?.text).toBe(testCase.expectedText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -498,28 +481,18 @@ describe("handleCommands /config configWrites gating", () => {
|
||||
});
|
||||
|
||||
describe("handleCommands bash alias", () => {
|
||||
it("routes !poll through the /bash handler", async () => {
|
||||
resetBashChatCommandForTests();
|
||||
it("routes !poll and !stop through the /bash handler", async () => {
|
||||
const cfg = {
|
||||
commands: { bash: true, text: true },
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("!poll", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("No active bash job");
|
||||
});
|
||||
|
||||
it("routes !stop through the /bash handler", async () => {
|
||||
resetBashChatCommandForTests();
|
||||
const cfg = {
|
||||
commands: { bash: true, text: true },
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("!stop", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("No active bash job");
|
||||
for (const aliasCommand of ["!poll", "!stop"]) {
|
||||
resetBashChatCommandForTests();
|
||||
const params = buildParams(aliasCommand, cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("No active bash job");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -623,90 +596,66 @@ describe("handleCommands /allowlist", () => {
|
||||
expect(result.reply?.text).toContain("DM allowlist added");
|
||||
});
|
||||
|
||||
it("removes Slack DM allowlist entries from canonical allowFrom and deletes legacy dm.allowFrom", async () => {
|
||||
readConfigFileSnapshotMock.mockResolvedValueOnce({
|
||||
valid: true,
|
||||
parsed: {
|
||||
channels: {
|
||||
slack: {
|
||||
allowFrom: ["U111", "U222"],
|
||||
dm: { allowFrom: ["U111", "U222"] },
|
||||
configWrites: true,
|
||||
},
|
||||
},
|
||||
it("removes DM allowlist entries from canonical allowFrom and deletes legacy dm.allowFrom", async () => {
|
||||
const cases = [
|
||||
{
|
||||
provider: "slack",
|
||||
removeId: "U111",
|
||||
initialAllowFrom: ["U111", "U222"],
|
||||
expectedAllowFrom: ["U222"],
|
||||
},
|
||||
});
|
||||
{
|
||||
provider: "discord",
|
||||
removeId: "111",
|
||||
initialAllowFrom: ["111", "222"],
|
||||
expectedAllowFrom: ["222"],
|
||||
},
|
||||
] as const;
|
||||
validateConfigObjectWithPluginsMock.mockImplementation((config: unknown) => ({
|
||||
ok: true,
|
||||
config,
|
||||
}));
|
||||
|
||||
const cfg = {
|
||||
commands: { text: true, config: true },
|
||||
channels: {
|
||||
slack: {
|
||||
allowFrom: ["U111", "U222"],
|
||||
dm: { allowFrom: ["U111", "U222"] },
|
||||
configWrites: true,
|
||||
for (const testCase of cases) {
|
||||
const previousWriteCount = writeConfigFileMock.mock.calls.length;
|
||||
readConfigFileSnapshotMock.mockResolvedValueOnce({
|
||||
valid: true,
|
||||
parsed: {
|
||||
channels: {
|
||||
[testCase.provider]: {
|
||||
allowFrom: testCase.initialAllowFrom,
|
||||
dm: { allowFrom: testCase.initialAllowFrom },
|
||||
configWrites: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
});
|
||||
|
||||
const params = buildPolicyParams("/allowlist remove dm U111", cfg, {
|
||||
Provider: "slack",
|
||||
Surface: "slack",
|
||||
});
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(writeConfigFileMock).toHaveBeenCalledTimes(1);
|
||||
const written = writeConfigFileMock.mock.calls[0]?.[0] as OpenClawConfig;
|
||||
expect(written.channels?.slack?.allowFrom).toEqual(["U222"]);
|
||||
expect(written.channels?.slack?.dm?.allowFrom).toBeUndefined();
|
||||
expect(result.reply?.text).toContain("channels.slack.allowFrom");
|
||||
});
|
||||
|
||||
it("removes Discord DM allowlist entries from canonical allowFrom and deletes legacy dm.allowFrom", async () => {
|
||||
readConfigFileSnapshotMock.mockResolvedValueOnce({
|
||||
valid: true,
|
||||
parsed: {
|
||||
const cfg = {
|
||||
commands: { text: true, config: true },
|
||||
channels: {
|
||||
discord: {
|
||||
allowFrom: ["111", "222"],
|
||||
dm: { allowFrom: ["111", "222"] },
|
||||
[testCase.provider]: {
|
||||
allowFrom: testCase.initialAllowFrom,
|
||||
dm: { allowFrom: testCase.initialAllowFrom },
|
||||
configWrites: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
validateConfigObjectWithPluginsMock.mockImplementation((config: unknown) => ({
|
||||
ok: true,
|
||||
config,
|
||||
}));
|
||||
} as OpenClawConfig;
|
||||
|
||||
const cfg = {
|
||||
commands: { text: true, config: true },
|
||||
channels: {
|
||||
discord: {
|
||||
allowFrom: ["111", "222"],
|
||||
dm: { allowFrom: ["111", "222"] },
|
||||
configWrites: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const params = buildPolicyParams(`/allowlist remove dm ${testCase.removeId}`, cfg, {
|
||||
Provider: testCase.provider,
|
||||
Surface: testCase.provider,
|
||||
});
|
||||
const result = await handleCommands(params);
|
||||
|
||||
const params = buildPolicyParams("/allowlist remove dm 111", cfg, {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
});
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(writeConfigFileMock).toHaveBeenCalledTimes(1);
|
||||
const written = writeConfigFileMock.mock.calls[0]?.[0] as OpenClawConfig;
|
||||
expect(written.channels?.discord?.allowFrom).toEqual(["222"]);
|
||||
expect(written.channels?.discord?.dm?.allowFrom).toBeUndefined();
|
||||
expect(result.reply?.text).toContain("channels.discord.allowFrom");
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(writeConfigFileMock.mock.calls.length).toBe(previousWriteCount + 1);
|
||||
const written = writeConfigFileMock.mock.calls.at(-1)?.[0] as OpenClawConfig;
|
||||
const channelConfig = written.channels?.[testCase.provider];
|
||||
expect(channelConfig?.allowFrom).toEqual(testCase.expectedAllowFrom);
|
||||
expect(channelConfig?.dm?.allowFrom).toBeUndefined();
|
||||
expect(result.reply?.text).toContain(`channels.${testCase.provider}.allowFrom`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -736,44 +685,56 @@ describe("/models command", () => {
|
||||
expect(buttons?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("lists provider models with pagination hints", async () => {
|
||||
// Use discord surface for text-based output tests
|
||||
const params = buildPolicyParams("/models anthropic", cfg, { Surface: "discord" });
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Models (anthropic");
|
||||
expect(result.reply?.text).toContain("page 1/");
|
||||
expect(result.reply?.text).toContain("anthropic/claude-opus-4-5");
|
||||
expect(result.reply?.text).toContain("Switch: /model <provider/model>");
|
||||
expect(result.reply?.text).toContain("All: /models anthropic all");
|
||||
});
|
||||
it("handles provider model pagination, all mode, and unknown providers", async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "lists provider models with pagination hints",
|
||||
command: "/models anthropic",
|
||||
includes: [
|
||||
"Models (anthropic",
|
||||
"page 1/",
|
||||
"anthropic/claude-opus-4-5",
|
||||
"Switch: /model <provider/model>",
|
||||
"All: /models anthropic all",
|
||||
],
|
||||
excludes: [],
|
||||
},
|
||||
{
|
||||
name: "ignores page argument when all flag is present",
|
||||
command: "/models anthropic 3 all",
|
||||
includes: ["Models (anthropic", "page 1/1", "anthropic/claude-opus-4-5"],
|
||||
excludes: ["Page out of range"],
|
||||
},
|
||||
{
|
||||
name: "errors on out-of-range pages",
|
||||
command: "/models anthropic 4",
|
||||
includes: ["Page out of range", "valid: 1-"],
|
||||
excludes: [],
|
||||
},
|
||||
{
|
||||
name: "handles unknown providers",
|
||||
command: "/models not-a-provider",
|
||||
includes: ["Unknown provider", "Available providers"],
|
||||
excludes: [],
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("ignores page argument when all flag is present", async () => {
|
||||
// Use discord surface for text-based output tests
|
||||
const params = buildPolicyParams("/models anthropic 3 all", cfg, { Surface: "discord" });
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Models (anthropic");
|
||||
expect(result.reply?.text).toContain("page 1/1");
|
||||
expect(result.reply?.text).toContain("anthropic/claude-opus-4-5");
|
||||
expect(result.reply?.text).not.toContain("Page out of range");
|
||||
});
|
||||
|
||||
it("errors on out-of-range pages", async () => {
|
||||
// Use discord surface for text-based output tests
|
||||
const params = buildPolicyParams("/models anthropic 4", cfg, { Surface: "discord" });
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Page out of range");
|
||||
expect(result.reply?.text).toContain("valid: 1-");
|
||||
});
|
||||
|
||||
it("handles unknown providers", async () => {
|
||||
const params = buildPolicyParams("/models not-a-provider", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Unknown provider");
|
||||
expect(result.reply?.text).toContain("Available providers");
|
||||
for (const testCase of cases) {
|
||||
// Use discord surface for deterministic text-based output assertions.
|
||||
const result = await handleCommands(
|
||||
buildPolicyParams(testCase.command, cfg, {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
}),
|
||||
);
|
||||
expect(result.shouldContinue, testCase.name).toBe(false);
|
||||
for (const expected of testCase.includes) {
|
||||
expect(result.reply?.text, `${testCase.name}: ${expected}`).toContain(expected);
|
||||
}
|
||||
for (const blocked of testCase.excludes ?? []) {
|
||||
expect(result.reply?.text, `${testCase.name}: !${blocked}`).not.toContain(blocked);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("lists configured models outside the curated catalog", async () => {
|
||||
@@ -867,40 +828,33 @@ describe("handleCommands hooks", () => {
|
||||
});
|
||||
|
||||
describe("handleCommands context", () => {
|
||||
it("returns context help for /context", async () => {
|
||||
it("returns expected details for /context commands", async () => {
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/context", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("/context list");
|
||||
expect(result.reply?.text).toContain("Inline shortcut");
|
||||
});
|
||||
|
||||
it("returns a per-file breakdown for /context list", async () => {
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/context list", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Injected workspace files:");
|
||||
expect(result.reply?.text).toContain("AGENTS.md");
|
||||
});
|
||||
|
||||
it("returns a detailed breakdown for /context detail", async () => {
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/context detail", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("Context breakdown (detailed)");
|
||||
expect(result.reply?.text).toContain("Top tools (schema size):");
|
||||
const cases = [
|
||||
{
|
||||
commandBody: "/context",
|
||||
expectedText: ["/context list", "Inline shortcut"],
|
||||
},
|
||||
{
|
||||
commandBody: "/context list",
|
||||
expectedText: ["Injected workspace files:", "AGENTS.md"],
|
||||
},
|
||||
{
|
||||
commandBody: "/context detail",
|
||||
expectedText: ["Context breakdown (detailed)", "Top tools (schema size):"],
|
||||
},
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
const params = buildParams(testCase.commandBody, cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
for (const expectedText of testCase.expectedText) {
|
||||
expect(result.reply?.text).toContain(expectedText);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1039,30 +993,23 @@ describe("handleCommands subagents", () => {
|
||||
expect(result.reply?.text).not.toContain("Subagents:");
|
||||
});
|
||||
|
||||
it("returns help for unknown subagents action", async () => {
|
||||
it("returns help/usage for invalid or incomplete subagents commands", async () => {
|
||||
resetSubagentRegistryForTests();
|
||||
callGatewayMock.mockReset();
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/subagents foo", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("/subagents");
|
||||
});
|
||||
|
||||
it("returns usage for subagents info without target", async () => {
|
||||
resetSubagentRegistryForTests();
|
||||
callGatewayMock.mockReset();
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/subagents info", cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("/subagents info");
|
||||
const cases = [
|
||||
{ commandBody: "/subagents foo", expectedText: "/subagents" },
|
||||
{ commandBody: "/subagents info", expectedText: "/subagents info" },
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
const params = buildParams(testCase.commandBody, cfg);
|
||||
const result = await handleCommands(params);
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain(testCase.expectedText);
|
||||
}
|
||||
});
|
||||
|
||||
it("includes subagent count in /status when active", async () => {
|
||||
|
||||
Reference in New Issue
Block a user