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
package/tests/tools/auth.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { registerAuthTools, authToolsFor } from '../../src/tools/auth.js';
|
|
3
3
|
import * as runner from '../../src/runner.js';
|
|
4
4
|
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
import { pos } from '../../src/argv.js';
|
|
5
6
|
|
|
6
7
|
vi.mock('../../src/runner.js');
|
|
7
8
|
|
|
@@ -108,7 +109,7 @@ describe('gog_auth_add', () => {
|
|
|
108
109
|
const harness = await setupHandlers();
|
|
109
110
|
const result = await harness.callTool('gog_auth_add', { email: 'user@gmail.com' });
|
|
110
111
|
expect(runner.run).toHaveBeenCalledWith(
|
|
111
|
-
['auth', 'add', 'user@gmail.com', '--services', 'all'],
|
|
112
|
+
['auth', 'add', pos('user@gmail.com'), '--services', 'all'],
|
|
112
113
|
{ interactive: true, timeout: 300_000 },
|
|
113
114
|
);
|
|
114
115
|
expect(result.content[0].text).toBe('Authorization successful for user@gmail.com');
|
|
@@ -119,7 +120,7 @@ describe('gog_auth_add', () => {
|
|
|
119
120
|
const harness = await setupHandlers();
|
|
120
121
|
await harness.callTool('gog_auth_add', { email: 'user@gmail.com', services: 'sheets,gmail' });
|
|
121
122
|
expect(runner.run).toHaveBeenCalledWith(
|
|
122
|
-
['auth', 'add', 'user@gmail.com', '--services', 'sheets,gmail'],
|
|
123
|
+
['auth', 'add', pos('user@gmail.com'), '--services', 'sheets,gmail'],
|
|
123
124
|
{ interactive: true, timeout: 300_000 },
|
|
124
125
|
);
|
|
125
126
|
});
|
|
@@ -137,7 +138,7 @@ describe('gog_auth_add', () => {
|
|
|
137
138
|
extraScopes: 'https://www.googleapis.com/auth/bigquery.readonly',
|
|
138
139
|
});
|
|
139
140
|
expect(runner.run).toHaveBeenCalledWith(
|
|
140
|
-
['auth', 'add', 'user@gmail.com', '--services', 'sheets',
|
|
141
|
+
['auth', 'add', pos('user@gmail.com'), '--services', 'sheets',
|
|
141
142
|
'--extra-scopes=https://www.googleapis.com/auth/bigquery.readonly', '--force-consent'],
|
|
142
143
|
{ interactive: true, timeout: 300_000 },
|
|
143
144
|
);
|
|
@@ -148,7 +149,7 @@ describe('gog_auth_add', () => {
|
|
|
148
149
|
const harness = await setupHandlers();
|
|
149
150
|
await harness.callTool('gog_auth_add', { email: 'user@gmail.com' });
|
|
150
151
|
expect(runner.run).toHaveBeenCalledWith(
|
|
151
|
-
['auth', 'add', 'user@gmail.com', '--services', 'all'],
|
|
152
|
+
['auth', 'add', pos('user@gmail.com'), '--services', 'all'],
|
|
152
153
|
{ interactive: true, timeout: 300_000 },
|
|
153
154
|
);
|
|
154
155
|
});
|
|
@@ -229,7 +230,7 @@ describe('gog_auth_add_url', () => {
|
|
|
229
230
|
const harness = await setupHandlers();
|
|
230
231
|
const result = await harness.callTool('gog_auth_add_url', { email: 'user@gmail.com' });
|
|
231
232
|
expect(runner.run).toHaveBeenCalledWith(
|
|
232
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '1', '--services', 'all', '--force-consent'],
|
|
233
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '1', '--services', 'all', '--force-consent'],
|
|
233
234
|
{ redactMode: 'tokens' },
|
|
234
235
|
);
|
|
235
236
|
expect(result.content[0].text).toContain('accounts.google.com');
|
|
@@ -240,7 +241,7 @@ describe('gog_auth_add_url', () => {
|
|
|
240
241
|
const harness = await setupHandlers();
|
|
241
242
|
await harness.callTool('gog_auth_add_url', { email: 'user@gmail.com', services: 'gmail,drive' });
|
|
242
243
|
expect(runner.run).toHaveBeenCalledWith(
|
|
243
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
|
|
244
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
|
|
244
245
|
{ redactMode: 'tokens' },
|
|
245
246
|
);
|
|
246
247
|
});
|
|
@@ -254,7 +255,7 @@ describe('gog_auth_add_url', () => {
|
|
|
254
255
|
extraScopes: 'https://www.googleapis.com/auth/bigquery.readonly',
|
|
255
256
|
});
|
|
256
257
|
expect(runner.run).toHaveBeenCalledWith(
|
|
257
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '1', '--services', 'sheets', '--force-consent',
|
|
258
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '1', '--services', 'sheets', '--force-consent',
|
|
258
259
|
'--extra-scopes=https://www.googleapis.com/auth/bigquery.readonly'],
|
|
259
260
|
{ redactMode: 'tokens' },
|
|
260
261
|
);
|
|
@@ -277,7 +278,7 @@ describe('gog_auth_add_complete', () => {
|
|
|
277
278
|
redirectUrl: 'http://127.0.0.1:59436/oauth2/callback?code=abc&state=xyz',
|
|
278
279
|
});
|
|
279
280
|
expect(runner.run).toHaveBeenCalledWith(
|
|
280
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '2', '--auth-url',
|
|
281
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '2', '--auth-url',
|
|
281
282
|
'http://127.0.0.1:59436/oauth2/callback?code=abc&state=xyz', '--services', 'all', '--force-consent'],
|
|
282
283
|
);
|
|
283
284
|
expect(result.content[0].text).toContain('stored');
|
|
@@ -292,7 +293,7 @@ describe('gog_auth_add_complete', () => {
|
|
|
292
293
|
services: 'gmail,drive',
|
|
293
294
|
});
|
|
294
295
|
expect(runner.run).toHaveBeenCalledWith(
|
|
295
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '2', '--auth-url',
|
|
296
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '2', '--auth-url',
|
|
296
297
|
'http://127.0.0.1/cb?code=c&state=s', '--services', 'gmail,drive', '--force-consent'],
|
|
297
298
|
);
|
|
298
299
|
});
|
|
@@ -307,7 +308,7 @@ describe('gog_auth_add_complete', () => {
|
|
|
307
308
|
extraScopes: 'https://www.googleapis.com/auth/bigquery.readonly',
|
|
308
309
|
});
|
|
309
310
|
expect(runner.run).toHaveBeenCalledWith(
|
|
310
|
-
['auth', 'add', 'user@gmail.com', '--remote', '--step', '2', '--auth-url',
|
|
311
|
+
['auth', 'add', pos('user@gmail.com'), '--remote', '--step', '2', '--auth-url',
|
|
311
312
|
'http://127.0.0.1/cb?code=c&state=s', '--services', 'sheets', '--force-consent',
|
|
312
313
|
'--extra-scopes=https://www.googleapis.com/auth/bigquery.readonly'],
|
|
313
314
|
);
|
|
@@ -332,7 +333,7 @@ describe('authToolsFor (least-privilege default services)', () => {
|
|
|
332
333
|
const harness = await gmailHarness();
|
|
333
334
|
await harness.callTool('gog_auth_add_url', { email: 'u@x.com' });
|
|
334
335
|
expect(runner.run).toHaveBeenCalledWith(
|
|
335
|
-
['auth', 'add', 'u@x.com', '--remote', '--step', '1', '--services', 'gmail', '--force-consent'],
|
|
336
|
+
['auth', 'add', pos('u@x.com'), '--remote', '--step', '1', '--services', 'gmail', '--force-consent'],
|
|
336
337
|
{ redactMode: 'tokens' },
|
|
337
338
|
);
|
|
338
339
|
});
|
|
@@ -342,7 +343,7 @@ describe('authToolsFor (least-privilege default services)', () => {
|
|
|
342
343
|
const harness = await gmailHarness();
|
|
343
344
|
await harness.callTool('gog_auth_add', { email: 'u@x.com' });
|
|
344
345
|
expect(runner.run).toHaveBeenCalledWith(
|
|
345
|
-
['auth', 'add', 'u@x.com', '--services', 'gmail'],
|
|
346
|
+
['auth', 'add', pos('u@x.com'), '--services', 'gmail'],
|
|
346
347
|
{ interactive: true, timeout: 300_000 },
|
|
347
348
|
);
|
|
348
349
|
});
|
|
@@ -355,7 +356,7 @@ describe('authToolsFor (least-privilege default services)', () => {
|
|
|
355
356
|
redirectUrl: 'http://127.0.0.1/cb?code=c&state=s',
|
|
356
357
|
});
|
|
357
358
|
expect(runner.run).toHaveBeenCalledWith(
|
|
358
|
-
['auth', 'add', 'u@x.com', '--remote', '--step', '2', '--auth-url',
|
|
359
|
+
['auth', 'add', pos('u@x.com'), '--remote', '--step', '2', '--auth-url',
|
|
359
360
|
'http://127.0.0.1/cb?code=c&state=s', '--services', 'gmail', '--force-consent'],
|
|
360
361
|
);
|
|
361
362
|
});
|
|
@@ -365,7 +366,7 @@ describe('authToolsFor (least-privilege default services)', () => {
|
|
|
365
366
|
const harness = await gmailHarness();
|
|
366
367
|
await harness.callTool('gog_auth_add_url', { email: 'u@x.com', services: 'gmail,drive' });
|
|
367
368
|
expect(runner.run).toHaveBeenCalledWith(
|
|
368
|
-
['auth', 'add', 'u@x.com', '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
|
|
369
|
+
['auth', 'add', pos('u@x.com'), '--remote', '--step', '1', '--services', 'gmail,drive', '--force-consent'],
|
|
369
370
|
{ redactMode: 'tokens' },
|
|
370
371
|
);
|
|
371
372
|
});
|
|
@@ -381,10 +382,36 @@ describe('gog_auth_run', () => {
|
|
|
381
382
|
});
|
|
382
383
|
|
|
383
384
|
it('works with empty args array', async () => {
|
|
384
|
-
vi.mocked(runner.run).mockResolvedValue('
|
|
385
|
+
vi.mocked(runner.run).mockResolvedValue('accounts');
|
|
385
386
|
const harness = await setupHandlers();
|
|
386
|
-
await harness.callTool('gog_auth_run', { subcommand: '
|
|
387
|
-
expect(runner.run).toHaveBeenCalledWith(['auth', '
|
|
387
|
+
await harness.callTool('gog_auth_run', { subcommand: 'list', args: [] });
|
|
388
|
+
expect(runner.run).toHaveBeenCalledWith(['auth', 'list'], {});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it.each(['list', 'status', 'services', 'remove', 'alias'])('allows %s', async (subcommand) => {
|
|
392
|
+
vi.mocked(runner.run).mockResolvedValue('ok');
|
|
393
|
+
const harness = await setupHandlers();
|
|
394
|
+
const result = await harness.callTool('gog_auth_run', { subcommand, args: [] });
|
|
395
|
+
expect(result.isError).toBeFalsy();
|
|
396
|
+
expect(runner.run).toHaveBeenCalledWith(['auth', subcommand], {});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// SEC-3: `auth tokens export --out <path>` writes a long-lived refresh token
|
|
400
|
+
// to disk, where an attach path can read it back out. Credentials and the
|
|
401
|
+
// keyring are the same class. Only the account-management subset is exposed.
|
|
402
|
+
it.each([
|
|
403
|
+
['tokens', ['export', 'me@example.com', '--out=/tmp/t.json']],
|
|
404
|
+
['tokens', ['import', '/tmp/t.json']],
|
|
405
|
+
['credentials', ['list']],
|
|
406
|
+
['keyring', []],
|
|
407
|
+
['add', ['me@example.com']],
|
|
408
|
+
['service-account', ['set']],
|
|
409
|
+
])('refuses auth %s %j', async (subcommand, args) => {
|
|
410
|
+
const harness = await setupHandlers();
|
|
411
|
+
const result = await harness.callTool('gog_auth_run', { subcommand, args });
|
|
412
|
+
expect(result.isError).toBe(true);
|
|
413
|
+
expect(result.content[0].text).toMatch(/not available through gog_auth_run/);
|
|
414
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
388
415
|
});
|
|
389
416
|
|
|
390
417
|
it('returns error text on failure', async () => {
|
|
@@ -2,15 +2,20 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { registerCalendarTools, CALENDAR_EVENTS_COMPACT_FIELDS } from '../../src/tools/calendar.js';
|
|
3
3
|
import * as runner from '../../src/runner.js';
|
|
4
4
|
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
import { pos } from '../../src/argv.js';
|
|
6
|
+
import type { GogArg } from '../../src/runner.js';
|
|
5
7
|
|
|
6
8
|
vi.mock('../../src/runner.js');
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
// The dispatch rail asks before guest-visible / outbound calls; this client accepts.
|
|
11
|
+
const setupHandlers = () => createTestHarness(registerCalendarTools, {
|
|
12
|
+
elicitation: async () => ({ action: 'accept', content: { confirmed: true } }),
|
|
13
|
+
});
|
|
9
14
|
|
|
10
15
|
// gog_calendar_events answers in the compact rung by default, so every call
|
|
11
16
|
// carries the mask. These keep the window-flag tests below about window flags
|
|
12
17
|
// while still asserting the shipped default rather than an opted-out view.
|
|
13
|
-
const evArgs = (...extra:
|
|
18
|
+
const evArgs = (...extra: GogArg[]) =>
|
|
14
19
|
['calendar', 'events', ...extra, `--fields=${CALENDAR_EVENTS_COMPACT_FIELDS}`];
|
|
15
20
|
const evOpts = { account: undefined, fieldsMask: CALENDAR_EVENTS_COMPACT_FIELDS };
|
|
16
21
|
|
|
@@ -67,7 +72,7 @@ describe('gog_calendar_events', () => {
|
|
|
67
72
|
query: 'standup',
|
|
68
73
|
all: true,
|
|
69
74
|
});
|
|
70
|
-
expect(runner.run).toHaveBeenCalledWith(evArgs('primary', '--from=2026-01-01', '--to=2026-01-31', '--query=standup', '--all'), evOpts);
|
|
75
|
+
expect(runner.run).toHaveBeenCalledWith(evArgs(pos('primary'), '--from=2026-01-01', '--to=2026-01-31', '--query=standup', '--all'), evOpts);
|
|
71
76
|
});
|
|
72
77
|
|
|
73
78
|
it('appends --today on its own', async () => {
|
|
@@ -118,7 +123,7 @@ describe('gog_calendar_get', () => {
|
|
|
118
123
|
vi.mocked(runner.run).mockResolvedValue('{"id":"evt1"}');
|
|
119
124
|
const harness = await setupHandlers();
|
|
120
125
|
await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1' });
|
|
121
|
-
expect(runner.run).toHaveBeenCalledWith(['calendar', 'event', 'primary', 'evt1'], { account: undefined });
|
|
126
|
+
expect(runner.run).toHaveBeenCalledWith(['calendar', 'event', pos('primary'), pos('evt1')], { account: undefined });
|
|
122
127
|
});
|
|
123
128
|
|
|
124
129
|
it('appends --timezone when provided', async () => {
|
|
@@ -126,7 +131,7 @@ describe('gog_calendar_get', () => {
|
|
|
126
131
|
const harness = await setupHandlers();
|
|
127
132
|
await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1', timezone: 'local' });
|
|
128
133
|
expect(runner.run).toHaveBeenCalledWith(
|
|
129
|
-
['calendar', 'event', 'primary', 'evt1', '--timezone=local'],
|
|
134
|
+
['calendar', 'event', pos('primary'), pos('evt1'), '--timezone=local'],
|
|
130
135
|
{ account: undefined },
|
|
131
136
|
);
|
|
132
137
|
});
|
|
@@ -150,7 +155,7 @@ describe('gog_calendar_create', () => {
|
|
|
150
155
|
to: '2026-04-14T09:30:00Z',
|
|
151
156
|
});
|
|
152
157
|
expect(runner.run).toHaveBeenCalledWith(
|
|
153
|
-
['calendar', 'create', 'primary', '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z'],
|
|
158
|
+
['calendar', 'create', pos('primary'), '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z'],
|
|
154
159
|
{ account: undefined },
|
|
155
160
|
);
|
|
156
161
|
});
|
|
@@ -171,7 +176,7 @@ describe('gog_calendar_create', () => {
|
|
|
171
176
|
});
|
|
172
177
|
expect(runner.run).toHaveBeenCalledWith(
|
|
173
178
|
[
|
|
174
|
-
'calendar', 'create', 'primary',
|
|
179
|
+
'calendar', 'create', pos('primary'),
|
|
175
180
|
'--summary=All-day', '--from=2026-04-14', '--to=2026-04-15',
|
|
176
181
|
'--description=Desc', '--location=NYC', '--attendees=a@b.com,c@d.com', '--all-day',
|
|
177
182
|
'--timezone=America/New_York',
|
|
@@ -195,7 +200,7 @@ describe('gog_calendar_create', () => {
|
|
|
195
200
|
});
|
|
196
201
|
expect(runner.run).toHaveBeenCalledWith(
|
|
197
202
|
[
|
|
198
|
-
'calendar', 'create', 'primary',
|
|
203
|
+
'calendar', 'create', pos('primary'),
|
|
199
204
|
'--summary=Sync', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
200
205
|
'--with-zoom',
|
|
201
206
|
],
|
|
@@ -210,7 +215,7 @@ describe('gog_calendar_create', () => {
|
|
|
210
215
|
calendarId: 'primary', summary: 's', from: 'f', to: 't', withZoom: false,
|
|
211
216
|
});
|
|
212
217
|
expect(runner.run).toHaveBeenCalledWith(
|
|
213
|
-
['calendar', 'create', 'primary', '--summary=s', '--from=f', '--to=t'],
|
|
218
|
+
['calendar', 'create', pos('primary'), '--summary=s', '--from=f', '--to=t'],
|
|
214
219
|
{ account: undefined },
|
|
215
220
|
);
|
|
216
221
|
});
|
|
@@ -233,7 +238,7 @@ describe('gog_calendar_update', () => {
|
|
|
233
238
|
summary: 'New Title',
|
|
234
239
|
});
|
|
235
240
|
expect(runner.run).toHaveBeenCalledWith(
|
|
236
|
-
['calendar', 'update', 'primary', 'evt1', '--summary=New Title'],
|
|
241
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--summary=New Title'],
|
|
237
242
|
{ account: undefined },
|
|
238
243
|
);
|
|
239
244
|
});
|
|
@@ -246,12 +251,12 @@ describe('gog_calendar_update', () => {
|
|
|
246
251
|
calendarId: 'primary', eventId: 'evt1', attachments: ['https://drive.google.com/file/d/a', 'https://x.test/b.pdf'],
|
|
247
252
|
});
|
|
248
253
|
expect(runner.run).toHaveBeenCalledWith(
|
|
249
|
-
['calendar', 'update', 'primary', 'evt1', '--attachment=https://drive.google.com/file/d/a', '--attachment=https://x.test/b.pdf'],
|
|
254
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--attachment=https://drive.google.com/file/d/a', '--attachment=https://x.test/b.pdf'],
|
|
250
255
|
{ account: undefined },
|
|
251
256
|
);
|
|
252
257
|
await harness.callTool('gog_calendar_update', { calendarId: 'primary', eventId: 'evt1', attachments: [''] });
|
|
253
258
|
expect(runner.run).toHaveBeenCalledWith(
|
|
254
|
-
['calendar', 'update', 'primary', 'evt1', '--attachment='],
|
|
259
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--attachment='],
|
|
255
260
|
{ account: undefined },
|
|
256
261
|
);
|
|
257
262
|
});
|
|
@@ -271,7 +276,7 @@ describe('gog_calendar_update', () => {
|
|
|
271
276
|
});
|
|
272
277
|
expect(runner.run).toHaveBeenCalledWith(
|
|
273
278
|
[
|
|
274
|
-
'calendar', 'update', 'primary', 'evt1',
|
|
279
|
+
'calendar', 'update', pos('primary'), pos('evt1'),
|
|
275
280
|
'--summary=New', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T10:00:00Z',
|
|
276
281
|
'--description=Desc', '--location=NYC', '--attendees=a@b.com',
|
|
277
282
|
],
|
|
@@ -290,7 +295,7 @@ describe('gog_calendar_update', () => {
|
|
|
290
295
|
});
|
|
291
296
|
expect(runner.run).toHaveBeenCalledWith(
|
|
292
297
|
[
|
|
293
|
-
'calendar', 'update', 'primary', 'evt1',
|
|
298
|
+
'calendar', 'update', pos('primary'), pos('evt1'),
|
|
294
299
|
'--add-attendee=room@resource.calendar.google.com;resource,x@y.com;optional',
|
|
295
300
|
],
|
|
296
301
|
{ account: undefined },
|
|
@@ -305,7 +310,7 @@ describe('gog_calendar_update', () => {
|
|
|
305
310
|
calendarId: 'primary', eventId: 'evt1', withZoom: true,
|
|
306
311
|
});
|
|
307
312
|
expect(runner.run).toHaveBeenCalledWith(
|
|
308
|
-
['calendar', 'update', 'primary', 'evt1', '--with-zoom'],
|
|
313
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--with-zoom'],
|
|
309
314
|
{ account: undefined },
|
|
310
315
|
);
|
|
311
316
|
|
|
@@ -315,7 +320,7 @@ describe('gog_calendar_update', () => {
|
|
|
315
320
|
calendarId: 'primary', eventId: 'evt1', regenerateZoom: true,
|
|
316
321
|
});
|
|
317
322
|
expect(runner.run).toHaveBeenCalledWith(
|
|
318
|
-
['calendar', 'update', 'primary', 'evt1', '--regenerate-zoom'],
|
|
323
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--regenerate-zoom'],
|
|
319
324
|
{ account: undefined },
|
|
320
325
|
);
|
|
321
326
|
|
|
@@ -325,7 +330,7 @@ describe('gog_calendar_update', () => {
|
|
|
325
330
|
calendarId: 'primary', eventId: 'evt1', removeZoom: true,
|
|
326
331
|
});
|
|
327
332
|
expect(runner.run).toHaveBeenCalledWith(
|
|
328
|
-
['calendar', 'update', 'primary', 'evt1', '--remove-zoom'],
|
|
333
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--remove-zoom'],
|
|
329
334
|
{ account: undefined },
|
|
330
335
|
);
|
|
331
336
|
});
|
|
@@ -338,7 +343,7 @@ describe('gog_calendar_update', () => {
|
|
|
338
343
|
calendarId: 'primary', eventId: 'evt1', removeMeet: true,
|
|
339
344
|
});
|
|
340
345
|
expect(runner.run).toHaveBeenCalledWith(
|
|
341
|
-
['calendar', 'update', 'primary', 'evt1', '--remove-meet'],
|
|
346
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--remove-meet'],
|
|
342
347
|
{ account: undefined },
|
|
343
348
|
);
|
|
344
349
|
});
|
|
@@ -351,7 +356,7 @@ describe('gog_calendar_update', () => {
|
|
|
351
356
|
withZoom: false, regenerateZoom: false, removeZoom: false,
|
|
352
357
|
});
|
|
353
358
|
expect(runner.run).toHaveBeenCalledWith(
|
|
354
|
-
['calendar', 'update', 'primary', 'evt1'],
|
|
359
|
+
['calendar', 'update', pos('primary'), pos('evt1')],
|
|
355
360
|
{ account: undefined },
|
|
356
361
|
);
|
|
357
362
|
});
|
|
@@ -369,7 +374,7 @@ describe('gog_calendar_delete', () => {
|
|
|
369
374
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
370
375
|
const harness = await setupHandlers();
|
|
371
376
|
await harness.callTool('gog_calendar_delete', { calendarId: 'primary', eventId: 'evt1' });
|
|
372
|
-
expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', 'primary', 'evt1', '--force'], { account: undefined });
|
|
377
|
+
expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', pos('primary'), pos('evt1'), '--force'], { account: undefined });
|
|
373
378
|
});
|
|
374
379
|
|
|
375
380
|
it('returns error text on failure', async () => {
|
|
@@ -386,7 +391,7 @@ describe('gog_calendar_respond', () => {
|
|
|
386
391
|
const harness = await setupHandlers();
|
|
387
392
|
await harness.callTool('gog_calendar_respond', { calendarId: 'primary', eventId: 'evt1', status: 'accepted' });
|
|
388
393
|
expect(runner.run).toHaveBeenCalledWith(
|
|
389
|
-
['calendar', 'respond', 'primary', 'evt1', '--status=accepted'],
|
|
394
|
+
['calendar', 'respond', pos('primary'), pos('evt1'), '--status=accepted'],
|
|
390
395
|
{ account: undefined },
|
|
391
396
|
);
|
|
392
397
|
});
|
|
@@ -401,7 +406,7 @@ describe('gog_calendar_respond', () => {
|
|
|
401
406
|
comment: 'Can\'t make it',
|
|
402
407
|
});
|
|
403
408
|
expect(runner.run).toHaveBeenCalledWith(
|
|
404
|
-
['calendar', 'respond', 'primary', 'evt1', '--status=declined', '--comment=Can\'t make it'],
|
|
409
|
+
['calendar', 'respond', pos('primary'), pos('evt1'), '--status=declined', '--comment=Can\'t make it'],
|
|
405
410
|
{ account: undefined },
|
|
406
411
|
);
|
|
407
412
|
});
|
|
@@ -500,7 +505,7 @@ describe('gog_calendar_create reminders', () => {
|
|
|
500
505
|
reminders: ['popup:30m', 'email:1d'],
|
|
501
506
|
});
|
|
502
507
|
expect(runner.run).toHaveBeenCalledWith(
|
|
503
|
-
['calendar', 'create', 'primary', '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
508
|
+
['calendar', 'create', pos('primary'), '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
504
509
|
'--reminder=popup:30m', '--reminder=email:1d'],
|
|
505
510
|
{ account: undefined },
|
|
506
511
|
);
|
|
@@ -517,7 +522,7 @@ describe('gog_calendar_create reminders', () => {
|
|
|
517
522
|
noReminders: true,
|
|
518
523
|
});
|
|
519
524
|
expect(runner.run).toHaveBeenCalledWith(
|
|
520
|
-
['calendar', 'create', 'primary', '--summary=Quiet', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
525
|
+
['calendar', 'create', pos('primary'), '--summary=Quiet', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
521
526
|
'--no-reminders'],
|
|
522
527
|
{ account: undefined },
|
|
523
528
|
);
|
|
@@ -559,7 +564,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
559
564
|
calendarId: 'primary', eventId: 'evt1', reminders: ['popup:15m'],
|
|
560
565
|
});
|
|
561
566
|
expect(runner.run).toHaveBeenCalledWith(
|
|
562
|
-
['calendar', 'update', 'primary', 'evt1', '--reminder=popup:15m'],
|
|
567
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--reminder=popup:15m'],
|
|
563
568
|
{ account: undefined },
|
|
564
569
|
);
|
|
565
570
|
});
|
|
@@ -573,7 +578,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
573
578
|
calendarId: 'primary', eventId: 'evt1', reminders: [],
|
|
574
579
|
});
|
|
575
580
|
expect(runner.run).toHaveBeenCalledWith(
|
|
576
|
-
['calendar', 'update', 'primary', 'evt1', '--reminder='],
|
|
581
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--reminder='],
|
|
577
582
|
{ account: undefined },
|
|
578
583
|
);
|
|
579
584
|
});
|
|
@@ -585,7 +590,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
585
590
|
calendarId: 'primary', eventId: 'evt1', noReminders: true,
|
|
586
591
|
});
|
|
587
592
|
expect(runner.run).toHaveBeenCalledWith(
|
|
588
|
-
['calendar', 'update', 'primary', 'evt1', '--no-reminders'],
|
|
593
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--no-reminders'],
|
|
589
594
|
{ account: undefined },
|
|
590
595
|
);
|
|
591
596
|
});
|
|
@@ -597,7 +602,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
597
602
|
calendarId: 'primary', eventId: 'evt1', summary: 'Renamed',
|
|
598
603
|
});
|
|
599
604
|
expect(runner.run).toHaveBeenCalledWith(
|
|
600
|
-
['calendar', 'update', 'primary', 'evt1', '--summary=Renamed'],
|
|
605
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--summary=Renamed'],
|
|
601
606
|
{ account: undefined },
|
|
602
607
|
);
|
|
603
608
|
});
|
package/tests/tools/chat.test.ts
CHANGED
|
@@ -2,10 +2,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { registerChatTools } from '../../src/tools/chat.js';
|
|
3
3
|
import * as runner from '../../src/runner.js';
|
|
4
4
|
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
import { pos } from '../../src/argv.js';
|
|
5
6
|
|
|
6
7
|
vi.mock('../../src/runner.js');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// The dispatch rail asks before guest-visible / outbound calls; this client accepts.
|
|
10
|
+
const setupHandlers = () => createTestHarness(registerChatTools, {
|
|
11
|
+
elicitation: async () => ({ action: 'accept', content: { confirmed: true } }),
|
|
12
|
+
});
|
|
9
13
|
|
|
10
14
|
beforeEach(() => vi.clearAllMocks());
|
|
11
15
|
|
|
@@ -58,7 +62,7 @@ describe('gog_chat_spaces_find', () => {
|
|
|
58
62
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
59
63
|
const harness = await setupHandlers();
|
|
60
64
|
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team' });
|
|
61
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'find', 'Team'], { account: undefined });
|
|
65
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'find', pos('Team')], { account: undefined });
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
it('passes --exact and --max', async () => {
|
|
@@ -66,7 +70,7 @@ describe('gog_chat_spaces_find', () => {
|
|
|
66
70
|
const harness = await setupHandlers();
|
|
67
71
|
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team', exact: true, max: 5 });
|
|
68
72
|
expect(runner.run).toHaveBeenCalledWith(
|
|
69
|
-
['chat', 'spaces', 'find', 'Team', '--exact', '--max=5'],
|
|
73
|
+
['chat', 'spaces', 'find', pos('Team'), '--exact', '--max=5'],
|
|
70
74
|
{ account: undefined },
|
|
71
75
|
);
|
|
72
76
|
});
|
|
@@ -78,7 +82,7 @@ describe('gog_chat_spaces_create', () => {
|
|
|
78
82
|
const harness = await setupHandlers();
|
|
79
83
|
await harness.callTool('gog_chat_spaces_create', { displayName: 'Launch', members: ['a@b.com', 'c@d.com'] });
|
|
80
84
|
expect(runner.run).toHaveBeenCalledWith(
|
|
81
|
-
['chat', 'spaces', 'create', 'Launch', '--member=a@b.com', '--member=c@d.com'],
|
|
85
|
+
['chat', 'spaces', 'create', pos('Launch'), '--member=a@b.com', '--member=c@d.com'],
|
|
82
86
|
{ account: undefined },
|
|
83
87
|
);
|
|
84
88
|
});
|
|
@@ -87,7 +91,7 @@ describe('gog_chat_spaces_create', () => {
|
|
|
87
91
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
88
92
|
const harness = await setupHandlers();
|
|
89
93
|
await harness.callTool('gog_chat_spaces_create', { displayName: 'Solo' });
|
|
90
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'create', 'Solo'], { account: undefined });
|
|
94
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'create', pos('Solo')], { account: undefined });
|
|
91
95
|
});
|
|
92
96
|
});
|
|
93
97
|
|
|
@@ -97,7 +101,7 @@ describe('gog_chat_threads_list', () => {
|
|
|
97
101
|
const harness = await setupHandlers();
|
|
98
102
|
await harness.callTool('gog_chat_threads_list', { space: 'spaces/AAA', max: 10 });
|
|
99
103
|
expect(runner.run).toHaveBeenCalledWith(
|
|
100
|
-
['chat', 'threads', 'list', 'spaces/AAA', '--max=10'],
|
|
104
|
+
['chat', 'threads', 'list', pos('spaces/AAA'), '--max=10'],
|
|
101
105
|
{ account: undefined },
|
|
102
106
|
);
|
|
103
107
|
});
|
|
@@ -108,7 +112,7 @@ describe('gog_chat_messages_list', () => {
|
|
|
108
112
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
109
113
|
const harness = await setupHandlers();
|
|
110
114
|
await harness.callTool('gog_chat_messages_list', { space: 'spaces/AAA' });
|
|
111
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'messages', 'list', 'spaces/AAA'], { account: undefined });
|
|
115
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'messages', 'list', pos('spaces/AAA')], { account: undefined });
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
it('filters by thread, unread and order', async () => {
|
|
@@ -118,7 +122,7 @@ describe('gog_chat_messages_list', () => {
|
|
|
118
122
|
space: 'spaces/AAA', thread: 'spaces/AAA/threads/T', unread: true, order: 'createTime desc', max: 50,
|
|
119
123
|
});
|
|
120
124
|
expect(runner.run).toHaveBeenCalledWith(
|
|
121
|
-
['chat', 'messages', 'list', 'spaces/AAA', '--thread=spaces/AAA/threads/T', '--unread',
|
|
125
|
+
['chat', 'messages', 'list', pos('spaces/AAA'), '--thread=spaces/AAA/threads/T', '--unread',
|
|
122
126
|
'--order=createTime desc', '--max=50'],
|
|
123
127
|
{ account: undefined },
|
|
124
128
|
);
|
|
@@ -138,7 +142,7 @@ describe('gog_chat_messages_search', () => {
|
|
|
138
142
|
const harness = await setupHandlers();
|
|
139
143
|
await harness.callTool('gog_chat_messages_search', { query: 'project decision' });
|
|
140
144
|
expect(runner.run).toHaveBeenCalledWith(
|
|
141
|
-
['chat', 'messages', 'search', 'project decision'],
|
|
145
|
+
['chat', 'messages', 'search', pos('project decision')],
|
|
142
146
|
{ account: undefined },
|
|
143
147
|
);
|
|
144
148
|
});
|
|
@@ -156,7 +160,7 @@ describe('gog_chat_messages_search', () => {
|
|
|
156
160
|
all: true,
|
|
157
161
|
});
|
|
158
162
|
expect(runner.run).toHaveBeenCalledWith(
|
|
159
|
-
['chat', 'messages', 'search', 'from:alice@example.com budget', '--order=create_time desc',
|
|
163
|
+
['chat', 'messages', 'search', pos('from:alice@example.com budget'), '--order=create_time desc',
|
|
160
164
|
'--view=full', '--markup=markdown', '--max=100', '--page=tok', '--all'],
|
|
161
165
|
{ account: undefined },
|
|
162
166
|
);
|
|
@@ -198,7 +202,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
198
202
|
const harness = await setupHandlers();
|
|
199
203
|
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', text: 'hi' });
|
|
200
204
|
expect(runner.run).toHaveBeenCalledWith(
|
|
201
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--text=hi'],
|
|
205
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--text=hi'],
|
|
202
206
|
{ account: undefined },
|
|
203
207
|
);
|
|
204
208
|
});
|
|
@@ -210,7 +214,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
210
214
|
space: 'spaces/AAA', text: 'see this', thread: 'spaces/AAA/threads/T', attach: ['/tmp/a.png'],
|
|
211
215
|
});
|
|
212
216
|
expect(runner.run).toHaveBeenCalledWith(
|
|
213
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--text=see this', '--thread=spaces/AAA/threads/T', '--attach=/tmp/a.png'],
|
|
217
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--text=see this', '--thread=spaces/AAA/threads/T', '--attach=/tmp/a.png'],
|
|
214
218
|
{ account: undefined },
|
|
215
219
|
);
|
|
216
220
|
});
|
|
@@ -234,7 +238,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
234
238
|
const harness = await setupHandlers();
|
|
235
239
|
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', attach: ['/tmp/a.png'] });
|
|
236
240
|
expect(runner.run).toHaveBeenCalledWith(
|
|
237
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--attach=/tmp/a.png'],
|
|
241
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--attach=/tmp/a.png'],
|
|
238
242
|
{ account: undefined },
|
|
239
243
|
);
|
|
240
244
|
});
|
|
@@ -253,7 +257,7 @@ describe('gog_chat_dm_send', () => {
|
|
|
253
257
|
const harness = await setupHandlers();
|
|
254
258
|
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi' });
|
|
255
259
|
expect(runner.run).toHaveBeenCalledWith(
|
|
256
|
-
['chat', 'dm', 'send', 'a@b.com', '--text=hi'],
|
|
260
|
+
['chat', 'dm', 'send', pos('a@b.com'), '--text=hi'],
|
|
257
261
|
{ account: undefined },
|
|
258
262
|
);
|
|
259
263
|
});
|
|
@@ -263,7 +267,7 @@ describe('gog_chat_dm_send', () => {
|
|
|
263
267
|
const harness = await setupHandlers();
|
|
264
268
|
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi', thread: 'spaces/D/threads/T' });
|
|
265
269
|
expect(runner.run).toHaveBeenCalledWith(
|
|
266
|
-
['chat', 'dm', 'send', 'a@b.com', '--text=hi', '--thread=spaces/D/threads/T'],
|
|
270
|
+
['chat', 'dm', 'send', pos('a@b.com'), '--text=hi', '--thread=spaces/D/threads/T'],
|
|
267
271
|
{ account: undefined },
|
|
268
272
|
);
|
|
269
273
|
});
|
|
@@ -274,7 +278,7 @@ describe('gog_chat_dm_space', () => {
|
|
|
274
278
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
275
279
|
const harness = await setupHandlers();
|
|
276
280
|
await harness.callTool('gog_chat_dm_space', { email: 'a@b.com' });
|
|
277
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'dm', 'space', 'a@b.com'], { account: undefined });
|
|
281
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'dm', 'space', pos('a@b.com')], { account: undefined });
|
|
278
282
|
});
|
|
279
283
|
});
|
|
280
284
|
|
|
@@ -284,7 +288,7 @@ describe('gog_chat_reactions_list', () => {
|
|
|
284
288
|
const harness = await setupHandlers();
|
|
285
289
|
await harness.callTool('gog_chat_reactions_list', { message: 'MSG', space: 'spaces/AAA', max: 10 });
|
|
286
290
|
expect(runner.run).toHaveBeenCalledWith(
|
|
287
|
-
['chat', 'messages', 'reactions', 'list', 'MSG', '--space=spaces/AAA', '--max=10'],
|
|
291
|
+
['chat', 'messages', 'reactions', 'list', pos('MSG'), '--space=spaces/AAA', '--max=10'],
|
|
288
292
|
{ account: undefined },
|
|
289
293
|
);
|
|
290
294
|
});
|
|
@@ -294,7 +298,7 @@ describe('gog_chat_reactions_list', () => {
|
|
|
294
298
|
const harness = await setupHandlers();
|
|
295
299
|
await harness.callTool('gog_chat_reactions_list', { message: 'spaces/AAA/messages/M' });
|
|
296
300
|
expect(runner.run).toHaveBeenCalledWith(
|
|
297
|
-
['chat', 'messages', 'reactions', 'list', 'spaces/AAA/messages/M'],
|
|
301
|
+
['chat', 'messages', 'reactions', 'list', pos('spaces/AAA/messages/M')],
|
|
298
302
|
{ account: undefined },
|
|
299
303
|
);
|
|
300
304
|
});
|
|
@@ -306,7 +310,7 @@ describe('gog_chat_reactions_create', () => {
|
|
|
306
310
|
const harness = await setupHandlers();
|
|
307
311
|
await harness.callTool('gog_chat_reactions_create', { message: 'spaces/AAA/messages/M', emoji: '👍' });
|
|
308
312
|
expect(runner.run).toHaveBeenCalledWith(
|
|
309
|
-
['chat', 'messages', 'reactions', 'create', 'spaces/AAA/messages/M', '👍'],
|
|
313
|
+
['chat', 'messages', 'reactions', 'create', pos('spaces/AAA/messages/M'), pos('👍')],
|
|
310
314
|
{ account: undefined },
|
|
311
315
|
);
|
|
312
316
|
});
|
|
@@ -316,7 +320,7 @@ describe('gog_chat_reactions_create', () => {
|
|
|
316
320
|
const harness = await setupHandlers();
|
|
317
321
|
await harness.callTool('gog_chat_reactions_create', { message: 'M', emoji: '👍', space: 'spaces/AAA' });
|
|
318
322
|
expect(runner.run).toHaveBeenCalledWith(
|
|
319
|
-
['chat', 'messages', 'reactions', 'create', 'M', '👍', '--space=spaces/AAA'],
|
|
323
|
+
['chat', 'messages', 'reactions', 'create', pos('M'), pos('👍'), '--space=spaces/AAA'],
|
|
320
324
|
{ account: undefined },
|
|
321
325
|
);
|
|
322
326
|
});
|
|
@@ -328,7 +332,7 @@ describe('gog_chat_reactions_delete', () => {
|
|
|
328
332
|
const harness = await setupHandlers();
|
|
329
333
|
await harness.callTool('gog_chat_reactions_delete', { reaction: 'spaces/AAA/messages/M/reactions/R' });
|
|
330
334
|
expect(runner.run).toHaveBeenCalledWith(
|
|
331
|
-
['chat', 'messages', 'reactions', 'delete', 'spaces/AAA/messages/M/reactions/R'],
|
|
335
|
+
['chat', 'messages', 'reactions', 'delete', pos('spaces/AAA/messages/M/reactions/R')],
|
|
332
336
|
{ account: undefined },
|
|
333
337
|
);
|
|
334
338
|
});
|
|
@@ -342,3 +346,28 @@ describe('gog_chat_run', () => {
|
|
|
342
346
|
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'list'], { account: undefined });
|
|
343
347
|
});
|
|
344
348
|
});
|
|
349
|
+
|
|
350
|
+
// SEC-3/SEC-4: every model-supplied server path must resolve inside an
|
|
351
|
+
// operator-configured root (GOG_FILE_ROOTS). The suite runs with '/' so the
|
|
352
|
+
// arg-shape tests can use any path; these narrow it.
|
|
353
|
+
async function withFileRoots<T>(roots: string, fn: () => Promise<T>): Promise<T> {
|
|
354
|
+
const prev = process.env.GOG_FILE_ROOTS;
|
|
355
|
+
process.env.GOG_FILE_ROOTS = roots;
|
|
356
|
+
try {
|
|
357
|
+
return await fn();
|
|
358
|
+
} finally {
|
|
359
|
+
process.env.GOG_FILE_ROOTS = prev;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
describe('gog_chat_messages_send — server paths', () => {
|
|
364
|
+
it('refuses an attach path outside GOG_FILE_ROOTS', async () => {
|
|
365
|
+
const harness = await setupHandlers();
|
|
366
|
+
const result = await withFileRoots('/srv/gog-files', () => harness.callTool('gog_chat_messages_send', {
|
|
367
|
+
space: 'spaces/AAA', attach: ['/etc/passwd'],
|
|
368
|
+
}));
|
|
369
|
+
expect(result.isError).toBe(true);
|
|
370
|
+
expect(result.content[0].text).toContain('attach "/etc/passwd" is outside');
|
|
371
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
372
|
+
});
|
|
373
|
+
});
|