Skip to content

Commit fafcabe

Browse files
committed
goose module
1 parent ed79d26 commit fafcabe

File tree

8 files changed

+426
-232
lines changed

8 files changed

+426
-232
lines changed

bun.lockb

391 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"devDependencies": {
1111
"@types/bun": "^1.2.9",
1212
"bun-types": "^1.1.23",
13+
"dedent": "^1.6.0",
1314
"gray-matter": "^4.0.3",
1415
"marked": "^12.0.2",
1516
"prettier": "^3.3.3",
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import {
2+
test,
3+
afterEach,
4+
describe,
5+
setDefaultTimeout,
6+
beforeAll,
7+
expect,
8+
} from "bun:test";
9+
import { execContainer, readFileContainer, runTerraformInit } from "~test";
10+
import {
11+
loadTestFile,
12+
writeExecutable,
13+
setup as setupUtil,
14+
execModuleScript,
15+
expectAgentAPIStarted,
16+
} from "../agentapi/test-util";
17+
import dedent from "dedent";
18+
19+
let cleanupFunctions: (() => Promise<void>)[] = [];
20+
21+
const registerCleanup = (cleanup: () => Promise<void>) => {
22+
cleanupFunctions.push(cleanup);
23+
};
24+
25+
// Cleanup logic depends on the fact that bun's built-in test runner
26+
// runs tests sequentially.
27+
// https://bun.sh/docs/test/discovery#execution-order
28+
// Weird things would happen if tried to run tests in parallel.
29+
// One test could clean up resources that another test was still using.
30+
afterEach(async () => {
31+
// reverse the cleanup functions so that they are run in the correct order
32+
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
33+
cleanupFunctions = [];
34+
for (const cleanup of cleanupFnsCopy) {
35+
try {
36+
await cleanup();
37+
} catch (error) {
38+
console.error("Error during cleanup:", error);
39+
}
40+
}
41+
});
42+
43+
interface SetupProps {
44+
skipAgentAPIMock?: boolean;
45+
skipGooseMock?: boolean;
46+
moduleVariables?: Record<string, string>;
47+
agentapiMockScript?: string;
48+
}
49+
50+
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
51+
const projectDir = "/home/coder/project";
52+
const { id } = await setupUtil({
53+
moduleDir: import.meta.dir,
54+
moduleVariables: {
55+
install_goose: props?.skipGooseMock ? "true" : "false",
56+
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
57+
goose_provider: "test-provider",
58+
goose_model: "test-model",
59+
...props?.moduleVariables,
60+
},
61+
registerCleanup,
62+
projectDir,
63+
skipAgentAPIMock: props?.skipAgentAPIMock,
64+
agentapiMockScript: props?.agentapiMockScript,
65+
});
66+
if (!props?.skipGooseMock) {
67+
await writeExecutable({
68+
containerId: id,
69+
filePath: "/usr/bin/goose",
70+
content: await loadTestFile(import.meta.dir, "goose-mock.sh"),
71+
});
72+
}
73+
return { id };
74+
};
75+
76+
// increase the default timeout to 60 seconds
77+
setDefaultTimeout(60 * 1000);
78+
79+
describe("goose", async () => {
80+
beforeAll(async () => {
81+
await runTerraformInit(import.meta.dir);
82+
});
83+
84+
test("happy-path", async () => {
85+
const { id } = await setup();
86+
87+
await execModuleScript(id);
88+
89+
await expectAgentAPIStarted(id);
90+
});
91+
92+
test("install-version", async () => {
93+
const { id } = await setup({
94+
skipGooseMock: true,
95+
moduleVariables: {
96+
install_goose: "true",
97+
goose_version: "v1.0.24",
98+
},
99+
});
100+
101+
await execModuleScript(id);
102+
103+
const resp = await execContainer(id, [
104+
"bash",
105+
"-c",
106+
`"$HOME/.local/bin/goose" --version`,
107+
]);
108+
if (resp.exitCode !== 0) {
109+
console.log(resp.stdout);
110+
console.log(resp.stderr);
111+
}
112+
expect(resp.exitCode).toBe(0);
113+
expect(resp.stdout).toContain("1.0.24");
114+
});
115+
116+
test("install-stable", async () => {
117+
const { id } = await setup({
118+
skipGooseMock: true,
119+
moduleVariables: {
120+
install_goose: "true",
121+
goose_version: "stable",
122+
},
123+
});
124+
125+
await execModuleScript(id);
126+
127+
const resp = await execContainer(id, [
128+
"bash",
129+
"-c",
130+
`"$HOME/.local/bin/goose" --version`,
131+
]);
132+
if (resp.exitCode !== 0) {
133+
console.log(resp.stdout);
134+
console.log(resp.stderr);
135+
}
136+
expect(resp.exitCode).toBe(0);
137+
});
138+
139+
test("config", async () => {
140+
const expected =
141+
dedent`
142+
GOOSE_PROVIDER: anthropic
143+
GOOSE_MODEL: claude-3-5-sonnet-latest
144+
extensions:
145+
coder:
146+
args:
147+
- exp
148+
- mcp
149+
- server
150+
cmd: coder
151+
description: Report ALL tasks and statuses (in progress, done, failed) you are working on.
152+
enabled: true
153+
envs:
154+
CODER_MCP_APP_STATUS_SLUG: goose
155+
CODER_MCP_AI_AGENTAPI_URL: http://localhost:3284
156+
name: Coder
157+
timeout: 3000
158+
type: stdio
159+
developer:
160+
display_name: Developer
161+
enabled: true
162+
name: developer
163+
timeout: 300
164+
type: builtin
165+
custom-stuff:
166+
enabled: true
167+
name: custom-stuff
168+
timeout: 300
169+
type: builtin
170+
`.trim() + "\n";
171+
172+
const { id } = await setup({
173+
moduleVariables: {
174+
goose_provider: "anthropic",
175+
goose_model: "claude-3-5-sonnet-latest",
176+
additional_extensions: dedent`
177+
custom-stuff:
178+
enabled: true
179+
name: custom-stuff
180+
timeout: 300
181+
type: builtin
182+
`.trim(),
183+
},
184+
});
185+
await execModuleScript(id);
186+
const resp = await readFileContainer(
187+
id,
188+
"/home/coder/.config/goose/config.yaml",
189+
);
190+
expect(resp).toEqual(expected);
191+
});
192+
193+
test("pre-post-install-scripts", async () => {
194+
const { id } = await setup({
195+
moduleVariables: {
196+
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
197+
post_install_script: "#!/bin/bash\necho 'post-install-script'",
198+
},
199+
});
200+
201+
await execModuleScript(id);
202+
203+
const preInstallLog = await readFileContainer(
204+
id,
205+
"/home/coder/.goose-module/pre_install.log",
206+
);
207+
expect(preInstallLog).toContain("pre-install-script");
208+
209+
const postInstallLog = await readFileContainer(
210+
id,
211+
"/home/coder/.goose-module/post_install.log",
212+
);
213+
expect(postInstallLog).toContain("post-install-script");
214+
});
215+
216+
const promptFile = "/home/coder/.goose-module/prompt.txt";
217+
const agentapiStartLog = "/home/coder/.goose-module/agentapi-start.log";
218+
219+
test("start-with-prompt", async () => {
220+
const { id } = await setup({
221+
agentapiMockScript: await loadTestFile(
222+
import.meta.dir,
223+
"agentapi-mock-print-args.js",
224+
),
225+
});
226+
await execModuleScript(id, {
227+
GOOSE_TASK_PROMPT: "custom-test-prompt",
228+
});
229+
const prompt = await readFileContainer(id, promptFile);
230+
expect(prompt).toContain("custom-test-prompt");
231+
232+
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
233+
expect(agentapiMockOutput).toContain(
234+
"'goose run --interactive --instructions /home/coder/.goose-module/prompt.txt '",
235+
);
236+
});
237+
238+
test("start-without-prompt", async () => {
239+
const { id } = await setup({
240+
agentapiMockScript: await loadTestFile(
241+
import.meta.dir,
242+
"agentapi-mock-print-args.js",
243+
),
244+
});
245+
await execModuleScript(id);
246+
247+
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
248+
expect(agentapiMockOutput).toContain("'goose '");
249+
250+
const prompt = await execContainer(id, ["ls", "-l", promptFile]);
251+
expect(prompt.exitCode).not.toBe(0);
252+
expect(prompt.stderr).toContain("No such file or directory");
253+
});
254+
});

0 commit comments

Comments
 (0)