@aaronsb/google-workspace-mcp 2.7.0 → 3.0.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/README.md +21 -10
- package/build/factory/generator.d.ts +0 -2
- package/build/factory/generator.js +32 -27
- package/build/factory/generator.js.map +1 -1
- package/build/factory/manifest/gmail.yaml +24 -2
- package/build/factory/registry.d.ts +2 -3
- package/build/factory/registry.js +3 -9
- package/build/factory/registry.js.map +1 -1
- package/build/index.js +27 -1
- package/build/index.js.map +1 -1
- package/build/node-floor.d.ts +16 -0
- package/build/node-floor.js +73 -0
- package/build/node-floor.js.map +1 -0
- package/build/services/gmail/patch.js +67 -3
- package/build/services/gmail/patch.js.map +1 -1
- package/build/version.d.ts +1 -1
- package/build/version.js +2 -2
- package/build/version.js.map +1 -1
- package/package.json +17 -10
- package/build/__helpers__/testSetup.d.ts +0 -1
- package/build/__helpers__/testSetup.js +0 -6
- package/build/__helpers__/testSetup.js.map +0 -1
- package/build/__tests__/accounts/credentials.test.d.ts +0 -1
- package/build/__tests__/accounts/credentials.test.js +0 -127
- package/build/__tests__/accounts/credentials.test.js.map +0 -1
- package/build/__tests__/accounts/oauth.test.d.ts +0 -1
- package/build/__tests__/accounts/oauth.test.js +0 -71
- package/build/__tests__/accounts/oauth.test.js.map +0 -1
- package/build/__tests__/accounts/registry.test.d.ts +0 -1
- package/build/__tests__/accounts/registry.test.js +0 -74
- package/build/__tests__/accounts/registry.test.js.map +0 -1
- package/build/__tests__/accounts/token-service.test.d.ts +0 -1
- package/build/__tests__/accounts/token-service.test.js +0 -126
- package/build/__tests__/accounts/token-service.test.js.map +0 -1
- package/build/__tests__/executor/errors.test.d.ts +0 -1
- package/build/__tests__/executor/errors.test.js +0 -42
- package/build/__tests__/executor/errors.test.js.map +0 -1
- package/build/__tests__/executor/gws.test.d.ts +0 -1
- package/build/__tests__/executor/gws.test.js +0 -187
- package/build/__tests__/executor/gws.test.js.map +0 -1
- package/build/__tests__/executor/paths.test.d.ts +0 -1
- package/build/__tests__/executor/paths.test.js +0 -60
- package/build/__tests__/executor/paths.test.js.map +0 -1
- package/build/__tests__/executor/resolve-binary.test.d.ts +0 -1
- package/build/__tests__/executor/resolve-binary.test.js +0 -58
- package/build/__tests__/executor/resolve-binary.test.js.map +0 -1
- package/build/__tests__/executor/workspace.test.d.ts +0 -1
- package/build/__tests__/executor/workspace.test.js +0 -143
- package/build/__tests__/executor/workspace.test.js.map +0 -1
- package/build/__tests__/factory/calendar-patch.test.d.ts +0 -5
- package/build/__tests__/factory/calendar-patch.test.js +0 -269
- package/build/__tests__/factory/calendar-patch.test.js.map +0 -1
- package/build/__tests__/factory/drive-patch.test.d.ts +0 -5
- package/build/__tests__/factory/drive-patch.test.js +0 -344
- package/build/__tests__/factory/drive-patch.test.js.map +0 -1
- package/build/__tests__/factory/generator.test.d.ts +0 -1
- package/build/__tests__/factory/generator.test.js +0 -240
- package/build/__tests__/factory/generator.test.js.map +0 -1
- package/build/__tests__/factory/patch-coverage.test.d.ts +0 -10
- package/build/__tests__/factory/patch-coverage.test.js +0 -182
- package/build/__tests__/factory/patch-coverage.test.js.map +0 -1
- package/build/__tests__/factory/safety.test.d.ts +0 -1
- package/build/__tests__/factory/safety.test.js +0 -107
- package/build/__tests__/factory/safety.test.js.map +0 -1
- package/build/__tests__/factory/sheets-patch.test.d.ts +0 -6
- package/build/__tests__/factory/sheets-patch.test.js +0 -498
- package/build/__tests__/factory/sheets-patch.test.js.map +0 -1
- package/build/__tests__/integration/executor.test.d.ts +0 -5
- package/build/__tests__/integration/executor.test.js +0 -38
- package/build/__tests__/integration/executor.test.js.map +0 -1
- package/build/__tests__/integration/handlers.test.d.ts +0 -6
- package/build/__tests__/integration/handlers.test.js +0 -95
- package/build/__tests__/integration/handlers.test.js.map +0 -1
- package/build/__tests__/integration/setup.d.ts +0 -19
- package/build/__tests__/integration/setup.js +0 -61
- package/build/__tests__/integration/setup.js.map +0 -1
- package/build/__tests__/server/formatting/html-sanitize.test.d.ts +0 -7
- package/build/__tests__/server/formatting/html-sanitize.test.js +0 -174
- package/build/__tests__/server/formatting/html-sanitize.test.js.map +0 -1
- package/build/__tests__/server/formatting/markdown.test.d.ts +0 -1
- package/build/__tests__/server/formatting/markdown.test.js +0 -259
- package/build/__tests__/server/formatting/markdown.test.js.map +0 -1
- package/build/__tests__/server/formatting/next-steps.test.d.ts +0 -1
- package/build/__tests__/server/formatting/next-steps.test.js +0 -42
- package/build/__tests__/server/formatting/next-steps.test.js.map +0 -1
- package/build/__tests__/server/handler.test.d.ts +0 -1
- package/build/__tests__/server/handler.test.js +0 -97
- package/build/__tests__/server/handler.test.js.map +0 -1
- package/build/__tests__/server/handlers/__mocks__/executor.d.ts +0 -173
- package/build/__tests__/server/handlers/__mocks__/executor.js +0 -135
- package/build/__tests__/server/handlers/__mocks__/executor.js.map +0 -1
- package/build/__tests__/server/handlers/accounts.test.d.ts +0 -1
- package/build/__tests__/server/handlers/accounts.test.js +0 -129
- package/build/__tests__/server/handlers/accounts.test.js.map +0 -1
- package/build/__tests__/server/handlers/calendar.test.d.ts +0 -1
- package/build/__tests__/server/handlers/calendar.test.js +0 -96
- package/build/__tests__/server/handlers/calendar.test.js.map +0 -1
- package/build/__tests__/server/handlers/drive.test.d.ts +0 -1
- package/build/__tests__/server/handlers/drive.test.js +0 -68
- package/build/__tests__/server/handlers/drive.test.js.map +0 -1
- package/build/__tests__/server/handlers/email.test.d.ts +0 -1
- package/build/__tests__/server/handlers/email.test.js +0 -99
- package/build/__tests__/server/handlers/email.test.js.map +0 -1
- package/build/__tests__/server/handlers/validate.test.d.ts +0 -1
- package/build/__tests__/server/handlers/validate.test.js +0 -88
- package/build/__tests__/server/handlers/validate.test.js.map +0 -1
- package/build/__tests__/server/queue.test.d.ts +0 -1
- package/build/__tests__/server/queue.test.js +0 -199
- package/build/__tests__/server/queue.test.js.map +0 -1
- package/build/__tests__/server/server.test.d.ts +0 -7
- package/build/__tests__/server/server.test.js +0 -135
- package/build/__tests__/server/server.test.js.map +0 -1
- package/build/__tests__/server/session/context.test.d.ts +0 -1
- package/build/__tests__/server/session/context.test.js +0 -101
- package/build/__tests__/server/session/context.test.js.map +0 -1
- package/build/__tests__/server/session/tracker.test.d.ts +0 -1
- package/build/__tests__/server/session/tracker.test.js +0 -146
- package/build/__tests__/server/session/tracker.test.js.map +0 -1
- package/build/__tests__/server/tools.test.d.ts +0 -1
- package/build/__tests__/server/tools.test.js +0 -91
- package/build/__tests__/server/tools.test.js.map +0 -1
- package/build/__tests__/services/gmail/mime.test.d.ts +0 -1
- package/build/__tests__/services/gmail/mime.test.js +0 -23
- package/build/__tests__/services/gmail/mime.test.js.map +0 -1
- package/build/__tests__/services/meet/patch.test.d.ts +0 -4
- package/build/__tests__/services/meet/patch.test.js +0 -374
- package/build/__tests__/services/meet/patch.test.js.map +0 -1
- package/build/server/scratchpad/__tests__/docs-sync-integration.test.d.ts +0 -11
- package/build/server/scratchpad/__tests__/docs-sync-integration.test.js +0 -187
- package/build/server/scratchpad/__tests__/docs-sync-integration.test.js.map +0 -1
- package/build/server/scratchpad/__tests__/docs-sync.test.d.ts +0 -16
- package/build/server/scratchpad/__tests__/docs-sync.test.js +0 -213
- package/build/server/scratchpad/__tests__/docs-sync.test.js.map +0 -1
- package/build/server/scratchpad/__tests__/json-path.test.d.ts +0 -4
- package/build/server/scratchpad/__tests__/json-path.test.js +0 -121
- package/build/server/scratchpad/__tests__/json-path.test.js.map +0 -1
- package/build/server/scratchpad/__tests__/manager.test.d.ts +0 -5
- package/build/server/scratchpad/__tests__/manager.test.js +0 -886
- package/build/server/scratchpad/__tests__/manager.test.js.map +0 -1
- package/build/server/scratchpad/__tests__/validate.test.d.ts +0 -4
- package/build/server/scratchpad/__tests__/validate.test.js +0 -112
- package/build/server/scratchpad/__tests__/validate.test.js.map +0 -1
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the drive service patch — custom handlers for download/export
|
|
3
|
-
* that ensure parent directories are created before writing files.
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'node:fs/promises';
|
|
6
|
-
import * as path from 'node:path';
|
|
7
|
-
import * as os from 'node:os';
|
|
8
|
-
// Mock executor before importing patch
|
|
9
|
-
jest.mock('../../executor/gws.js');
|
|
10
|
-
import { execute } from '../../executor/gws.js';
|
|
11
|
-
// Mock workspace module with a temp dir
|
|
12
|
-
const tmpWorkspace = path.join(os.tmpdir(), `gws-test-${Date.now()}`);
|
|
13
|
-
jest.mock('../../executor/workspace.js', () => ({
|
|
14
|
-
ensureWorkspaceDir: jest.fn(async () => ({ path: tmpWorkspace, valid: true })),
|
|
15
|
-
getWorkspaceDir: jest.fn(() => tmpWorkspace),
|
|
16
|
-
resolveWorkspacePath: jest.fn((filename) => path.join(tmpWorkspace, filename)),
|
|
17
|
-
verifyPathSafety: jest.fn(async () => { }),
|
|
18
|
-
}));
|
|
19
|
-
// Mock file-output to avoid reading actual files
|
|
20
|
-
jest.mock('../../executor/file-output.js', () => ({
|
|
21
|
-
isTextFile: jest.fn(() => true),
|
|
22
|
-
isImageFile: jest.fn(() => false),
|
|
23
|
-
formatFileOutput: jest.fn(() => '## Exported file\n'),
|
|
24
|
-
buildImageBlock: jest.fn(() => null),
|
|
25
|
-
buildImageBlockFromFile: jest.fn(() => null),
|
|
26
|
-
}));
|
|
27
|
-
import { drivePatch } from '../../services/drive/patch.js';
|
|
28
|
-
const mockExecute = execute;
|
|
29
|
-
describe('drivePatch custom handlers', () => {
|
|
30
|
-
beforeEach(async () => {
|
|
31
|
-
mockExecute.mockReset();
|
|
32
|
-
// Start with a clean workspace — only the root dir exists
|
|
33
|
-
await fs.rm(tmpWorkspace, { recursive: true, force: true });
|
|
34
|
-
await fs.mkdir(tmpWorkspace, { recursive: true });
|
|
35
|
-
});
|
|
36
|
-
afterAll(async () => {
|
|
37
|
-
await fs.rm(tmpWorkspace, { recursive: true, force: true });
|
|
38
|
-
});
|
|
39
|
-
describe('export', () => {
|
|
40
|
-
it('creates parent directories before calling gws', async () => {
|
|
41
|
-
const outputPath = path.join(tmpWorkspace, 'subdir', 'Report.txt');
|
|
42
|
-
const { resolveWorkspacePath } = require('../../executor/workspace.js');
|
|
43
|
-
resolveWorkspacePath.mockReturnValue(outputPath);
|
|
44
|
-
// First call: get file metadata
|
|
45
|
-
// Second call: export — simulate gws writing the file
|
|
46
|
-
mockExecute
|
|
47
|
-
.mockResolvedValueOnce({ success: true, data: { name: 'Report.gdoc' }, stderr: '' })
|
|
48
|
-
.mockImplementationOnce(async () => {
|
|
49
|
-
// gws would write the file here — parent dir must already exist
|
|
50
|
-
await fs.writeFile(outputPath, 'exported content');
|
|
51
|
-
return { success: true, data: {}, stderr: '' };
|
|
52
|
-
});
|
|
53
|
-
const handler = drivePatch.customHandlers.export;
|
|
54
|
-
await handler({ fileId: 'abc123', mimeType: 'text/plain', outputPath: 'subdir/Report.txt' }, 'user@test.com');
|
|
55
|
-
// The export call should have --output pointing to the nested path
|
|
56
|
-
const exportCall = mockExecute.mock.calls[1][0];
|
|
57
|
-
expect(exportCall).toContain('--output');
|
|
58
|
-
expect(exportCall[exportCall.indexOf('--output') + 1]).toBe(outputPath);
|
|
59
|
-
});
|
|
60
|
-
it('handler mkdir is required — without it gws write would fail', async () => {
|
|
61
|
-
const outputPath = path.join(tmpWorkspace, 'deep', 'nested', 'Doc.txt');
|
|
62
|
-
const { resolveWorkspacePath } = require('../../executor/workspace.js');
|
|
63
|
-
resolveWorkspacePath.mockReturnValue(outputPath);
|
|
64
|
-
// Verify the parent directory does NOT exist before handler runs
|
|
65
|
-
const parentBefore = await fs.stat(path.dirname(outputPath)).catch(() => null);
|
|
66
|
-
expect(parentBefore).toBeNull();
|
|
67
|
-
mockExecute
|
|
68
|
-
.mockResolvedValueOnce({ success: true, data: { name: 'Doc' }, stderr: '' })
|
|
69
|
-
.mockImplementationOnce(async () => {
|
|
70
|
-
// Parent directory must exist at this point (handler created it)
|
|
71
|
-
const stat = await fs.stat(path.dirname(outputPath));
|
|
72
|
-
expect(stat.isDirectory()).toBe(true);
|
|
73
|
-
await fs.writeFile(outputPath, 'content');
|
|
74
|
-
return { success: true, data: {}, stderr: '' };
|
|
75
|
-
});
|
|
76
|
-
const handler = drivePatch.customHandlers.export;
|
|
77
|
-
await handler({ fileId: 'abc123', mimeType: 'text/plain' }, 'user@test.com');
|
|
78
|
-
});
|
|
79
|
-
it('works with flat filename (no subdirectory)', async () => {
|
|
80
|
-
const outputPath = path.join(tmpWorkspace, 'Doc.pdf');
|
|
81
|
-
const { resolveWorkspacePath } = require('../../executor/workspace.js');
|
|
82
|
-
resolveWorkspacePath.mockReturnValue(outputPath);
|
|
83
|
-
mockExecute
|
|
84
|
-
.mockResolvedValueOnce({ success: true, data: { name: 'Doc' }, stderr: '' })
|
|
85
|
-
.mockImplementationOnce(async () => {
|
|
86
|
-
await fs.writeFile(outputPath, 'pdf content');
|
|
87
|
-
return { success: true, data: {}, stderr: '' };
|
|
88
|
-
});
|
|
89
|
-
const handler = drivePatch.customHandlers.export;
|
|
90
|
-
const result = await handler({ fileId: 'abc123', mimeType: 'application/pdf' }, 'user@test.com');
|
|
91
|
-
expect(result.text).toBeDefined();
|
|
92
|
-
expect(mockExecute).toHaveBeenCalledTimes(2);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe('share', () => {
|
|
96
|
-
it('sends type + role + emailAddress as JSON body, not via --params', async () => {
|
|
97
|
-
mockExecute.mockResolvedValueOnce({
|
|
98
|
-
success: true,
|
|
99
|
-
data: { id: 'perm-1', emailAddress: 'bob@test.com', role: 'reader', type: 'user' },
|
|
100
|
-
stderr: '',
|
|
101
|
-
});
|
|
102
|
-
const handler = drivePatch.customHandlers.share;
|
|
103
|
-
await handler({ fileId: 'file-1', shareEmail: 'bob@test.com', role: 'reader' }, 'user@test.com');
|
|
104
|
-
const args = mockExecute.mock.calls[0][0];
|
|
105
|
-
expect(args.slice(0, 3)).toEqual(['drive', 'permissions', 'create']);
|
|
106
|
-
expect(args).toContain('--json');
|
|
107
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
108
|
-
// type MUST be in the body — omitting it caused
|
|
109
|
-
// "The permission type field is required." on every share call.
|
|
110
|
-
expect(body.type).toBe('user');
|
|
111
|
-
expect(body.role).toBe('reader');
|
|
112
|
-
expect(body.emailAddress).toBe('bob@test.com');
|
|
113
|
-
// --params should only carry query params, not the permission body.
|
|
114
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
115
|
-
expect(queryParams.fileId).toBe('file-1');
|
|
116
|
-
expect(queryParams.type).toBeUndefined();
|
|
117
|
-
expect(queryParams.role).toBeUndefined();
|
|
118
|
-
});
|
|
119
|
-
it("defaults type to 'user' when not provided", async () => {
|
|
120
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'perm-1' }, stderr: '' });
|
|
121
|
-
const handler = drivePatch.customHandlers.share;
|
|
122
|
-
await handler({ fileId: 'file-1', shareEmail: 'bob@test.com' }, 'user@test.com');
|
|
123
|
-
const args = mockExecute.mock.calls[0][0];
|
|
124
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
125
|
-
expect(body.type).toBe('user');
|
|
126
|
-
expect(body.role).toBe('reader'); // default from manifest flows through
|
|
127
|
-
});
|
|
128
|
-
it('rejects user/group share without shareEmail', async () => {
|
|
129
|
-
const handler = drivePatch.customHandlers.share;
|
|
130
|
-
await expect(handler({ fileId: 'file-1' }, 'user@test.com')).rejects.toThrow('shareEmail');
|
|
131
|
-
await expect(handler({ fileId: 'file-1', type: 'group' }, 'user@test.com')).rejects.toThrow('shareEmail');
|
|
132
|
-
});
|
|
133
|
-
it("uses domain field when type is 'domain'", async () => {
|
|
134
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'perm-1' }, stderr: '' });
|
|
135
|
-
const handler = drivePatch.customHandlers.share;
|
|
136
|
-
await handler({ fileId: 'file-1', type: 'domain', domain: 'acme.com', role: 'writer' }, 'user@test.com');
|
|
137
|
-
const args = mockExecute.mock.calls[0][0];
|
|
138
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
139
|
-
expect(body.type).toBe('domain');
|
|
140
|
-
expect(body.domain).toBe('acme.com');
|
|
141
|
-
expect(body.emailAddress).toBeUndefined();
|
|
142
|
-
});
|
|
143
|
-
it("requires domain when type is 'domain'", async () => {
|
|
144
|
-
const handler = drivePatch.customHandlers.share;
|
|
145
|
-
await expect(handler({ fileId: 'file-1', type: 'domain' }, 'user@test.com')).rejects.toThrow('domain');
|
|
146
|
-
});
|
|
147
|
-
it("accepts type 'anyone' with no target", async () => {
|
|
148
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'perm-1' }, stderr: '' });
|
|
149
|
-
const handler = drivePatch.customHandlers.share;
|
|
150
|
-
await handler({ fileId: 'file-1', type: 'anyone', role: 'reader' }, 'user@test.com');
|
|
151
|
-
const args = mockExecute.mock.calls[0][0];
|
|
152
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
153
|
-
expect(body.type).toBe('anyone');
|
|
154
|
-
expect(body.emailAddress).toBeUndefined();
|
|
155
|
-
expect(body.domain).toBeUndefined();
|
|
156
|
-
});
|
|
157
|
-
it('suppresses email notifications by default for user/group shares', async () => {
|
|
158
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'perm-1' }, stderr: '' });
|
|
159
|
-
const handler = drivePatch.customHandlers.share;
|
|
160
|
-
await handler({ fileId: 'file-1', shareEmail: 'bob@test.com' }, 'user@test.com');
|
|
161
|
-
const args = mockExecute.mock.calls[0][0];
|
|
162
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
163
|
-
expect(queryParams.sendNotificationEmail).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('listPermissions', () => {
|
|
167
|
-
it('hits permissions.list and requests the fields the API omits by default', async () => {
|
|
168
|
-
mockExecute.mockResolvedValueOnce({
|
|
169
|
-
success: true,
|
|
170
|
-
data: {
|
|
171
|
-
permissions: [
|
|
172
|
-
{ id: 'owner-perm', type: 'user', role: 'owner', emailAddress: 'me@test.com' },
|
|
173
|
-
{ id: 'reader-perm', type: 'user', role: 'reader', emailAddress: 'bob@test.com' },
|
|
174
|
-
],
|
|
175
|
-
},
|
|
176
|
-
stderr: '',
|
|
177
|
-
});
|
|
178
|
-
const handler = drivePatch.customHandlers.listPermissions;
|
|
179
|
-
const result = await handler({ fileId: 'file-1' }, 'me@test.com');
|
|
180
|
-
const args = mockExecute.mock.calls[0][0];
|
|
181
|
-
expect(args.slice(0, 3)).toEqual(['drive', 'permissions', 'list']);
|
|
182
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
183
|
-
expect(queryParams.fileId).toBe('file-1');
|
|
184
|
-
// Without an explicit fields mask the Drive API returns only id/type — not
|
|
185
|
-
// role or emailAddress — which made the listing useless.
|
|
186
|
-
expect(queryParams.fields).toContain('role');
|
|
187
|
-
expect(queryParams.fields).toContain('emailAddress');
|
|
188
|
-
expect(result.text).toContain('Permissions on file-1 (2)');
|
|
189
|
-
expect(result.text).toContain('owner-perm | owner | user | me@test.com');
|
|
190
|
-
expect(result.text).toContain('reader-perm | reader | user | bob@test.com');
|
|
191
|
-
expect(result.refs.count).toBe(2);
|
|
192
|
-
// A list has no canonical "the" permission — no singular permissionId ref.
|
|
193
|
-
expect(result.refs.permissionId).toBeUndefined();
|
|
194
|
-
expect(result.refs.permissions).toEqual([
|
|
195
|
-
{ permissionId: 'owner-perm', type: 'user', role: 'owner', target: 'me@test.com' },
|
|
196
|
-
{ permissionId: 'reader-perm', type: 'user', role: 'reader', target: 'bob@test.com' },
|
|
197
|
-
]);
|
|
198
|
-
});
|
|
199
|
-
it('reports an empty permission list without claiming "No files found"', async () => {
|
|
200
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { permissions: [] }, stderr: '' });
|
|
201
|
-
const handler = drivePatch.customHandlers.listPermissions;
|
|
202
|
-
const result = await handler({ fileId: 'file-2' }, 'me@test.com');
|
|
203
|
-
expect(result.text).toContain('No sharing permissions on file file-2');
|
|
204
|
-
expect(result.text).not.toContain('No files found');
|
|
205
|
-
expect(result.refs.count).toBe(0);
|
|
206
|
-
expect(result.refs.permissions).toEqual([]);
|
|
207
|
-
});
|
|
208
|
-
it('labels anyone-with-link, pending-owner, deleted, and domain/displayName targets', async () => {
|
|
209
|
-
mockExecute.mockResolvedValueOnce({
|
|
210
|
-
success: true,
|
|
211
|
-
data: {
|
|
212
|
-
permissions: [
|
|
213
|
-
{ id: 'anyone-perm', type: 'anyone', role: 'reader' },
|
|
214
|
-
{ id: 'pending-perm', type: 'user', role: 'writer', emailAddress: 'new@test.com', pendingOwner: true },
|
|
215
|
-
{ id: 'domain-perm', type: 'domain', role: 'reader', domain: 'acme.com' },
|
|
216
|
-
{ id: 'group-perm', type: 'group', role: 'commenter', displayName: 'Eng Team' },
|
|
217
|
-
{ id: 'gone-perm', type: 'user', role: 'reader', emailAddress: 'old@test.com', deleted: true },
|
|
218
|
-
],
|
|
219
|
-
},
|
|
220
|
-
stderr: '',
|
|
221
|
-
});
|
|
222
|
-
const handler = drivePatch.customHandlers.listPermissions;
|
|
223
|
-
const result = await handler({ fileId: 'file-3' }, 'me@test.com');
|
|
224
|
-
expect(result.text).toContain('anyone-perm | reader | anyone | anyone with the link');
|
|
225
|
-
expect(result.text).toContain('[pending owner]');
|
|
226
|
-
expect(result.text).toContain('domain-perm | reader | domain | acme.com');
|
|
227
|
-
expect(result.text).toContain('group-perm | commenter | group | Eng Team');
|
|
228
|
-
expect(result.text).toContain('gone-perm | reader | user | old@test.com [deleted account]');
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
describe('copy', () => {
|
|
232
|
-
it('sends name as the request body, not via --params, so the copy is renamed', async () => {
|
|
233
|
-
mockExecute.mockResolvedValueOnce({
|
|
234
|
-
success: true,
|
|
235
|
-
data: { id: 'copy-1', name: 'My New Name', mimeType: 'application/vnd.google-apps.spreadsheet' },
|
|
236
|
-
stderr: '',
|
|
237
|
-
});
|
|
238
|
-
const handler = drivePatch.customHandlers.copy;
|
|
239
|
-
const result = await handler({ fileId: 'src-1', name: 'My New Name' }, 'user@test.com');
|
|
240
|
-
const args = mockExecute.mock.calls[0][0];
|
|
241
|
-
expect(args.slice(0, 3)).toEqual(['drive', 'files', 'copy']);
|
|
242
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
243
|
-
expect(body.name).toBe('My New Name');
|
|
244
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
245
|
-
expect(queryParams.fileId).toBe('src-1');
|
|
246
|
-
// name must NOT leak into the query — that's the bug being fixed.
|
|
247
|
-
expect(queryParams.name).toBeUndefined();
|
|
248
|
-
expect(result.text).toContain('File copied: **My New Name**');
|
|
249
|
-
expect(result.text).toContain('**File ID:** copy-1');
|
|
250
|
-
expect(result.refs.fileId).toBe('copy-1');
|
|
251
|
-
expect(result.refs.sourceFileId).toBe('src-1');
|
|
252
|
-
});
|
|
253
|
-
it('forwards parentFolderId as body.parents', async () => {
|
|
254
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'copy-2', name: 'Copy of X', parents: ['folder-9'] }, stderr: '' });
|
|
255
|
-
const handler = drivePatch.customHandlers.copy;
|
|
256
|
-
await handler({ fileId: 'src-2', parentFolderId: 'folder-9' }, 'user@test.com');
|
|
257
|
-
const args = mockExecute.mock.calls[0][0];
|
|
258
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
259
|
-
expect(body.parents).toEqual(['folder-9']);
|
|
260
|
-
});
|
|
261
|
-
it('omits --json entirely when neither name nor parentFolderId is given', async () => {
|
|
262
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'copy-3', name: 'Copy of X' }, stderr: '' });
|
|
263
|
-
const handler = drivePatch.customHandlers.copy;
|
|
264
|
-
await handler({ fileId: 'src-3' }, 'user@test.com');
|
|
265
|
-
const args = mockExecute.mock.calls[0][0];
|
|
266
|
-
expect(args).not.toContain('--json');
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
describe('update', () => {
|
|
270
|
-
it('renames via the request body and surfaces the new name', async () => {
|
|
271
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'file-1', name: 'Renamed.pdf', parents: ['root'] }, stderr: '' });
|
|
272
|
-
const handler = drivePatch.customHandlers.update;
|
|
273
|
-
const result = await handler({ fileId: 'file-1', name: 'Renamed.pdf' }, 'user@test.com');
|
|
274
|
-
const args = mockExecute.mock.calls[0][0];
|
|
275
|
-
expect(args.slice(0, 3)).toEqual(['drive', 'files', 'update']);
|
|
276
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
277
|
-
expect(body.name).toBe('Renamed.pdf');
|
|
278
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
279
|
-
expect(queryParams.name).toBeUndefined();
|
|
280
|
-
expect(result.text).toContain('File updated: **Renamed.pdf**');
|
|
281
|
-
expect(result.refs.name).toBe('Renamed.pdf');
|
|
282
|
-
});
|
|
283
|
-
it('moves between folders via addParents/removeParents query params (no body)', async () => {
|
|
284
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'file-2', name: 'doc', parents: ['new-folder'] }, stderr: '' });
|
|
285
|
-
const handler = drivePatch.customHandlers.update;
|
|
286
|
-
await handler({ fileId: 'file-2', addParents: 'new-folder', removeParents: 'old-folder' }, 'user@test.com');
|
|
287
|
-
const args = mockExecute.mock.calls[0][0];
|
|
288
|
-
expect(args).not.toContain('--json'); // nothing to put in the body
|
|
289
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
290
|
-
expect(queryParams.addParents).toBe('new-folder');
|
|
291
|
-
expect(queryParams.removeParents).toBe('old-folder');
|
|
292
|
-
});
|
|
293
|
-
it('rename + move in one call populates both --json (name) and --params (parents)', async () => {
|
|
294
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'file-4', name: 'Moved.pdf', parents: ['dest'] }, stderr: '' });
|
|
295
|
-
const handler = drivePatch.customHandlers.update;
|
|
296
|
-
const result = await handler({ fileId: 'file-4', name: 'Moved.pdf', addParents: 'dest', removeParents: 'src' }, 'user@test.com');
|
|
297
|
-
const args = mockExecute.mock.calls[0][0];
|
|
298
|
-
const body = JSON.parse(args[args.indexOf('--json') + 1]);
|
|
299
|
-
expect(body.name).toBe('Moved.pdf');
|
|
300
|
-
const queryParams = JSON.parse(args[args.indexOf('--params') + 1]);
|
|
301
|
-
expect(queryParams.addParents).toBe('dest');
|
|
302
|
-
expect(queryParams.removeParents).toBe('src');
|
|
303
|
-
expect(result.text).toContain('File updated: **Moved.pdf**');
|
|
304
|
-
expect(result.text).toContain('**Parents:** dest');
|
|
305
|
-
expect(result.refs.parents).toEqual(['dest']);
|
|
306
|
-
});
|
|
307
|
-
it('omits refs.parents when the API response has none', async () => {
|
|
308
|
-
mockExecute.mockResolvedValueOnce({ success: true, data: { id: 'file-5', name: 'R.pdf' }, stderr: '' });
|
|
309
|
-
const handler = drivePatch.customHandlers.update;
|
|
310
|
-
const result = await handler({ fileId: 'file-5', name: 'R.pdf' }, 'user@test.com');
|
|
311
|
-
expect('parents' in result.refs).toBe(false);
|
|
312
|
-
});
|
|
313
|
-
it('rejects a no-op update (none of name/addParents/removeParents)', async () => {
|
|
314
|
-
const handler = drivePatch.customHandlers.update;
|
|
315
|
-
await expect(handler({ fileId: 'file-3' }, 'user@test.com')).rejects.toThrow(/name.*addParents.*removeParents/);
|
|
316
|
-
expect(mockExecute).not.toHaveBeenCalled();
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
describe('download', () => {
|
|
320
|
-
it('creates parent directories before calling gws', async () => {
|
|
321
|
-
const outputPath = path.join(tmpWorkspace, 'images', 'photo.png');
|
|
322
|
-
const { resolveWorkspacePath } = require('../../executor/workspace.js');
|
|
323
|
-
resolveWorkspacePath.mockReturnValue(outputPath);
|
|
324
|
-
// Verify parent does NOT exist before handler runs
|
|
325
|
-
const parentBefore = await fs.stat(path.dirname(outputPath)).catch(() => null);
|
|
326
|
-
expect(parentBefore).toBeNull();
|
|
327
|
-
mockExecute
|
|
328
|
-
.mockResolvedValueOnce({ success: true, data: { name: 'photo.png', mimeType: 'image/png' }, stderr: '' })
|
|
329
|
-
.mockImplementationOnce(async () => {
|
|
330
|
-
// Parent directory must exist at this point
|
|
331
|
-
const stat = await fs.stat(path.dirname(outputPath));
|
|
332
|
-
expect(stat.isDirectory()).toBe(true);
|
|
333
|
-
await fs.writeFile(outputPath, 'png data');
|
|
334
|
-
return { success: true, data: {}, stderr: '' };
|
|
335
|
-
});
|
|
336
|
-
const handler = drivePatch.customHandlers.download;
|
|
337
|
-
await handler({ fileId: 'img-1', outputPath: 'images/photo.png' }, 'user@test.com');
|
|
338
|
-
const downloadCall = mockExecute.mock.calls[1][0];
|
|
339
|
-
expect(downloadCall).toContain('--output');
|
|
340
|
-
expect(downloadCall[downloadCall.indexOf('--output') + 1]).toBe(outputPath);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
//# sourceMappingURL=drive-patch.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"drive-patch.test.js","sourceRoot":"","sources":["../../../src/__tests__/factory/drive-patch.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { loadManifest, generateTools, generateSchema, generateHandler } from '../../factory/generator.js';
|
|
2
|
-
import { patches } from '../../factory/patches.js';
|
|
3
|
-
// Mock executor for handler tests
|
|
4
|
-
jest.mock('../../executor/gws.js');
|
|
5
|
-
import { execute } from '../../executor/gws.js';
|
|
6
|
-
const mockExecute = execute;
|
|
7
|
-
describe('loadManifest', () => {
|
|
8
|
-
it('loads and parses the manifest YAML', () => {
|
|
9
|
-
const manifest = loadManifest();
|
|
10
|
-
expect(manifest.services).toBeDefined();
|
|
11
|
-
expect(manifest.services.gmail).toBeDefined();
|
|
12
|
-
expect(manifest.services.calendar).toBeDefined();
|
|
13
|
-
expect(manifest.services.drive).toBeDefined();
|
|
14
|
-
});
|
|
15
|
-
it('has correct tool names', () => {
|
|
16
|
-
const manifest = loadManifest();
|
|
17
|
-
expect(manifest.services.gmail.tool_name).toBe('manage_email');
|
|
18
|
-
expect(manifest.services.calendar.tool_name).toBe('manage_calendar');
|
|
19
|
-
expect(manifest.services.drive.tool_name).toBe('manage_drive');
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
describe('generateSchema', () => {
|
|
23
|
-
const manifest = loadManifest();
|
|
24
|
-
it('generates operation enum from manifest operations', () => {
|
|
25
|
-
const schema = generateSchema(manifest.services.gmail);
|
|
26
|
-
const props = schema.inputSchema.properties;
|
|
27
|
-
// Core operations present (manifest may expand)
|
|
28
|
-
expect(props.operation.enum).toContain('search');
|
|
29
|
-
expect(props.operation.enum).toContain('read');
|
|
30
|
-
expect(props.operation.enum).toContain('send');
|
|
31
|
-
expect(props.operation.enum).toContain('reply');
|
|
32
|
-
expect(props.operation.enum).toContain('triage');
|
|
33
|
-
expect(props.operation.enum).toContain('forward');
|
|
34
|
-
expect(props.operation.enum).toContain('trash');
|
|
35
|
-
expect(props.operation.enum).toContain('labels');
|
|
36
|
-
});
|
|
37
|
-
it('includes email param when requires_email is true', () => {
|
|
38
|
-
const schema = generateSchema(manifest.services.gmail);
|
|
39
|
-
const required = schema.inputSchema.required;
|
|
40
|
-
expect(required).toContain('email');
|
|
41
|
-
});
|
|
42
|
-
it('collects params from all operations', () => {
|
|
43
|
-
const schema = generateSchema(manifest.services.gmail);
|
|
44
|
-
const props = schema.inputSchema.properties;
|
|
45
|
-
// From search
|
|
46
|
-
expect(props.query).toBeDefined();
|
|
47
|
-
expect(props.maxResults).toBeDefined();
|
|
48
|
-
// From read
|
|
49
|
-
expect(props.messageId).toBeDefined();
|
|
50
|
-
// From send
|
|
51
|
-
expect(props.to).toBeDefined();
|
|
52
|
-
expect(props.subject).toBeDefined();
|
|
53
|
-
expect(props.body).toBeDefined();
|
|
54
|
-
});
|
|
55
|
-
it('sets additionalProperties: false', () => {
|
|
56
|
-
const schema = generateSchema(manifest.services.gmail);
|
|
57
|
-
expect(schema.inputSchema.additionalProperties).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
it('uses tool_name from service def', () => {
|
|
60
|
-
const schema = generateSchema(manifest.services.drive);
|
|
61
|
-
expect(schema.name).toBe('manage_drive');
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
describe('generateTools', () => {
|
|
65
|
-
it('produces one tool per manifest service', () => {
|
|
66
|
-
const manifest = loadManifest();
|
|
67
|
-
const tools = generateTools(manifest, patches);
|
|
68
|
-
expect(tools.length).toBeGreaterThanOrEqual(6);
|
|
69
|
-
const names = tools.map(t => t.schema.name);
|
|
70
|
-
expect(names).toContain('manage_email');
|
|
71
|
-
expect(names).toContain('manage_calendar');
|
|
72
|
-
expect(names).toContain('manage_drive');
|
|
73
|
-
expect(names).toContain('manage_sheets');
|
|
74
|
-
expect(names).toContain('manage_tasks');
|
|
75
|
-
expect(names).toContain('manage_meet');
|
|
76
|
-
// manage_contacts excluded pending gws auth scope support
|
|
77
|
-
});
|
|
78
|
-
it('each tool has both schema and handler', () => {
|
|
79
|
-
const manifest = loadManifest();
|
|
80
|
-
const tools = generateTools(manifest, patches);
|
|
81
|
-
for (const tool of tools) {
|
|
82
|
-
expect(tool.schema).toHaveProperty('name');
|
|
83
|
-
expect(tool.schema).toHaveProperty('inputSchema');
|
|
84
|
-
expect(typeof tool.handler).toBe('function');
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe('generateHandler', () => {
|
|
89
|
-
const manifest = loadManifest();
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
mockExecute.mockReset();
|
|
92
|
-
});
|
|
93
|
-
it('calls execute with correct args for resource-based operations', async () => {
|
|
94
|
-
mockExecute.mockResolvedValue({ success: true, data: { files: [] }, stderr: '' });
|
|
95
|
-
const handler = generateHandler(manifest.services.drive, patches.drive);
|
|
96
|
-
await handler({ operation: 'search', email: 'u@t.com', query: 'budget' });
|
|
97
|
-
expect(mockExecute).toHaveBeenCalledWith(expect.arrayContaining(['drive', 'files', 'list']), expect.objectContaining({ account: 'u@t.com' }));
|
|
98
|
-
});
|
|
99
|
-
it('calls execute with correct args for helper-based operations', async () => {
|
|
100
|
-
mockExecute.mockResolvedValue({ success: true, data: { messages: [] }, stderr: '' });
|
|
101
|
-
const handler = generateHandler(manifest.services.gmail, patches.gmail);
|
|
102
|
-
await handler({ operation: 'triage', email: 'u@t.com' });
|
|
103
|
-
expect(mockExecute).toHaveBeenCalledWith(['gmail', '+triage'], expect.objectContaining({ account: 'u@t.com' }));
|
|
104
|
-
});
|
|
105
|
-
it('uses patch formatList when available', async () => {
|
|
106
|
-
mockExecute.mockResolvedValue({
|
|
107
|
-
success: true,
|
|
108
|
-
data: { messages: [{ id: 'msg-1', from: 'alice', subject: 'hi', date: '2024-01-01' }] },
|
|
109
|
-
stderr: '',
|
|
110
|
-
});
|
|
111
|
-
const handler = generateHandler(manifest.services.gmail, patches.gmail);
|
|
112
|
-
const result = await handler({ operation: 'triage', email: 'u@t.com' });
|
|
113
|
-
// Gmail patch uses formatEmailList which produces pipe-delimited format
|
|
114
|
-
expect(result.text).toContain('msg-1');
|
|
115
|
-
expect(result.text).toContain('|');
|
|
116
|
-
});
|
|
117
|
-
it('delegates to customHandler when defined', async () => {
|
|
118
|
-
// Gmail send is a custom handler
|
|
119
|
-
mockExecute.mockResolvedValue({
|
|
120
|
-
success: true,
|
|
121
|
-
data: { id: 'sent-1', threadId: 'thread-1' },
|
|
122
|
-
stderr: '',
|
|
123
|
-
});
|
|
124
|
-
const handler = generateHandler(manifest.services.gmail, patches.gmail);
|
|
125
|
-
const result = await handler({
|
|
126
|
-
operation: 'send',
|
|
127
|
-
email: 'u@t.com',
|
|
128
|
-
to: 'bob@t.com',
|
|
129
|
-
subject: 'hello',
|
|
130
|
-
body: 'hi bob',
|
|
131
|
-
});
|
|
132
|
-
expect(result.text).toContain('Email sent to bob@t.com');
|
|
133
|
-
expect(result.refs).toHaveProperty('to', 'bob@t.com');
|
|
134
|
-
});
|
|
135
|
-
it('throws on unknown operation', async () => {
|
|
136
|
-
const handler = generateHandler(manifest.services.gmail, patches.gmail);
|
|
137
|
-
await expect(handler({ operation: 'nonexistent', email: 'u@t.com' })).rejects.toThrow('Unknown gmail operation: nonexistent');
|
|
138
|
-
});
|
|
139
|
-
it('applies afterExecute hook for gmail search hydration', async () => {
|
|
140
|
-
// First call: messages.list returns IDs
|
|
141
|
-
mockExecute.mockResolvedValueOnce({
|
|
142
|
-
success: true,
|
|
143
|
-
data: { messages: [{ id: 'msg-1' }, { id: 'msg-2' }] },
|
|
144
|
-
stderr: '',
|
|
145
|
-
});
|
|
146
|
-
// Hydration calls for each message
|
|
147
|
-
mockExecute.mockResolvedValueOnce({
|
|
148
|
-
success: true,
|
|
149
|
-
data: {
|
|
150
|
-
id: 'msg-1', threadId: 't1', snippet: 'hello',
|
|
151
|
-
payload: { headers: [
|
|
152
|
-
{ name: 'From', value: 'alice@t.com' },
|
|
153
|
-
{ name: 'Subject', value: 'Meeting' },
|
|
154
|
-
{ name: 'Date', value: '2024-01-15' },
|
|
155
|
-
] },
|
|
156
|
-
},
|
|
157
|
-
stderr: '',
|
|
158
|
-
});
|
|
159
|
-
mockExecute.mockResolvedValueOnce({
|
|
160
|
-
success: true,
|
|
161
|
-
data: {
|
|
162
|
-
id: 'msg-2', threadId: 't2', snippet: 'world',
|
|
163
|
-
payload: { headers: [
|
|
164
|
-
{ name: 'From', value: 'bob@t.com' },
|
|
165
|
-
{ name: 'Subject', value: 'Update' },
|
|
166
|
-
{ name: 'Date', value: '2024-01-16' },
|
|
167
|
-
] },
|
|
168
|
-
},
|
|
169
|
-
stderr: '',
|
|
170
|
-
});
|
|
171
|
-
const handler = generateHandler(manifest.services.gmail, patches.gmail);
|
|
172
|
-
const result = await handler({ operation: 'search', email: 'u@t.com', query: 'test' });
|
|
173
|
-
// Should have hydrated the messages
|
|
174
|
-
expect(result.text).toContain('alice@t.com');
|
|
175
|
-
expect(result.text).toContain('Meeting');
|
|
176
|
-
expect(result.refs).toHaveProperty('count', 2);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
// ADR-303: the generator appends next-steps for custom handlers, so patches
|
|
180
|
-
// no longer need to call nextSteps() inline. This is the architectural
|
|
181
|
-
// guarantee that replaces the per-handler regression tests.
|
|
182
|
-
describe('generateHandler — custom-handler next-steps wrapping', () => {
|
|
183
|
-
const manifest = loadManifest();
|
|
184
|
-
beforeEach(() => mockExecute.mockReset());
|
|
185
|
-
it('appends next-steps to a custom handler response', async () => {
|
|
186
|
-
// sheets.addSheet is a customHandler — its handler return has no footer,
|
|
187
|
-
// but the factory should wrap with one from the next-steps registry.
|
|
188
|
-
mockExecute.mockResolvedValueOnce({
|
|
189
|
-
success: true,
|
|
190
|
-
data: { replies: [{ addSheet: { properties: { sheetId: 42, title: 'T', gridProperties: {} } } }] },
|
|
191
|
-
stderr: '',
|
|
192
|
-
});
|
|
193
|
-
const handler = generateHandler(manifest.services.sheets, patches.sheets);
|
|
194
|
-
const result = await handler({
|
|
195
|
-
operation: 'addSheet',
|
|
196
|
-
email: 'u@t.com',
|
|
197
|
-
spreadsheetId: 'sheet-123',
|
|
198
|
-
title: 'T',
|
|
199
|
-
});
|
|
200
|
-
expect(result.text).toContain('Sheet added');
|
|
201
|
-
expect(result.text).toContain('Next steps:');
|
|
202
|
-
});
|
|
203
|
-
it('resolves placeholder values from the input params on custom handlers', async () => {
|
|
204
|
-
mockExecute.mockResolvedValueOnce({
|
|
205
|
-
success: true,
|
|
206
|
-
data: { replies: [{}] },
|
|
207
|
-
stderr: '',
|
|
208
|
-
});
|
|
209
|
-
const handler = generateHandler(manifest.services.sheets, patches.sheets);
|
|
210
|
-
const result = await handler({
|
|
211
|
-
operation: 'renameSheet',
|
|
212
|
-
email: 'u@t.com',
|
|
213
|
-
spreadsheetId: 'sheet-xyz',
|
|
214
|
-
sheetId: 0,
|
|
215
|
-
title: 'Main',
|
|
216
|
-
});
|
|
217
|
-
// The sheets.renameSheet next-steps entry references <spreadsheetId> —
|
|
218
|
-
// the generator's contextMap should have resolved it.
|
|
219
|
-
expect(result.text).toContain('sheet-xyz');
|
|
220
|
-
expect(result.text).not.toContain('<spreadsheetId>');
|
|
221
|
-
});
|
|
222
|
-
it('does not double-append next-steps (regression for ADR-303 migration)', async () => {
|
|
223
|
-
mockExecute.mockResolvedValueOnce({
|
|
224
|
-
success: true,
|
|
225
|
-
data: { replies: [{ addSheet: { properties: { sheetId: 99, title: 'Q', gridProperties: {} } } }] },
|
|
226
|
-
stderr: '',
|
|
227
|
-
});
|
|
228
|
-
const handler = generateHandler(manifest.services.sheets, patches.sheets);
|
|
229
|
-
const result = await handler({
|
|
230
|
-
operation: 'addSheet',
|
|
231
|
-
email: 'u@t.com',
|
|
232
|
-
spreadsheetId: 'sheet-123',
|
|
233
|
-
title: 'Q',
|
|
234
|
-
});
|
|
235
|
-
// Exactly one footer marker in the response
|
|
236
|
-
const matches = result.text.match(/---\n\*\*Next steps:\*\*/g) ?? [];
|
|
237
|
-
expect(matches.length).toBe(1);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
//# sourceMappingURL=generator.test.js.map
|