gogcli-mcp 4.2.5 → 4.4.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +1340 -262
- package/dist/lib.js +1381 -260
- package/manifest.json +9 -2
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/arg-guard.ts +68 -0
- package/src/argv.ts +27 -0
- package/src/attachment-root.ts +111 -0
- package/src/attachments.ts +1 -0
- package/src/blob-upload.ts +4 -2
- package/src/dispatch-confirmation.ts +277 -0
- package/src/file-roots.ts +66 -0
- package/src/gmail-dispatch-guard.ts +70 -30
- package/src/gmail-results.ts +21 -2
- package/src/lib.ts +15 -0
- package/src/run-path-guard.ts +118 -0
- package/src/runner.ts +86 -18
- package/src/send-confirm-token.ts +167 -0
- package/src/tools/api.ts +60 -7
- package/src/tools/appscript.ts +12 -8
- package/src/tools/auth.ts +13 -3
- package/src/tools/calendar.ts +178 -15
- package/src/tools/chat.ts +94 -18
- package/src/tools/classroom.ts +110 -28
- package/src/tools/contacts.ts +5 -3
- package/src/tools/docs.ts +8 -6
- package/src/tools/drive.ts +96 -20
- package/src/tools/gmail.ts +175 -24
- package/src/tools/sheets.ts +10 -8
- package/src/tools/slides.ts +14 -9
- package/src/tools/tasks.ts +7 -5
- package/src/tools/utils.ts +52 -6
- package/tests/arg-guard.test.ts +80 -0
- package/tests/attachment-root.test.ts +130 -0
- package/tests/attachments.test.ts +8 -0
- package/tests/blob-upload.test.ts +4 -3
- package/tests/file-roots.test.ts +101 -0
- package/tests/gmail-dispatch-guard.test.ts +270 -11
- package/tests/gmail-results.test.ts +35 -1
- package/tests/run-path-guard.test.ts +142 -0
- package/tests/runner.test.ts +136 -0
- package/tests/send-confirm-token.test.ts +200 -0
- package/tests/tools/api.test.ts +74 -8
- package/tests/tools/appscript.test.ts +34 -8
- package/tests/tools/auth.test.ts +44 -17
- package/tests/tools/calendar.test.ts +33 -28
- package/tests/tools/chat.test.ts +50 -21
- package/tests/tools/classroom.test.ts +43 -39
- package/tests/tools/contacts.test.ts +3 -2
- package/tests/tools/dispatch-gates.test.ts +396 -0
- package/tests/tools/docs.test.ts +44 -15
- package/tests/tools/drive.test.ts +110 -20
- package/tests/tools/gmail-confirm-token.test.ts +274 -0
- package/tests/tools/gmail.test.ts +227 -29
- package/tests/tools/run-tool-examples.test.ts +69 -0
- package/tests/tools/run-vets.test.ts +131 -0
- package/tests/tools/sheets.test.ts +16 -15
- package/tests/tools/slides.test.ts +47 -11
- package/tests/tools/tasks.test.ts +7 -6
- package/tests/tools/utils.test.ts +32 -31
- package/vitest.config.ts +5 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import * as runner from '../../src/runner.js';
|
|
3
3
|
import { runOrDiagnose, pushPaginationFlags, formatAccountList, formatAuthHealth } from '../../src/tools/utils.js';
|
|
4
|
+
import { pos } from '../../src/argv.js';
|
|
4
5
|
|
|
5
6
|
// PARTIAL mock: only `run` is stubbed; everything else stays real.
|
|
6
7
|
vi.mock('../../src/runner.js', async (importOriginal) => ({
|
|
@@ -51,7 +52,7 @@ describe('formatAccountList', () => {
|
|
|
51
52
|
email: 'chris.c.hall@gmail.com',
|
|
52
53
|
subject: '109876543210987654321',
|
|
53
54
|
client: 'default',
|
|
54
|
-
services: ['gmail', 'drive'],
|
|
55
|
+
services: ['gmail', pos('drive')],
|
|
55
56
|
scopes: ['https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/drive'],
|
|
56
57
|
created_at: '2026-01-02T03:04:05Z',
|
|
57
58
|
},
|
|
@@ -94,7 +95,7 @@ describe('formatAccountList', () => {
|
|
|
94
95
|
describe('runOrDiagnose', () => {
|
|
95
96
|
it('returns output text on success (not flagged as an error)', async () => {
|
|
96
97
|
vi.mocked(runner.run).mockResolvedValue('{"ok":true}');
|
|
97
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
98
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
98
99
|
expect(result.content[0].text).toBe('{"ok":true}');
|
|
99
100
|
expect(result.isError).toBeUndefined();
|
|
100
101
|
});
|
|
@@ -106,7 +107,7 @@ describe('runOrDiagnose', () => {
|
|
|
106
107
|
it('leaves a lossless response byte-for-byte untouched', async () => {
|
|
107
108
|
const raw = '{\n "id": "m1",\n "internalDate": "1785209760000"\n}';
|
|
108
109
|
vi.mocked(runner.run).mockResolvedValue(raw);
|
|
109
|
-
const result = await runOrDiagnose(['gmail', 'raw', 'm1'], { lossless: true });
|
|
110
|
+
const result = await runOrDiagnose(['gmail', 'raw', pos('m1')], { lossless: true });
|
|
110
111
|
expect(result.content[0].text).toBe(raw);
|
|
111
112
|
});
|
|
112
113
|
|
|
@@ -182,7 +183,7 @@ describe('runOrDiagnose', () => {
|
|
|
182
183
|
it('does NOT minify a lossless response', async () => {
|
|
183
184
|
const raw = '{\n "id": "m1"\n}';
|
|
184
185
|
vi.mocked(runner.run).mockResolvedValue(raw);
|
|
185
|
-
const result = await runOrDiagnose(['gmail', 'raw', 'm1'], { lossless: true });
|
|
186
|
+
const result = await runOrDiagnose(['gmail', 'raw', pos('m1')], { lossless: true });
|
|
186
187
|
expect(result.content[0].text).toBe(raw);
|
|
187
188
|
});
|
|
188
189
|
|
|
@@ -230,7 +231,7 @@ describe('runOrDiagnose', () => {
|
|
|
230
231
|
vi.mocked(runner.run).mockResolvedValue(
|
|
231
232
|
'{\n "id": "f1",\n "thumbnailLink": "https://lh3.googleusercontent.com/x=s220",\n "webViewLink": "https://docs.google.com/d/f1"\n}',
|
|
232
233
|
);
|
|
233
|
-
const result = await runOrDiagnose(['drive', 'get', 'f1'], { stripMedia: true });
|
|
234
|
+
const result = await runOrDiagnose(['drive', 'get', pos('f1')], { stripMedia: true });
|
|
234
235
|
expect(result.content[0].text).toBe('{"id":"f1","webViewLink":"https://docs.google.com/d/f1"}');
|
|
235
236
|
});
|
|
236
237
|
|
|
@@ -240,14 +241,14 @@ describe('runOrDiagnose', () => {
|
|
|
240
241
|
vi.mocked(runner.run).mockResolvedValue(
|
|
241
242
|
JSON.stringify({ files: [{ id: 'f1', hasThumbnail: false, thumbnailLink: 'https://x/y=s220', webViewLink: 'https://docs/f1' }] }),
|
|
242
243
|
);
|
|
243
|
-
const result = await runOrDiagnose(['drive', 'search', 'q'], { stripMedia: true });
|
|
244
|
+
const result = await runOrDiagnose(['drive', 'search', pos('q')], { stripMedia: true });
|
|
244
245
|
const parsed = JSON.parse(result.content[0].text as string);
|
|
245
246
|
expect(parsed.files[0]).toEqual({ id: 'f1', hasThumbnail: false, webViewLink: 'https://docs/f1' });
|
|
246
247
|
});
|
|
247
248
|
|
|
248
249
|
it('does not strip media unless asked', async () => {
|
|
249
250
|
vi.mocked(runner.run).mockResolvedValue('{"thumbnailLink":"https://x/y=s220"}');
|
|
250
|
-
const result = await runOrDiagnose(['drive', 'get', 'f1'], {});
|
|
251
|
+
const result = await runOrDiagnose(['drive', 'get', pos('f1')], {});
|
|
251
252
|
expect(result.content[0].text).toBe('{"thumbnailLink":"https://x/y=s220"}');
|
|
252
253
|
});
|
|
253
254
|
|
|
@@ -263,7 +264,7 @@ describe('runOrDiagnose', () => {
|
|
|
263
264
|
vi.mocked(runner.run)
|
|
264
265
|
.mockRejectedValueOnce(new Error('Doc not found'))
|
|
265
266
|
.mockResolvedValueOnce('user@gmail.com');
|
|
266
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
267
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
267
268
|
expect(result.content[0].text).toBe(
|
|
268
269
|
'Error: Doc not found\n\nConfigured accounts:\nuser@gmail.com',
|
|
269
270
|
);
|
|
@@ -284,7 +285,7 @@ describe('runOrDiagnose', () => {
|
|
|
284
285
|
vi.mocked(runner.run)
|
|
285
286
|
.mockRejectedValueOnce(new Error('refusing to delete gmail draft r123 without --force (non-interactive)'))
|
|
286
287
|
.mockResolvedValueOnce(authListJson);
|
|
287
|
-
const result = await runOrDiagnose(['gmail', 'drafts', 'delete', 'r123'], {});
|
|
288
|
+
const result = await runOrDiagnose(['gmail', 'drafts', 'delete', pos('r123')], {});
|
|
288
289
|
const text = result.content[0].text;
|
|
289
290
|
expect(text).toContain('Configured accounts:\nchris.c.hall@gmail.com');
|
|
290
291
|
expect(text).not.toContain('scopes');
|
|
@@ -297,7 +298,7 @@ describe('runOrDiagnose', () => {
|
|
|
297
298
|
vi.mocked(runner.run)
|
|
298
299
|
.mockRejectedValueOnce(new Error('Doc not found'))
|
|
299
300
|
.mockResolvedValueOnce('{"accounts":[]}');
|
|
300
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
301
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
301
302
|
expect(result.content[0].text).toBe('Error: Doc not found\n\nConfigured accounts:\n(none)');
|
|
302
303
|
});
|
|
303
304
|
|
|
@@ -305,7 +306,7 @@ describe('runOrDiagnose', () => {
|
|
|
305
306
|
vi.mocked(runner.run)
|
|
306
307
|
.mockRejectedValueOnce(new Error('Request failed with status 401'))
|
|
307
308
|
.mockResolvedValueOnce('user@gmail.com');
|
|
308
|
-
const result = await runOrDiagnose(['docs', 'comments', 'list', 'abc'], {});
|
|
309
|
+
const result = await runOrDiagnose(['docs', 'comments', 'list', pos('abc')], {});
|
|
309
310
|
expect(result.content[0].text).toContain('Error: Request failed with status 401');
|
|
310
311
|
expect(result.content[0].text).toContain('Configured accounts:\nuser@gmail.com');
|
|
311
312
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
@@ -315,7 +316,7 @@ describe('runOrDiagnose', () => {
|
|
|
315
316
|
vi.mocked(runner.run)
|
|
316
317
|
.mockRejectedValueOnce(new Error('unauthorized access'))
|
|
317
318
|
.mockResolvedValueOnce('user@gmail.com');
|
|
318
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
319
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
319
320
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
320
321
|
});
|
|
321
322
|
|
|
@@ -323,7 +324,7 @@ describe('runOrDiagnose', () => {
|
|
|
323
324
|
vi.mocked(runner.run)
|
|
324
325
|
.mockRejectedValueOnce(new Error('token has been expired or revoked'))
|
|
325
326
|
.mockResolvedValueOnce('user@gmail.com');
|
|
326
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
327
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
327
328
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
328
329
|
});
|
|
329
330
|
|
|
@@ -331,7 +332,7 @@ describe('runOrDiagnose', () => {
|
|
|
331
332
|
vi.mocked(runner.run)
|
|
332
333
|
.mockRejectedValueOnce(new Error('invalid_grant'))
|
|
333
334
|
.mockResolvedValueOnce('user@gmail.com');
|
|
334
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
335
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
335
336
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
336
337
|
});
|
|
337
338
|
|
|
@@ -346,7 +347,7 @@ describe('runOrDiagnose', () => {
|
|
|
346
347
|
vi.mocked(runner.run)
|
|
347
348
|
.mockRejectedValueOnce(new Error('429 rateLimitExceeded: the access token expired mid-request, retry'))
|
|
348
349
|
.mockResolvedValueOnce('user@gmail.com');
|
|
349
|
-
const result = await runOrDiagnose(['sheets', 'get', 'A1'], {});
|
|
350
|
+
const result = await runOrDiagnose(['sheets', 'get', pos('A1')], {});
|
|
350
351
|
const text = result.content[0].text as string;
|
|
351
352
|
expect(text).toContain('often transient');
|
|
352
353
|
expect(text).not.toContain('gog_auth_add');
|
|
@@ -359,7 +360,7 @@ describe('runOrDiagnose', () => {
|
|
|
359
360
|
vi.mocked(runner.run)
|
|
360
361
|
.mockRejectedValueOnce(new Error('401 unauthorized (quota project unset)'))
|
|
361
362
|
.mockResolvedValueOnce('user@gmail.com');
|
|
362
|
-
const result = await runOrDiagnose(['sheets', 'get', 'A1'], {});
|
|
363
|
+
const result = await runOrDiagnose(['sheets', 'get', pos('A1')], {});
|
|
363
364
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
364
365
|
});
|
|
365
366
|
|
|
@@ -375,7 +376,7 @@ describe('runOrDiagnose', () => {
|
|
|
375
376
|
new Error('page token accepted; the requested export link has expired and must be regenerated'),
|
|
376
377
|
)
|
|
377
378
|
.mockResolvedValueOnce('user@gmail.com');
|
|
378
|
-
const result = await runOrDiagnose(['drive', 'export', 'abc'], {});
|
|
379
|
+
const result = await runOrDiagnose(['drive', pos('export'), pos('abc')], {});
|
|
379
380
|
expect(result.content[0].text).not.toContain('gog_auth_add');
|
|
380
381
|
});
|
|
381
382
|
|
|
@@ -383,7 +384,7 @@ describe('runOrDiagnose', () => {
|
|
|
383
384
|
vi.mocked(runner.run)
|
|
384
385
|
.mockRejectedValueOnce(new Error('oauth2: "invalid_grant" "Token has been expired or revoked."'))
|
|
385
386
|
.mockResolvedValueOnce('user@gmail.com');
|
|
386
|
-
const result = await runOrDiagnose(['gmail', 'search', 'is:unread'], {});
|
|
387
|
+
const result = await runOrDiagnose(['gmail', 'search', pos('is:unread')], {});
|
|
387
388
|
const text = result.content[0].text as string;
|
|
388
389
|
// plain-English cause
|
|
389
390
|
expect(text).toContain('7-day');
|
|
@@ -398,7 +399,7 @@ describe('runOrDiagnose', () => {
|
|
|
398
399
|
vi.mocked(runner.run)
|
|
399
400
|
.mockRejectedValueOnce(new Error('Request failed with status 401'))
|
|
400
401
|
.mockResolvedValueOnce('user@gmail.com');
|
|
401
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
402
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
402
403
|
const text = result.content[0].text as string;
|
|
403
404
|
expect(text).toContain('gog_auth_add');
|
|
404
405
|
expect(text).not.toContain('In production');
|
|
@@ -408,7 +409,7 @@ describe('runOrDiagnose', () => {
|
|
|
408
409
|
vi.mocked(runner.run)
|
|
409
410
|
.mockRejectedValueOnce(new Error('Request failed with status 401'))
|
|
410
411
|
.mockRejectedValueOnce(new Error('auth list failed'));
|
|
411
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
412
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
412
413
|
expect(result.content[0].text).toContain('Error: Request failed with status 401');
|
|
413
414
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
414
415
|
expect(result.content[0].text).not.toContain('Configured accounts');
|
|
@@ -418,7 +419,7 @@ describe('runOrDiagnose', () => {
|
|
|
418
419
|
vi.mocked(runner.run)
|
|
419
420
|
.mockRejectedValueOnce(new Error('Doc not found'))
|
|
420
421
|
.mockRejectedValueOnce(new Error('auth list failed'));
|
|
421
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
422
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
422
423
|
expect(result.content[0].text).toBe('Error: Doc not found');
|
|
423
424
|
expect(result.isError).toBe(true);
|
|
424
425
|
expect(result.content[0].text).not.toContain('gog_auth_add');
|
|
@@ -428,7 +429,7 @@ describe('runOrDiagnose', () => {
|
|
|
428
429
|
vi.mocked(runner.run)
|
|
429
430
|
.mockRejectedValueOnce(new Error('Request failed with status 429'))
|
|
430
431
|
.mockResolvedValueOnce('user@gmail.com');
|
|
431
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
432
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
432
433
|
expect(result.content[0].text).toContain('transient');
|
|
433
434
|
expect(result.content[0].text).toContain('Retry');
|
|
434
435
|
});
|
|
@@ -439,7 +440,7 @@ describe('runOrDiagnose', () => {
|
|
|
439
440
|
vi.mocked(runner.run)
|
|
440
441
|
.mockRejectedValueOnce(new Error(`Request failed with status ${status}`))
|
|
441
442
|
.mockResolvedValueOnce('user@gmail.com');
|
|
442
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
443
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
443
444
|
expect(result.content[0].text, `status ${status}`).toContain('transient');
|
|
444
445
|
}
|
|
445
446
|
});
|
|
@@ -450,7 +451,7 @@ describe('runOrDiagnose', () => {
|
|
|
450
451
|
vi.mocked(runner.run)
|
|
451
452
|
.mockRejectedValueOnce(new Error(msg))
|
|
452
453
|
.mockResolvedValueOnce('user@gmail.com');
|
|
453
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
454
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
454
455
|
expect(result.content[0].text, msg).toContain('transient');
|
|
455
456
|
}
|
|
456
457
|
});
|
|
@@ -459,7 +460,7 @@ describe('runOrDiagnose', () => {
|
|
|
459
460
|
vi.mocked(runner.run)
|
|
460
461
|
.mockRejectedValueOnce(new Error('DEADLINE_EXCEEDED: context deadline exceeded'))
|
|
461
462
|
.mockResolvedValueOnce('user@gmail.com');
|
|
462
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
463
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
463
464
|
expect(result.content[0].text).toContain('transient');
|
|
464
465
|
});
|
|
465
466
|
|
|
@@ -467,7 +468,7 @@ describe('runOrDiagnose', () => {
|
|
|
467
468
|
vi.mocked(runner.run)
|
|
468
469
|
.mockRejectedValueOnce(new Error('Request failed with status 404'))
|
|
469
470
|
.mockResolvedValueOnce('user@gmail.com');
|
|
470
|
-
const result = await runOrDiagnose(['sheets', 'get', 'abc', 'A1'], {});
|
|
471
|
+
const result = await runOrDiagnose(['sheets', 'get', pos('abc'), pos('A1')], {});
|
|
471
472
|
expect(result.content[0].text).not.toContain('transient');
|
|
472
473
|
});
|
|
473
474
|
|
|
@@ -475,7 +476,7 @@ describe('runOrDiagnose', () => {
|
|
|
475
476
|
vi.mocked(runner.run)
|
|
476
477
|
.mockRejectedValueOnce(new Error('Request failed with status 401'))
|
|
477
478
|
.mockResolvedValueOnce('user@gmail.com');
|
|
478
|
-
const result = await runOrDiagnose(['docs', 'cat', 'abc'], {});
|
|
479
|
+
const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
|
|
479
480
|
expect(result.content[0].text).toContain('gog_auth_add');
|
|
480
481
|
expect(result.content[0].text).not.toContain('transient');
|
|
481
482
|
});
|
|
@@ -484,7 +485,7 @@ describe('runOrDiagnose', () => {
|
|
|
484
485
|
vi.mocked(runner.run)
|
|
485
486
|
.mockRejectedValueOnce(new Error('Request failed with status 503'))
|
|
486
487
|
.mockRejectedValueOnce(new Error('auth list failed'));
|
|
487
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
488
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
488
489
|
expect(result.content[0].text).toContain('transient');
|
|
489
490
|
expect(result.content[0].text).not.toContain('Configured accounts');
|
|
490
491
|
});
|
|
@@ -493,7 +494,7 @@ describe('runOrDiagnose', () => {
|
|
|
493
494
|
vi.mocked(runner.run)
|
|
494
495
|
.mockRejectedValueOnce(new Error('Range (Sheet1!AP1:AW1) exceeds grid limits. Max rows: 1000, max columns: 41'))
|
|
495
496
|
.mockResolvedValueOnce('user@gmail.com');
|
|
496
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'AP1'], {});
|
|
497
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('AP1')], {});
|
|
497
498
|
expect(result.content[0].text).toContain('exceeds grid limits');
|
|
498
499
|
expect(result.content[0].text).toContain('gog_sheets_insert');
|
|
499
500
|
});
|
|
@@ -502,7 +503,7 @@ describe('runOrDiagnose', () => {
|
|
|
502
503
|
vi.mocked(runner.run)
|
|
503
504
|
.mockRejectedValueOnce(new Error('Spreadsheet not found'))
|
|
504
505
|
.mockResolvedValueOnce('user@gmail.com');
|
|
505
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
506
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
506
507
|
expect(result.content[0].text).not.toContain('gog_sheets_insert');
|
|
507
508
|
});
|
|
508
509
|
|
|
@@ -510,7 +511,7 @@ describe('runOrDiagnose', () => {
|
|
|
510
511
|
vi.mocked(runner.run)
|
|
511
512
|
.mockRejectedValueOnce(new Error('exceeds grid limits. Max rows: 1000, max columns: 41'))
|
|
512
513
|
.mockRejectedValueOnce(new Error('auth list failed'));
|
|
513
|
-
const result = await runOrDiagnose(['sheets', 'update', 'abc', 'A1'], {});
|
|
514
|
+
const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
|
|
514
515
|
expect(result.content[0].text).toContain('gog_sheets_insert');
|
|
515
516
|
expect(result.content[0].text).not.toContain('Configured accounts');
|
|
516
517
|
});
|
package/vitest.config.ts
CHANGED
|
@@ -20,6 +20,11 @@ export default defineConfig({
|
|
|
20
20
|
GOG_ACCOUNT: '',
|
|
21
21
|
GOG_PATH: '',
|
|
22
22
|
GOG_READONLY: '',
|
|
23
|
+
// The arg-shape tests pass arbitrary server paths (/tmp/shot.png,
|
|
24
|
+
// /path/to/file); confinement is exercised explicitly by the tests that
|
|
25
|
+
// narrow GOG_FILE_ROOTS themselves (tests/file-roots.test.ts and the
|
|
26
|
+
// per-tool "refuses a path outside" cases).
|
|
27
|
+
GOG_FILE_ROOTS: '/',
|
|
23
28
|
},
|
|
24
29
|
coverage: {
|
|
25
30
|
provider: 'v8',
|