Skip to content

Commit 97ac2df

Browse files
committed
tests
1 parent 114b6ad commit 97ac2df

7 files changed

Lines changed: 929 additions & 27 deletions

File tree

src/agents/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type PluginConfig,
77
SUBAGENT_NAMES,
88
} from '../config';
9-
import { canAgentUseMcp, DEFAULT_AGENT_MCPS, getAgentMcpList } from '../tools/skill/builtin';
9+
import { getAgentMcpList } from '../tools/skill/builtin';
1010
import { createDesignerAgent } from './designer';
1111
import { createExplorerAgent } from './explorer';
1212
import { createFixerAgent } from './fixer';

src/cli/providers.test.ts

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { describe, expect, test } from 'bun:test';
44
import { generateLiteConfig, MODEL_MAPPINGS } from './providers';
55

66
describe('providers', () => {
7-
test('generateLiteConfig generates antigravity config by default', () => {
7+
test('generateLiteConfig generates antigravity config when only antigravity selected', () => {
88
const config = generateLiteConfig({
99
hasAntigravity: true,
1010
hasOpenAI: false,
@@ -14,15 +14,19 @@ describe('providers', () => {
1414

1515
expect(config.preset).toBe('antigravity');
1616
const agents = (config.presets as any).antigravity;
17+
expect(agents).toBeDefined();
1718
expect(agents.orchestrator.model).toBe(
1819
MODEL_MAPPINGS.antigravity.orchestrator.model,
1920
);
2021
expect(agents.orchestrator.variant).toBeUndefined();
2122
expect(agents.fixer.model).toBe(MODEL_MAPPINGS.antigravity.fixer.model);
2223
expect(agents.fixer.variant).toBe(MODEL_MAPPINGS.antigravity.fixer.variant);
24+
// Should NOT include other presets
25+
expect((config.presets as any).openai).toBeUndefined();
26+
expect((config.presets as any)['zen-free']).toBeUndefined();
2327
});
2428

25-
test('generateLiteConfig always includes antigravity-openai preset', () => {
29+
test('generateLiteConfig generates antigravity-openai preset when both selected', () => {
2630
const config = generateLiteConfig({
2731
hasAntigravity: true,
2832
hasOpenAI: true,
@@ -32,45 +36,57 @@ describe('providers', () => {
3236

3337
expect(config.preset).toBe('antigravity-openai');
3438
const agents = (config.presets as any)['antigravity-openai'];
39+
expect(agents).toBeDefined();
3540
expect(agents.orchestrator.model).toBe(
3641
MODEL_MAPPINGS.antigravity.orchestrator.model,
3742
);
3843
expect(agents.orchestrator.variant).toBeUndefined();
3944
expect(agents.oracle.model).toBe('openai/gpt-5.2-codex');
4045
expect(agents.oracle.variant).toBe('high');
46+
// Should NOT include other presets
47+
expect((config.presets as any).antigravity).toBeUndefined();
48+
expect((config.presets as any).openai).toBeUndefined();
4149
});
4250

43-
test('generateLiteConfig includes antigravity-openai preset even with only antigravity', () => {
51+
test('generateLiteConfig generates openai preset when only openai selected', () => {
4452
const config = generateLiteConfig({
45-
hasAntigravity: true,
46-
hasOpenAI: false,
53+
hasAntigravity: false,
54+
hasOpenAI: true,
4755
hasOpencodeZen: false,
4856
hasTmux: false,
4957
});
5058

51-
expect(config.preset).toBe('antigravity');
52-
const agents = (config.presets as any)['antigravity-openai'];
59+
expect(config.preset).toBe('openai');
60+
const agents = (config.presets as any).openai;
5361
expect(agents).toBeDefined();
54-
expect(agents.oracle.model).toBe('openai/gpt-5.2-codex');
62+
expect(agents.orchestrator.model).toBe(
63+
MODEL_MAPPINGS.openai.orchestrator.model,
64+
);
65+
expect(agents.orchestrator.variant).toBeUndefined();
66+
// Should NOT include other presets
67+
expect((config.presets as any).antigravity).toBeUndefined();
68+
expect((config.presets as any)['zen-free']).toBeUndefined();
5569
});
5670

57-
test('generateLiteConfig uses openai if no antigravity', () => {
71+
test('generateLiteConfig generates zen-free preset when no providers selected', () => {
5872
const config = generateLiteConfig({
5973
hasAntigravity: false,
60-
hasOpenAI: true,
74+
hasOpenAI: false,
6175
hasOpencodeZen: false,
6276
hasTmux: false,
6377
});
6478

65-
expect(config.preset).toBe('openai');
66-
const agents = (config.presets as any).openai;
67-
expect(agents.orchestrator.model).toBe(
68-
MODEL_MAPPINGS.openai.orchestrator.model,
69-
);
79+
expect(config.preset).toBe('zen-free');
80+
const agents = (config.presets as any)['zen-free'];
81+
expect(agents).toBeDefined();
82+
expect(agents.orchestrator.model).toBe('opencode/grok-code');
7083
expect(agents.orchestrator.variant).toBeUndefined();
84+
// Should NOT include other presets
85+
expect((config.presets as any).antigravity).toBeUndefined();
86+
expect((config.presets as any).openai).toBeUndefined();
7187
});
7288

73-
test('generateLiteConfig uses zen-free if no antigravity or openai', () => {
89+
test('generateLiteConfig uses zen-free grok-code models', () => {
7490
const config = generateLiteConfig({
7591
hasAntigravity: false,
7692
hasOpenAI: false,
@@ -80,10 +96,11 @@ describe('providers', () => {
8096

8197
expect(config.preset).toBe('zen-free');
8298
const agents = (config.presets as any)['zen-free'];
83-
expect(agents.orchestrator.model).toBe(
84-
MODEL_MAPPINGS['zen-free'].orchestrator.model,
85-
);
86-
expect(agents.orchestrator.variant).toBeUndefined();
99+
expect(agents.orchestrator.model).toBe('opencode/grok-code');
100+
expect(agents.oracle.model).toBe('opencode/grok-code');
101+
expect(agents.oracle.variant).toBe('high');
102+
expect(agents.librarian.model).toBe('opencode/grok-code');
103+
expect(agents.librarian.variant).toBe('low');
87104
});
88105

89106
test('generateLiteConfig enables tmux when requested', () => {
@@ -110,4 +127,35 @@ describe('providers', () => {
110127
expect(agents.orchestrator.skills).toContain('*');
111128
expect(agents.fixer.skills).toBeDefined();
112129
});
130+
131+
test('generateLiteConfig includes mcps field', () => {
132+
const config = generateLiteConfig({
133+
hasAntigravity: true,
134+
hasOpenAI: false,
135+
hasOpencodeZen: false,
136+
hasTmux: false,
137+
});
138+
139+
const agents = (config.presets as any).antigravity;
140+
expect(agents.orchestrator.mcps).toBeDefined();
141+
expect(Array.isArray(agents.orchestrator.mcps)).toBe(true);
142+
expect(agents.librarian.mcps).toBeDefined();
143+
expect(Array.isArray(agents.librarian.mcps)).toBe(true);
144+
});
145+
146+
test('generateLiteConfig zen-free includes correct mcps', () => {
147+
const config = generateLiteConfig({
148+
hasAntigravity: false,
149+
hasOpenAI: false,
150+
hasOpencodeZen: false,
151+
hasTmux: false,
152+
});
153+
154+
const agents = (config.presets as any)['zen-free'];
155+
expect(agents.orchestrator.mcps).toContain('websearch');
156+
expect(agents.librarian.mcps).toContain('websearch');
157+
expect(agents.librarian.mcps).toContain('context7');
158+
expect(agents.librarian.mcps).toContain('grep_app');
159+
expect(agents.designer.mcps).toEqual([]);
160+
});
113161
});

src/tools/grep/cli.test.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/// <reference types="bun-types" />
2+
3+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
4+
import { mkdir, rm, writeFile } from 'node:fs/promises';
5+
import { tmpdir } from 'node:os';
6+
import { join } from 'node:path';
7+
import { runRg, runRgCount } from './cli';
8+
import { grep } from './tools';
9+
import { formatGrepResult } from './utils';
10+
11+
describe('grep tool', () => {
12+
const testDir = join(tmpdir(), `grep-test-${Date.now()}`);
13+
const testFile1 = join(testDir, 'test1.txt');
14+
const testFile2 = join(testDir, 'test2.ts');
15+
16+
beforeAll(async () => {
17+
await mkdir(testDir, { recursive: true });
18+
await writeFile(
19+
testFile1,
20+
'Hello world\nThis is a test file\nAnother line with match',
21+
);
22+
await writeFile(
23+
testFile2,
24+
"const x = 'Hello world';\nconsole.log('test');",
25+
);
26+
});
27+
28+
afterAll(async () => {
29+
await rm(testDir, { recursive: true, force: true });
30+
});
31+
32+
describe('formatGrepResult', () => {
33+
test('formats empty results', () => {
34+
const result = {
35+
matches: [],
36+
totalMatches: 0,
37+
filesSearched: 10,
38+
truncated: false,
39+
};
40+
expect(formatGrepResult(result)).toBe('No matches found.');
41+
});
42+
43+
test('formats error results', () => {
44+
const result = {
45+
matches: [],
46+
totalMatches: 0,
47+
filesSearched: 0,
48+
truncated: false,
49+
error: 'Something went wrong',
50+
};
51+
expect(formatGrepResult(result)).toBe('Error: Something went wrong');
52+
});
53+
54+
test('formats matches correctly', () => {
55+
const result = {
56+
matches: [
57+
{ file: 'file1.ts', line: 10, text: "const foo = 'bar'" },
58+
{ file: 'file1.ts', line: 15, text: 'console.log(foo)' },
59+
{ file: 'file2.ts', line: 5, text: "import { foo } from './file1'" },
60+
],
61+
totalMatches: 3,
62+
filesSearched: 2,
63+
truncated: false,
64+
};
65+
66+
const output = formatGrepResult(result);
67+
expect(output).toContain('file1.ts:');
68+
expect(output).toContain(" 10: const foo = 'bar'");
69+
expect(output).toContain(' 15: console.log(foo)');
70+
expect(output).toContain('file2.ts:');
71+
expect(output).toContain(" 5: import { foo } from './file1'");
72+
expect(output).toContain('Found 3 matches in 2 files');
73+
});
74+
75+
test('indicates truncation', () => {
76+
const result = {
77+
matches: [{ file: 'foo.txt', line: 1, text: 'bar' }],
78+
totalMatches: 100,
79+
filesSearched: 50,
80+
truncated: true,
81+
};
82+
expect(formatGrepResult(result)).toContain('(output truncated)');
83+
});
84+
});
85+
86+
describe('runRg', () => {
87+
test('finds matches in files', async () => {
88+
const result = await runRg({
89+
pattern: 'Hello',
90+
paths: [testDir],
91+
});
92+
93+
expect(result.totalMatches).toBeGreaterThanOrEqual(2);
94+
expect(
95+
result.matches.some(
96+
(m) => m.file.includes('test1.txt') && m.text.includes('Hello'),
97+
),
98+
).toBe(true);
99+
expect(
100+
result.matches.some(
101+
(m) => m.file.includes('test2.ts') && m.text.includes('Hello'),
102+
),
103+
).toBe(true);
104+
});
105+
106+
test('respects file inclusion patterns', async () => {
107+
const result = await runRg({
108+
pattern: 'Hello',
109+
paths: [testDir],
110+
globs: ['*.txt'],
111+
});
112+
113+
expect(result.matches.some((m) => m.file.includes('test1.txt'))).toBe(
114+
true,
115+
);
116+
expect(result.matches.some((m) => m.file.includes('test2.ts'))).toBe(
117+
false,
118+
);
119+
});
120+
121+
test('handles no matches', async () => {
122+
const result = await runRg({
123+
pattern: 'NonExistentString12345',
124+
paths: [testDir],
125+
});
126+
127+
expect(result.totalMatches).toBe(0);
128+
expect(result.matches).toHaveLength(0);
129+
});
130+
131+
test('respects case sensitivity', async () => {
132+
// Test with exact case match
133+
const resultExact = await runRg({
134+
pattern: 'Hello',
135+
paths: [testDir],
136+
});
137+
expect(resultExact.totalMatches).toBeGreaterThan(0);
138+
139+
// Test with caseSensitive flag set to true (should not match lowercase pattern)
140+
const resultSensitive = await runRg({
141+
pattern: 'hello', // File has "Hello"
142+
paths: [testDir],
143+
caseSensitive: true,
144+
});
145+
expect(resultSensitive.totalMatches).toBe(0);
146+
});
147+
148+
test('respects whole word match', async () => {
149+
const resultPartial = await runRg({
150+
pattern: 'Hell',
151+
paths: [testDir],
152+
wholeWord: false,
153+
});
154+
expect(resultPartial.totalMatches).toBeGreaterThan(0);
155+
156+
const resultWhole = await runRg({
157+
pattern: 'Hell',
158+
paths: [testDir],
159+
wholeWord: true,
160+
});
161+
expect(resultWhole.totalMatches).toBe(0);
162+
});
163+
164+
test('respects max count', async () => {
165+
const result = await runRg({
166+
pattern: 'Hello',
167+
paths: [testDir],
168+
maxCount: 1,
169+
});
170+
// maxCount is per file
171+
expect(
172+
result.matches.filter((m) => m.file.includes('test1.txt')).length,
173+
).toBeLessThanOrEqual(1);
174+
});
175+
});
176+
177+
describe('runRgCount', () => {
178+
test('counts matches correctly', async () => {
179+
const results = await runRgCount({
180+
pattern: 'Hello',
181+
paths: [testDir],
182+
});
183+
184+
expect(results.length).toBeGreaterThan(0);
185+
const file1Result = results.find((r) => r.file.includes('test1.txt'));
186+
expect(file1Result).toBeDefined();
187+
expect(file1Result?.count).toBe(1);
188+
});
189+
});
190+
191+
describe('grep tool execute', () => {
192+
test('executes successfully', async () => {
193+
// @ts-expect-error
194+
const result = await grep.execute({
195+
pattern: 'Hello',
196+
path: testDir,
197+
});
198+
199+
expect(typeof result).toBe('string');
200+
expect(result).toContain('Found');
201+
expect(result).toContain('matches');
202+
});
203+
204+
test('handles errors gracefully', async () => {
205+
// @ts-expect-error
206+
const result = await grep.execute({
207+
pattern: 'Hello',
208+
path: '/non/existent/path/12345',
209+
});
210+
211+
// Depending on implementation, it might return "No matches found" or an error string
212+
// But it should not throw
213+
expect(typeof result).toBe('string');
214+
});
215+
216+
test('respects include pattern in execute', async () => {
217+
// @ts-expect-error
218+
const result = await grep.execute({
219+
pattern: 'Hello',
220+
path: testDir,
221+
include: '*.txt',
222+
});
223+
224+
expect(result).toContain('test1.txt');
225+
expect(result).not.toContain('test2.ts');
226+
});
227+
});
228+
});

0 commit comments

Comments
 (0)