Skip to content

Commit 5c5dd84

Browse files
xesrevinuGit Agent
andcommitted
test: extend tests for cli, config, hooks
- Add CLI integration test for dry-run commit - Update config test to validate malformed hooks - Add openai-client test for isReasoningModel extends the test suite to cover CLI integration, config validation, hook error handling, and reasoning model detection in the OpenAI client Co-Authored-By: Git Agent <noreply@git-agent.dev>
1 parent 87ab84d commit 5c5dd84

4 files changed

Lines changed: 112 additions & 6 deletions

File tree

tests/cli-integration.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,53 @@ describe.concurrent("CLI integration", () => {
478478
}),
479479
);
480480

481+
it.effect(
482+
"git commit --dry-run does not persist auto-generated scopes",
483+
Effect.fn(function* () {
484+
const repo = yield* seedGitRepo();
485+
yield* writeTextFile(repo, "api/routes.ts", "export const route = 'v2';\n");
486+
487+
const llm = yield* startMockLlmServer([
488+
{
489+
content: {
490+
scopes: [{ name: "api", description: "Backend API handlers" }],
491+
},
492+
},
493+
{
494+
content: {
495+
scopes: [{ name: "api", description: "Backend API handlers" }],
496+
},
497+
},
498+
{
499+
content: {
500+
title: "feat(api): update routes",
501+
bullets: ["Adjust API routing output"],
502+
explanation: "Updates the API route response shape.",
503+
},
504+
},
505+
]);
506+
507+
const result = yield* runCli(
508+
[
509+
"commit",
510+
"--dry-run",
511+
"--api-key",
512+
"test-key",
513+
"--base-url",
514+
llm.baseUrl,
515+
"--model",
516+
"test-model",
517+
],
518+
{ cwd: repo },
519+
);
520+
521+
expect(result.exitCode).toBe(0);
522+
expect(result.stdout).toContain("feat(api): update routes");
523+
expect(yield* fileExists(repo, ".git-agent/config.yml")).toBe(false);
524+
expect(llm.requests).toHaveLength(3);
525+
}),
526+
);
527+
481528
it.effect(
482529
"git commit creates split commits that match the planner groups",
483530
Effect.fn(function* () {

tests/config.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1-
import { describe, expect, it } from "vitest";
1+
import { NodeServices } from "@effect/platform-node";
2+
import { Effect } from "effect";
3+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { afterEach, describe, expect, it } from "vitest";
27
import { defaultScopeForKey, normalizeValue, resolveKey } from "../src/config/keys";
8+
import { loadProjectConfig } from "../src/config/project";
9+
10+
const tempDirs: Array<string> = [];
11+
12+
const runEffect = <A>(effect: Effect.Effect<A, unknown, any>) =>
13+
Effect.runPromise(Effect.provide(effect, NodeServices.layer) as Effect.Effect<A, unknown, never>);
14+
15+
afterEach(() => {
16+
while (tempDirs.length > 0) {
17+
const dir = tempDirs.pop();
18+
if (dir != null) {
19+
rmSync(dir, { recursive: true, force: true });
20+
}
21+
}
22+
});
323

424
describe("config keys", () => {
525
it("normalizes kebab-case aliases", () => {
@@ -15,4 +35,18 @@ describe("config keys", () => {
1535
it("normalizes slice values", () => {
1636
expect(normalizeValue("hook", "conventional, empty")).toBe("conventional,empty");
1737
});
38+
39+
it("fails when project config contains malformed hook values", async () => {
40+
const repoRoot = mkdtempSync(join(tmpdir(), "git-agent-config-"));
41+
tempDirs.push(repoRoot);
42+
mkdirSync(join(repoRoot, ".git-agent"), { recursive: true });
43+
writeFileSync(join(repoRoot, ".git-agent", "config.yml"), "hook: 123\n");
44+
45+
await expect(runEffect(loadProjectConfig(repoRoot))).rejects.toMatchObject({
46+
message: expect.stringContaining("invalid config"),
47+
});
48+
await expect(runEffect(loadProjectConfig(repoRoot))).rejects.toMatchObject({
49+
message: expect.stringContaining("hook"),
50+
});
51+
});
1852
});

tests/hooks.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ afterEach(() => {
3636
});
3737

3838
describe("executeHooks", () => {
39-
it("skips missing shell hooks like the Go version", async () => {
39+
it("fails when a configured shell hook is missing", async () => {
4040
const dir = mkdtempSync(join(tmpdir(), "git-agent-hooks-"));
4141
tempDirs.push(dir);
42+
const hookPath = join(dir, "missing-hook.sh");
4243

43-
const result = await runEffect(executeHooks([join(dir, "missing-hook.sh")], hookInput));
44-
expect(result).toEqual({
45-
exitCode: 0,
46-
stderr: "",
44+
await expect(runEffect(executeHooks([hookPath], hookInput))).rejects.toMatchObject({
45+
message: expect.stringContaining(`failed to read hook "${hookPath}"`),
4746
});
4847
});
4948

tests/openai-client.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, it } from "vitest";
2+
import { isReasoningModel } from "../src/services/openai-client";
3+
4+
describe("isReasoningModel", () => {
5+
it("accepts current reasoning model families", () => {
6+
expect(isReasoningModel("o1")).toBe(true);
7+
expect(isReasoningModel("o3-mini")).toBe(true);
8+
expect(isReasoningModel("o4-mini")).toBe(true);
9+
expect(isReasoningModel("codex-mini-latest")).toBe(true);
10+
expect(isReasoningModel("computer-use-preview")).toBe(true);
11+
expect(isReasoningModel("gpt-5")).toBe(true);
12+
expect(isReasoningModel("gpt-5-mini")).toBe(true);
13+
});
14+
15+
it("rejects non-reasoning models", () => {
16+
expect(isReasoningModel("gpt-4o-mini")).toBe(false);
17+
expect(isReasoningModel("gpt-5-chat-latest")).toBe(false);
18+
expect(isReasoningModel("claude-sonnet-4")).toBe(false);
19+
});
20+
21+
it("handles provider-prefixed model ids", () => {
22+
expect(isReasoningModel("openai/gpt-5")).toBe(true);
23+
expect(isReasoningModel("openrouter/openai/o4-mini")).toBe(true);
24+
expect(isReasoningModel("openai/gpt-5-chat-latest")).toBe(false);
25+
});
26+
});

0 commit comments

Comments
 (0)