gemini-executor 0.1.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.
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Command Building Tests for Gemini Executor
3
+ *
4
+ * Tests the command construction logic for Gemini CLI,
5
+ * ensuring proper formatting, escaping, and option handling.
6
+ */
7
+
8
+ import { GeminiConfig, ExecutionOptions } from '../index';
9
+
10
+ describe('Command Building Tests', () => {
11
+ const defaultConfig: GeminiConfig = {
12
+ cliPath: '/opt/homebrew/bin/gemini',
13
+ defaultModel: 'gemini-2.0-flash',
14
+ maxRetries: 3,
15
+ timeout: 120000,
16
+ yolo: true
17
+ };
18
+
19
+ describe('Basic Command Construction', () => {
20
+ test('should build simple command with query', () => {
21
+ const options: ExecutionOptions = {
22
+ query: 'Hello World'
23
+ };
24
+
25
+ const command = buildCommand(options, defaultConfig);
26
+
27
+ expect(command).toContain('/opt/homebrew/bin/gemini');
28
+ expect(command).toContain('"Hello World"');
29
+ });
30
+
31
+ test('should include yolo flag by default', () => {
32
+ const options: ExecutionOptions = {
33
+ query: 'Test query'
34
+ };
35
+
36
+ const command = buildCommand(options, defaultConfig);
37
+
38
+ expect(command).toContain('-y');
39
+ });
40
+
41
+ test('should not include yolo flag when disabled', () => {
42
+ const options: ExecutionOptions = {
43
+ query: 'Test query'
44
+ };
45
+
46
+ const config = { ...defaultConfig, yolo: false };
47
+ const command = buildCommand(options, config);
48
+
49
+ expect(command).not.toContain('-y');
50
+ });
51
+
52
+ test('should include model flag', () => {
53
+ const options: ExecutionOptions = {
54
+ query: 'Test query',
55
+ model: 'gemini-2.0-flash-thinking-exp'
56
+ };
57
+
58
+ const command = buildCommand(options, defaultConfig);
59
+
60
+ expect(command).toContain('-m gemini-2.0-flash-thinking-exp');
61
+ });
62
+
63
+ test('should use default model if not specified', () => {
64
+ const options: ExecutionOptions = {
65
+ query: 'Test query'
66
+ };
67
+
68
+ const command = buildCommand(options, defaultConfig);
69
+
70
+ expect(command).toContain('-m gemini-2.0-flash');
71
+ });
72
+ });
73
+
74
+ describe('Output Format Options', () => {
75
+ test('should not include output flag for text format', () => {
76
+ const options: ExecutionOptions = {
77
+ query: 'Test query',
78
+ outputFormat: 'text'
79
+ };
80
+
81
+ const command = buildCommand(options, defaultConfig);
82
+
83
+ expect(command).not.toContain('-o');
84
+ });
85
+
86
+ test('should include output flag for json format', () => {
87
+ const options: ExecutionOptions = {
88
+ query: 'Test query',
89
+ outputFormat: 'json'
90
+ };
91
+
92
+ const command = buildCommand(options, defaultConfig);
93
+
94
+ expect(command).toContain('-o json');
95
+ });
96
+
97
+ test('should include output flag for stream-json format', () => {
98
+ const options: ExecutionOptions = {
99
+ query: 'Test query',
100
+ outputFormat: 'stream-json'
101
+ };
102
+
103
+ const command = buildCommand(options, defaultConfig);
104
+
105
+ expect(command).toContain('-o stream-json');
106
+ });
107
+ });
108
+
109
+ describe('Interactive Mode', () => {
110
+ test('should include interactive flag when enabled', () => {
111
+ const options: ExecutionOptions = {
112
+ query: 'Test query',
113
+ interactive: true
114
+ };
115
+
116
+ const command = buildCommand(options, defaultConfig);
117
+
118
+ expect(command).toContain('-i');
119
+ });
120
+
121
+ test('should not include yolo flag in interactive mode', () => {
122
+ const options: ExecutionOptions = {
123
+ query: 'Test query',
124
+ interactive: true
125
+ };
126
+
127
+ const command = buildCommand(options, defaultConfig);
128
+
129
+ expect(command).not.toContain('-y');
130
+ expect(command).toContain('-i');
131
+ });
132
+ });
133
+
134
+ describe('File References', () => {
135
+ test('should include file references in query', () => {
136
+ const options: ExecutionOptions = {
137
+ query: 'Analyze these files',
138
+ files: ['/path/to/file1.txt', '/path/to/file2.txt']
139
+ };
140
+
141
+ const command = buildCommand(options, defaultConfig);
142
+
143
+ expect(command).toContain('file1.txt');
144
+ expect(command).toContain('file2.txt');
145
+ });
146
+
147
+ test('should handle single file reference', () => {
148
+ const options: ExecutionOptions = {
149
+ query: 'Analyze this file',
150
+ files: ['/path/to/single.txt']
151
+ };
152
+
153
+ const command = buildCommand(options, defaultConfig);
154
+
155
+ expect(command).toContain('single.txt');
156
+ });
157
+
158
+ test('should handle empty file array', () => {
159
+ const options: ExecutionOptions = {
160
+ query: 'Test query',
161
+ files: []
162
+ };
163
+
164
+ const command = buildCommand(options, defaultConfig);
165
+
166
+ expect(command).toContain('"Test query"');
167
+ expect(command).not.toContain('Files to analyze:');
168
+ });
169
+ });
170
+
171
+ describe('Query Escaping', () => {
172
+ test('should escape double quotes in query', () => {
173
+ const options: ExecutionOptions = {
174
+ query: 'Say "hello world"'
175
+ };
176
+
177
+ const command = buildCommand(options, defaultConfig);
178
+
179
+ expect(command).toContain('\\"hello world\\"');
180
+ });
181
+
182
+ test('should handle special characters safely', () => {
183
+ const options: ExecutionOptions = {
184
+ query: 'Test $ symbols & stuff'
185
+ };
186
+
187
+ const command = buildCommand(options, defaultConfig);
188
+
189
+ // Special characters should be removed by sanitization
190
+ expect(command).not.toContain('$');
191
+ expect(command).not.toContain('&');
192
+ });
193
+
194
+ test('should handle multi-line queries', () => {
195
+ const options: ExecutionOptions = {
196
+ query: 'Line 1\nLine 2\nLine 3'
197
+ };
198
+
199
+ const command = buildCommand(options, defaultConfig);
200
+
201
+ expect(command).toContain('Line 1');
202
+ expect(command).toContain('Line 2');
203
+ expect(command).toContain('Line 3');
204
+ });
205
+
206
+ test('should handle empty lines in query', () => {
207
+ const options: ExecutionOptions = {
208
+ query: 'Line 1\n\nLine 3'
209
+ };
210
+
211
+ const result = buildCommand(options, defaultConfig);
212
+ expect(result).toBeTruthy();
213
+ });
214
+ });
215
+
216
+ describe('Complex Command Scenarios', () => {
217
+ test('should build complex command with all options', () => {
218
+ const options: ExecutionOptions = {
219
+ query: 'Analyze project architecture',
220
+ model: 'gemini-2.0-flash-thinking-exp',
221
+ outputFormat: 'json',
222
+ files: ['/path/to/project/src']
223
+ };
224
+
225
+ const command = buildCommand(options, defaultConfig);
226
+
227
+ expect(command).toContain('-m gemini-2.0-flash-thinking-exp');
228
+ expect(command).toContain('-o json');
229
+ expect(command).toContain('-y');
230
+ expect(command).toContain('Analyze project architecture');
231
+ expect(command).toContain('src');
232
+ });
233
+
234
+ test('should handle very long queries', () => {
235
+ const longQuery = 'a'.repeat(5000);
236
+ const options: ExecutionOptions = {
237
+ query: longQuery
238
+ };
239
+
240
+ const command = buildCommand(options, defaultConfig);
241
+
242
+ expect(command.length).toBeGreaterThan(5000);
243
+ expect(command).toContain('a'.repeat(100)); // Check a portion
244
+ });
245
+
246
+ test('should handle Unicode characters', () => {
247
+ const options: ExecutionOptions = {
248
+ query: 'こんにちは世界 🌍 مرحبا'
249
+ };
250
+
251
+ const command = buildCommand(options, defaultConfig);
252
+
253
+ expect(command).toContain('こんにちは世界');
254
+ expect(command).toContain('🌍');
255
+ expect(command).toContain('مرحبا');
256
+ });
257
+ });
258
+
259
+ describe('Custom CLI Path', () => {
260
+ test('should use custom CLI path', () => {
261
+ const options: ExecutionOptions = {
262
+ query: 'Test query'
263
+ };
264
+
265
+ const config = { ...defaultConfig, cliPath: '/custom/path/gemini' };
266
+ const command = buildCommand(options, config);
267
+
268
+ expect(command).toContain('/custom/path/gemini');
269
+ expect(command).not.toContain('/opt/homebrew/bin/gemini');
270
+ });
271
+
272
+ test('should handle Windows-style paths', () => {
273
+ const options: ExecutionOptions = {
274
+ query: 'Test query'
275
+ };
276
+
277
+ const config = { ...defaultConfig, cliPath: 'C:\\Program Files\\gemini\\gemini.exe' };
278
+ const command = buildCommand(options, config);
279
+
280
+ expect(command).toContain('gemini.exe');
281
+ });
282
+ });
283
+
284
+ describe('Edge Cases', () => {
285
+ test('should handle query with only spaces', () => {
286
+ const options: ExecutionOptions = {
287
+ query: ' '
288
+ };
289
+
290
+ // Sanitization preserves spaces (validation layer handles empty checks)
291
+ const command = buildCommand(options, defaultConfig);
292
+ expect(command).toContain('" "'); // Spaces preserved
293
+ });
294
+
295
+ test('should handle null bytes in query', () => {
296
+ const options: ExecutionOptions = {
297
+ query: 'test\x00malicious'
298
+ };
299
+
300
+ const command = buildCommand(options, defaultConfig);
301
+ // Null bytes should be handled safely
302
+ expect(command).toBeTruthy();
303
+ });
304
+
305
+ test('should handle extremely long file paths', () => {
306
+ const longPath = '/very/long/path/' + 'a'.repeat(1000) + '/file.txt';
307
+ const options: ExecutionOptions = {
308
+ query: 'Analyze file',
309
+ files: [longPath]
310
+ };
311
+
312
+ const command = buildCommand(options, defaultConfig);
313
+ expect(command).toBeTruthy();
314
+ });
315
+ });
316
+ });
317
+
318
+ // Helper function (should be exported from index.ts)
319
+ function buildCommand(options: ExecutionOptions, config: GeminiConfig): string {
320
+ const parts: string[] = [config.cliPath];
321
+
322
+ // Add model flag
323
+ if (options.model || config.defaultModel) {
324
+ parts.push('-m', options.model || config.defaultModel);
325
+ }
326
+
327
+ // Add output format flag
328
+ if (options.outputFormat && options.outputFormat !== 'text') {
329
+ parts.push('-o', options.outputFormat);
330
+ }
331
+
332
+ // Add yolo flag for non-interactive mode
333
+ if (config.yolo && !options.interactive) {
334
+ parts.push('-y');
335
+ }
336
+
337
+ // Add interactive flag
338
+ if (options.interactive) {
339
+ parts.push('-i');
340
+ }
341
+
342
+ // Sanitize query
343
+ let query = sanitizeInput(options.query);
344
+
345
+ // Add file references in the prompt
346
+ if (options.files && options.files.length > 0) {
347
+ const fileList = options.files.join(', ');
348
+ query = `${query}\n\nFiles to analyze: ${fileList}`;
349
+ }
350
+
351
+ // Add the query (properly quoted)
352
+ parts.push(`"${query}"`);
353
+
354
+ return parts.join(' ');
355
+ }
356
+
357
+ function sanitizeInput(input: string): string {
358
+ return input
359
+ .replace(/[;&|`$()<>]/g, '')
360
+ .replace(/\\/g, '\\\\')
361
+ .replace(/"/g, '\\"');
362
+ }