google-drive-mock 1.0.13 → 1.1.1
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/.agent/rules/project-guidelines.md +5 -0
- package/.github/workflows/release.yml +12 -4
- package/dist/batch.js +1 -1
- package/dist/index.js +1 -1
- package/dist/mappers.d.ts +8 -0
- package/dist/mappers.js +79 -0
- package/dist/routes/v3.js +68 -6
- package/dist/store.js +1 -1
- package/package.json +1 -1
- package/src/batch.ts +2 -1
- package/src/index.ts +1 -1
- package/src/mappers.ts +84 -0
- package/src/routes/v3.ts +71 -5
- package/src/store.ts +1 -1
- package/test/batch_insert_download.test.ts +150 -0
- package/test/concurrent_fetch.test.ts +17 -10
- package/test/dates_and_sorting.test.ts +0 -2
- package/test/folder_query.test.ts +177 -0
- package/test/iterate_changes.test.ts +920 -0
- package/test/parallel_update.test.ts +138 -0
- package/test/url_parameters.test.ts +76 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { getTestConfig, TestConfig } from './config';
|
|
4
|
+
|
|
5
|
+
describe('Parallel Content Update Test', () => {
|
|
6
|
+
let config: TestConfig;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
config = await getTestConfig();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
if (config) config.stop();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Helper from user request (adapted)
|
|
17
|
+
async function updateDocumentFiles<DocType>(
|
|
18
|
+
googleDriveOptions: { apiEndpoint: string, authToken: string },
|
|
19
|
+
primaryPath: string,
|
|
20
|
+
docs: DocType[],
|
|
21
|
+
fileIdByDocId: Record<string, string>,
|
|
22
|
+
concurrency = 5
|
|
23
|
+
) {
|
|
24
|
+
const queue = [...docs];
|
|
25
|
+
const results: Record<string, { id: string }> = {};
|
|
26
|
+
|
|
27
|
+
async function worker() {
|
|
28
|
+
while (queue.length) {
|
|
29
|
+
const doc = queue.shift()!;
|
|
30
|
+
|
|
31
|
+
const docId = (doc as Record<string, unknown>)[primaryPath] as string;
|
|
32
|
+
const fileId = fileIdByDocId[docId];
|
|
33
|
+
|
|
34
|
+
if (!fileId) throw new Error(`File ID not found for doc ${docId}`);
|
|
35
|
+
|
|
36
|
+
const url =
|
|
37
|
+
googleDriveOptions.apiEndpoint +
|
|
38
|
+
`/upload/drive/v3/files/${encodeURIComponent(fileId)}` +
|
|
39
|
+
`?uploadType=media&supportsAllDrives=true&fields=id`;
|
|
40
|
+
|
|
41
|
+
const res = await fetch(url, {
|
|
42
|
+
method: "PATCH",
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: `Bearer ${googleDriveOptions.authToken}`,
|
|
45
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(doc),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const text = await res.text().catch(() => "");
|
|
52
|
+
throw new Error(`GDR15: Update failed for ${docId}. Status: ${res.status}. Error: ${text}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
results[docId] = await res.json(); // { id }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async function createFile(name: string, content: unknown): Promise<string> {
|
|
65
|
+
const metadata = {
|
|
66
|
+
name: name,
|
|
67
|
+
parents: [config.testFolderId],
|
|
68
|
+
mimeType: 'application/json'
|
|
69
|
+
};
|
|
70
|
+
const multipartBoundary = '-------TestBoundary' + Date.now();
|
|
71
|
+
const delimiter = '\r\n--' + multipartBoundary + '\r\n';
|
|
72
|
+
const closeDelim = '\r\n--' + multipartBoundary + '--';
|
|
73
|
+
|
|
74
|
+
const body = delimiter +
|
|
75
|
+
'Content-Type: application/json\r\n\r\n' +
|
|
76
|
+
JSON.stringify(metadata) +
|
|
77
|
+
delimiter +
|
|
78
|
+
'Content-Type: application/json\r\n\r\n' +
|
|
79
|
+
JSON.stringify(content) +
|
|
80
|
+
closeDelim;
|
|
81
|
+
|
|
82
|
+
const res = await fetch(`${config.baseUrl}/upload/drive/v3/files?uploadType=multipart&fields=id`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: 'Bearer ' + config.token,
|
|
86
|
+
'Content-Type': 'multipart/related; boundary="' + multipartBoundary + '"'
|
|
87
|
+
},
|
|
88
|
+
body: body
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) throw new Error(`Create failed: ${res.status}`);
|
|
91
|
+
const data = await res.json();
|
|
92
|
+
return data.id;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
it('should update file contents in parallel', async () => {
|
|
96
|
+
const docCount = 5;
|
|
97
|
+
const docs = [];
|
|
98
|
+
const fileIdByDocId: Record<string, string> = {};
|
|
99
|
+
|
|
100
|
+
// 1. Create initial files
|
|
101
|
+
console.log(`Creating ${docCount} initial files...`);
|
|
102
|
+
for (let i = 0; i < docCount; i++) {
|
|
103
|
+
const docId = `doc_${Date.now()}_${i}`;
|
|
104
|
+
const initialContent = { id: docId, data: 'initial' };
|
|
105
|
+
const fileName = `${docId}.json`;
|
|
106
|
+
const fileId = await createFile(fileName, initialContent);
|
|
107
|
+
fileIdByDocId[docId] = fileId;
|
|
108
|
+
docs.push({ id: docId, data: 'updated_' + i, random: Math.random() });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2. Run parallel update
|
|
112
|
+
console.log('Running parallel updates...');
|
|
113
|
+
await updateDocumentFiles(
|
|
114
|
+
{ apiEndpoint: config.baseUrl, authToken: config.token },
|
|
115
|
+
'id',
|
|
116
|
+
docs,
|
|
117
|
+
fileIdByDocId,
|
|
118
|
+
3 // Concurrency
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// 3. Verify updates
|
|
122
|
+
console.log('Verifying content...');
|
|
123
|
+
for (const doc of docs) {
|
|
124
|
+
const fileId = fileIdByDocId[doc.id];
|
|
125
|
+
const url = `${config.baseUrl}/drive/v3/files/${fileId}?alt=media`;
|
|
126
|
+
const res = await fetch(url, {
|
|
127
|
+
headers: { Authorization: `Bearer ${config.token}` }
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
130
|
+
const downloadedContent = await res.json();
|
|
131
|
+
|
|
132
|
+
// Note: Drive might not return exact JSON identical if it adds properties?
|
|
133
|
+
// Usually strict JSON equality works.
|
|
134
|
+
expect(downloadedContent).toEqual(doc);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
}, 30000);
|
|
138
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { getTestConfig, TestConfig } from './config';
|
|
4
|
+
|
|
5
|
+
describe('URL Parameters Test', () => {
|
|
6
|
+
let config: TestConfig;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
config = await getTestConfig();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
if (config) config.stop();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should download file content using alt=media and supportsAllDrives=true', async () => {
|
|
17
|
+
const fileName = `UrlParamTest_${Date.now()}.json`;
|
|
18
|
+
const content = { msg: 'Hello World', timestamp: Date.now() };
|
|
19
|
+
|
|
20
|
+
// 1. Upload File
|
|
21
|
+
const metadata = {
|
|
22
|
+
name: fileName,
|
|
23
|
+
parents: [config.testFolderId],
|
|
24
|
+
mimeType: 'application/json'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const multipartBoundary = '-------TestBoundary' + Date.now();
|
|
28
|
+
const delimiter = '\r\n--' + multipartBoundary + '\r\n';
|
|
29
|
+
const closeDelim = '\r\n--' + multipartBoundary + '--';
|
|
30
|
+
|
|
31
|
+
const body = delimiter +
|
|
32
|
+
'Content-Type: application/json\r\n\r\n' +
|
|
33
|
+
JSON.stringify(metadata) +
|
|
34
|
+
delimiter +
|
|
35
|
+
'Content-Type: application/json\r\n\r\n' +
|
|
36
|
+
JSON.stringify(content) +
|
|
37
|
+
closeDelim;
|
|
38
|
+
|
|
39
|
+
const uploadUrl = `${config.baseUrl}/upload/drive/v3/files?uploadType=multipart&fields=id`;
|
|
40
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: 'Bearer ' + config.token,
|
|
44
|
+
'Content-Type': 'multipart/related; boundary="' + multipartBoundary + '"'
|
|
45
|
+
},
|
|
46
|
+
body: body
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!uploadRes.ok) {
|
|
50
|
+
throw new Error(`Failed to upload file. Status: ${uploadRes.status} ${await uploadRes.text()}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await uploadRes.json();
|
|
54
|
+
const fileId = data.id;
|
|
55
|
+
expect(fileId).toBeDefined();
|
|
56
|
+
|
|
57
|
+
// 2. Download with specific URL parameters
|
|
58
|
+
const downloadUrl = `${config.baseUrl}/drive/v3/files/${encodeURIComponent(fileId)}?alt=media&supportsAllDrives=true`;
|
|
59
|
+
console.log('Downloading from:', downloadUrl);
|
|
60
|
+
|
|
61
|
+
const res = await fetch(downloadUrl, {
|
|
62
|
+
method: 'GET',
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: 'Bearer ' + config.token
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
throw new Error(`Failed to download file. Status: ${res.status} ${await res.text()}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const downloadedContent = await res.json();
|
|
73
|
+
expect(downloadedContent).toEqual(content);
|
|
74
|
+
|
|
75
|
+
}, 30000);
|
|
76
|
+
});
|