gogcli-mcp 4.5.0 → 4.5.2

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.
@@ -217,8 +217,7 @@ describe('runOrDiagnose', () => {
217
217
  // mask, and retrying it would just spend a second call to fail identically.
218
218
  it('does NOT retry an error that is not a rejected mask', async () => {
219
219
  vi.mocked(runner.run)
220
- .mockRejectedValueOnce(new Error('File not found'))
221
- .mockResolvedValueOnce('user@gmail.com');
220
+ .mockRejectedValueOnce(new Error('File not found'));
222
221
  const result = await runOrDiagnose(['drive', 'ls'], { fieldsMask: 'files(id)' });
223
222
  expect(result.isError).toBe(true);
224
223
  expect(runner.run).not.toHaveBeenNthCalledWith(2, ['drive', 'ls'], expect.anything());
@@ -260,34 +259,35 @@ describe('runOrDiagnose', () => {
260
259
  expect(result.content[0].text).toBe('user@gmail.com');
261
260
  });
262
261
 
263
- it('appends auth list on non-auth failure', async () => {
264
- vi.mocked(runner.run)
265
- .mockRejectedValueOnce(new Error('Doc not found'))
266
- .mockResolvedValueOnce('user@gmail.com');
262
+ // PRIV-1 (fleet-audit #1012): the account list is PII and only helps when
263
+ // the failure is about WHICH account is signed in. A "Doc not found" told the
264
+ // model every identity on the host for nothing — and cost a `gog auth list`
265
+ // spawn on every error path.
266
+ it('omits the account list on a non-auth failure and does not spawn auth list', async () => {
267
+ vi.mocked(runner.run).mockRejectedValueOnce(new Error('Doc not found'));
267
268
  const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
268
- expect(result.content[0].text).toBe(
269
- 'Error: Doc not found\n\nConfigured accounts:\nuser@gmail.com',
270
- );
269
+ expect(result.content[0].text).toBe('Error: Doc not found');
271
270
  // mcp-utils errorResult flags diagnosed failures for the client.
272
271
  expect(result.isError).toBe(true);
273
272
  expect(result.content[0].text).not.toContain('gog_auth_add');
273
+ expect(runner.run).toHaveBeenCalledTimes(1);
274
274
  });
275
275
 
276
276
  it('redacts scopes/subject/timestamps from the configured-accounts block', async () => {
277
277
  const authListJson = JSON.stringify({
278
278
  accounts: [{
279
- email: 'chris.c.hall@gmail.com',
279
+ email: 'user@example.com',
280
280
  subject: '109876543210987654321',
281
281
  scopes: ['https://www.googleapis.com/auth/gmail.modify'],
282
282
  created_at: '2026-01-02T03:04:05Z',
283
283
  }],
284
284
  });
285
285
  vi.mocked(runner.run)
286
- .mockRejectedValueOnce(new Error('refusing to delete gmail draft r123 without --force (non-interactive)'))
286
+ .mockRejectedValueOnce(new Error('Google API error (401 authError): invalid credentials'))
287
287
  .mockResolvedValueOnce(authListJson);
288
288
  const result = await runOrDiagnose(['gmail', 'drafts', 'delete', pos('r123')], {});
289
289
  const text = result.content[0].text;
290
- expect(text).toContain('Configured accounts:\nchris.c.hall@gmail.com');
290
+ expect(text).toContain('Configured accounts:\nuser@example.com');
291
291
  expect(text).not.toContain('scopes');
292
292
  expect(text).not.toContain('gmail.modify');
293
293
  expect(text).not.toContain('109876543210987654321');
@@ -296,10 +296,19 @@ describe('runOrDiagnose', () => {
296
296
 
297
297
  it('shows (none) when no accounts are configured', async () => {
298
298
  vi.mocked(runner.run)
299
- .mockRejectedValueOnce(new Error('Doc not found'))
299
+ .mockRejectedValueOnce(new Error('Request failed with status 401'))
300
300
  .mockResolvedValueOnce('{"accounts":[]}');
301
301
  const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
302
- expect(result.content[0].text).toBe('Error: Doc not found\n\nConfigured accounts:\n(none)');
302
+ expect(result.content[0].text).toContain('Error: Request failed with status 401\n\nConfigured accounts:\n(none)');
303
+ });
304
+
305
+ it('appends the account list on a dead refresh token even when a transient signal is present', async () => {
306
+ vi.mocked(runner.run)
307
+ .mockRejectedValueOnce(new Error('503: token has been expired or revoked'))
308
+ .mockResolvedValueOnce('user@example.com');
309
+ const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
310
+ expect(result.content[0].text).toContain('Configured accounts:\nuser@example.com');
311
+ expect(result.content[0].text).toContain('invalid_grant');
303
312
  });
304
313
 
305
314
  it('appends re-auth hint on 401 error', async () => {
@@ -345,8 +354,7 @@ describe('runOrDiagnose', () => {
345
354
  // `invalid_grant` is exempt from this and stays auth (below) — it is the
346
355
  // one signal that definitively means the refresh token is dead.
347
356
  vi.mocked(runner.run)
348
- .mockRejectedValueOnce(new Error('429 rateLimitExceeded: the access token expired mid-request, retry'))
349
- .mockResolvedValueOnce('user@gmail.com');
357
+ .mockRejectedValueOnce(new Error('429 rateLimitExceeded: the access token expired mid-request, retry'));
350
358
  const result = await runOrDiagnose(['sheets', 'get', pos('A1')], {});
351
359
  const text = result.content[0].text as string;
352
360
  expect(text).toContain('often transient');
@@ -374,8 +382,7 @@ describe('runOrDiagnose', () => {
374
382
  // exercised — and gog's real errors are frequently one long line.
375
383
  .mockRejectedValueOnce(
376
384
  new Error('page token accepted; the requested export link has expired and must be regenerated'),
377
- )
378
- .mockResolvedValueOnce('user@gmail.com');
385
+ );
379
386
  const result = await runOrDiagnose(['drive', pos('export'), pos('abc')], {});
380
387
  expect(result.content[0].text).not.toContain('gog_auth_add');
381
388
  });
@@ -415,10 +422,9 @@ describe('runOrDiagnose', () => {
415
422
  expect(result.content[0].text).not.toContain('Configured accounts');
416
423
  });
417
424
 
418
- it('returns plain error when auth list also fails on non-auth error', async () => {
425
+ it('returns plain error on a non-auth error (auth list is never consulted)', async () => {
419
426
  vi.mocked(runner.run)
420
- .mockRejectedValueOnce(new Error('Doc not found'))
421
- .mockRejectedValueOnce(new Error('auth list failed'));
427
+ .mockRejectedValueOnce(new Error('Doc not found'));
422
428
  const result = await runOrDiagnose(['docs', 'cat', pos('abc')], {});
423
429
  expect(result.content[0].text).toBe('Error: Doc not found');
424
430
  expect(result.isError).toBe(true);
@@ -427,8 +433,7 @@ describe('runOrDiagnose', () => {
427
433
 
428
434
  it('appends transient-retry hint on 429 error', async () => {
429
435
  vi.mocked(runner.run)
430
- .mockRejectedValueOnce(new Error('Request failed with status 429'))
431
- .mockResolvedValueOnce('user@gmail.com');
436
+ .mockRejectedValueOnce(new Error('Request failed with status 429'));
432
437
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
433
438
  expect(result.content[0].text).toContain('transient');
434
439
  expect(result.content[0].text).toContain('Retry');
@@ -438,8 +443,7 @@ describe('runOrDiagnose', () => {
438
443
  for (const status of [500, 502, 503, 504]) {
439
444
  vi.clearAllMocks();
440
445
  vi.mocked(runner.run)
441
- .mockRejectedValueOnce(new Error(`Request failed with status ${status}`))
442
- .mockResolvedValueOnce('user@gmail.com');
446
+ .mockRejectedValueOnce(new Error(`Request failed with status ${status}`));
443
447
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
444
448
  expect(result.content[0].text, `status ${status}`).toContain('transient');
445
449
  }
@@ -449,8 +453,7 @@ describe('runOrDiagnose', () => {
449
453
  for (const msg of ['Quota exceeded', 'rateLimitExceeded', 'userRateLimitExceeded']) {
450
454
  vi.clearAllMocks();
451
455
  vi.mocked(runner.run)
452
- .mockRejectedValueOnce(new Error(msg))
453
- .mockResolvedValueOnce('user@gmail.com');
456
+ .mockRejectedValueOnce(new Error(msg));
454
457
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
455
458
  expect(result.content[0].text, msg).toContain('transient');
456
459
  }
@@ -458,16 +461,14 @@ describe('runOrDiagnose', () => {
458
461
 
459
462
  it('appends transient-retry hint on DEADLINE_EXCEEDED error', async () => {
460
463
  vi.mocked(runner.run)
461
- .mockRejectedValueOnce(new Error('DEADLINE_EXCEEDED: context deadline exceeded'))
462
- .mockResolvedValueOnce('user@gmail.com');
464
+ .mockRejectedValueOnce(new Error('DEADLINE_EXCEEDED: context deadline exceeded'));
463
465
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
464
466
  expect(result.content[0].text).toContain('transient');
465
467
  });
466
468
 
467
469
  it('does not append transient hint on 404 error', async () => {
468
470
  vi.mocked(runner.run)
469
- .mockRejectedValueOnce(new Error('Request failed with status 404'))
470
- .mockResolvedValueOnce('user@gmail.com');
471
+ .mockRejectedValueOnce(new Error('Request failed with status 404'));
471
472
  const result = await runOrDiagnose(['sheets', 'get', pos('abc'), pos('A1')], {});
472
473
  expect(result.content[0].text).not.toContain('transient');
473
474
  });
@@ -481,10 +482,9 @@ describe('runOrDiagnose', () => {
481
482
  expect(result.content[0].text).not.toContain('transient');
482
483
  });
483
484
 
484
- it('keeps transient hint when auth list also fails', async () => {
485
+ it('keeps the transient hint and names no accounts on a transient failure', async () => {
485
486
  vi.mocked(runner.run)
486
- .mockRejectedValueOnce(new Error('Request failed with status 503'))
487
- .mockRejectedValueOnce(new Error('auth list failed'));
487
+ .mockRejectedValueOnce(new Error('Request failed with status 503'));
488
488
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
489
489
  expect(result.content[0].text).toContain('transient');
490
490
  expect(result.content[0].text).not.toContain('Configured accounts');
@@ -492,8 +492,7 @@ describe('runOrDiagnose', () => {
492
492
 
493
493
  it('appends grid-limit hint pointing at gog_sheets_insert', async () => {
494
494
  vi.mocked(runner.run)
495
- .mockRejectedValueOnce(new Error('Range (Sheet1!AP1:AW1) exceeds grid limits. Max rows: 1000, max columns: 41'))
496
- .mockResolvedValueOnce('user@gmail.com');
495
+ .mockRejectedValueOnce(new Error('Range (Sheet1!AP1:AW1) exceeds grid limits. Max rows: 1000, max columns: 41'));
497
496
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('AP1')], {});
498
497
  expect(result.content[0].text).toContain('exceeds grid limits');
499
498
  expect(result.content[0].text).toContain('gog_sheets_insert');
@@ -501,16 +500,14 @@ describe('runOrDiagnose', () => {
501
500
 
502
501
  it('does not append grid-limit hint on unrelated errors', async () => {
503
502
  vi.mocked(runner.run)
504
- .mockRejectedValueOnce(new Error('Spreadsheet not found'))
505
- .mockResolvedValueOnce('user@gmail.com');
503
+ .mockRejectedValueOnce(new Error('Spreadsheet not found'));
506
504
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
507
505
  expect(result.content[0].text).not.toContain('gog_sheets_insert');
508
506
  });
509
507
 
510
- it('keeps grid-limit hint when auth list also fails', async () => {
508
+ it('keeps the grid-limit hint and names no accounts on a grid failure', async () => {
511
509
  vi.mocked(runner.run)
512
- .mockRejectedValueOnce(new Error('exceeds grid limits. Max rows: 1000, max columns: 41'))
513
- .mockRejectedValueOnce(new Error('auth list failed'));
510
+ .mockRejectedValueOnce(new Error('exceeds grid limits. Max rows: 1000, max columns: 41'));
514
511
  const result = await runOrDiagnose(['sheets', 'update', pos('abc'), pos('A1')], {});
515
512
  expect(result.content[0].text).toContain('gog_sheets_insert');
516
513
  expect(result.content[0].text).not.toContain('Configured accounts');