Relates to https://github.com/coder/coder/issues/18779 See also https://github.com/coder/registry/pull/419 By default, we set subdomain = true. Most folks testing this out don't have a wildcard subdomain setup. This switches to path-based behaviour by default and adds a note to the troubleshooting section.
295 lines
7.8 KiB
TypeScript
295 lines
7.8 KiB
TypeScript
import {
|
|
test,
|
|
afterEach,
|
|
describe,
|
|
it,
|
|
setDefaultTimeout,
|
|
beforeAll,
|
|
expect,
|
|
} from "bun:test";
|
|
import { execContainer, readFileContainer, runTerraformInit } from "~test";
|
|
import {
|
|
loadTestFile,
|
|
writeExecutable,
|
|
setup as setupUtil,
|
|
execModuleScript,
|
|
expectAgentAPIStarted,
|
|
} from "../agentapi/test-util";
|
|
import dedent from "dedent";
|
|
|
|
let cleanupFunctions: (() => Promise<void>)[] = [];
|
|
|
|
const registerCleanup = (cleanup: () => Promise<void>) => {
|
|
cleanupFunctions.push(cleanup);
|
|
};
|
|
|
|
// Cleanup logic depends on the fact that bun's built-in test runner
|
|
// runs tests sequentially.
|
|
// https://bun.sh/docs/test/discovery#execution-order
|
|
// Weird things would happen if tried to run tests in parallel.
|
|
// One test could clean up resources that another test was still using.
|
|
afterEach(async () => {
|
|
// reverse the cleanup functions so that they are run in the correct order
|
|
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
|
|
cleanupFunctions = [];
|
|
for (const cleanup of cleanupFnsCopy) {
|
|
try {
|
|
await cleanup();
|
|
} catch (error) {
|
|
console.error("Error during cleanup:", error);
|
|
}
|
|
}
|
|
});
|
|
|
|
interface SetupProps {
|
|
skipAgentAPIMock?: boolean;
|
|
skipGooseMock?: boolean;
|
|
moduleVariables?: Record<string, string>;
|
|
agentapiMockScript?: string;
|
|
}
|
|
|
|
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
|
|
const projectDir = "/home/coder/project";
|
|
const { id } = await setupUtil({
|
|
moduleDir: import.meta.dir,
|
|
moduleVariables: {
|
|
install_goose: props?.skipGooseMock ? "true" : "false",
|
|
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
|
|
goose_provider: "test-provider",
|
|
goose_model: "test-model",
|
|
...props?.moduleVariables,
|
|
},
|
|
registerCleanup,
|
|
projectDir,
|
|
skipAgentAPIMock: props?.skipAgentAPIMock,
|
|
agentapiMockScript: props?.agentapiMockScript,
|
|
});
|
|
if (!props?.skipGooseMock) {
|
|
await writeExecutable({
|
|
containerId: id,
|
|
filePath: "/usr/bin/goose",
|
|
content: await loadTestFile(import.meta.dir, "goose-mock.sh"),
|
|
});
|
|
}
|
|
return { id };
|
|
};
|
|
|
|
// increase the default timeout to 60 seconds
|
|
setDefaultTimeout(60 * 1000);
|
|
|
|
describe("goose", async () => {
|
|
beforeAll(async () => {
|
|
await runTerraformInit(import.meta.dir);
|
|
});
|
|
|
|
test("happy-path", async () => {
|
|
const { id } = await setup();
|
|
|
|
await execModuleScript(id);
|
|
|
|
await expectAgentAPIStarted(id);
|
|
});
|
|
|
|
test("install-version", async () => {
|
|
const { id } = await setup({
|
|
skipGooseMock: true,
|
|
moduleVariables: {
|
|
install_goose: "true",
|
|
goose_version: "v1.0.24",
|
|
},
|
|
});
|
|
|
|
await execModuleScript(id);
|
|
|
|
const resp = await execContainer(id, [
|
|
"bash",
|
|
"-c",
|
|
`"$HOME/.local/bin/goose" --version`,
|
|
]);
|
|
if (resp.exitCode !== 0) {
|
|
console.log(resp.stdout);
|
|
console.log(resp.stderr);
|
|
}
|
|
expect(resp.exitCode).toBe(0);
|
|
expect(resp.stdout).toContain("1.0.24");
|
|
});
|
|
|
|
test("install-stable", async () => {
|
|
const { id } = await setup({
|
|
skipGooseMock: true,
|
|
moduleVariables: {
|
|
install_goose: "true",
|
|
goose_version: "stable",
|
|
},
|
|
});
|
|
|
|
await execModuleScript(id);
|
|
|
|
const resp = await execContainer(id, [
|
|
"bash",
|
|
"-c",
|
|
`"$HOME/.local/bin/goose" --version`,
|
|
]);
|
|
if (resp.exitCode !== 0) {
|
|
console.log(resp.stdout);
|
|
console.log(resp.stderr);
|
|
}
|
|
expect(resp.exitCode).toBe(0);
|
|
await expectAgentAPIStarted(id);
|
|
});
|
|
|
|
test("config", async () => {
|
|
const expected =
|
|
dedent`
|
|
GOOSE_PROVIDER: anthropic
|
|
GOOSE_MODEL: claude-3-5-sonnet-latest
|
|
extensions:
|
|
coder:
|
|
args:
|
|
- exp
|
|
- mcp
|
|
- server
|
|
cmd: coder
|
|
description: Report ALL tasks and statuses (in progress, done, failed) you are working on.
|
|
enabled: true
|
|
envs:
|
|
CODER_MCP_APP_STATUS_SLUG: goose
|
|
CODER_MCP_AI_AGENTAPI_URL: http://localhost:3284
|
|
name: Coder
|
|
timeout: 3000
|
|
type: stdio
|
|
developer:
|
|
display_name: Developer
|
|
enabled: true
|
|
name: developer
|
|
timeout: 300
|
|
type: builtin
|
|
custom-stuff:
|
|
enabled: true
|
|
name: custom-stuff
|
|
timeout: 300
|
|
type: builtin
|
|
`.trim() + "\n";
|
|
|
|
const { id } = await setup({
|
|
moduleVariables: {
|
|
goose_provider: "anthropic",
|
|
goose_model: "claude-3-5-sonnet-latest",
|
|
additional_extensions: dedent`
|
|
custom-stuff:
|
|
enabled: true
|
|
name: custom-stuff
|
|
timeout: 300
|
|
type: builtin
|
|
`.trim(),
|
|
},
|
|
});
|
|
await execModuleScript(id);
|
|
const resp = await readFileContainer(
|
|
id,
|
|
"/home/coder/.config/goose/config.yaml",
|
|
);
|
|
expect(resp).toEqual(expected);
|
|
});
|
|
|
|
test("pre-post-install-scripts", async () => {
|
|
const { id } = await setup({
|
|
moduleVariables: {
|
|
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
|
|
post_install_script: "#!/bin/bash\necho 'post-install-script'",
|
|
},
|
|
});
|
|
|
|
await execModuleScript(id);
|
|
|
|
const preInstallLog = await readFileContainer(
|
|
id,
|
|
"/home/coder/.goose-module/pre_install.log",
|
|
);
|
|
expect(preInstallLog).toContain("pre-install-script");
|
|
|
|
const postInstallLog = await readFileContainer(
|
|
id,
|
|
"/home/coder/.goose-module/post_install.log",
|
|
);
|
|
expect(postInstallLog).toContain("post-install-script");
|
|
});
|
|
|
|
const promptFile = "/home/coder/.goose-module/prompt.txt";
|
|
const agentapiStartLog = "/home/coder/.goose-module/agentapi-start.log";
|
|
|
|
test("start-with-prompt", async () => {
|
|
const { id } = await setup({
|
|
agentapiMockScript: await loadTestFile(
|
|
import.meta.dir,
|
|
"agentapi-mock-print-args.js",
|
|
),
|
|
});
|
|
await execModuleScript(id, {
|
|
GOOSE_TASK_PROMPT: "custom-test-prompt",
|
|
});
|
|
const prompt = await readFileContainer(id, promptFile);
|
|
expect(prompt).toContain("custom-test-prompt");
|
|
|
|
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
|
|
expect(agentapiMockOutput).toContain(
|
|
"'goose run --interactive --instructions /home/coder/.goose-module/prompt.txt '",
|
|
);
|
|
});
|
|
|
|
test("start-without-prompt", async () => {
|
|
const { id } = await setup({
|
|
agentapiMockScript: await loadTestFile(
|
|
import.meta.dir,
|
|
"agentapi-mock-print-args.js",
|
|
),
|
|
});
|
|
await execModuleScript(id);
|
|
|
|
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
|
|
expect(agentapiMockOutput).toContain("'goose '");
|
|
|
|
const prompt = await execContainer(id, ["ls", "-l", promptFile]);
|
|
expect(prompt.exitCode).not.toBe(0);
|
|
expect(prompt.stderr).toContain("No such file or directory");
|
|
});
|
|
|
|
describe("subdomain", async () => {
|
|
it("sets AGENTAPI_CHAT_BASE_PATH when false", async () => {
|
|
const { id } = await setup({
|
|
agentapiMockScript: await loadTestFile(
|
|
import.meta.dir,
|
|
"agentapi-mock-print-args.js",
|
|
),
|
|
moduleVariables: {
|
|
subdomain: "false",
|
|
},
|
|
});
|
|
|
|
await execModuleScript(id);
|
|
|
|
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
|
|
expect(agentapiMockOutput).toContain(
|
|
"AGENTAPI_CHAT_BASE_PATH=/@default/default.foo/apps/goose/chat",
|
|
);
|
|
});
|
|
|
|
it("does not set AGENTAPI_CHAT_BASE_PATH when true", async () => {
|
|
const { id } = await setup({
|
|
agentapiMockScript: await loadTestFile(
|
|
import.meta.dir,
|
|
"agentapi-mock-print-args.js",
|
|
),
|
|
moduleVariables: {
|
|
subdomain: "true",
|
|
},
|
|
});
|
|
|
|
await execModuleScript(id);
|
|
|
|
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
|
|
expect(agentapiMockOutput).toMatch(/AGENTAPI_CHAT_BASE_PATH=$/m);
|
|
});
|
|
});
|
|
});
|