@juppytt/fws 0.1.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/settings.local.json +72 -0
- package/README.md +126 -0
- package/bin/fws-cli.sh +4 -0
- package/bin/fws.ts +421 -0
- package/docs/cli-reference.md +211 -0
- package/docs/gws-support.md +276 -0
- package/package.json +28 -0
- package/src/config/rewrite-cache.ts +73 -0
- package/src/index.ts +3 -0
- package/src/proxy/mitm.ts +285 -0
- package/src/server/app.ts +26 -0
- package/src/server/middleware.ts +38 -0
- package/src/server/routes/calendar.ts +483 -0
- package/src/server/routes/control.ts +151 -0
- package/src/server/routes/drive.ts +342 -0
- package/src/server/routes/gmail.ts +758 -0
- package/src/server/routes/people.ts +239 -0
- package/src/server/routes/sheets.ts +242 -0
- package/src/server/routes/tasks.ts +191 -0
- package/src/store/index.ts +24 -0
- package/src/store/seed.ts +313 -0
- package/src/store/types.ts +225 -0
- package/src/util/id.ts +9 -0
- package/test/calendar.test.ts +227 -0
- package/test/drive.test.ts +153 -0
- package/test/gmail.test.ts +215 -0
- package/test/gws-validation.test.ts +883 -0
- package/test/helpers/harness.ts +109 -0
- package/test/snapshot.test.ts +80 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { createTestHarness, type TestHarness } from './helpers/harness.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* End-to-end validation: every implemented endpoint tested through the actual gws CLI.
|
|
6
|
+
*/
|
|
7
|
+
describe('gws CLI validation', () => {
|
|
8
|
+
let h: TestHarness;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
h = await createTestHarness();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await h.cleanup();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// ===== Gmail =====
|
|
19
|
+
|
|
20
|
+
describe('gmail', () => {
|
|
21
|
+
describe('profile', () => {
|
|
22
|
+
it('gws gmail users getProfile', async () => {
|
|
23
|
+
const { stdout, exitCode } = await h.gws('gmail users getProfile --params {"userId":"me"}');
|
|
24
|
+
expect(exitCode).toBe(0);
|
|
25
|
+
const data = JSON.parse(stdout);
|
|
26
|
+
expect(data.emailAddress).toBe('testuser@example.com');
|
|
27
|
+
expect(data.messagesTotal).toBeTypeOf('number');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('labels', () => {
|
|
32
|
+
it('gws gmail users labels list', async () => {
|
|
33
|
+
const { stdout, exitCode } = await h.gws('gmail users labels list --params {"userId":"me"}');
|
|
34
|
+
expect(exitCode).toBe(0);
|
|
35
|
+
const data = JSON.parse(stdout);
|
|
36
|
+
expect(data.labels.map((l: any) => l.id)).toContain('INBOX');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('gws gmail users labels get', async () => {
|
|
40
|
+
const { stdout, exitCode } = await h.gws('gmail users labels get --params {"userId":"me","id":"INBOX"}');
|
|
41
|
+
expect(exitCode).toBe(0);
|
|
42
|
+
const data = JSON.parse(stdout);
|
|
43
|
+
expect(data.id).toBe('INBOX');
|
|
44
|
+
expect(data.type).toBe('system');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
let createdLabelId: string;
|
|
48
|
+
|
|
49
|
+
it('gws gmail users labels create', async () => {
|
|
50
|
+
const { stdout, exitCode } = await h.gws('gmail users labels create --params {"userId":"me"} --json {"name":"GwsTestLabel"}');
|
|
51
|
+
expect(exitCode).toBe(0);
|
|
52
|
+
const data = JSON.parse(stdout);
|
|
53
|
+
expect(data.name).toBe('GwsTestLabel');
|
|
54
|
+
expect(data.type).toBe('user');
|
|
55
|
+
createdLabelId = data.id;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('gws gmail users labels patch', async () => {
|
|
59
|
+
const { stdout, exitCode } = await h.gws(`gmail users labels patch --params {"userId":"me","id":"${createdLabelId}"} --json {"name":"RenamedLabel"}`);
|
|
60
|
+
expect(exitCode).toBe(0);
|
|
61
|
+
const data = JSON.parse(stdout);
|
|
62
|
+
expect(data.name).toBe('RenamedLabel');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('gws gmail users labels update (PUT)', async () => {
|
|
66
|
+
const { stdout, exitCode } = await h.gws(`gmail users labels update --params {"userId":"me","id":"${createdLabelId}"} --json {"name":"PutLabel","messageListVisibility":"show","labelListVisibility":"labelShow"}`);
|
|
67
|
+
expect(exitCode).toBe(0);
|
|
68
|
+
const data = JSON.parse(stdout);
|
|
69
|
+
expect(data.name).toBe('PutLabel');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('gws gmail users labels delete', async () => {
|
|
73
|
+
const { stdout, exitCode } = await h.gws(`gmail users labels delete --params {"userId":"me","id":"${createdLabelId}"}`);
|
|
74
|
+
expect(exitCode).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('messages', () => {
|
|
79
|
+
it('gws gmail users messages list', async () => {
|
|
80
|
+
const { stdout, exitCode } = await h.gws('gmail users messages list --params {"userId":"me"}');
|
|
81
|
+
expect(exitCode).toBe(0);
|
|
82
|
+
const data = JSON.parse(stdout);
|
|
83
|
+
expect(data.messages.length).toBeGreaterThan(0);
|
|
84
|
+
expect(data.messages[0]).toHaveProperty('id');
|
|
85
|
+
expect(data.messages[0]).toHaveProperty('threadId');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('gws gmail users messages list with q filter', async () => {
|
|
89
|
+
const { stdout, exitCode } = await h.gws('gmail users messages list --params {"userId":"me","q":"from:alice"}');
|
|
90
|
+
expect(exitCode).toBe(0);
|
|
91
|
+
const data = JSON.parse(stdout);
|
|
92
|
+
expect(data.messages.length).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('gws gmail users messages list with maxResults', async () => {
|
|
96
|
+
const { stdout, exitCode } = await h.gws('gmail users messages list --params {"userId":"me","maxResults":2}');
|
|
97
|
+
expect(exitCode).toBe(0);
|
|
98
|
+
const data = JSON.parse(stdout);
|
|
99
|
+
expect(data.messages.length).toBeLessThanOrEqual(2);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('gws gmail users messages get', async () => {
|
|
103
|
+
const { stdout, exitCode } = await h.gws('gmail users messages get --params {"userId":"me","id":"msg001"}');
|
|
104
|
+
expect(exitCode).toBe(0);
|
|
105
|
+
const data = JSON.parse(stdout);
|
|
106
|
+
expect(data.id).toBe('msg001');
|
|
107
|
+
expect(data.payload).toBeDefined();
|
|
108
|
+
expect(data.payload.headers).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('gws gmail users messages get with format=minimal', async () => {
|
|
112
|
+
const { stdout, exitCode } = await h.gws('gmail users messages get --params {"userId":"me","id":"msg001","format":"minimal"}');
|
|
113
|
+
expect(exitCode).toBe(0);
|
|
114
|
+
const data = JSON.parse(stdout);
|
|
115
|
+
expect(data.id).toBe('msg001');
|
|
116
|
+
expect(data.labelIds).toBeDefined();
|
|
117
|
+
expect(data.payload).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('gws gmail users messages send', async () => {
|
|
121
|
+
const raw = Buffer.from(
|
|
122
|
+
'From: testuser@example.com\r\nTo: bob@example.com\r\nSubject: Gws Send Test\r\n\r\nHello from gws'
|
|
123
|
+
).toString('base64url');
|
|
124
|
+
const { stdout, exitCode } = await h.gws(`gmail users messages send --params {"userId":"me"} --json {"raw":"${raw}"}`);
|
|
125
|
+
expect(exitCode).toBe(0);
|
|
126
|
+
const data = JSON.parse(stdout);
|
|
127
|
+
expect(data.id).toBeTruthy();
|
|
128
|
+
expect(data.labelIds).toContain('SENT');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('gws gmail users messages modify', async () => {
|
|
132
|
+
const { stdout, exitCode } = await h.gws('gmail users messages modify --params {"userId":"me","id":"msg001"} --json {"addLabelIds":["STARRED"],"removeLabelIds":["UNREAD"]}');
|
|
133
|
+
expect(exitCode).toBe(0);
|
|
134
|
+
const data = JSON.parse(stdout);
|
|
135
|
+
expect(data.labelIds).toContain('STARRED');
|
|
136
|
+
expect(data.labelIds).not.toContain('UNREAD');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('gws gmail users messages trash', async () => {
|
|
140
|
+
// Setup a message to trash
|
|
141
|
+
await h.fetch('/__fws/setup/gmail/message', {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: { 'Content-Type': 'application/json' },
|
|
144
|
+
body: JSON.stringify({ from: 'trash-gws@test.com', subject: 'Trash via gws', body: 'x' }),
|
|
145
|
+
});
|
|
146
|
+
const listRes = await h.fetch('/gmail/v1/users/me/messages?q=from:trash-gws@test.com');
|
|
147
|
+
const list = await listRes.json();
|
|
148
|
+
const msgId = list.messages[0].id;
|
|
149
|
+
|
|
150
|
+
const { stdout, exitCode } = await h.gws(`gmail users messages trash --params {"userId":"me","id":"${msgId}"}`);
|
|
151
|
+
expect(exitCode).toBe(0);
|
|
152
|
+
const data = JSON.parse(stdout);
|
|
153
|
+
expect(data.labelIds).toContain('TRASH');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('gws gmail users messages untrash', async () => {
|
|
157
|
+
const listRes = await h.fetch('/gmail/v1/users/me/messages?q=from:trash-gws@test.com');
|
|
158
|
+
const list = await listRes.json();
|
|
159
|
+
// Find trashed message
|
|
160
|
+
const getRes = await h.fetch(`/gmail/v1/users/me/messages/${list.messages[0].id}`);
|
|
161
|
+
const msg = await getRes.json();
|
|
162
|
+
if (!msg.labelIds.includes('TRASH')) return; // already untrashed
|
|
163
|
+
|
|
164
|
+
const { stdout, exitCode } = await h.gws(`gmail users messages untrash --params {"userId":"me","id":"${msg.id}"}`);
|
|
165
|
+
expect(exitCode).toBe(0);
|
|
166
|
+
const data = JSON.parse(stdout);
|
|
167
|
+
expect(data.labelIds).not.toContain('TRASH');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('gws gmail users messages delete', async () => {
|
|
171
|
+
// Create a throwaway message
|
|
172
|
+
const setup = await h.fetch('/__fws/setup/gmail/message', {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({ from: 'del-gws@test.com', subject: 'Delete via gws', body: 'x' }),
|
|
176
|
+
});
|
|
177
|
+
const { id } = await setup.json();
|
|
178
|
+
|
|
179
|
+
const { exitCode } = await h.gws(`gmail users messages delete --params {"userId":"me","id":"${id}"}`);
|
|
180
|
+
expect(exitCode).toBe(0);
|
|
181
|
+
|
|
182
|
+
// Verify deleted
|
|
183
|
+
const getRes = await h.fetch(`/gmail/v1/users/me/messages/${id}`);
|
|
184
|
+
expect(getRes.status).toBe(404);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('gws gmail users messages insert', async () => {
|
|
188
|
+
const raw = Buffer.from(
|
|
189
|
+
'From: external@test.com\r\nTo: testuser@example.com\r\nSubject: Inserted\r\n\r\nInserted body'
|
|
190
|
+
).toString('base64url');
|
|
191
|
+
const { stdout, exitCode } = await h.gws(`gmail users messages insert --params {"userId":"me"} --json {"raw":"${raw}"}`);
|
|
192
|
+
expect(exitCode).toBe(0);
|
|
193
|
+
const data = JSON.parse(stdout);
|
|
194
|
+
expect(data.id).toBeTruthy();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('gws gmail users messages import', async () => {
|
|
198
|
+
const raw = Buffer.from(
|
|
199
|
+
'From: imported@test.com\r\nTo: testuser@example.com\r\nSubject: Imported\r\n\r\nImported body'
|
|
200
|
+
).toString('base64url');
|
|
201
|
+
const { stdout, exitCode } = await h.gws(`gmail users messages import --params {"userId":"me"} --json {"raw":"${raw}"}`);
|
|
202
|
+
expect(exitCode).toBe(0);
|
|
203
|
+
const data = JSON.parse(stdout);
|
|
204
|
+
expect(data.id).toBeTruthy();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('gws gmail users messages batchModify', async () => {
|
|
208
|
+
// Create two messages
|
|
209
|
+
const s1 = await h.fetch('/__fws/setup/gmail/message', {
|
|
210
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({ from: 'batch1@test.com', subject: 'Batch 1', body: 'x', labels: ['INBOX'] }),
|
|
212
|
+
});
|
|
213
|
+
const s2 = await h.fetch('/__fws/setup/gmail/message', {
|
|
214
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
body: JSON.stringify({ from: 'batch2@test.com', subject: 'Batch 2', body: 'x', labels: ['INBOX'] }),
|
|
216
|
+
});
|
|
217
|
+
const id1 = (await s1.json()).id;
|
|
218
|
+
const id2 = (await s2.json()).id;
|
|
219
|
+
|
|
220
|
+
const { exitCode } = await h.gws(`gmail users messages batchModify --params {"userId":"me"} --json {"ids":["${id1}","${id2}"],"addLabelIds":["STARRED"]}`);
|
|
221
|
+
expect(exitCode).toBe(0);
|
|
222
|
+
|
|
223
|
+
// Verify both starred
|
|
224
|
+
const g1 = await h.fetch(`/gmail/v1/users/me/messages/${id1}`);
|
|
225
|
+
expect((await g1.json()).labelIds).toContain('STARRED');
|
|
226
|
+
const g2 = await h.fetch(`/gmail/v1/users/me/messages/${id2}`);
|
|
227
|
+
expect((await g2.json()).labelIds).toContain('STARRED');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('gws gmail users messages batchDelete', async () => {
|
|
231
|
+
const s1 = await h.fetch('/__fws/setup/gmail/message', {
|
|
232
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
233
|
+
body: JSON.stringify({ from: 'batchdel@test.com', subject: 'Del 1', body: 'x' }),
|
|
234
|
+
});
|
|
235
|
+
const s2 = await h.fetch('/__fws/setup/gmail/message', {
|
|
236
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({ from: 'batchdel@test.com', subject: 'Del 2', body: 'x' }),
|
|
238
|
+
});
|
|
239
|
+
const id1 = (await s1.json()).id;
|
|
240
|
+
const id2 = (await s2.json()).id;
|
|
241
|
+
|
|
242
|
+
const { exitCode } = await h.gws(`gmail users messages batchDelete --params {"userId":"me"} --json {"ids":["${id1}","${id2}"]}`);
|
|
243
|
+
expect(exitCode).toBe(0);
|
|
244
|
+
|
|
245
|
+
// Verify both deleted
|
|
246
|
+
expect((await h.fetch(`/gmail/v1/users/me/messages/${id1}`)).status).toBe(404);
|
|
247
|
+
expect((await h.fetch(`/gmail/v1/users/me/messages/${id2}`)).status).toBe(404);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('threads', () => {
|
|
252
|
+
it('gws gmail users threads list', async () => {
|
|
253
|
+
const { stdout, exitCode } = await h.gws('gmail users threads list --params {"userId":"me"}');
|
|
254
|
+
expect(exitCode).toBe(0);
|
|
255
|
+
const data = JSON.parse(stdout);
|
|
256
|
+
expect(data.threads.length).toBeGreaterThan(0);
|
|
257
|
+
expect(data.threads[0]).toHaveProperty('id');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('gws gmail users threads get', async () => {
|
|
261
|
+
const { stdout, exitCode } = await h.gws('gmail users threads get --params {"userId":"me","id":"thread001"}');
|
|
262
|
+
expect(exitCode).toBe(0);
|
|
263
|
+
const data = JSON.parse(stdout);
|
|
264
|
+
expect(data.id).toBe('thread001');
|
|
265
|
+
expect(data.messages.length).toBeGreaterThan(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('gws gmail users threads modify', async () => {
|
|
269
|
+
const { stdout, exitCode } = await h.gws('gmail users threads modify --params {"userId":"me","id":"thread001"} --json {"addLabelIds":["STARRED"]}');
|
|
270
|
+
expect(exitCode).toBe(0);
|
|
271
|
+
const data = JSON.parse(stdout);
|
|
272
|
+
expect(data.messages[0].labelIds).toContain('STARRED');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('gws gmail users threads trash', async () => {
|
|
276
|
+
// Setup a thread to trash
|
|
277
|
+
await h.fetch('/__fws/setup/gmail/message', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { 'Content-Type': 'application/json' },
|
|
280
|
+
body: JSON.stringify({ from: 'thread-trash@test.com', subject: 'Thread trash', body: 'x' }),
|
|
281
|
+
});
|
|
282
|
+
const listRes = await h.fetch('/gmail/v1/users/me/messages?q=from:thread-trash@test.com');
|
|
283
|
+
const list = await listRes.json();
|
|
284
|
+
const threadId = list.messages[0].threadId;
|
|
285
|
+
|
|
286
|
+
const { stdout, exitCode } = await h.gws(`gmail users threads trash --params {"userId":"me","id":"${threadId}"}`);
|
|
287
|
+
expect(exitCode).toBe(0);
|
|
288
|
+
const data = JSON.parse(stdout);
|
|
289
|
+
expect(data.messages[0].labelIds).toContain('TRASH');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('gws gmail users threads untrash', async () => {
|
|
293
|
+
const listRes = await h.fetch('/gmail/v1/users/me/messages?q=from:thread-trash@test.com');
|
|
294
|
+
const list = await listRes.json();
|
|
295
|
+
const threadId = list.messages[0].threadId;
|
|
296
|
+
|
|
297
|
+
const { stdout, exitCode } = await h.gws(`gmail users threads untrash --params {"userId":"me","id":"${threadId}"}`);
|
|
298
|
+
expect(exitCode).toBe(0);
|
|
299
|
+
const data = JSON.parse(stdout);
|
|
300
|
+
expect(data.messages[0].labelIds).not.toContain('TRASH');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('gws gmail users threads delete', async () => {
|
|
304
|
+
const listRes = await h.fetch('/gmail/v1/users/me/messages?q=from:thread-trash@test.com');
|
|
305
|
+
const list = await listRes.json();
|
|
306
|
+
const threadId = list.messages[0].threadId;
|
|
307
|
+
|
|
308
|
+
const { exitCode } = await h.gws(`gmail users threads delete --params {"userId":"me","id":"${threadId}"}`);
|
|
309
|
+
expect(exitCode).toBe(0);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('helpers', () => {
|
|
314
|
+
it('gws gmail +triage', async () => {
|
|
315
|
+
const { stdout, exitCode } = await h.gwsProxy('gmail +triage --max 3 --format json');
|
|
316
|
+
expect(exitCode).toBe(0);
|
|
317
|
+
const data = JSON.parse(stdout);
|
|
318
|
+
expect(data.messages.length).toBeGreaterThan(0);
|
|
319
|
+
expect(data.messages[0]).toHaveProperty('subject');
|
|
320
|
+
expect(data.messages[0]).toHaveProperty('from');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('gws gmail +send', async () => {
|
|
324
|
+
const { stdout, exitCode } = await h.gwsProxy('gmail +send --to bob@example.com --subject "Test send" --body "Hello"');
|
|
325
|
+
expect(exitCode).toBe(0);
|
|
326
|
+
const data = JSON.parse(stdout);
|
|
327
|
+
expect(data.id).toBeTruthy();
|
|
328
|
+
expect(data.labelIds).toContain('SENT');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('gws gmail +reply', async () => {
|
|
332
|
+
const { stdout, exitCode } = await h.gwsProxy('gmail +reply --message-id msg001 --body "Got it"');
|
|
333
|
+
expect(exitCode).toBe(0);
|
|
334
|
+
const data = JSON.parse(stdout);
|
|
335
|
+
expect(data.id).toBeTruthy();
|
|
336
|
+
expect(data.threadId).toBe('thread001');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('gws gmail +reply-all', async () => {
|
|
340
|
+
const { stdout, exitCode } = await h.gwsProxy('gmail +reply-all --message-id msg001 --body "Sounds good"');
|
|
341
|
+
expect(exitCode).toBe(0);
|
|
342
|
+
const data = JSON.parse(stdout);
|
|
343
|
+
expect(data.id).toBeTruthy();
|
|
344
|
+
expect(data.threadId).toBe('thread001');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('gws gmail +forward', async () => {
|
|
348
|
+
const { stdout, exitCode } = await h.gwsProxy('gmail +forward --message-id msg001 --to carol@example.com --body "FYI"');
|
|
349
|
+
expect(exitCode).toBe(0);
|
|
350
|
+
const data = JSON.parse(stdout);
|
|
351
|
+
expect(data.id).toBeTruthy();
|
|
352
|
+
expect(data.threadId).toBe('thread001');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('settings', () => {
|
|
357
|
+
it('gws gmail users settings sendAs list', async () => {
|
|
358
|
+
const { stdout, exitCode } = await h.gws('gmail users settings sendAs list --params {"userId":"me"}');
|
|
359
|
+
expect(exitCode).toBe(0);
|
|
360
|
+
const data = JSON.parse(stdout);
|
|
361
|
+
expect(data.sendAs.length).toBeGreaterThan(0);
|
|
362
|
+
expect(data.sendAs[0].sendAsEmail).toBe('testuser@example.com');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('drafts', () => {
|
|
367
|
+
it('gws gmail users drafts list', async () => {
|
|
368
|
+
const { stdout, exitCode } = await h.gws('gmail users drafts list --params {"userId":"me"}');
|
|
369
|
+
expect(exitCode).toBe(0);
|
|
370
|
+
const data = JSON.parse(stdout);
|
|
371
|
+
expect(data.drafts).toBeDefined();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
let draftId: string;
|
|
375
|
+
|
|
376
|
+
it('gws gmail users drafts create', async () => {
|
|
377
|
+
const raw = Buffer.from(
|
|
378
|
+
'From: testuser@example.com\r\nTo: bob@example.com\r\nSubject: Draft Test\r\n\r\nDraft body'
|
|
379
|
+
).toString('base64url');
|
|
380
|
+
const { stdout, exitCode } = await h.gws(`gmail users drafts create --params {"userId":"me"} --json {"message":{"raw":"${raw}"}}`);
|
|
381
|
+
expect(exitCode).toBe(0);
|
|
382
|
+
const data = JSON.parse(stdout);
|
|
383
|
+
expect(data.id).toBeTruthy();
|
|
384
|
+
draftId = data.id;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('gws gmail users drafts get', async () => {
|
|
388
|
+
const { stdout, exitCode } = await h.gws(`gmail users drafts get --params {"userId":"me","id":"${draftId}"}`);
|
|
389
|
+
expect(exitCode).toBe(0);
|
|
390
|
+
const data = JSON.parse(stdout);
|
|
391
|
+
expect(data.id).toBe(draftId);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('gws gmail users drafts delete', async () => {
|
|
395
|
+
const { exitCode } = await h.gws(`gmail users drafts delete --params {"userId":"me","id":"${draftId}"}`);
|
|
396
|
+
expect(exitCode).toBe(0);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
describe('history', () => {
|
|
401
|
+
it('gws gmail users history list', async () => {
|
|
402
|
+
const { stdout, exitCode } = await h.gws('gmail users history list --params {"userId":"me","startHistoryId":"1000"}');
|
|
403
|
+
expect(exitCode).toBe(0);
|
|
404
|
+
const data = JSON.parse(stdout);
|
|
405
|
+
expect(data.history).toBeDefined();
|
|
406
|
+
expect(data.historyId).toBeDefined();
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// ===== Calendar =====
|
|
412
|
+
|
|
413
|
+
describe('calendar', () => {
|
|
414
|
+
describe('calendarList', () => {
|
|
415
|
+
it('gws calendar calendarList list', async () => {
|
|
416
|
+
const { stdout, exitCode } = await h.gws('calendar calendarList list');
|
|
417
|
+
expect(exitCode).toBe(0);
|
|
418
|
+
const data = JSON.parse(stdout);
|
|
419
|
+
expect(data.items.length).toBeGreaterThan(0);
|
|
420
|
+
expect(data.items.some((c: any) => c.primary)).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('gws calendar calendarList get', async () => {
|
|
424
|
+
const { stdout, exitCode } = await h.gws('calendar calendarList get --params {"calendarId":"testuser@example.com"}');
|
|
425
|
+
expect(exitCode).toBe(0);
|
|
426
|
+
const data = JSON.parse(stdout);
|
|
427
|
+
expect(data.id).toBe('testuser@example.com');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('gws calendar calendarList patch', async () => {
|
|
431
|
+
const { stdout, exitCode } = await h.gws('calendar calendarList patch --params {"calendarId":"testuser@example.com"} --json {"colorId":"9"}');
|
|
432
|
+
expect(exitCode).toBe(0);
|
|
433
|
+
const data = JSON.parse(stdout);
|
|
434
|
+
expect(data.id).toBe('testuser@example.com');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('gws calendar calendarList delete + insert', async () => {
|
|
438
|
+
// Create a calendar, delete from list, re-insert
|
|
439
|
+
const createRes = await h.fetch('/calendar/v3/calendars', {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: { 'Content-Type': 'application/json' },
|
|
442
|
+
body: JSON.stringify({ summary: 'ListTest' }),
|
|
443
|
+
});
|
|
444
|
+
const cal = await createRes.json();
|
|
445
|
+
|
|
446
|
+
// Delete from list
|
|
447
|
+
const { exitCode: delCode } = await h.gws(`calendar calendarList delete --params {"calendarId":"${cal.id}"}`);
|
|
448
|
+
expect(delCode).toBe(0);
|
|
449
|
+
|
|
450
|
+
// Re-insert
|
|
451
|
+
const { stdout, exitCode } = await h.gws(`calendar calendarList insert --json {"id":"${cal.id}"}`);
|
|
452
|
+
expect(exitCode).toBe(0);
|
|
453
|
+
const data = JSON.parse(stdout);
|
|
454
|
+
expect(data.id).toBe(cal.id);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe('calendars', () => {
|
|
459
|
+
let newCalId: string;
|
|
460
|
+
|
|
461
|
+
it('gws calendar calendars insert', async () => {
|
|
462
|
+
const { stdout, exitCode } = await h.gws('calendar calendars insert --json {"summary":"GWS Test Calendar"}');
|
|
463
|
+
expect(exitCode).toBe(0);
|
|
464
|
+
const data = JSON.parse(stdout);
|
|
465
|
+
expect(data.summary).toBe('GWS Test Calendar');
|
|
466
|
+
newCalId = data.id;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('gws calendar calendars get', async () => {
|
|
470
|
+
const { stdout, exitCode } = await h.gws(`calendar calendars get --params {"calendarId":"${newCalId}"}`);
|
|
471
|
+
expect(exitCode).toBe(0);
|
|
472
|
+
const data = JSON.parse(stdout);
|
|
473
|
+
expect(data.id).toBe(newCalId);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('gws calendar calendars patch', async () => {
|
|
477
|
+
const { stdout, exitCode } = await h.gws(`calendar calendars patch --params {"calendarId":"${newCalId}"} --json {"summary":"Patched Calendar"}`);
|
|
478
|
+
expect(exitCode).toBe(0);
|
|
479
|
+
const data = JSON.parse(stdout);
|
|
480
|
+
expect(data.summary).toBe('Patched Calendar');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('gws calendar calendars update (PUT)', async () => {
|
|
484
|
+
const { stdout, exitCode } = await h.gws(`calendar calendars update --params {"calendarId":"${newCalId}"} --json {"summary":"PUT Calendar","timeZone":"America/New_York"}`);
|
|
485
|
+
expect(exitCode).toBe(0);
|
|
486
|
+
const data = JSON.parse(stdout);
|
|
487
|
+
expect(data.summary).toBe('PUT Calendar');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('gws calendar calendars clear', async () => {
|
|
491
|
+
// Add an event first
|
|
492
|
+
await h.fetch(`/calendar/v3/calendars/${newCalId}/events`, {
|
|
493
|
+
method: 'POST',
|
|
494
|
+
headers: { 'Content-Type': 'application/json' },
|
|
495
|
+
body: JSON.stringify({ summary: 'To clear', start: { dateTime: '2026-06-01T10:00:00Z' }, end: { dateTime: '2026-06-01T11:00:00Z' } }),
|
|
496
|
+
});
|
|
497
|
+
const { exitCode } = await h.gws(`calendar calendars clear --params {"calendarId":"${newCalId}"}`);
|
|
498
|
+
expect(exitCode).toBe(0);
|
|
499
|
+
// Verify events cleared
|
|
500
|
+
const eventsRes = await h.fetch(`/calendar/v3/calendars/${newCalId}/events`);
|
|
501
|
+
const events = await eventsRes.json();
|
|
502
|
+
expect(events.items.length).toBe(0);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('gws calendar calendars delete', async () => {
|
|
506
|
+
const { exitCode } = await h.gws(`calendar calendars delete --params {"calendarId":"${newCalId}"}`);
|
|
507
|
+
expect(exitCode).toBe(0);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('events', () => {
|
|
512
|
+
it('gws calendar events list', async () => {
|
|
513
|
+
const { stdout, exitCode } = await h.gws('calendar events list --params {"calendarId":"primary"}');
|
|
514
|
+
expect(exitCode).toBe(0);
|
|
515
|
+
const data = JSON.parse(stdout);
|
|
516
|
+
expect(data.items.length).toBeGreaterThan(0);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('gws calendar events list with timeMin', async () => {
|
|
520
|
+
const { stdout, exitCode } = await h.gws('calendar events list --params {"calendarId":"primary","timeMin":"2026-04-09T00:00:00Z"}');
|
|
521
|
+
expect(exitCode).toBe(0);
|
|
522
|
+
const data = JSON.parse(stdout);
|
|
523
|
+
// Should exclude events before Apr 9
|
|
524
|
+
const summaries = data.items.map((e: any) => e.summary);
|
|
525
|
+
expect(summaries).not.toContain('Daily Standup'); // Apr 8 09:00
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
let newEventId: string;
|
|
529
|
+
|
|
530
|
+
it('gws calendar events insert', async () => {
|
|
531
|
+
const { stdout, exitCode } = await h.gws('calendar events insert --params {"calendarId":"primary"} --json {"summary":"GWS Event","start":{"dateTime":"2026-05-01T10:00:00Z"},"end":{"dateTime":"2026-05-01T11:00:00Z"}}');
|
|
532
|
+
expect(exitCode).toBe(0);
|
|
533
|
+
const data = JSON.parse(stdout);
|
|
534
|
+
expect(data.summary).toBe('GWS Event');
|
|
535
|
+
newEventId = data.id;
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('gws calendar events get', async () => {
|
|
539
|
+
const { stdout, exitCode } = await h.gws(`calendar events get --params {"calendarId":"primary","eventId":"${newEventId}"}`);
|
|
540
|
+
expect(exitCode).toBe(0);
|
|
541
|
+
const data = JSON.parse(stdout);
|
|
542
|
+
expect(data.summary).toBe('GWS Event');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('gws calendar events patch', async () => {
|
|
546
|
+
const { stdout, exitCode } = await h.gws(`calendar events patch --params {"calendarId":"primary","eventId":"${newEventId}"} --json {"summary":"Patched Event"}`);
|
|
547
|
+
expect(exitCode).toBe(0);
|
|
548
|
+
const data = JSON.parse(stdout);
|
|
549
|
+
expect(data.summary).toBe('Patched Event');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('gws calendar events update (PUT)', async () => {
|
|
553
|
+
const { stdout, exitCode } = await h.gws(`calendar events update --params {"calendarId":"primary","eventId":"${newEventId}"} --json {"summary":"Replaced Event","start":{"dateTime":"2026-05-01T10:00:00Z"},"end":{"dateTime":"2026-05-01T11:00:00Z"}}`);
|
|
554
|
+
expect(exitCode).toBe(0);
|
|
555
|
+
const data = JSON.parse(stdout);
|
|
556
|
+
expect(data.summary).toBe('Replaced Event');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('gws calendar events delete', async () => {
|
|
560
|
+
const { exitCode } = await h.gws(`calendar events delete --params {"calendarId":"primary","eventId":"${newEventId}"}`);
|
|
561
|
+
expect(exitCode).toBe(0);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('gws calendar events import', async () => {
|
|
565
|
+
const { stdout, exitCode } = await h.gws('calendar events import --params {"calendarId":"primary"} --json {"summary":"Imported Event","start":{"dateTime":"2026-07-01T10:00:00Z"},"end":{"dateTime":"2026-07-01T11:00:00Z"},"iCalUID":"imported@example.com"}');
|
|
566
|
+
expect(exitCode).toBe(0);
|
|
567
|
+
const data = JSON.parse(stdout);
|
|
568
|
+
expect(data.summary).toBe('Imported Event');
|
|
569
|
+
expect(data.iCalUID).toBe('imported@example.com');
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('gws calendar events quickAdd', async () => {
|
|
573
|
+
const { stdout, exitCode } = await h.gws('calendar events quickAdd --params {"calendarId":"primary","text":"Lunch with Alice"}');
|
|
574
|
+
expect(exitCode).toBe(0);
|
|
575
|
+
const data = JSON.parse(stdout);
|
|
576
|
+
expect(data.summary).toBe('Lunch with Alice');
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('gws calendar events move', async () => {
|
|
580
|
+
// Create event in primary, create second calendar, move event
|
|
581
|
+
const evtRes = await h.fetch('/calendar/v3/calendars/primary/events', {
|
|
582
|
+
method: 'POST',
|
|
583
|
+
headers: { 'Content-Type': 'application/json' },
|
|
584
|
+
body: JSON.stringify({ summary: 'Move me', start: { dateTime: '2026-08-01T10:00:00Z' }, end: { dateTime: '2026-08-01T11:00:00Z' } }),
|
|
585
|
+
});
|
|
586
|
+
const evt = await evtRes.json();
|
|
587
|
+
|
|
588
|
+
const calRes = await h.fetch('/calendar/v3/calendars', {
|
|
589
|
+
method: 'POST',
|
|
590
|
+
headers: { 'Content-Type': 'application/json' },
|
|
591
|
+
body: JSON.stringify({ summary: 'Dest Calendar' }),
|
|
592
|
+
});
|
|
593
|
+
const destCal = await calRes.json();
|
|
594
|
+
|
|
595
|
+
const { stdout, exitCode } = await h.gws(`calendar events move --params {"calendarId":"primary","eventId":"${evt.id}","destination":"${destCal.id}"}`);
|
|
596
|
+
expect(exitCode).toBe(0);
|
|
597
|
+
const data = JSON.parse(stdout);
|
|
598
|
+
expect(data.summary).toBe('Move me');
|
|
599
|
+
|
|
600
|
+
// Verify moved to dest
|
|
601
|
+
const destEventsRes = await h.fetch(`/calendar/v3/calendars/${destCal.id}/events`);
|
|
602
|
+
const destEvents = await destEventsRes.json();
|
|
603
|
+
expect(destEvents.items.some((e: any) => e.id === evt.id)).toBe(true);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// ===== Drive =====
|
|
609
|
+
|
|
610
|
+
describe('drive', () => {
|
|
611
|
+
describe('about', () => {
|
|
612
|
+
it('gws drive about get', async () => {
|
|
613
|
+
const { stdout, exitCode } = await h.gws('drive about get --params {"fields":"*"}');
|
|
614
|
+
expect(exitCode).toBe(0);
|
|
615
|
+
const data = JSON.parse(stdout);
|
|
616
|
+
expect(data.kind).toBe('drive#about');
|
|
617
|
+
expect(data.user.emailAddress).toBe('testuser@example.com');
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
describe('files', () => {
|
|
622
|
+
it('gws drive files list', async () => {
|
|
623
|
+
const { stdout, exitCode } = await h.gws('drive files list');
|
|
624
|
+
expect(exitCode).toBe(0);
|
|
625
|
+
const data = JSON.parse(stdout);
|
|
626
|
+
expect(data.files.length).toBeGreaterThan(0);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('gws drive files list with q filter', async () => {
|
|
630
|
+
const { stdout, exitCode } = await h.gws(`drive files list --params {"q":"name contains 'Budget'"}`);
|
|
631
|
+
expect(exitCode).toBe(0);
|
|
632
|
+
const data = JSON.parse(stdout);
|
|
633
|
+
expect(data.files.length).toBe(1);
|
|
634
|
+
expect(data.files[0].name).toContain('Budget');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
let newFileId: string;
|
|
638
|
+
|
|
639
|
+
it('gws drive files create', async () => {
|
|
640
|
+
const { stdout, exitCode } = await h.gws('drive files create --json {"name":"gws-created.txt","mimeType":"text/plain"}');
|
|
641
|
+
expect(exitCode).toBe(0);
|
|
642
|
+
const data = JSON.parse(stdout);
|
|
643
|
+
expect(data.name).toBe('gws-created.txt');
|
|
644
|
+
newFileId = data.id;
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('gws drive files get', async () => {
|
|
648
|
+
const { stdout, exitCode } = await h.gws(`drive files get --params {"fileId":"${newFileId}"}`);
|
|
649
|
+
expect(exitCode).toBe(0);
|
|
650
|
+
const data = JSON.parse(stdout);
|
|
651
|
+
expect(data.name).toBe('gws-created.txt');
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('gws drive files update (PATCH)', async () => {
|
|
655
|
+
const { stdout, exitCode } = await h.gws(`drive files update --params {"fileId":"${newFileId}"} --json {"name":"gws-renamed.txt"}`);
|
|
656
|
+
expect(exitCode).toBe(0);
|
|
657
|
+
const data = JSON.parse(stdout);
|
|
658
|
+
expect(data.name).toBe('gws-renamed.txt');
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('gws drive files copy', async () => {
|
|
662
|
+
const { stdout, exitCode } = await h.gws(`drive files copy --params {"fileId":"${newFileId}"} --json {"name":"gws-copy.txt"}`);
|
|
663
|
+
expect(exitCode).toBe(0);
|
|
664
|
+
const data = JSON.parse(stdout);
|
|
665
|
+
expect(data.name).toBe('gws-copy.txt');
|
|
666
|
+
expect(data.id).not.toBe(newFileId);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('gws drive files delete', async () => {
|
|
670
|
+
const { exitCode } = await h.gws(`drive files delete --params {"fileId":"${newFileId}"}`);
|
|
671
|
+
expect(exitCode).toBe(0);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('gws drive files emptyTrash', async () => {
|
|
675
|
+
// Create and trash a file
|
|
676
|
+
const createRes = await h.fetch('/drive/v3/files', {
|
|
677
|
+
method: 'POST',
|
|
678
|
+
headers: { 'Content-Type': 'application/json' },
|
|
679
|
+
body: JSON.stringify({ name: 'trash-me.txt' }),
|
|
680
|
+
});
|
|
681
|
+
const file = await createRes.json();
|
|
682
|
+
await h.fetch(`/drive/v3/files/${file.id}`, {
|
|
683
|
+
method: 'PATCH',
|
|
684
|
+
headers: { 'Content-Type': 'application/json' },
|
|
685
|
+
body: JSON.stringify({ trashed: true }),
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const { exitCode } = await h.gws('drive files emptyTrash');
|
|
689
|
+
expect(exitCode).toBe(0);
|
|
690
|
+
|
|
691
|
+
// Verify trashed file is gone
|
|
692
|
+
const getRes = await h.fetch(`/drive/v3/files/${file.id}`);
|
|
693
|
+
expect(getRes.status).toBe(404);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe('permissions', () => {
|
|
698
|
+
it('gws drive permissions list', async () => {
|
|
699
|
+
const { stdout, exitCode } = await h.gws('drive permissions list --params {"fileId":"file001"}');
|
|
700
|
+
expect(exitCode).toBe(0);
|
|
701
|
+
const data = JSON.parse(stdout);
|
|
702
|
+
expect(data.permissions.length).toBeGreaterThan(0);
|
|
703
|
+
expect(data.permissions[0].role).toBe('owner');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('gws drive permissions create', async () => {
|
|
707
|
+
const { stdout, exitCode } = await h.gws('drive permissions create --params {"fileId":"file001"} --json {"type":"user","role":"reader","emailAddress":"reader@example.com"}');
|
|
708
|
+
expect(exitCode).toBe(0);
|
|
709
|
+
const data = JSON.parse(stdout);
|
|
710
|
+
expect(data.role).toBe('reader');
|
|
711
|
+
expect(data.emailAddress).toBe('reader@example.com');
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('gws drive permissions get', async () => {
|
|
715
|
+
const { stdout, exitCode } = await h.gws('drive permissions get --params {"fileId":"file001","permissionId":"owner"}');
|
|
716
|
+
expect(exitCode).toBe(0);
|
|
717
|
+
const data = JSON.parse(stdout);
|
|
718
|
+
expect(data.role).toBe('owner');
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe('drives', () => {
|
|
723
|
+
it('gws drive drives list', async () => {
|
|
724
|
+
const { stdout, exitCode } = await h.gws('drive drives list');
|
|
725
|
+
expect(exitCode).toBe(0);
|
|
726
|
+
const data = JSON.parse(stdout);
|
|
727
|
+
expect(data.drives).toBeDefined();
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// ===== Tasks =====
|
|
733
|
+
|
|
734
|
+
describe('tasks', () => {
|
|
735
|
+
describe('tasklists', () => {
|
|
736
|
+
it('gws tasks tasklists list', async () => {
|
|
737
|
+
const { stdout, exitCode } = await h.gws('tasks tasklists list');
|
|
738
|
+
expect(exitCode).toBe(0);
|
|
739
|
+
const data = JSON.parse(stdout);
|
|
740
|
+
expect(data.items.length).toBeGreaterThan(0);
|
|
741
|
+
expect(data.items[0].title).toBe('My Tasks');
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
let newListId: string;
|
|
745
|
+
|
|
746
|
+
it('gws tasks tasklists insert', async () => {
|
|
747
|
+
const { stdout, exitCode } = await h.gws('tasks tasklists insert --json {"title":"Work Tasks"}');
|
|
748
|
+
expect(exitCode).toBe(0);
|
|
749
|
+
const data = JSON.parse(stdout);
|
|
750
|
+
expect(data.title).toBe('Work Tasks');
|
|
751
|
+
newListId = data.id;
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it('gws tasks tasklists get', async () => {
|
|
755
|
+
const { stdout, exitCode } = await h.gws(`tasks tasklists get --params {"tasklist":"${newListId}"}`);
|
|
756
|
+
expect(exitCode).toBe(0);
|
|
757
|
+
const data = JSON.parse(stdout);
|
|
758
|
+
expect(data.title).toBe('Work Tasks');
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('gws tasks tasklists patch', async () => {
|
|
762
|
+
const { stdout, exitCode } = await h.gws(`tasks tasklists patch --params {"tasklist":"${newListId}"} --json {"title":"Updated Tasks"}`);
|
|
763
|
+
expect(exitCode).toBe(0);
|
|
764
|
+
const data = JSON.parse(stdout);
|
|
765
|
+
expect(data.title).toBe('Updated Tasks');
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('gws tasks tasklists delete', async () => {
|
|
769
|
+
const { exitCode } = await h.gws(`tasks tasklists delete --params {"tasklist":"${newListId}"}`);
|
|
770
|
+
expect(exitCode).toBe(0);
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe('tasks', () => {
|
|
775
|
+
it('gws tasks tasks list', async () => {
|
|
776
|
+
const { stdout, exitCode } = await h.gws('tasks tasks list --params {"tasklist":"default"}');
|
|
777
|
+
expect(exitCode).toBe(0);
|
|
778
|
+
const data = JSON.parse(stdout);
|
|
779
|
+
expect(data.items.length).toBeGreaterThan(0);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
let newTaskId: string;
|
|
783
|
+
|
|
784
|
+
it('gws tasks tasks insert', async () => {
|
|
785
|
+
const { stdout, exitCode } = await h.gws('tasks tasks insert --params {"tasklist":"default"} --json {"title":"New Task","notes":"Do this"}');
|
|
786
|
+
expect(exitCode).toBe(0);
|
|
787
|
+
const data = JSON.parse(stdout);
|
|
788
|
+
expect(data.title).toBe('New Task');
|
|
789
|
+
newTaskId = data.id;
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('gws tasks tasks get', async () => {
|
|
793
|
+
const { stdout, exitCode } = await h.gws(`tasks tasks get --params {"tasklist":"default","task":"${newTaskId}"}`);
|
|
794
|
+
expect(exitCode).toBe(0);
|
|
795
|
+
const data = JSON.parse(stdout);
|
|
796
|
+
expect(data.title).toBe('New Task');
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('gws tasks tasks patch', async () => {
|
|
800
|
+
const { stdout, exitCode } = await h.gws(`tasks tasks patch --params {"tasklist":"default","task":"${newTaskId}"} --json {"status":"completed"}`);
|
|
801
|
+
expect(exitCode).toBe(0);
|
|
802
|
+
const data = JSON.parse(stdout);
|
|
803
|
+
expect(data.status).toBe('completed');
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it('gws tasks tasks delete', async () => {
|
|
807
|
+
const { exitCode } = await h.gws(`tasks tasks delete --params {"tasklist":"default","task":"${newTaskId}"}`);
|
|
808
|
+
expect(exitCode).toBe(0);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('gws tasks tasks clear', async () => {
|
|
812
|
+
const { exitCode } = await h.gws('tasks tasks clear --params {"tasklist":"default"}');
|
|
813
|
+
expect(exitCode).toBe(0);
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// ===== Sheets =====
|
|
819
|
+
|
|
820
|
+
describe('sheets', () => {
|
|
821
|
+
it('gws sheets spreadsheets get', async () => {
|
|
822
|
+
const { stdout, exitCode } = await h.gws('sheets spreadsheets get --params {"spreadsheetId":"sheet001"}');
|
|
823
|
+
expect(exitCode).toBe(0);
|
|
824
|
+
const data = JSON.parse(stdout);
|
|
825
|
+
expect(data.properties.title).toBe('Budget 2026');
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
let newSheetId: string;
|
|
829
|
+
|
|
830
|
+
it('gws sheets spreadsheets create', async () => {
|
|
831
|
+
const { stdout, exitCode } = await h.gws('sheets spreadsheets create --json {"properties":{"title":"Test Sheet"}}');
|
|
832
|
+
expect(exitCode).toBe(0);
|
|
833
|
+
const data = JSON.parse(stdout);
|
|
834
|
+
expect(data.properties.title).toBe('Test Sheet');
|
|
835
|
+
newSheetId = data.spreadsheetId;
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('gws sheets spreadsheets values update + get', async () => {
|
|
839
|
+
// Write values
|
|
840
|
+
const { exitCode: writeCode } = await h.gws(`sheets spreadsheets values update --params {"spreadsheetId":"${newSheetId}","range":"Sheet1!A1:B2","valueInputOption":"RAW"} --json {"values":[["Name","Age"],["Alice","30"]]}`);
|
|
841
|
+
expect(writeCode).toBe(0);
|
|
842
|
+
|
|
843
|
+
// Read values back
|
|
844
|
+
const { stdout, exitCode } = await h.gws(`sheets spreadsheets values get --params {"spreadsheetId":"${newSheetId}","range":"Sheet1!A1:B2"}`);
|
|
845
|
+
expect(exitCode).toBe(0);
|
|
846
|
+
const data = JSON.parse(stdout);
|
|
847
|
+
expect(data.values[0][0]).toBe('Name');
|
|
848
|
+
expect(data.values[1][1]).toBe('30');
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// ===== People =====
|
|
853
|
+
|
|
854
|
+
describe('people', () => {
|
|
855
|
+
it('gws people people get', async () => {
|
|
856
|
+
const { stdout, exitCode } = await h.gws('people people get --params {"personFields":"names,emailAddresses","resourceName":"people/c001"}');
|
|
857
|
+
expect(exitCode).toBe(0);
|
|
858
|
+
const data = JSON.parse(stdout);
|
|
859
|
+
expect(data.names[0].displayName).toBe('Alice Johnson');
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('gws people people connections list', async () => {
|
|
863
|
+
const { stdout, exitCode } = await h.gws('people people connections list --params {"resourceName":"people/me","personFields":"names"}');
|
|
864
|
+
expect(exitCode).toBe(0);
|
|
865
|
+
const data = JSON.parse(stdout);
|
|
866
|
+
expect(data.connections.length).toBeGreaterThan(0);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it('gws people contactGroups list', async () => {
|
|
870
|
+
const { stdout, exitCode } = await h.gws('people contactGroups list');
|
|
871
|
+
expect(exitCode).toBe(0);
|
|
872
|
+
const data = JSON.parse(stdout);
|
|
873
|
+
expect(data.contactGroups.length).toBeGreaterThan(0);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('gws people contactGroups get', async () => {
|
|
877
|
+
const { stdout, exitCode } = await h.gws('people contactGroups get --params {"resourceName":"contactGroups/myContacts"}');
|
|
878
|
+
expect(exitCode).toBe(0);
|
|
879
|
+
const data = JSON.parse(stdout);
|
|
880
|
+
expect(data.name).toBe('My Contacts');
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
});
|