@google/gemini-cli-core 0.13.0-preview.2 → 0.14.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/agents/executor.d.ts +3 -0
- package/dist/src/agents/executor.js +21 -0
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/executor.test.js +177 -2
- package/dist/src/agents/executor.test.js.map +1 -1
- package/dist/src/config/config.d.ts +4 -0
- package/dist/src/config/config.js +21 -5
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +88 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.d.ts +7 -0
- package/dist/src/config/defaultModelConfigs.js +127 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.js +1 -1
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/types.d.ts +1 -0
- package/dist/src/core/prompts.js +0 -2
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/fallback/handler.js +1 -3
- package/dist/src/fallback/handler.js.map +1 -1
- package/dist/src/fallback/handler.test.js +4 -12
- package/dist/src/fallback/handler.test.js.map +1 -1
- package/dist/src/fallback/types.d.ts +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/policy/config.test.js +17 -0
- package/dist/src/policy/config.test.js.map +1 -1
- package/dist/src/policy/policies/discovered.toml +8 -0
- package/dist/src/policy/policy-engine.d.ts +1 -1
- package/dist/src/policy/policy-engine.js +11 -3
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +98 -50
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/toml-loader.test.js +220 -336
- package/dist/src/policy/toml-loader.test.js.map +1 -1
- package/dist/src/services/modelConfig.golden.test.d.ts +6 -0
- package/dist/src/services/modelConfig.golden.test.js +42 -0
- package/dist/src/services/modelConfig.golden.test.js.map +1 -0
- package/dist/src/services/modelConfig.integration.test.d.ts +6 -0
- package/dist/src/services/modelConfig.integration.test.js +213 -0
- package/dist/src/services/modelConfig.integration.test.js.map +1 -0
- package/dist/src/services/modelConfigService.d.ts +46 -0
- package/dist/src/services/modelConfigService.js +146 -0
- package/dist/src/services/modelConfigService.js.map +1 -0
- package/dist/src/services/modelConfigService.test.d.ts +6 -0
- package/dist/src/services/modelConfigService.test.js +509 -0
- package/dist/src/services/modelConfigService.test.js.map +1 -0
- package/dist/src/services/test-data/resolved-aliases.golden.json +123 -0
- package/dist/src/tools/base-tool-invocation.test.d.ts +6 -0
- package/dist/src/tools/base-tool-invocation.test.js +85 -0
- package/dist/src/tools/base-tool-invocation.test.js.map +1 -0
- package/dist/src/tools/edit.d.ts +1 -1
- package/dist/src/tools/edit.js +31 -33
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +30 -20
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +1 -1
- package/dist/src/tools/glob.js +7 -7
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +20 -17
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.d.ts +1 -1
- package/dist/src/tools/grep.js +9 -9
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/grep.test.js +15 -12
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/ls.d.ts +1 -1
- package/dist/src/tools/ls.js +14 -15
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +32 -33
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/mcp-client.js +2 -0
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +5 -0
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.js +1 -1
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +2 -2
- package/dist/src/tools/read-file.js +20 -24
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +62 -51
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +2 -9
- package/dist/src/tools/read-many-files.js +10 -21
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +35 -35
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +1 -1
- package/dist/src/tools/ripGrep.js +8 -8
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +23 -14
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +1 -1
- package/dist/src/tools/shell.js +16 -14
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +64 -34
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +1 -1
- package/dist/src/tools/smart-edit.js +8 -11
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +12 -2
- package/dist/src/tools/tool-registry.js +49 -14
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +70 -4
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +2 -1
- package/dist/src/tools/tools.js +4 -1
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/write-file.js +31 -31
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +23 -5
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/extensionLoader.js +7 -3
- package/dist/src/utils/extensionLoader.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +75 -60
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/pathReader.js +4 -4
- package/dist/src/utils/pathReader.js.map +1 -1
- package/dist/src/utils/pathReader.test.js +44 -1
- package/dist/src/utils/pathReader.test.js.map +1 -1
- package/dist/src/utils/paths.d.ts +1 -1
- package/dist/src/utils/paths.js +5 -3
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/workspaceContext.d.ts +4 -3
- package/dist/src/utils/workspaceContext.js +10 -11
- package/dist/src/utils/workspaceContext.js.map +1 -1
- package/dist/src/utils/workspaceContext.test.js +1 -1
- package/dist/src/utils/workspaceContext.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/google-gemini-cli-core-0.13.0-preview.1.tgz +0 -0
|
@@ -6,6 +6,37 @@
|
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
7
|
import { ApprovalMode, PolicyDecision } from './types.js';
|
|
8
8
|
import nodePath from 'node:path';
|
|
9
|
+
async function runLoadPoliciesFromToml(tomlContent, fileName = 'test.toml') {
|
|
10
|
+
const actualFs = await vi.importActual('node:fs/promises');
|
|
11
|
+
const mockReaddir = vi.fn(async (path, _options) => {
|
|
12
|
+
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
name: fileName,
|
|
16
|
+
isFile: () => true,
|
|
17
|
+
isDirectory: () => false,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
});
|
|
23
|
+
const mockReadFile = vi.fn(async (path) => {
|
|
24
|
+
if (nodePath.normalize(path) ===
|
|
25
|
+
nodePath.normalize(nodePath.join('/policies', fileName))) {
|
|
26
|
+
return tomlContent;
|
|
27
|
+
}
|
|
28
|
+
throw new Error('File not found');
|
|
29
|
+
});
|
|
30
|
+
vi.doMock('node:fs/promises', () => ({
|
|
31
|
+
...actualFs,
|
|
32
|
+
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
33
|
+
readFile: mockReadFile,
|
|
34
|
+
readdir: mockReaddir,
|
|
35
|
+
}));
|
|
36
|
+
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
37
|
+
const getPolicyTier = (_dir) => 1;
|
|
38
|
+
return load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
39
|
+
}
|
|
9
40
|
describe('policy-toml-loader', () => {
|
|
10
41
|
beforeEach(() => {
|
|
11
42
|
vi.resetModules();
|
|
@@ -16,40 +47,12 @@ describe('policy-toml-loader', () => {
|
|
|
16
47
|
});
|
|
17
48
|
describe('loadPoliciesFromToml', () => {
|
|
18
49
|
it('should load and parse a simple policy file', async () => {
|
|
19
|
-
const
|
|
20
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
21
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
22
|
-
return [
|
|
23
|
-
{
|
|
24
|
-
name: 'test.toml',
|
|
25
|
-
isFile: () => true,
|
|
26
|
-
isDirectory: () => false,
|
|
27
|
-
},
|
|
28
|
-
];
|
|
29
|
-
}
|
|
30
|
-
return [];
|
|
31
|
-
});
|
|
32
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
33
|
-
if (nodePath.normalize(path) ===
|
|
34
|
-
nodePath.normalize(nodePath.join('/policies', 'test.toml'))) {
|
|
35
|
-
return `
|
|
50
|
+
const result = await runLoadPoliciesFromToml(`
|
|
36
51
|
[[rule]]
|
|
37
52
|
toolName = "glob"
|
|
38
53
|
decision = "allow"
|
|
39
54
|
priority = 100
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
throw new Error('File not found');
|
|
43
|
-
});
|
|
44
|
-
vi.doMock('node:fs/promises', () => ({
|
|
45
|
-
...actualFs,
|
|
46
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
47
|
-
readFile: mockReadFile,
|
|
48
|
-
readdir: mockReaddir,
|
|
49
|
-
}));
|
|
50
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
51
|
-
const getPolicyTier = (_dir) => 1;
|
|
52
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
55
|
+
`);
|
|
53
56
|
expect(result.rules).toHaveLength(1);
|
|
54
57
|
expect(result.rules[0]).toEqual({
|
|
55
58
|
toolName: 'glob',
|
|
@@ -59,41 +62,13 @@ priority = 100
|
|
|
59
62
|
expect(result.errors).toHaveLength(0);
|
|
60
63
|
});
|
|
61
64
|
it('should expand commandPrefix array to multiple rules', async () => {
|
|
62
|
-
const
|
|
63
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
64
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
65
|
-
return [
|
|
66
|
-
{
|
|
67
|
-
name: 'shell.toml',
|
|
68
|
-
isFile: () => true,
|
|
69
|
-
isDirectory: () => false,
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
}
|
|
73
|
-
return [];
|
|
74
|
-
});
|
|
75
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
76
|
-
if (nodePath.normalize(path) ===
|
|
77
|
-
nodePath.normalize(nodePath.join('/policies', 'shell.toml'))) {
|
|
78
|
-
return `
|
|
65
|
+
const result = await runLoadPoliciesFromToml(`
|
|
79
66
|
[[rule]]
|
|
80
67
|
toolName = "run_shell_command"
|
|
81
68
|
commandPrefix = ["git status", "git log"]
|
|
82
69
|
decision = "allow"
|
|
83
70
|
priority = 100
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
throw new Error('File not found');
|
|
87
|
-
});
|
|
88
|
-
vi.doMock('node:fs/promises', () => ({
|
|
89
|
-
...actualFs,
|
|
90
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
91
|
-
readFile: mockReadFile,
|
|
92
|
-
readdir: mockReaddir,
|
|
93
|
-
}));
|
|
94
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
95
|
-
const getPolicyTier = (_dir) => 2;
|
|
96
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
71
|
+
`);
|
|
97
72
|
expect(result.rules).toHaveLength(2);
|
|
98
73
|
expect(result.rules[0].toolName).toBe('run_shell_command');
|
|
99
74
|
expect(result.rules[1].toolName).toBe('run_shell_command');
|
|
@@ -102,41 +77,13 @@ priority = 100
|
|
|
102
77
|
expect(result.errors).toHaveLength(0);
|
|
103
78
|
});
|
|
104
79
|
it('should transform commandRegex to argsPattern', async () => {
|
|
105
|
-
const
|
|
106
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
107
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
108
|
-
return [
|
|
109
|
-
{
|
|
110
|
-
name: 'shell.toml',
|
|
111
|
-
isFile: () => true,
|
|
112
|
-
isDirectory: () => false,
|
|
113
|
-
},
|
|
114
|
-
];
|
|
115
|
-
}
|
|
116
|
-
return [];
|
|
117
|
-
});
|
|
118
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
119
|
-
if (nodePath.normalize(path) ===
|
|
120
|
-
nodePath.normalize(nodePath.join('/policies', 'shell.toml'))) {
|
|
121
|
-
return `
|
|
80
|
+
const result = await runLoadPoliciesFromToml(`
|
|
122
81
|
[[rule]]
|
|
123
82
|
toolName = "run_shell_command"
|
|
124
83
|
commandRegex = "git (status|log).*"
|
|
125
84
|
decision = "allow"
|
|
126
85
|
priority = 100
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
throw new Error('File not found');
|
|
130
|
-
});
|
|
131
|
-
vi.doMock('node:fs/promises', () => ({
|
|
132
|
-
...actualFs,
|
|
133
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
134
|
-
readFile: mockReadFile,
|
|
135
|
-
readdir: mockReaddir,
|
|
136
|
-
}));
|
|
137
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
138
|
-
const getPolicyTier = (_dir) => 2;
|
|
139
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
86
|
+
`);
|
|
140
87
|
expect(result.rules).toHaveLength(1);
|
|
141
88
|
expect(result.rules[0].argsPattern?.test('{"command":"git status"}')).toBe(true);
|
|
142
89
|
expect(result.rules[0].argsPattern?.test('{"command":"git log --all"}')).toBe(true);
|
|
@@ -144,40 +91,12 @@ priority = 100
|
|
|
144
91
|
expect(result.errors).toHaveLength(0);
|
|
145
92
|
});
|
|
146
93
|
it('should expand toolName array', async () => {
|
|
147
|
-
const
|
|
148
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
149
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
150
|
-
return [
|
|
151
|
-
{
|
|
152
|
-
name: 'tools.toml',
|
|
153
|
-
isFile: () => true,
|
|
154
|
-
isDirectory: () => false,
|
|
155
|
-
},
|
|
156
|
-
];
|
|
157
|
-
}
|
|
158
|
-
return [];
|
|
159
|
-
});
|
|
160
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
161
|
-
if (nodePath.normalize(path) ===
|
|
162
|
-
nodePath.normalize(nodePath.join('/policies', 'tools.toml'))) {
|
|
163
|
-
return `
|
|
94
|
+
const result = await runLoadPoliciesFromToml(`
|
|
164
95
|
[[rule]]
|
|
165
96
|
toolName = ["glob", "grep", "read"]
|
|
166
97
|
decision = "allow"
|
|
167
98
|
priority = 100
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
throw new Error('File not found');
|
|
171
|
-
});
|
|
172
|
-
vi.doMock('node:fs/promises', () => ({
|
|
173
|
-
...actualFs,
|
|
174
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
175
|
-
readFile: mockReadFile,
|
|
176
|
-
readdir: mockReaddir,
|
|
177
|
-
}));
|
|
178
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
179
|
-
const getPolicyTier = (_dir) => 1;
|
|
180
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
99
|
+
`);
|
|
181
100
|
expect(result.rules).toHaveLength(3);
|
|
182
101
|
expect(result.rules.map((r) => r.toolName)).toEqual([
|
|
183
102
|
'glob',
|
|
@@ -187,64 +106,20 @@ priority = 100
|
|
|
187
106
|
expect(result.errors).toHaveLength(0);
|
|
188
107
|
});
|
|
189
108
|
it('should transform mcpName to composite toolName', async () => {
|
|
190
|
-
const
|
|
191
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
192
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
193
|
-
return [
|
|
194
|
-
{
|
|
195
|
-
name: 'mcp.toml',
|
|
196
|
-
isFile: () => true,
|
|
197
|
-
isDirectory: () => false,
|
|
198
|
-
},
|
|
199
|
-
];
|
|
200
|
-
}
|
|
201
|
-
return [];
|
|
202
|
-
});
|
|
203
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
204
|
-
if (nodePath.normalize(path) ===
|
|
205
|
-
nodePath.normalize(nodePath.join('/policies', 'mcp.toml'))) {
|
|
206
|
-
return `
|
|
109
|
+
const result = await runLoadPoliciesFromToml(`
|
|
207
110
|
[[rule]]
|
|
208
111
|
mcpName = "google-workspace"
|
|
209
112
|
toolName = ["calendar.list", "calendar.get"]
|
|
210
113
|
decision = "allow"
|
|
211
114
|
priority = 100
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
throw new Error('File not found');
|
|
215
|
-
});
|
|
216
|
-
vi.doMock('node:fs/promises', () => ({
|
|
217
|
-
...actualFs,
|
|
218
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
219
|
-
readFile: mockReadFile,
|
|
220
|
-
readdir: mockReaddir,
|
|
221
|
-
}));
|
|
222
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
223
|
-
const getPolicyTier = (_dir) => 2;
|
|
224
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
115
|
+
`);
|
|
225
116
|
expect(result.rules).toHaveLength(2);
|
|
226
117
|
expect(result.rules[0].toolName).toBe('google-workspace__calendar.list');
|
|
227
118
|
expect(result.rules[1].toolName).toBe('google-workspace__calendar.get');
|
|
228
119
|
expect(result.errors).toHaveLength(0);
|
|
229
120
|
});
|
|
230
121
|
it('should filter rules by mode', async () => {
|
|
231
|
-
const
|
|
232
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
233
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
234
|
-
return [
|
|
235
|
-
{
|
|
236
|
-
name: 'modes.toml',
|
|
237
|
-
isFile: () => true,
|
|
238
|
-
isDirectory: () => false,
|
|
239
|
-
},
|
|
240
|
-
];
|
|
241
|
-
}
|
|
242
|
-
return [];
|
|
243
|
-
});
|
|
244
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
245
|
-
if (nodePath.normalize(path) ===
|
|
246
|
-
nodePath.normalize(nodePath.join('/policies', 'modes.toml'))) {
|
|
247
|
-
return `
|
|
122
|
+
const result = await runLoadPoliciesFromToml(`
|
|
248
123
|
[[rule]]
|
|
249
124
|
toolName = "glob"
|
|
250
125
|
decision = "allow"
|
|
@@ -256,189 +131,97 @@ toolName = "grep"
|
|
|
256
131
|
decision = "allow"
|
|
257
132
|
priority = 100
|
|
258
133
|
modes = ["yolo"]
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
throw new Error('File not found');
|
|
262
|
-
});
|
|
263
|
-
vi.doMock('node:fs/promises', () => ({
|
|
264
|
-
...actualFs,
|
|
265
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
266
|
-
readFile: mockReadFile,
|
|
267
|
-
readdir: mockReaddir,
|
|
268
|
-
}));
|
|
269
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
270
|
-
const getPolicyTier = (_dir) => 1;
|
|
271
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
134
|
+
`);
|
|
272
135
|
// Only the first rule should be included (modes includes "default")
|
|
273
136
|
expect(result.rules).toHaveLength(1);
|
|
274
137
|
expect(result.rules[0].toolName).toBe('glob');
|
|
275
138
|
expect(result.errors).toHaveLength(0);
|
|
276
139
|
});
|
|
277
140
|
it('should handle TOML parse errors', async () => {
|
|
278
|
-
const
|
|
279
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
280
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
281
|
-
return [
|
|
282
|
-
{
|
|
283
|
-
name: 'invalid.toml',
|
|
284
|
-
isFile: () => true,
|
|
285
|
-
isDirectory: () => false,
|
|
286
|
-
},
|
|
287
|
-
];
|
|
288
|
-
}
|
|
289
|
-
return [];
|
|
290
|
-
});
|
|
291
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
292
|
-
if (nodePath.normalize(path) ===
|
|
293
|
-
nodePath.normalize(nodePath.join('/policies', 'invalid.toml'))) {
|
|
294
|
-
return `
|
|
141
|
+
const result = await runLoadPoliciesFromToml(`
|
|
295
142
|
[[rule]
|
|
296
143
|
toolName = "glob"
|
|
297
144
|
decision = "allow"
|
|
298
145
|
priority = 100
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
throw new Error('File not found');
|
|
302
|
-
});
|
|
303
|
-
vi.doMock('node:fs/promises', () => ({
|
|
304
|
-
...actualFs,
|
|
305
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
306
|
-
readFile: mockReadFile,
|
|
307
|
-
readdir: mockReaddir,
|
|
308
|
-
}));
|
|
309
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
310
|
-
const getPolicyTier = (_dir) => 1;
|
|
311
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
146
|
+
`);
|
|
312
147
|
expect(result.rules).toHaveLength(0);
|
|
313
148
|
expect(result.errors).toHaveLength(1);
|
|
314
149
|
expect(result.errors[0].errorType).toBe('toml_parse');
|
|
315
|
-
expect(result.errors[0].fileName).toBe('
|
|
150
|
+
expect(result.errors[0].fileName).toBe('test.toml');
|
|
316
151
|
});
|
|
317
152
|
it('should handle schema validation errors', async () => {
|
|
318
|
-
const
|
|
319
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
320
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
321
|
-
return [
|
|
322
|
-
{
|
|
323
|
-
name: 'invalid.toml',
|
|
324
|
-
isFile: () => true,
|
|
325
|
-
isDirectory: () => false,
|
|
326
|
-
},
|
|
327
|
-
];
|
|
328
|
-
}
|
|
329
|
-
return [];
|
|
330
|
-
});
|
|
331
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
332
|
-
if (nodePath.normalize(path) ===
|
|
333
|
-
nodePath.normalize(nodePath.join('/policies', 'invalid.toml'))) {
|
|
334
|
-
return `
|
|
153
|
+
const result = await runLoadPoliciesFromToml(`
|
|
335
154
|
[[rule]]
|
|
336
155
|
toolName = "glob"
|
|
337
156
|
priority = 100
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
throw new Error('File not found');
|
|
341
|
-
});
|
|
342
|
-
vi.doMock('node:fs/promises', () => ({
|
|
343
|
-
...actualFs,
|
|
344
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
345
|
-
readFile: mockReadFile,
|
|
346
|
-
readdir: mockReaddir,
|
|
347
|
-
}));
|
|
348
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
349
|
-
const getPolicyTier = (_dir) => 1;
|
|
350
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
157
|
+
`);
|
|
351
158
|
expect(result.rules).toHaveLength(0);
|
|
352
159
|
expect(result.errors).toHaveLength(1);
|
|
353
160
|
expect(result.errors[0].errorType).toBe('schema_validation');
|
|
354
161
|
expect(result.errors[0].details).toContain('decision');
|
|
355
162
|
});
|
|
356
163
|
it('should reject commandPrefix without run_shell_command', async () => {
|
|
357
|
-
const
|
|
358
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
359
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
360
|
-
return [
|
|
361
|
-
{
|
|
362
|
-
name: 'invalid.toml',
|
|
363
|
-
isFile: () => true,
|
|
364
|
-
isDirectory: () => false,
|
|
365
|
-
},
|
|
366
|
-
];
|
|
367
|
-
}
|
|
368
|
-
return [];
|
|
369
|
-
});
|
|
370
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
371
|
-
if (nodePath.normalize(path) ===
|
|
372
|
-
nodePath.normalize(nodePath.join('/policies', 'invalid.toml'))) {
|
|
373
|
-
return `
|
|
164
|
+
const result = await runLoadPoliciesFromToml(`
|
|
374
165
|
[[rule]]
|
|
375
166
|
toolName = "glob"
|
|
376
167
|
commandPrefix = "git status"
|
|
377
168
|
decision = "allow"
|
|
378
169
|
priority = 100
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
throw new Error('File not found');
|
|
382
|
-
});
|
|
383
|
-
vi.doMock('node:fs/promises', () => ({
|
|
384
|
-
...actualFs,
|
|
385
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
386
|
-
readFile: mockReadFile,
|
|
387
|
-
readdir: mockReaddir,
|
|
388
|
-
}));
|
|
389
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
390
|
-
const getPolicyTier = (_dir) => 1;
|
|
391
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
170
|
+
`);
|
|
392
171
|
expect(result.errors).toHaveLength(1);
|
|
393
172
|
expect(result.errors[0].errorType).toBe('rule_validation');
|
|
394
173
|
expect(result.errors[0].details).toContain('run_shell_command');
|
|
395
174
|
});
|
|
396
175
|
it('should reject commandPrefix + argsPattern combination', async () => {
|
|
397
|
-
const
|
|
398
|
-
const mockReaddir = vi.fn(async (path, _options) => {
|
|
399
|
-
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
400
|
-
return [
|
|
401
|
-
{
|
|
402
|
-
name: 'invalid.toml',
|
|
403
|
-
isFile: () => true,
|
|
404
|
-
isDirectory: () => false,
|
|
405
|
-
},
|
|
406
|
-
];
|
|
407
|
-
}
|
|
408
|
-
return [];
|
|
409
|
-
});
|
|
410
|
-
const mockReadFile = vi.fn(async (path) => {
|
|
411
|
-
if (nodePath.normalize(path) ===
|
|
412
|
-
nodePath.normalize(nodePath.join('/policies', 'invalid.toml'))) {
|
|
413
|
-
return `
|
|
176
|
+
const result = await runLoadPoliciesFromToml(`
|
|
414
177
|
[[rule]]
|
|
415
178
|
toolName = "run_shell_command"
|
|
416
179
|
commandPrefix = "git status"
|
|
417
180
|
argsPattern = "test"
|
|
418
181
|
decision = "allow"
|
|
419
182
|
priority = 100
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
throw new Error('File not found');
|
|
423
|
-
});
|
|
424
|
-
vi.doMock('node:fs/promises', () => ({
|
|
425
|
-
...actualFs,
|
|
426
|
-
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
|
|
427
|
-
readFile: mockReadFile,
|
|
428
|
-
readdir: mockReaddir,
|
|
429
|
-
}));
|
|
430
|
-
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
431
|
-
const getPolicyTier = (_dir) => 1;
|
|
432
|
-
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
183
|
+
`);
|
|
433
184
|
expect(result.errors).toHaveLength(1);
|
|
434
185
|
expect(result.errors[0].errorType).toBe('rule_validation');
|
|
435
186
|
expect(result.errors[0].details).toContain('mutually exclusive');
|
|
436
187
|
});
|
|
437
188
|
it('should handle invalid regex patterns', async () => {
|
|
189
|
+
const result = await runLoadPoliciesFromToml(`
|
|
190
|
+
[[rule]]
|
|
191
|
+
toolName = "run_shell_command"
|
|
192
|
+
commandRegex = "git (status|branch"
|
|
193
|
+
decision = "allow"
|
|
194
|
+
priority = 100
|
|
195
|
+
`);
|
|
196
|
+
expect(result.rules).toHaveLength(0);
|
|
197
|
+
expect(result.errors).toHaveLength(1);
|
|
198
|
+
expect(result.errors[0].errorType).toBe('regex_compilation');
|
|
199
|
+
expect(result.errors[0].details).toContain('git (status|branch');
|
|
200
|
+
});
|
|
201
|
+
it('should escape regex special characters in commandPrefix', async () => {
|
|
202
|
+
const result = await runLoadPoliciesFromToml(`
|
|
203
|
+
[[rule]]
|
|
204
|
+
toolName = "run_shell_command"
|
|
205
|
+
commandPrefix = "git log *.txt"
|
|
206
|
+
decision = "allow"
|
|
207
|
+
priority = 100
|
|
208
|
+
`);
|
|
209
|
+
expect(result.rules).toHaveLength(1);
|
|
210
|
+
// The regex should have escaped the * and .
|
|
211
|
+
expect(result.rules[0].argsPattern?.test('{"command":"git log file.txt"}')).toBe(false);
|
|
212
|
+
expect(result.rules[0].argsPattern?.test('{"command":"git log *.txt"}')).toBe(true);
|
|
213
|
+
expect(result.errors).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
it('should handle a mix of valid and invalid policy files', async () => {
|
|
438
216
|
const actualFs = await vi.importActual('node:fs/promises');
|
|
439
217
|
const mockReaddir = vi.fn(async (path, _options) => {
|
|
440
218
|
if (nodePath.normalize(path) === nodePath.normalize('/policies')) {
|
|
441
219
|
return [
|
|
220
|
+
{
|
|
221
|
+
name: 'valid.toml',
|
|
222
|
+
isFile: () => true,
|
|
223
|
+
isDirectory: () => false,
|
|
224
|
+
},
|
|
442
225
|
{
|
|
443
226
|
name: 'invalid.toml',
|
|
444
227
|
isFile: () => true,
|
|
@@ -450,13 +233,21 @@ priority = 100
|
|
|
450
233
|
});
|
|
451
234
|
const mockReadFile = vi.fn(async (path) => {
|
|
452
235
|
if (nodePath.normalize(path) ===
|
|
453
|
-
nodePath.normalize(nodePath.join('/policies', '
|
|
236
|
+
nodePath.normalize(nodePath.join('/policies', 'valid.toml'))) {
|
|
454
237
|
return `
|
|
455
238
|
[[rule]]
|
|
456
|
-
toolName = "
|
|
457
|
-
commandRegex = "git (status|branch"
|
|
239
|
+
toolName = "glob"
|
|
458
240
|
decision = "allow"
|
|
459
241
|
priority = 100
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
if (nodePath.normalize(path) ===
|
|
245
|
+
nodePath.normalize(nodePath.join('/policies', 'invalid.toml'))) {
|
|
246
|
+
return `
|
|
247
|
+
[[rule]]
|
|
248
|
+
toolName = "grep"
|
|
249
|
+
decision = "allow"
|
|
250
|
+
priority = -1
|
|
460
251
|
`;
|
|
461
252
|
}
|
|
462
253
|
throw new Error('File not found');
|
|
@@ -470,52 +261,145 @@ priority = 100
|
|
|
470
261
|
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
471
262
|
const getPolicyTier = (_dir) => 1;
|
|
472
263
|
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
473
|
-
expect(result.rules).toHaveLength(
|
|
264
|
+
expect(result.rules).toHaveLength(1);
|
|
265
|
+
expect(result.rules[0].toolName).toBe('glob');
|
|
474
266
|
expect(result.errors).toHaveLength(1);
|
|
475
|
-
expect(result.errors[0].
|
|
476
|
-
expect(result.errors[0].
|
|
267
|
+
expect(result.errors[0].fileName).toBe('invalid.toml');
|
|
268
|
+
expect(result.errors[0].errorType).toBe('schema_validation');
|
|
477
269
|
});
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
270
|
+
});
|
|
271
|
+
describe('Negative Tests', () => {
|
|
272
|
+
it('should return a schema_validation error if priority is missing', async () => {
|
|
273
|
+
const result = await runLoadPoliciesFromToml(`
|
|
274
|
+
[[rule]]
|
|
275
|
+
toolName = "test"
|
|
276
|
+
decision = "allow"
|
|
277
|
+
`);
|
|
278
|
+
expect(result.errors).toHaveLength(1);
|
|
279
|
+
const error = result.errors[0];
|
|
280
|
+
expect(error.errorType).toBe('schema_validation');
|
|
281
|
+
expect(error.details).toContain('priority');
|
|
282
|
+
});
|
|
283
|
+
it('should return a schema_validation error if priority is a float', async () => {
|
|
284
|
+
const result = await runLoadPoliciesFromToml(`
|
|
285
|
+
[[rule]]
|
|
286
|
+
toolName = "test"
|
|
287
|
+
decision = "allow"
|
|
288
|
+
priority = 1.5
|
|
289
|
+
`);
|
|
290
|
+
expect(result.errors).toHaveLength(1);
|
|
291
|
+
const error = result.errors[0];
|
|
292
|
+
expect(error.errorType).toBe('schema_validation');
|
|
293
|
+
expect(error.details).toContain('priority');
|
|
294
|
+
expect(error.details).toContain('integer');
|
|
295
|
+
});
|
|
296
|
+
it('should return a schema_validation error if priority is negative', async () => {
|
|
297
|
+
const result = await runLoadPoliciesFromToml(`
|
|
298
|
+
[[rule]]
|
|
299
|
+
toolName = "test"
|
|
300
|
+
decision = "allow"
|
|
301
|
+
priority = -1
|
|
302
|
+
`);
|
|
303
|
+
expect(result.errors).toHaveLength(1);
|
|
304
|
+
const error = result.errors[0];
|
|
305
|
+
expect(error.errorType).toBe('schema_validation');
|
|
306
|
+
expect(error.details).toContain('priority');
|
|
307
|
+
expect(error.details).toContain('>= 0');
|
|
308
|
+
});
|
|
309
|
+
it('should return a schema_validation error if priority is >= 1000', async () => {
|
|
310
|
+
const result = await runLoadPoliciesFromToml(`
|
|
311
|
+
[[rule]]
|
|
312
|
+
toolName = "test"
|
|
313
|
+
decision = "allow"
|
|
314
|
+
priority = 1000
|
|
315
|
+
`);
|
|
316
|
+
expect(result.errors).toHaveLength(1);
|
|
317
|
+
const error = result.errors[0];
|
|
318
|
+
expect(error.errorType).toBe('schema_validation');
|
|
319
|
+
expect(error.details).toContain('priority');
|
|
320
|
+
expect(error.details).toContain('<= 999');
|
|
321
|
+
});
|
|
322
|
+
it('should return a schema_validation error if decision is invalid', async () => {
|
|
323
|
+
const result = await runLoadPoliciesFromToml(`
|
|
324
|
+
[[rule]]
|
|
325
|
+
toolName = "test"
|
|
326
|
+
decision = "maybe"
|
|
327
|
+
priority = 100
|
|
328
|
+
`);
|
|
329
|
+
expect(result.errors).toHaveLength(1);
|
|
330
|
+
const error = result.errors[0];
|
|
331
|
+
expect(error.errorType).toBe('schema_validation');
|
|
332
|
+
expect(error.details).toContain('decision');
|
|
333
|
+
});
|
|
334
|
+
it('should return a schema_validation error if toolName is not a string or array', async () => {
|
|
335
|
+
const result = await runLoadPoliciesFromToml(`
|
|
336
|
+
[[rule]]
|
|
337
|
+
toolName = 123
|
|
338
|
+
decision = "allow"
|
|
339
|
+
priority = 100
|
|
340
|
+
`);
|
|
341
|
+
expect(result.errors).toHaveLength(1);
|
|
342
|
+
const error = result.errors[0];
|
|
343
|
+
expect(error.errorType).toBe('schema_validation');
|
|
344
|
+
expect(error.details).toContain('toolName');
|
|
345
|
+
});
|
|
346
|
+
it('should return a rule_validation error if commandRegex is used with wrong toolName', async () => {
|
|
347
|
+
const result = await runLoadPoliciesFromToml(`
|
|
348
|
+
[[rule]]
|
|
349
|
+
toolName = "not_shell"
|
|
350
|
+
commandRegex = ".*"
|
|
351
|
+
decision = "allow"
|
|
352
|
+
priority = 100
|
|
353
|
+
`);
|
|
354
|
+
expect(result.errors).toHaveLength(1);
|
|
355
|
+
const error = result.errors[0];
|
|
356
|
+
expect(error.errorType).toBe('rule_validation');
|
|
357
|
+
expect(error.details).toContain('run_shell_command');
|
|
358
|
+
});
|
|
359
|
+
it('should return a rule_validation error if commandPrefix and commandRegex are combined', async () => {
|
|
360
|
+
const result = await runLoadPoliciesFromToml(`
|
|
496
361
|
[[rule]]
|
|
497
362
|
toolName = "run_shell_command"
|
|
498
|
-
commandPrefix = "git
|
|
363
|
+
commandPrefix = "git"
|
|
364
|
+
commandRegex = ".*"
|
|
499
365
|
decision = "allow"
|
|
500
366
|
priority = 100
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
367
|
+
`);
|
|
368
|
+
expect(result.errors).toHaveLength(1);
|
|
369
|
+
const error = result.errors[0];
|
|
370
|
+
expect(error.errorType).toBe('rule_validation');
|
|
371
|
+
expect(error.details).toContain('mutually exclusive');
|
|
372
|
+
});
|
|
373
|
+
it('should return a regex_compilation error for invalid argsPattern', async () => {
|
|
374
|
+
const result = await runLoadPoliciesFromToml(`
|
|
375
|
+
[[rule]]
|
|
376
|
+
toolName = "test"
|
|
377
|
+
argsPattern = "([a-z)"
|
|
378
|
+
decision = "allow"
|
|
379
|
+
priority = 100
|
|
380
|
+
`);
|
|
381
|
+
expect(result.errors).toHaveLength(1);
|
|
382
|
+
const error = result.errors[0];
|
|
383
|
+
expect(error.errorType).toBe('regex_compilation');
|
|
384
|
+
expect(error.message).toBe('Invalid regex pattern');
|
|
385
|
+
});
|
|
386
|
+
it('should return a file_read error if readdir fails', async () => {
|
|
387
|
+
const actualFs = await vi.importActual('node:fs/promises');
|
|
388
|
+
const mockReaddir = vi.fn(async () => {
|
|
389
|
+
throw new Error('Permission denied');
|
|
504
390
|
});
|
|
505
391
|
vi.doMock('node:fs/promises', () => ({
|
|
506
392
|
...actualFs,
|
|
507
|
-
default: { ...actualFs,
|
|
508
|
-
readFile: mockReadFile,
|
|
393
|
+
default: { ...actualFs, readdir: mockReaddir },
|
|
509
394
|
readdir: mockReaddir,
|
|
510
395
|
}));
|
|
511
396
|
const { loadPoliciesFromToml: load } = await import('./toml-loader.js');
|
|
512
397
|
const getPolicyTier = (_dir) => 1;
|
|
513
398
|
const result = await load(ApprovalMode.DEFAULT, ['/policies'], getPolicyTier);
|
|
514
|
-
expect(result.
|
|
515
|
-
|
|
516
|
-
expect(
|
|
517
|
-
expect(
|
|
518
|
-
expect(result.errors).toHaveLength(0);
|
|
399
|
+
expect(result.errors).toHaveLength(1);
|
|
400
|
+
const error = result.errors[0];
|
|
401
|
+
expect(error.errorType).toBe('file_read');
|
|
402
|
+
expect(error.message).toContain('Failed to read policy directory');
|
|
519
403
|
});
|
|
520
404
|
});
|
|
521
405
|
});
|