gogcli-mcp-gmail 2.22.0 → 2.23.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,81 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { registerExtraGmailTools } from '../../src/tools/gmail-extra.js';
3
+ import * as lib from '../../../gogcli-mcp/src/lib.js';
4
+ import { createTestHarness, type TestHarness } from '@chrischall/mcp-utils/test';
5
+ import { rawTextResult, errorResult } from '@chrischall/mcp-utils';
6
+
7
+ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
8
+ const actual = await importOriginal<typeof lib>();
9
+ return { ...actual, run: vi.fn(), runOrDiagnose: vi.fn(), diagnose: vi.fn() };
10
+ });
11
+
12
+ let harness: TestHarness;
13
+ beforeEach(async () => {
14
+ vi.clearAllMocks();
15
+ vi.mocked(lib.run).mockResolvedValue('{}');
16
+ vi.mocked(lib.diagnose).mockResolvedValue(errorResult('diagnosed'));
17
+ harness = await createTestHarness(registerExtraGmailTools);
18
+ });
19
+
20
+ /**
21
+ * #261: `returnFull` is a convenience READ performed after an acknowledged
22
+ * write. When that read 404s — an Apple Mail fork between the write and the
23
+ * read is exactly the case this file exists for — the write has still happened.
24
+ *
25
+ * The failed read used to REPLACE the successful write result, so every
26
+ * downstream consumer read the write as failed: the fork diagnosis fired, and
27
+ * the content-loss note told the caller "NOTHING WAS SAVED" about content that
28
+ * had just been saved. For someone about to tidy up the sibling copy, that is
29
+ * the most destructive thing this tool could say.
30
+ */
31
+ describe('a failed returnFull re-read does not erase a successful write', () => {
32
+ const ack = JSON.stringify({ draftId: 'r123', id: 'r123', message: { id: 'm1', threadId: 't1' } });
33
+
34
+ it('reports the write as succeeded, and says which half failed', async () => {
35
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: readonly string[]) =>
36
+ args[2] === 'update' ? rawTextResult(ack) : errorResult('Google API error (404 notFound)'));
37
+
38
+ const res = await harness.callTool('gog_gmail_drafts_update', { draftId: 'r123', subject: 'S', body: 'x', returnFull: true });
39
+ const text = res.content.map((c: any) => c.text).join('\n');
40
+
41
+ expect(res.isError).not.toBe(true);
42
+ expect(text).toContain('SUCCEEDED');
43
+ expect(text).toMatch(/read-back requested by returnFull could not be performed/i);
44
+ expect(text).not.toMatch(/NOTHING WAS SAVED/);
45
+ });
46
+
47
+ it('still adopts the re-read when it works', async () => {
48
+ const full = JSON.stringify({ id: 'r123', payload: { headers: [] } });
49
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: readonly string[]) =>
50
+ rawTextResult(args[2] === 'update' ? ack : full));
51
+
52
+ const res = await harness.callTool('gog_gmail_drafts_update', { draftId: 'r123', subject: 'S', body: 'x', returnFull: true });
53
+ expect(res.content.map((c: any) => c.text).join('\n')).toContain('payload');
54
+ });
55
+
56
+ it('still reports a genuinely failed WRITE as an error', async () => {
57
+ // The guard must not swallow a real failure: here the update itself fails.
58
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(errorResult('Google API error (404 notFound)'));
59
+ const res = await harness.callTool('gog_gmail_drafts_update', { draftId: 'r123', subject: 'S', body: 'x', returnFull: true });
60
+ expect(res.isError).toBe(true);
61
+ expect(res.content.map((c: any) => c.text).join('\n')).not.toContain('SUCCEEDED');
62
+ });
63
+ });
64
+
65
+ /**
66
+ * #261: `enrich` built its `messages search in:drafts` without the caller's
67
+ * `page` token, so enrich+page searched page 1 while the list was on page N —
68
+ * an extra gog spawn that joined ZERO rows and still reported applied:true.
69
+ */
70
+ describe('drafts_list enrich honours the caller page token', () => {
71
+ it('passes page through to the enrichment search', async () => {
72
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult(JSON.stringify({ drafts: [{ id: 'r1', messageId: 'm1' }] })));
73
+ vi.mocked(lib.run).mockResolvedValue(JSON.stringify({ messages: [{ id: 'm1', subject: 'S' }] }));
74
+
75
+ await harness.callTool('gog_gmail_drafts_list', { enrich: true, page: 'TOKEN123' });
76
+
77
+ const search = vi.mocked(lib.run).mock.calls.map((c) => c[0] as string[]).find((a) => a[2] === 'search');
78
+ expect(search).toBeDefined();
79
+ expect(search).toContain('--page=TOKEN123');
80
+ });
81
+ });