google-drive-mock 1.1.5 → 1.2.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/AGENTS.md +4 -1
- package/CLAUDE.md +17 -0
- package/dist/index.js +2 -1
- package/dist/mappers.d.ts +5 -0
- package/dist/mappers.js +15 -0
- package/dist/routes/v2.js +16 -8
- package/dist/routes/v3.js +59 -10
- package/dist/store.js +2 -2
- package/package.json +4 -4
- package/scripts/check-token.ts +107 -38
- package/scripts/run-loop.sh +18 -0
- package/src/index.ts +2 -1
- package/src/mappers.ts +15 -0
- package/src/routes/v2.ts +16 -8
- package/src/routes/v3.ts +65 -11
- package/src/store.ts +2 -2
- package/test/advanced_changes.test.ts +76 -29
- package/test/advanced_ordering.test.ts +2 -1
- package/test/basics.test.ts +34 -58
- package/test/batch_and_query.test.ts +28 -62
- package/test/batch_insert_download.test.ts +2 -1
- package/test/check_empty.test.ts +60 -0
- package/test/complex_query.test.ts +2 -1
- package/test/concurrent_fetch.test.ts +2 -1
- package/test/config.ts +164 -7
- package/test/dates_and_sorting.test.ts +2 -1
- package/test/etag.test.ts +8 -4
- package/test/features.test.ts +2 -1
- package/test/folder_query.test.ts +2 -1
- package/test/folder_search.test.ts +2 -1
- package/test/iterate_changes.test.ts +175 -74
- package/test/latency.test.ts +2 -1
- package/test/mime_types.test.ts +2 -1
- package/test/missing_fields.test.ts +2 -1
- package/test/multipart_behavior.test.ts +2 -1
- package/test/parallel_update.test.ts +2 -1
- package/test/parity_media_download.test.ts +2 -1
- package/test/routines.test.ts +15 -12
- package/test/upload.test.ts +2 -1
- package/test/url_parameters.test.ts +2 -1
- package/test/v2_basics.test.ts +22 -13
- package/test/v2_content.test.ts +2 -1
- package/test/v2_missing_ops.test.ts +75 -75
- package/test/v2_routes.test.ts +31 -21
- package/test/v2_upload.test.ts +17 -9
- package/test/v3_parity.test.ts +60 -18
- package/test_etag_headers.ts +92 -0
- package/vitest.config.ts +7 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, expect, beforeAll } from 'vitest';
|
|
3
|
+
import { it } from './config';;
|
|
2
4
|
import {
|
|
3
5
|
getTestConfig,
|
|
4
6
|
TestConfig
|
|
@@ -7,7 +9,7 @@ import { DriveFile } from '../src/store';
|
|
|
7
9
|
|
|
8
10
|
const randomString = () => Math.random().toString(36).substring(7);
|
|
9
11
|
|
|
10
|
-
const createFileWithContent = async (name: string, content: string, config: TestConfig) => {
|
|
12
|
+
const createFileWithContent = async (name: string, content: string, config: TestConfig, parentId?: string) => {
|
|
11
13
|
const res = await fetch(`${config.baseUrl}/upload/drive/v3/files?uploadType=media`, {
|
|
12
14
|
method: 'POST',
|
|
13
15
|
headers: {
|
|
@@ -24,7 +26,11 @@ const createFileWithContent = async (name: string, content: string, config: Test
|
|
|
24
26
|
// actually, let's just use the patch to set name/metadata to ensure it's correct for the test.
|
|
25
27
|
|
|
26
28
|
// Better: use multipart or just update after create.
|
|
27
|
-
|
|
29
|
+
const patchUrl = parentId
|
|
30
|
+
? `${config.baseUrl}/drive/v3/files/${file.id}?addParents=${parentId}`
|
|
31
|
+
: `${config.baseUrl}/drive/v3/files/${file.id}`;
|
|
32
|
+
|
|
33
|
+
await fetch(patchUrl, {
|
|
28
34
|
method: 'PATCH',
|
|
29
35
|
headers: {
|
|
30
36
|
'Authorization': `Bearer ${config.token}`,
|
|
@@ -52,61 +58,87 @@ describe('Iterate Changes Queries', () => {
|
|
|
52
58
|
});
|
|
53
59
|
|
|
54
60
|
it('should find files where last write time was greater than X, sorted by modifiedTime and id, with limit', async () => {
|
|
61
|
+
// Create a parent folder
|
|
62
|
+
const parentRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
name: 'ParentFolder_GreaterThan_' + randomString(),
|
|
67
|
+
mimeType: 'application/vnd.google-apps.folder'
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
expect(parentRes.status).toBe(200);
|
|
71
|
+
const parentId = (await parentRes.json()).id;
|
|
72
|
+
|
|
55
73
|
// Create 3 files with slight delays to ensure different modifiedTimes
|
|
56
|
-
const file1 = await createFileWithContent('file1', randomString(), config);
|
|
74
|
+
const file1 = await createFileWithContent('file1', randomString(), config, parentId);
|
|
57
75
|
await new Promise(r => setTimeout(r, 1100)); // Ensure > 1s diff for reliable sorting if seconds resolution
|
|
58
|
-
const file2 = await createFileWithContent('file2', randomString(), config);
|
|
76
|
+
const file2 = await createFileWithContent('file2', randomString(), config, parentId);
|
|
59
77
|
await new Promise(r => setTimeout(r, 1100));
|
|
60
|
-
const file3 = await createFileWithContent('file3', randomString(), config);
|
|
78
|
+
const file3 = await createFileWithContent('file3', randomString(), config, parentId);
|
|
61
79
|
|
|
62
80
|
// Use file1's modifiedTime as the baseline (X)
|
|
63
81
|
const timeX = file1.modifiedTime;
|
|
64
82
|
|
|
65
|
-
// Query: modifiedTime > X, orderBy modifiedTime asc, name asc
|
|
66
|
-
|
|
67
|
-
const q = `modifiedTime > '${timeX}' and trashed = false`;
|
|
83
|
+
// Query: modifiedTime > X, orderBy modifiedTime asc, name asc
|
|
84
|
+
const q = `modifiedTime > '${timeX}' and '${parentId}' in parents and trashed = false`;
|
|
68
85
|
const orderBy = 'modifiedTime asc, name asc';
|
|
69
|
-
|
|
86
|
+
|
|
87
|
+
// Poll until the search index is fully updated and returns both expected files
|
|
88
|
+
let data2: any;
|
|
89
|
+
const url2 = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&pageSize=2`;
|
|
90
|
+
for (let attempt = 1; attempt <= 15; attempt++) {
|
|
91
|
+
const res2 = await fetch(url2, { headers });
|
|
92
|
+
expect(res2.status).toBe(200);
|
|
93
|
+
data2 = await res2.json();
|
|
94
|
+
const ids = (data2.files || []).map((f: any) => f.id);
|
|
95
|
+
if (ids.includes(file2.id) && ids.includes(file3.id)) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
expect(data2.files.length).toBe(2);
|
|
102
|
+
expect(data2.files[0].id).toBe(file2.id);
|
|
103
|
+
expect(data2.files[1].id).toBe(file3.id);
|
|
70
104
|
|
|
71
105
|
// First page
|
|
106
|
+
const pageSize = 1;
|
|
72
107
|
const url1 = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&pageSize=${pageSize}`;
|
|
73
108
|
const res1 = await fetch(url1, { headers });
|
|
74
|
-
if (res1.status !== 200) {
|
|
75
|
-
const txt = await res1.text();
|
|
76
|
-
console.error('Error 1:', txt);
|
|
77
|
-
}
|
|
78
109
|
expect(res1.status).toBe(200);
|
|
79
110
|
const data1 = await res1.json();
|
|
80
111
|
|
|
81
112
|
expect(data1.files.length).toBe(1);
|
|
82
113
|
expect(data1.files[0].id).toBe(file2.id);
|
|
83
|
-
|
|
84
|
-
// If we want to simulate iteration, we would use nextPageToken or just offset logic if we supported it,
|
|
85
|
-
// but here we just test that the query works and LIMIT works.
|
|
86
|
-
|
|
87
|
-
// Verify we can get the next one if we increase limit
|
|
88
|
-
const url2 = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&pageSize=2`;
|
|
89
|
-
const res2 = await fetch(url2, { headers });
|
|
90
|
-
const data2 = await res2.json();
|
|
91
|
-
expect(data2.files.length).toBe(2);
|
|
92
|
-
expect(data2.files[0].id).toBe(file2.id);
|
|
93
|
-
expect(data2.files[1].id).toBe(file3.id);
|
|
94
114
|
}, 60000);
|
|
95
115
|
|
|
96
116
|
it('should find all files where write time was equal to X, sorted by name, with limit', async () => {
|
|
117
|
+
// Create a parent folder
|
|
118
|
+
const parentRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
name: 'ParentFolder_EqualTime_' + randomString(),
|
|
123
|
+
mimeType: 'application/vnd.google-apps.folder'
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
expect(parentRes.status).toBe(200);
|
|
127
|
+
const parentId = (await parentRes.json()).id;
|
|
128
|
+
|
|
97
129
|
// Create 3 files effectively at the "same" time.
|
|
98
130
|
// To do this reliably on Real API, we create one, get its time, and then PATCH the others to have that same time (if possible).
|
|
99
131
|
// However, Drive API might not allow arbitrary modifiedTime patching easily without setModifiedDate=true param or similar.
|
|
100
132
|
// Actually, V3 supports modifying modifiedTime.
|
|
101
133
|
|
|
102
|
-
const file1 = await createFileWithContent('file_B_middle', randomString(), config);
|
|
134
|
+
const file1 = await createFileWithContent('file_B_middle', randomString(), config, parentId);
|
|
103
135
|
// Get the time from file1 to use as target
|
|
104
136
|
const timeXRes = await fetch(`${config.baseUrl}/drive/v3/files/${file1.id}?fields=modifiedTime`, { headers });
|
|
105
137
|
const timeX = (await timeXRes.json()).modifiedTime;
|
|
106
138
|
|
|
107
139
|
// Create two more files
|
|
108
|
-
const file2 = await createFileWithContent('file_A_first', randomString(), config);
|
|
109
|
-
const file3 = await createFileWithContent('file_C_last', randomString(), config);
|
|
140
|
+
const file2 = await createFileWithContent('file_A_first', randomString(), config, parentId);
|
|
141
|
+
const file3 = await createFileWithContent('file_C_last', randomString(), config, parentId);
|
|
110
142
|
|
|
111
143
|
// Patch file2 and file3 to have the SAME modifiedTime as file1
|
|
112
144
|
// We need to wait a bit to ensure they would naturally have different times if we didn't patch,
|
|
@@ -117,16 +149,24 @@ describe('Iterate Changes Queries', () => {
|
|
|
117
149
|
await fetch(`${config.baseUrl}/drive/v3/files/${file2.id}`, { method: 'PATCH', headers: { ...headers, 'Content-Type': 'application/json' }, body: patchBody });
|
|
118
150
|
await fetch(`${config.baseUrl}/drive/v3/files/${file3.id}`, { method: 'PATCH', headers: { ...headers, 'Content-Type': 'application/json' }, body: patchBody });
|
|
119
151
|
|
|
120
|
-
const q = `modifiedTime = '${timeX}' and trashed = false`;
|
|
152
|
+
const q = `modifiedTime = '${timeX}' and '${parentId}' in parents and trashed = false`;
|
|
121
153
|
const orderBy = 'name asc';
|
|
122
154
|
const pageSize = 10;
|
|
123
155
|
|
|
156
|
+
let data: any;
|
|
124
157
|
const url = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&pageSize=${pageSize}&fields=files(id,name,modifiedTime)`;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
|
|
159
|
+
for (let attempt = 1; attempt <= 15; attempt++) {
|
|
160
|
+
const res = await fetch(url, { headers });
|
|
161
|
+
expect(res.status).toBe(200);
|
|
162
|
+
data = await res.json();
|
|
163
|
+
const relevantFiles = (data.files || []).filter((f: DriveFile) => [file1.id, file2.id, file3.id].includes(f.id));
|
|
164
|
+
if (relevantFiles.length === 3) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
168
|
+
}
|
|
128
169
|
|
|
129
|
-
// Should find all 3 files
|
|
130
170
|
const relevantFiles = data.files.filter((f: DriveFile) => [file1.id, file2.id, file3.id].includes(f.id));
|
|
131
171
|
expect(relevantFiles.length).toBe(3);
|
|
132
172
|
|
|
@@ -190,10 +230,19 @@ describe('Iterate Changes Queries', () => {
|
|
|
190
230
|
const q = `modifiedTime = '${timeX}' and '${parentId}' in parents and trashed = false`;
|
|
191
231
|
const orderBy = 'name asc';
|
|
192
232
|
|
|
233
|
+
let data: any;
|
|
193
234
|
const url = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&fields=files(id,name,modifiedTime,parents)`;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
235
|
+
|
|
236
|
+
for (let attempt = 1; attempt <= 15; attempt++) {
|
|
237
|
+
const res = await fetch(url, { headers });
|
|
238
|
+
expect(res.status).toBe(200);
|
|
239
|
+
data = await res.json();
|
|
240
|
+
const ids = (data.files || []).map((f: DriveFile) => f.id);
|
|
241
|
+
if (ids.includes(file1.id) && ids.includes(file2.id) && ids.includes(file3.id)) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
245
|
+
}
|
|
197
246
|
|
|
198
247
|
// 4. Verify results
|
|
199
248
|
// Should find file1, file2, file3
|
|
@@ -213,21 +262,33 @@ describe('Iterate Changes Queries', () => {
|
|
|
213
262
|
}, 60000);
|
|
214
263
|
|
|
215
264
|
it('should find files where write time >= X, sorted by modifiedTime and name', async () => {
|
|
265
|
+
// Create a parent folder
|
|
266
|
+
const parentRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
name: 'ParentFolder_GreaterOrEqual_' + randomString(),
|
|
271
|
+
mimeType: 'application/vnd.google-apps.folder'
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
expect(parentRes.status).toBe(200);
|
|
275
|
+
const parentId = (await parentRes.json()).id;
|
|
276
|
+
|
|
216
277
|
// 1. Create file OLD (should be excluded)
|
|
217
|
-
const fileOld = await createFileWithContent('file_A_Old', randomString(), config);
|
|
278
|
+
const fileOld = await createFileWithContent('file_A_Old', randomString(), config, parentId);
|
|
218
279
|
|
|
219
280
|
// Wait to ensure distinct time
|
|
220
281
|
await new Promise(r => setTimeout(r, 1500));
|
|
221
282
|
|
|
222
283
|
// 2. Create ISO-time target files (Equal Time)
|
|
223
284
|
// We create one, get its time, then create another and patch it to match.
|
|
224
|
-
const fileMiddle1 = await createFileWithContent('file_B_Middle1', randomString(), config);
|
|
285
|
+
const fileMiddle1 = await createFileWithContent('file_B_Middle1', randomString(), config, parentId);
|
|
225
286
|
// Get target time
|
|
226
287
|
const timeXRes = await fetch(`${config.baseUrl}/drive/v3/files/${fileMiddle1.id}?fields=modifiedTime`, { headers });
|
|
227
288
|
const timeX = (await timeXRes.json()).modifiedTime;
|
|
228
289
|
|
|
229
290
|
// Create second middle file
|
|
230
|
-
const fileMiddle2 = await createFileWithContent('file_B_Middle2', randomString(), config);
|
|
291
|
+
const fileMiddle2 = await createFileWithContent('file_B_Middle2', randomString(), config, parentId);
|
|
231
292
|
|
|
232
293
|
// Patch fileMiddle2 to match fileMiddle1 time
|
|
233
294
|
await new Promise(r => setTimeout(r, 1100)); // Wait before patching to ensure it would be different otherwise
|
|
@@ -238,20 +299,26 @@ describe('Iterate Changes Queries', () => {
|
|
|
238
299
|
await new Promise(r => setTimeout(r, 1500));
|
|
239
300
|
|
|
240
301
|
// 3. Create file New (should be included, greater match logic)
|
|
241
|
-
const fileNew = await createFileWithContent('file_C_New', randomString(), config);
|
|
302
|
+
const fileNew = await createFileWithContent('file_C_New', randomString(), config, parentId);
|
|
242
303
|
|
|
243
304
|
// 4. Query: modifiedTime >= timeX
|
|
244
305
|
// Sort by modifiedTime asc, name asc
|
|
245
|
-
const q = `modifiedTime >= '${timeX}' and trashed = false`;
|
|
306
|
+
const q = `modifiedTime >= '${timeX}' and '${parentId}' in parents and trashed = false`;
|
|
246
307
|
const orderBy = 'modifiedTime asc, name asc';
|
|
247
308
|
|
|
309
|
+
let data: any;
|
|
248
310
|
const url = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&fields=files(id,name,modifiedTime)`;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
311
|
+
|
|
312
|
+
for (let attempt = 1; attempt <= 15; attempt++) {
|
|
313
|
+
const res = await fetch(url, { headers });
|
|
314
|
+
expect(res.status).toBe(200);
|
|
315
|
+
data = await res.json();
|
|
316
|
+
const relevantFiles = (data.files || []).filter((f: DriveFile) => [fileMiddle1.id, fileMiddle2.id, fileNew.id].includes(f.id));
|
|
317
|
+
if (relevantFiles.length === 3) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
252
321
|
}
|
|
253
|
-
expect(res.status).toBe(200);
|
|
254
|
-
const data = await res.json();
|
|
255
322
|
|
|
256
323
|
// 5. Verify results
|
|
257
324
|
const resultIds = data.files.map((f: DriveFile) => f.id);
|
|
@@ -560,10 +627,19 @@ describe('Iterate Changes Queries', () => {
|
|
|
560
627
|
const q = `modifiedTime > '${timeX}' and '${parentId}' in parents and trashed = false`;
|
|
561
628
|
const orderBy = 'modifiedTime asc, name asc';
|
|
562
629
|
|
|
630
|
+
let data: any;
|
|
563
631
|
const url = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&fields=files(id,name,parents,modifiedTime)`;
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
632
|
+
|
|
633
|
+
for (let attempt = 1; attempt <= 15; attempt++) {
|
|
634
|
+
const res = await fetch(url, { headers });
|
|
635
|
+
expect(res.status).toBe(200);
|
|
636
|
+
data = await res.json();
|
|
637
|
+
const matchingFiles = (data.files || []).filter((f: DriveFile) => f.id === file2.id);
|
|
638
|
+
if (matchingFiles.length === 1) {
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
642
|
+
}
|
|
567
643
|
|
|
568
644
|
const matchingFiles = data.files.filter((f: DriveFile) => f.id === file2.id);
|
|
569
645
|
const nonMatchingFile1 = data.files.filter((f: DriveFile) => f.id === file1.id);
|
|
@@ -582,22 +658,39 @@ describe('Iterate Changes Queries', () => {
|
|
|
582
658
|
}, 60000);
|
|
583
659
|
|
|
584
660
|
it('should paginate through files using nextPageToken', async () => {
|
|
661
|
+
// Create a parent folder
|
|
662
|
+
const parentRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
|
|
663
|
+
method: 'POST',
|
|
664
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
665
|
+
body: JSON.stringify({
|
|
666
|
+
name: 'ParentFolder_PaginationToken_' + randomString(),
|
|
667
|
+
mimeType: 'application/vnd.google-apps.folder'
|
|
668
|
+
})
|
|
669
|
+
});
|
|
670
|
+
expect(parentRes.status).toBe(200);
|
|
671
|
+
const parentId = (await parentRes.json()).id;
|
|
672
|
+
|
|
585
673
|
// Create files
|
|
586
674
|
const totalFiles = 6;
|
|
587
675
|
const baseName = 'PaginatedFile_' + randomString();
|
|
588
676
|
for (let i = 0; i < totalFiles; i++) {
|
|
589
|
-
await createFileWithContent(`${baseName}_${i}`, `content_${i}`, config);
|
|
677
|
+
await createFileWithContent(`${baseName}_${i}`, `content_${i}`, config, parentId);
|
|
590
678
|
// Small delay to ensure order if we sort by time, but we'll sort by name to be deterministic
|
|
591
679
|
}
|
|
592
680
|
|
|
593
|
-
const q = `name contains '${baseName}' and trashed = false`;
|
|
681
|
+
const q = `'${parentId}' in parents and name contains '${baseName}' and trashed = false`;
|
|
594
682
|
const orderBy = 'name asc';
|
|
595
683
|
const pageSize = 2;
|
|
596
684
|
const collectedFiles: DriveFile[] = [];
|
|
597
685
|
let pageToken: string | undefined;
|
|
686
|
+
let pageCount = 0;
|
|
598
687
|
|
|
599
688
|
// Iterate pages until no token
|
|
600
689
|
do {
|
|
690
|
+
pageCount++;
|
|
691
|
+
if (pageCount > 50) {
|
|
692
|
+
throw new Error('Too many pages retrieved (infinite loop safety limit)');
|
|
693
|
+
}
|
|
601
694
|
const url: string = `${config.baseUrl}/drive/v3/files?q=${encodeURIComponent(q)}&orderBy=${encodeURIComponent(orderBy)}&pageSize=${pageSize}` + (pageToken ? `&pageToken=${pageToken}` : '');
|
|
602
695
|
const res = await fetch(url, { headers });
|
|
603
696
|
|
|
@@ -779,17 +872,23 @@ describe('Iterate Changes Queries', () => {
|
|
|
779
872
|
}
|
|
780
873
|
}
|
|
781
874
|
|
|
782
|
-
// Wait for
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
875
|
+
// Wait for indexing with retries
|
|
876
|
+
let refTime: string | undefined;
|
|
877
|
+
console.log('Waiting for indexing...');
|
|
878
|
+
for (let attempt = 0; attempt < 15; attempt++) {
|
|
879
|
+
const listOne = await fetch(`${config.baseUrl}/drive/v3/files?q='${parentId}' in parents&pageSize=${totalFiles}&fields=files(modifiedTime)`, { headers });
|
|
880
|
+
if (listOne.status === 200) {
|
|
881
|
+
const oneData = await listOne.json();
|
|
882
|
+
if (oneData.files && oneData.files.length === totalFiles) {
|
|
883
|
+
refTime = oneData.files[0].modifiedTime;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
888
|
+
}
|
|
889
|
+
if (!refTime) {
|
|
890
|
+
throw new Error('All files not indexed in parent check!');
|
|
791
891
|
}
|
|
792
|
-
const refTime = oneData.files[0].modifiedTime;
|
|
793
892
|
console.log('Reference File Time:', refTime);
|
|
794
893
|
|
|
795
894
|
// Filter: modifiedTime > refTime - 1 hour (to safely include all)
|
|
@@ -861,21 +960,23 @@ describe('Iterate Changes Queries', () => {
|
|
|
861
960
|
expect(res.status).toBe(200);
|
|
862
961
|
}
|
|
863
962
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
963
|
+
// Wait for indexing with retries (v2)
|
|
964
|
+
let refTime: string | undefined;
|
|
965
|
+
console.log('Waiting for indexing (v2)...');
|
|
966
|
+
for (let attempt = 0; attempt < 15; attempt++) {
|
|
967
|
+
const listOne = await fetch(`${config.baseUrl}/drive/v2/files?q='${parentId}' in parents&maxResults=${totalFiles}`, { headers });
|
|
968
|
+
if (listOne.status === 200) {
|
|
969
|
+
const oneData = await listOne.json();
|
|
970
|
+
if (oneData.items && oneData.items.length === totalFiles) {
|
|
971
|
+
refTime = oneData.items[0].modifiedDate;
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
872
976
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
if (!oneData.items || oneData.items.length === 0) {
|
|
876
|
-
throw new Error('No files found in parent check v2!');
|
|
977
|
+
if (!refTime) {
|
|
978
|
+
throw new Error('All files not indexed in parent check v2!');
|
|
877
979
|
}
|
|
878
|
-
const refTime = oneData.items[0].modifiedDate;
|
|
879
980
|
console.log('Reference File Time v2:', refTime);
|
|
880
981
|
|
|
881
982
|
// Filter: modifiedDate > refTime - 1 hour
|
package/test/latency.test.ts
CHANGED
package/test/mime_types.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { it } from './config';;
|
|
2
3
|
import { getTestConfig, TestConfig } from './config';
|
|
3
4
|
|
|
4
5
|
describe('Missing Fields Support (Size & MD5)', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { it } from './config';;
|
|
3
4
|
import { getTestConfig, TestConfig } from './config';
|
|
4
5
|
|
|
5
6
|
describe('Multipart Upload Behavior (Conflicts, Overwrites, Replacements)', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { it } from './config';;
|
|
3
4
|
import { getTestConfig, TestConfig } from './config';
|
|
4
5
|
|
|
5
6
|
describe('Parallel Content Update Test', () => {
|
package/test/routines.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, expect, afterAll, beforeAll, vi } from 'vitest';
|
|
3
|
+
import { it } from './config';;
|
|
3
4
|
import { waitUntil } from 'async-test-util';
|
|
4
5
|
import { getTestConfig, TestConfig } from './config';
|
|
5
6
|
|
|
@@ -68,8 +69,9 @@ describe('Complex Routines', () => {
|
|
|
68
69
|
|
|
69
70
|
it('Lifecycle: Create -> Update -> Read -> Delete', async () => {
|
|
70
71
|
// 1. Create
|
|
72
|
+
const title = 'Lifecycle File ' + Math.random().toString(36).substring(7);
|
|
71
73
|
const newFile = {
|
|
72
|
-
name:
|
|
74
|
+
name: title,
|
|
73
75
|
mimeType: 'text/plain',
|
|
74
76
|
parents: [config.testFolderId]
|
|
75
77
|
};
|
|
@@ -78,14 +80,15 @@ describe('Complex Routines', () => {
|
|
|
78
80
|
const fileId = createRes.body.id;
|
|
79
81
|
|
|
80
82
|
// 2. Update
|
|
81
|
-
const
|
|
83
|
+
const updatedTitle = 'Lifecycle Updated ' + Math.random().toString(36).substring(7);
|
|
84
|
+
const updateRes = await req('PATCH', `/drive/v3/files/${fileId}`, { name: updatedTitle });
|
|
82
85
|
expect(updateRes.status).toBe(200);
|
|
83
|
-
expect(updateRes.body.name).toBe(
|
|
86
|
+
expect(updateRes.body.name).toBe(updatedTitle);
|
|
84
87
|
|
|
85
88
|
// 3. Read
|
|
86
89
|
const readRes = await req('GET', `/drive/v3/files/${fileId}`);
|
|
87
90
|
expect(readRes.status).toBe(200);
|
|
88
|
-
expect(readRes.body.name).toBe(
|
|
91
|
+
expect(readRes.body.name).toBe(updatedTitle);
|
|
89
92
|
|
|
90
93
|
// 4. Delete
|
|
91
94
|
const deleteRes = await req('DELETE', `/drive/v3/files/${fileId}`);
|
|
@@ -97,7 +100,7 @@ describe('Complex Routines', () => {
|
|
|
97
100
|
});
|
|
98
101
|
|
|
99
102
|
it('Transaction Simulation: Lock -> Wait -> Release', async () => {
|
|
100
|
-
const LOCK_FILE = 'transactions-lock-' +
|
|
103
|
+
const LOCK_FILE = 'transactions-lock-' + Math.random().toString(36).substring(7) + '.txt';
|
|
101
104
|
|
|
102
105
|
// Client A: Acquire Lock
|
|
103
106
|
const createLock = await req('POST', '/drive/v3/files', {
|
|
@@ -113,7 +116,7 @@ describe('Complex Routines', () => {
|
|
|
113
116
|
await Promise.all([
|
|
114
117
|
// Client B
|
|
115
118
|
waitUntil(async () => {
|
|
116
|
-
const check = await req('GET',
|
|
119
|
+
const check = await req('GET', `/drive/v3/files?q=${encodeURIComponent(`name = '${LOCK_FILE}' and trashed = false`)}`, null);
|
|
117
120
|
// Actually to filter we can iterate body.files
|
|
118
121
|
|
|
119
122
|
const files = check.body.files || [];
|
|
@@ -166,11 +169,11 @@ describe('Complex Routines', () => {
|
|
|
166
169
|
});
|
|
167
170
|
|
|
168
171
|
it('Routine: Concurrent Create (Duplicates Allowed)', async () => {
|
|
169
|
-
const UNIQUE_FILE = 'unique.txt';
|
|
172
|
+
const UNIQUE_FILE = 'unique-' + Math.random().toString(36).substring(7) + '.txt';
|
|
170
173
|
|
|
171
174
|
// 1. Clean
|
|
172
|
-
const check1 = await req('GET',
|
|
173
|
-
const existingFiles = check1.body.files.filter((f: any) => f.name === UNIQUE_FILE);
|
|
175
|
+
const check1 = await req('GET', `/drive/v3/files?q=${encodeURIComponent(`name = '${UNIQUE_FILE}' and trashed = false`)}`);
|
|
176
|
+
const existingFiles = (check1.body.files || []).filter((f: any) => f.name === UNIQUE_FILE);
|
|
174
177
|
for (const file of existingFiles) {
|
|
175
178
|
await req('DELETE', `/drive/v3/files/${file.id}`);
|
|
176
179
|
}
|
|
@@ -196,8 +199,8 @@ describe('Complex Routines', () => {
|
|
|
196
199
|
expect(statuses).toEqual([200, 200]);
|
|
197
200
|
|
|
198
201
|
// Verify duplicates exist
|
|
199
|
-
const check3 = await req('GET',
|
|
200
|
-
const files = check3.body.files.filter((f: any) => f.name === UNIQUE_FILE);
|
|
202
|
+
const check3 = await req('GET', `/drive/v3/files?q=${encodeURIComponent(`name = '${UNIQUE_FILE}' and trashed = false`)}`);
|
|
203
|
+
const files = (check3.body.files || []).filter((f: any) => f.name === UNIQUE_FILE);
|
|
201
204
|
expect(files.length).toBe(2);
|
|
202
205
|
});
|
|
203
206
|
});
|
package/test/upload.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { it } from './config';;
|
|
2
3
|
import { getTestConfig, TestConfig } from './config';
|
|
3
4
|
|
|
4
5
|
describe('Multipart Upload Feature', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { it } from './config';;
|
|
3
4
|
import { getTestConfig, TestConfig } from './config';
|
|
4
5
|
|
|
5
6
|
describe('URL Parameters Test', () => {
|
package/test/v2_basics.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { it } from './config';;
|
|
2
3
|
import { getTestConfig, TestConfig } from './config';
|
|
3
4
|
import { Server } from 'http';
|
|
4
5
|
|
|
@@ -63,12 +64,13 @@ describe('Google Drive API V2 Basics', () => {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
it('should create a file with V2 fields (title vs name)', async () => {
|
|
67
|
+
const title = 'V2 Created File ' + Math.random().toString(36).substring(7);
|
|
66
68
|
const createRes = await req('POST', '/drive/v2/files', {
|
|
67
|
-
title
|
|
69
|
+
title,
|
|
68
70
|
mimeType: 'text/plain'
|
|
69
71
|
});
|
|
70
72
|
expect(createRes.status).toBe(200);
|
|
71
|
-
expect(createRes.body.title).toBe(
|
|
73
|
+
expect(createRes.body.title).toBe(title);
|
|
72
74
|
expect(createRes.body.kind).toBe('drive#file');
|
|
73
75
|
expect(createRes.body.id).toBeTruthy();
|
|
74
76
|
|
|
@@ -77,38 +79,43 @@ describe('Google Drive API V2 Basics', () => {
|
|
|
77
79
|
// Verify with GET
|
|
78
80
|
const getRes = await req('GET', `/drive/v2/files/${fileId}`);
|
|
79
81
|
expect(getRes.status).toBe(200);
|
|
80
|
-
expect(getRes.body.title).toBe(
|
|
82
|
+
expect(getRes.body.title).toBe(title);
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
it('should update a file using PUT', async () => {
|
|
84
86
|
// Create
|
|
85
|
-
const
|
|
87
|
+
const title = 'To Update ' + Math.random().toString(36).substring(7);
|
|
88
|
+
const updatedTitle = 'Updated via PUT ' + Math.random().toString(36).substring(7);
|
|
89
|
+
const createRes = await req('POST', '/drive/v2/files', { title });
|
|
86
90
|
const fileId = createRes.body.id;
|
|
87
91
|
|
|
88
92
|
// Update
|
|
89
93
|
const updateRes = await req('PUT', `/drive/v2/files/${fileId}`, {
|
|
90
|
-
title:
|
|
94
|
+
title: updatedTitle
|
|
91
95
|
});
|
|
92
96
|
expect(updateRes.status).toBe(200);
|
|
93
|
-
expect(updateRes.body.title).toBe(
|
|
97
|
+
expect(updateRes.body.title).toBe(updatedTitle);
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
it('should patch a file using PATCH', async () => {
|
|
97
101
|
// Create
|
|
98
|
-
const
|
|
102
|
+
const title = 'To Patch ' + Math.random().toString(36).substring(7);
|
|
103
|
+
const patchedTitle = 'Patched Title ' + Math.random().toString(36).substring(7);
|
|
104
|
+
const createRes = await req('POST', '/drive/v2/files', { title });
|
|
99
105
|
const fileId = createRes.body.id;
|
|
100
106
|
|
|
101
107
|
// Patch
|
|
102
108
|
const patchRes = await req('PATCH', `/drive/v2/files/${fileId}`, {
|
|
103
|
-
title:
|
|
109
|
+
title: patchedTitle
|
|
104
110
|
});
|
|
105
111
|
expect(patchRes.status).toBe(200);
|
|
106
|
-
expect(patchRes.body.title).toBe(
|
|
112
|
+
expect(patchRes.body.title).toBe(patchedTitle);
|
|
107
113
|
});
|
|
108
114
|
|
|
109
115
|
it('should delete a file', async () => {
|
|
110
116
|
// Create
|
|
111
|
-
const
|
|
117
|
+
const title = 'To Delete ' + Math.random().toString(36).substring(7);
|
|
118
|
+
const createRes = await req('POST', '/drive/v2/files', { title });
|
|
112
119
|
const fileId = createRes.body.id;
|
|
113
120
|
|
|
114
121
|
// Delete
|
|
@@ -121,14 +128,16 @@ describe('Google Drive API V2 Basics', () => {
|
|
|
121
128
|
});
|
|
122
129
|
|
|
123
130
|
it('should handle V2 parent references correctly', async () => {
|
|
131
|
+
const parentTitle = 'V2 Parent Folder ' + Math.random().toString(36).substring(7);
|
|
132
|
+
const childTitle = 'V2 Child File ' + Math.random().toString(36).substring(7);
|
|
124
133
|
const parentRes = await req('POST', '/drive/v2/files', {
|
|
125
|
-
title:
|
|
134
|
+
title: parentTitle,
|
|
126
135
|
mimeType: 'application/vnd.google-apps.folder'
|
|
127
136
|
});
|
|
128
137
|
const parentId = parentRes.body.id;
|
|
129
138
|
|
|
130
139
|
const childRes = await req('POST', '/drive/v2/files', {
|
|
131
|
-
title:
|
|
140
|
+
title: childTitle,
|
|
132
141
|
parents: [{ id: parentId }]
|
|
133
142
|
});
|
|
134
143
|
|