Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions tests/tools/brainstorm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
import type { ConfigChatType, ThreadStateType } from "../../src/types";
import type { Message } from "telegraf/types";

const mockBuildMessages = jest.fn();
const mockLlmCall = jest.fn();
const mockReadConfig = jest.fn();

jest.unstable_mockModule("../../src/helpers/gpt.ts", () => ({
buildMessages: (...args: unknown[]) => mockBuildMessages(...args),
llmCall: (...args: unknown[]) => mockLlmCall(...args),
}));

jest.unstable_mockModule("../../src/config.ts", () => ({
readConfig: () => mockReadConfig(),
}));

let mod: typeof import("../../src/tools/brainstorm.ts");

beforeEach(async () => {
jest.resetModules();
mockBuildMessages.mockReset();
mockLlmCall.mockReset();
mockReadConfig.mockReset();
mod = await import("../../src/tools/brainstorm.ts");
});

describe("BrainstormClient", () => {
it("calls buildMessages and llmCall with prompts", async () => {
const cfg: ConfigChatType = {
name: "chat",
agent_name: "agent",
completionParams: {},
chatParams: {},
toolParams: {
brainstorm: { promptBefore: "BEFORE", promptAfter: "AFTER" },
},
} as ConfigChatType;

const thread: ThreadStateType = {
id: 1,
msgs: [{ text: "hi" } as Message.TextMessage],
messages: [],
} as ThreadStateType;

mockBuildMessages.mockResolvedValue([{ role: "system" }]);
mockLlmCall.mockResolvedValue({
res: { choices: [{ message: { content: "RES" } }] },
});

const client = new mod.BrainstormClient(cfg, thread);
const res = await client.brainstorm({ systemMessage: "SYS" });

expect(mockBuildMessages).toHaveBeenCalledWith(
"SYS\n\nBEFORE",
thread.messages,
);
expect(mockLlmCall).toHaveBeenCalled();
expect(res.content).toBe("RES\n\nAFTER");
});

it("options_string formats text", () => {
const client = new mod.BrainstormClient(
{} as ConfigChatType,
{ id: 1, msgs: [], messages: [] } as ThreadStateType,
);
expect(client.options_string('{"systemMessage":"p"}')).toBe(
"**Brainstorm:** `p`",
);
expect(client.options_string("{}" as string)).toBe("{}");
});

it("call returns instance", () => {
const client = mod.call(
{} as ConfigChatType,
{ id: 1, msgs: [], messages: [] } as ThreadStateType,
);
expect(client).toBeInstanceOf(mod.BrainstormClient);
});
});
63 changes: 63 additions & 0 deletions tests/tools/forget.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
import type { ConfigChatType, ThreadStateType } from "../../src/types";

const mockForgetHistory = jest.fn();
const mockLog = jest.fn();

jest.unstable_mockModule("../../src/helpers/history.ts", () => ({
forgetHistory: (...args: unknown[]) => mockForgetHistory(...args),
}));

jest.unstable_mockModule("../../src/helpers.ts", () => ({
log: (...args: unknown[]) => mockLog(...args),
}));

let mod: typeof import("../../src/tools/forget.ts");

beforeEach(async () => {
jest.resetModules();
mockForgetHistory.mockReset();
mockLog.mockReset();
mod = await import("../../src/tools/forget.ts");
});

describe("ForgetClient", () => {
const cfg = {} as ConfigChatType;
const thread = { id: 2 } as ThreadStateType;

it("forgets history and logs", async () => {
const client = new mod.ForgetClient(cfg, thread);
const res = await client.forget({});
expect(mockForgetHistory).toHaveBeenCalledWith(2);
expect(mockLog).toHaveBeenCalled();
expect(res.content).toBe("Forgot history");
});

it("uses custom message", async () => {
const client = new mod.ForgetClient(cfg, thread);
const res = await client.forget({ message: "Bye" });
expect(res.content).toBe("Bye");
});

it("handles errors", async () => {
mockForgetHistory.mockImplementation(() => {
throw new Error("boom");
});
const client = new mod.ForgetClient(cfg, thread);
const res = await client.forget({});
expect(res.content).toContain("boom");
expect(mockLog).toHaveBeenCalledWith(
expect.objectContaining({ logLevel: "error" }),
);
});

it("options_string constant", () => {
const client = new mod.ForgetClient(cfg, thread);
expect(client.options_string()).toBe("`Clear conversation history:`");
});

it("call returns instance", () => {
const client = mod.call(cfg, thread);
expect(client).toBeInstanceOf(mod.ForgetClient);
});
});
36 changes: 36 additions & 0 deletions tests/tools/javascript_interpreter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
import type { ToolResponse } from "../../src/types";

let mod: typeof import("../../src/tools/javascript_interpreter.ts");

beforeEach(async () => {
jest.resetModules();
mod = await import("../../src/tools/javascript_interpreter.ts");
});

describe("JavascriptInterpreterClient", () => {
it("executes code and returns result", async () => {
const client = new mod.JavascriptInterpreterClient();
const res = await client.javascript_interpreter({ code: "1+2" });
expect(res).toEqual({ content: "3" } as ToolResponse);
});

it("returns error string on exception", async () => {
const client = new mod.JavascriptInterpreterClient();
const res = await client.javascript_interpreter({
code: "throw new Error('x')",
});
expect(res.content).toContain("Error: Unknown error");
});

it("options_string formats code", () => {
const client = new mod.JavascriptInterpreterClient();
const formatted = client.options_string('{"code":"2+2"}');
expect(formatted).toBe("`Javascript:`\n```js\n2+2\n```");
});

it("call returns instance", () => {
const client = mod.call();
expect(client).toBeInstanceOf(mod.JavascriptInterpreterClient);
});
});
116 changes: 116 additions & 0 deletions tests/tools/ssh_command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
import type { ConfigChatType } from "../../src/types";

const mockExec = jest.fn();
const mockFileSync = jest.fn();
const mockWriteFileSync = jest.fn();

jest.unstable_mockModule("child_process", () => ({
exec: (...args: unknown[]) => mockExec(...args),
}));

jest.unstable_mockModule("tmp", () => ({
fileSync: (...args: unknown[]) => mockFileSync(...args),
}));
jest.unstable_mockModule("fs", () => {
const real = jest.requireActual("fs");
return {
__esModule: true,
...real,
default: {
...real,
writeFileSync: (...args: unknown[]) => mockWriteFileSync(...args),
},
writeFileSync: (...args: unknown[]) => mockWriteFileSync(...args),
};
});
let mod: typeof import("../../src/tools/ssh_command.ts");

beforeEach(async () => {
jest.resetModules();
mockExec.mockReset();
mockFileSync.mockReset();
mockWriteFileSync.mockReset();
mod = await import("../../src/tools/ssh_command.ts");
});

describe("SshCommandClient", () => {
const cfg: ConfigChatType = {
name: "chat",
agent_name: "agent",
completionParams: {},
chatParams: {},
toolParams: {
ssh_command: { user: "u", host: "h", strictHostKeyChecking: true },
},
} as ConfigChatType;

it("runs command via ssh", async () => {
mockFileSync.mockReturnValue({
name: "/tmp/tmp.sh",
removeCallback: jest.fn(),
});
mockExec
.mockImplementationOnce((_cmd: string, cb: (e: any) => void) => cb(null))
.mockImplementationOnce(
(_cmd: string, cb: (e: any, out: string, err: string) => void) =>
cb(null, "ok", ""),
);

const client = new mod.SshCommandClient(cfg);
const res = await client.ssh_command({ command: "ls" });

expect(mockWriteFileSync).toHaveBeenCalledWith("/tmp/tmp.sh", "ls");
expect(mockExec).toHaveBeenCalledTimes(2);
expect(res.content).toBe("```\nok\n```");
});

it("returns exit code when ssh fails", async () => {
const remove = jest.fn();
mockFileSync.mockReturnValue({
name: "/tmp/tmp.sh",
removeCallback: remove,
});
mockExec
.mockImplementationOnce((_c: string, cb: (e: any) => void) => cb(null))
.mockImplementationOnce(
(_c: string, cb: (e: any, out: string, err: string) => void) => {
const err = new Error("Command failed: ssh boom");
(err as any).code = 1;
cb(err, "sout", "serr");
},
);

const client = new mod.SshCommandClient(cfg);
const res = await client.ssh_command({ command: "do" });
expect(res.content).toContain("Exit code: 1");
expect(remove).toHaveBeenCalled();
});

it("getUserHost defaults", () => {
const client = new mod.SshCommandClient({
name: "c",
agent_name: "a",
completionParams: {},
chatParams: {},
toolParams: {},
} as ConfigChatType);
expect(client.getUserHost()).toEqual({
user: "root",
host: "localhost",
strictHostKeyChecking: false,
});
});

it("options_string and systemMessage", () => {
const client = new mod.SshCommandClient(cfg);
const str = client.options_string('{"command":"echo hi"}');
expect(str).toContain("`ssh u@h`");
expect(str).toContain("echo hi");
expect(client.systemMessage()).toContain("u@h");
});

it("call returns instance", () => {
expect(mod.call(cfg)).toBeInstanceOf(mod.SshCommandClient);
});
});