Skip to content

Commit ef38fb5

Browse files
committed
goose module
1 parent 08199d0 commit ef38fb5

File tree

8 files changed

+425
-232
lines changed

8 files changed

+425
-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: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
// TODO: this is just for testing purposes - REMOVE BEFORE MERGING
62+
registerCleanup: () => {},
63+
projectDir,
64+
skipAgentAPIMock: props?.skipAgentAPIMock,
65+
agentapiMockScript: props?.agentapiMockScript,
66+
});
67+
if (!props?.skipGooseMock) {
68+
await writeExecutable({
69+
containerId: id,
70+
filePath: "/usr/bin/goose",
71+
content: await loadTestFile(import.meta.dir, "goose-mock.sh"),
72+
});
73+
}
74+
return { id };
75+
};
76+
77+
// increase the default timeout to 60 seconds
78+
setDefaultTimeout(60 * 1000);
79+
80+
describe("goose", async () => {
81+
beforeAll(async () => {
82+
await runTerraformInit(import.meta.dir);
83+
});
84+
85+
test("happy-path", async () => {
86+
const { id } = await setup();
87+
88+
await execModuleScript(id);
89+
90+
await expectAgentAPIStarted(id);
91+
});
92+
93+
test("install-version", async () => {
94+
const { id } = await setup({
95+
skipGooseMock: true,
96+
moduleVariables: {
97+
install_goose: "true",
98+
goose_version: "v1.0.24",
99+
},
100+
});
101+
102+
await execModuleScript(id);
103+
104+
const resp = await execContainer(id, [
105+
"bash",
106+
"-c",
107+
`"$HOME/.local/bin/goose" --version`,
108+
]);
109+
if (resp.exitCode !== 0) {
110+
console.log(resp.stdout);
111+
console.log(resp.stderr);
112+
}
113+
expect(resp.exitCode).toBe(0);
114+
expect(resp.stdout).toContain("1.0.24");
115+
});
116+
117+
test("install-stable", async () => {
118+
const { id } = await setup({
119+
skipGooseMock: true,
120+
moduleVariables: {
121+
install_goose: "true",
122+
goose_version: "stable",
123+
},
124+
});
125+
126+
await execModuleScript(id);
127+
128+
const resp = await execContainer(id, [
129+
"bash",
130+
"-c",
131+
`"$HOME/.local/bin/goose" --version`,
132+
]);
133+
if (resp.exitCode !== 0) {
134+
console.log(resp.stdout);
135+
console.log(resp.stderr);
136+
}
137+
expect(resp.exitCode).toBe(0);
138+
});
139+
140+
test("config", async () => {
141+
const expected =
142+
dedent`
143+
GOOSE_PROVIDER: anthropic
144+
GOOSE_MODEL: claude-3-5-sonnet-latest
145+
extensions:
146+
coder:
147+
args:
148+
- exp
149+
- mcp
150+
- server
151+
cmd: coder
152+
description: Report ALL tasks and statuses (in progress, done, failed) you are working on.
153+
enabled: true
154+
envs:
155+
CODER_MCP_APP_STATUS_SLUG: goose
156+
CODER_MCP_AI_AGENTAPI_URL: http://localhost:3284
157+
name: Coder
158+
timeout: 3000
159+
type: stdio
160+
developer:
161+
display_name: Developer
162+
enabled: true
163+
name: developer
164+
timeout: 300
165+
type: builtin
166+
custom-stuff:
167+
enabled: true
168+
name: custom-stuff
169+
timeout: 300
170+
type: builtin
171+
`.trim() + "\n";
172+
173+
const { id } = await setup({
174+
moduleVariables: {
175+
goose_provider: "anthropic",
176+
goose_model: "claude-3-5-sonnet-latest",
177+
additional_extensions: dedent`
178+
custom-stuff:
179+
enabled: true
180+
name: custom-stuff
181+
timeout: 300
182+
type: builtin
183+
`.trim(),
184+
},
185+
});
186+
await execModuleScript(id);
187+
const resp = await readFileContainer(
188+
id,
189+
"/home/coder/.config/goose/config.yaml",
190+
);
191+
expect(resp).toEqual(expected);
192+
});
193+
194+
test("pre-post-install-scripts", async () => {
195+
const { id } = await setup({
196+
moduleVariables: {
197+
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
198+
post_install_script: "#!/bin/bash\necho 'post-install-script'",
199+
},
200+
});
201+
202+
await execModuleScript(id);
203+
204+
const preInstallLog = await readFileContainer(
205+
id,
206+
"/home/coder/.goose-module/pre_install.log",
207+
);
208+
expect(preInstallLog).toContain("pre-install-script");
209+
210+
const postInstallLog = await readFileContainer(
211+
id,
212+
"/home/coder/.goose-module/post_install.log",
213+
);
214+
expect(postInstallLog).toContain("post-install-script");
215+
});
216+
217+
const promptFile = "/home/coder/.goose-module/prompt.txt";
218+
const agentapiStartLog = "/home/coder/.goose-module/agentapi-start.log";
219+
220+
test("start-with-prompt", async () => {
221+
const { id } = await setup({
222+
agentapiMockScript: await loadTestFile(
223+
import.meta.dir,
224+
"agentapi-mock-print-args.js",
225+
),
226+
});
227+
await execModuleScript(id, {
228+
GOOSE_TASK_PROMPT: "custom-test-prompt",
229+
});
230+
const prompt = await readFileContainer(id, promptFile);
231+
expect(prompt).toContain("custom-test-prompt");
232+
233+
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
234+
expect(agentapiMockOutput).toContain(
235+
"'goose run --interactive --instructions /home/coder/.goose-module/prompt.txt '",
236+
);
237+
});
238+
239+
test("start-without-prompt", async () => {
240+
const { id } = await setup({
241+
agentapiMockScript: await loadTestFile(
242+
import.meta.dir,
243+
"agentapi-mock-print-args.js",
244+
),
245+
});
246+
await execModuleScript(id);
247+
248+
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
249+
expect(agentapiMockOutput).toContain("'goose '");
250+
251+
const prompt = await execContainer(id, ["ls", "-l", promptFile]);
252+
expect(prompt.exitCode).not.toBe(0);
253+
expect(prompt.stderr).toContain("No such file or directory");
254+
});
255+
});

0 commit comments

Comments
 (0)