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.
@@ -0,0 +1,150 @@
1
+
2
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3
+ import { getTestConfig, TestConfig } from './config';
4
+
5
+ describe('Batch Insert and Download 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 insertDocumentFiles<RxDocType>(
18
+ googleDriveOptions: { apiEndpoint: string, authToken: string },
19
+ init: { docsFolderId: string },
20
+ primaryPath: string,
21
+ docs: RxDocType[]
22
+ ) {
23
+ const boundary = "batch_" + Math.random().toString(16).slice(2);
24
+
25
+
26
+
27
+ const parts = docs.map((doc, i) => {
28
+ const id = (doc as Record<string, unknown>)[primaryPath] as string;
29
+ const body = JSON.stringify({
30
+ name: id + '.json',
31
+ mimeType: 'application/json',
32
+ parents: [init.docsFolderId],
33
+ });
34
+
35
+ return (
36
+ `--${boundary}\r\n` +
37
+ `Content-Type: application/http\r\n` +
38
+ `Content-ID: <item-${i}>\r\n\r\n` +
39
+ `POST /drive/v3/files HTTP/1.1\r\n` +
40
+ `Content-Type: application/json; charset=UTF-8\r\n\r\n` +
41
+ `${body}\r\n`
42
+ );
43
+ });
44
+ const batchBody = parts.join("") + `--${boundary}--`;
45
+ const res = await fetch(googleDriveOptions.apiEndpoint + "/batch/drive/v3", {
46
+ method: "POST",
47
+ headers: {
48
+ Authorization: `Bearer ${googleDriveOptions.authToken}`,
49
+ "Content-Type": `multipart/mixed; boundary=${boundary}`,
50
+ },
51
+ body: batchBody,
52
+ });
53
+
54
+
55
+ if (!res.ok) {
56
+ const text = await res.text().catch(() => "");
57
+ throw new Error(`GDR13: Batch insert failed. Status: ${res.status}. Error: ${text}`);
58
+ }
59
+ const text = await res.text();
60
+ console.log('Batch Response:', text.substring(0, 1000)); // Log snippet
61
+ return text;
62
+ }
63
+
64
+ it('should insert docs using batch POST and download them', async () => {
65
+ const docCount = 3;
66
+ const docs = [];
67
+ for (let i = 0; i < docCount; i++) {
68
+ docs.push({ id: `item_${Date.now()}_${i}`, data: `Data ${i}` });
69
+ }
70
+
71
+ console.log('Inserting docs via batch POST...');
72
+ const batchResponse = await insertDocumentFiles(
73
+ { apiEndpoint: config.baseUrl, authToken: config.token },
74
+ { docsFolderId: config.testFolderId },
75
+ 'id',
76
+ docs
77
+ );
78
+
79
+ // Parse IDs from batch response?
80
+ // The user snippet returns raw text.
81
+ // We usually rely on create returning the created file metadata (including ID).
82
+ // Let's parse the response to get the IDs.
83
+
84
+ const boundaryMatch = (batchResponse.match(/boundary=(.+)/) || [])[1];
85
+ let boundary = boundaryMatch;
86
+ // Or inspect Content-Type header from response? Ideally yes but user helper returns body text.
87
+ // Let's try to detect boundary from body first line if not found?
88
+ // But headers are gone.
89
+
90
+ // Wait, the user helper receives the Response object and returns text().
91
+ // We lose headers :(.
92
+ // Let's assume boundary from body first line.
93
+ const firstLine = batchResponse.trim().split(/\r?\n/)[0];
94
+ if (firstLine.startsWith('--')) {
95
+ boundary = firstLine.substring(2).trim();
96
+ } else {
97
+ // Maybe no boundary in body if empty? unlikely for batch.
98
+ }
99
+
100
+ if (!boundary) throw new Error('Could not detect boundary in batch response');
101
+
102
+ const parts = batchResponse.split(`--${boundary}`);
103
+ const createdIds: string[] = [];
104
+
105
+ for (const part of parts) {
106
+ if (!part.trim() || part.trim() === '--') continue;
107
+ // Find JSON body
108
+ const jsonStart = part.indexOf('{');
109
+ const jsonEnd = part.lastIndexOf('}');
110
+ if (jsonStart !== -1 && jsonEnd !== -1) {
111
+ try {
112
+ const jsonStr = part.substring(jsonStart, jsonEnd + 1);
113
+ const file = JSON.parse(jsonStr);
114
+ if (file.id) {
115
+ createdIds.push(file.id);
116
+ }
117
+
118
+ } catch {
119
+ // ignore parse errors
120
+ }
121
+ }
122
+ }
123
+
124
+ expect(createdIds.length).toBe(docCount);
125
+ console.log(`Created ${createdIds.length} files. Downloading...`);
126
+
127
+ for (const fileId of createdIds) {
128
+ const downloadUrl = `${config.baseUrl}/drive/v3/files/${encodeURIComponent(fileId)}?alt=media&supportsAllDrives=true`;
129
+ // console.log(`Downloading ${fileId} from ${downloadUrl}`);
130
+
131
+ const res = await fetch(downloadUrl, {
132
+ headers: { Authorization: `Bearer ${config.token}` }
133
+ });
134
+
135
+ if (!res.ok) {
136
+ throw new Error(`Download failed for ${fileId}: ${res.status} ${await res.text()}`);
137
+ }
138
+
139
+
140
+ const text = await res.text();
141
+ // console.log(`Content for ${fileId}:`, text);
142
+
143
+ // Expect empty content for metadata-only insert?
144
+ // Or maybe Drive defaults to empty JSON object '{}'?
145
+ // Real API behavior for empty file created via metadata POST: 0 bytes.
146
+ expect(text).toBe('');
147
+ }
148
+
149
+ }, 30000);
150
+ });
@@ -41,13 +41,12 @@ export async function batchFetchDocumentContentsRaw(
41
41
  }
42
42
 
43
43
  // This will be a multipart/mixed body that you must parse yourself.
44
- return await res.text();
44
+ const text = await res.text();
45
+ console.log('############################# text:');
46
+ console.log(text);
47
+ return text;
45
48
  }
46
49
 
47
- /**
48
- * Parses a multipart/mixed response body from Google Drive Batch API.
49
- * Returns an array of objects containing { status, headers, body }.
50
- */
51
50
  /**
52
51
  * Parses a multipart/mixed response body from Google Drive Batch API.
53
52
  * Returns an array of objects containing { status, headers, body }.
@@ -135,7 +134,15 @@ describe('Batch Fetch Test', () => {
135
134
  if (config) config.stop();
136
135
  });
137
136
 
138
- async function uploadJsonFile(name: string, content: unknown): Promise<string> {
137
+
138
+ interface TestContent {
139
+ index: number;
140
+ timestamp: number;
141
+ msg: string;
142
+ random: number;
143
+ }
144
+
145
+ async function uploadJsonFile(name: string, content: TestContent): Promise<string> {
139
146
  const metadata = {
140
147
  name: name,
141
148
  parents: [config.testFolderId],
@@ -175,13 +182,13 @@ describe('Batch Fetch Test', () => {
175
182
  it('should fetch content of many files in a single batch request', async () => {
176
183
  const fileCount = 5;
177
184
  const fileIds: string[] = [];
178
- const expectedContents: Record<string, unknown> = {};
185
+ const expectedContents: Record<string, TestContent> = {};
179
186
 
180
187
  console.log(`Creating ${fileCount} files...`);
181
188
 
182
189
  for (let i = 0; i < fileCount; i++) {
183
190
  const fileName = `BatchFile_${i}_${Date.now()}.json`;
184
- const content = { index: i, timestamp: Date.now(), msg: `Hello World ${i}`, random: Math.random() };
191
+ const content: TestContent = { index: i, timestamp: Date.now(), msg: `Hello World ${i}`, random: Math.random() };
185
192
 
186
193
  const id = await uploadJsonFile(fileName, content);
187
194
  fileIds.push(id);
@@ -215,7 +222,7 @@ describe('Batch Fetch Test', () => {
215
222
  expect(parsedResults.length).toBe(fileCount);
216
223
 
217
224
  for (const result of parsedResults) {
218
- let content = result.body;
225
+ let content = result.body as TestContent;
219
226
 
220
227
 
221
228
  // All environments (Mock and Real) must return 302 Redirect for alt=media in batch
@@ -236,7 +243,7 @@ describe('Batch Fetch Test', () => {
236
243
  });
237
244
 
238
245
  if (!res.ok) throw new Error(`Failed to follow redirect: ${res.status} ${await res.text()}`);
239
- content = await res.json();
246
+ content = await res.json() as TestContent;
240
247
 
241
248
 
242
249
  const expected = Object.values(expectedContents).find(c => c.index === content.index);
@@ -319,7 +319,6 @@ describe('Date Updates and Sorting', () => {
319
319
  throw new Error(`Create failed: ${createRes.status} ${text}`);
320
320
  }
321
321
  const file = await createRes.json();
322
- console.log('Explicit Time Check File:', JSON.stringify(file, null, 2));
323
322
  if (!file.modifiedTime) throw new Error('modifiedTime missing from response');
324
323
  const modifiedTimeCreate = new Date(file.modifiedTime).getTime();
325
324
 
@@ -468,7 +467,6 @@ describe('Date Updates and Sorting', () => {
468
467
  body: 'Time Check Separate'
469
468
  });
470
469
  const file = await createRes.json();
471
- console.log('Explicit Time Check File:', JSON.stringify(file, null, 2));
472
470
  if (!file.modifiedTime) throw new Error('modifiedTime missing from response');
473
471
  const modifiedTimeCreate = new Date(file.modifiedTime).getTime();
474
472
 
@@ -0,0 +1,177 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { getTestConfig, TestConfig } from './config';
3
+
4
+ const randomString = () => Math.random().toString(36).substring(7);
5
+
6
+ describe('Folder Query Investigation', () => {
7
+ let config: TestConfig;
8
+ let headers: Record<string, string>;
9
+
10
+ beforeAll(async () => {
11
+ config = await getTestConfig();
12
+ headers = {
13
+ Authorization: `Bearer ${config.token}`
14
+ };
15
+ });
16
+
17
+ it('should return files in a folder with specific query parameters', async () => {
18
+ // 1. Create a parent folder
19
+ const parentRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
20
+ method: 'POST',
21
+ headers: { ...headers, 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({
23
+ name: 'QueryTestParams_' + randomString(),
24
+ mimeType: 'application/vnd.google-apps.folder'
25
+ })
26
+ });
27
+ expect(parentRes.status).toBe(200);
28
+ const parentId = (await parentRes.json()).id;
29
+
30
+ // 2. Create a few files in the folder
31
+ const file1 = await fetch(`${config.baseUrl}/drive/v3/files`, {
32
+ method: 'POST',
33
+ headers: { ...headers, 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({
35
+ name: 'file1',
36
+ parents: [parentId]
37
+ })
38
+ }).then(res => res.json());
39
+
40
+ const file2 = await fetch(`${config.baseUrl}/drive/v3/files`, {
41
+ method: 'POST',
42
+ headers: { ...headers, 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({
44
+ name: 'file2',
45
+ parents: [parentId]
46
+ })
47
+ }).then(res => res.json());
48
+
49
+ // Wait for consistency
50
+ await new Promise(r => setTimeout(r, 2000));
51
+
52
+ // 3. Run the user's specific query
53
+ const queryParts = [
54
+ `'${parentId}' in parents`,
55
+ `and trashed = false`
56
+ ];
57
+
58
+ // Optional: Add modifiedTime check if needed, but let's start with basic structure
59
+ // const checkpoint = { modifiedTime: '...' };
60
+ // if (checkpoint) {
61
+ // queryParts.push(`and modifiedTime >= '${checkpoint.modifiedTime}'`);
62
+ // }
63
+
64
+ const batchSize = 10;
65
+ const params = new URLSearchParams({
66
+ q: queryParts.join(' '),
67
+ pageSize: (batchSize + 10) + '',
68
+ orderBy: "modifiedTime asc,name asc",
69
+ fields: "files(id,name,mimeType,parents,modifiedTime,size)",
70
+ supportsAllDrives: "true",
71
+ includeItemsFromAllDrives: "true",
72
+ });
73
+
74
+ const url =
75
+ config.baseUrl +
76
+ "/drive/v3/files?" +
77
+ params.toString();
78
+
79
+ console.log('Requesting URL:', url);
80
+
81
+ const res = await fetch(url, {
82
+ headers: {
83
+ Authorization: `Bearer ${config.token}`,
84
+ },
85
+ });
86
+
87
+ if (res.status !== 200) {
88
+ console.error('Error response:', await res.text());
89
+ }
90
+ expect(res.status).toBe(200);
91
+ const data = await res.json();
92
+
93
+ console.log('Found files:', data.files.length);
94
+ const ids = data.files.map((f: { id: string }) => f.id);
95
+ expect(ids).toContain(file1.id);
96
+ expect(ids).toContain(file2.id);
97
+
98
+ // Check if fields are returned
99
+ const f1 = data.files.find((f: { id: string }) => f.id === file1.id) as Record<string, unknown>;
100
+ expect(f1.name).toBeDefined();
101
+ expect(f1.modifiedTime).toBeDefined();
102
+ expect(f1.parents).toBeDefined();
103
+ expect(f1.parents).toContain(parentId);
104
+
105
+ // Strict key check
106
+ const expectedKeys = ['id', 'name', 'mimeType', 'parents', 'modifiedTime', 'size'].sort();
107
+ const actualKeys = Object.keys(f1).sort();
108
+
109
+ // Log for debugging if they don't match (Vitest will show diff)
110
+ if (JSON.stringify(actualKeys) !== JSON.stringify(expectedKeys)) {
111
+ console.log('Keys mismatch! Actual:', actualKeys);
112
+ }
113
+ expect(actualKeys).toEqual(expectedKeys);
114
+
115
+ }, 60000);
116
+
117
+ it('should return files with modifiedTime filter', async () => {
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: 'QueryTestTime_' + randomString(),
123
+ mimeType: 'application/vnd.google-apps.folder'
124
+ })
125
+ });
126
+ const parentId = (await parentRes.json()).id;
127
+
128
+ // Create file 1 (Old)
129
+ const file1 = await fetch(`${config.baseUrl}/drive/v3/files`, {
130
+ method: 'POST',
131
+ headers: { ...headers, 'Content-Type': 'application/json' },
132
+ body: JSON.stringify({ name: 'file1_old', parents: [parentId] })
133
+ }).then(res => res.json());
134
+
135
+ // Wait to ensure distinct modifiedTime
136
+ await new Promise(r => setTimeout(r, 1500));
137
+
138
+ // Checkpoint time
139
+ const checkpointTime = new Date().toISOString();
140
+
141
+ await new Promise(r => setTimeout(r, 1500));
142
+
143
+ // Create file 2 (New)
144
+ const file2 = await fetch(`${config.baseUrl}/drive/v3/files`, {
145
+ method: 'POST',
146
+ headers: { ...headers, 'Content-Type': 'application/json' },
147
+ body: JSON.stringify({ name: 'file2_new', parents: [parentId] })
148
+ }).then(res => res.json());
149
+
150
+ // Query: parent + trashed + modifiedTime >= checkpoint
151
+ const queryParts = [
152
+ `'${parentId}' in parents`,
153
+ `and trashed = false`,
154
+ `and modifiedTime >= '${checkpointTime}'`
155
+ ];
156
+
157
+ const params = new URLSearchParams({
158
+ q: queryParts.join(' '),
159
+ pageSize: '10',
160
+ orderBy: "modifiedTime asc,name asc",
161
+ fields: "files(id,name,mimeType,parents,modifiedTime,size)",
162
+ supportsAllDrives: "true",
163
+ includeItemsFromAllDrives: "true",
164
+ });
165
+
166
+ const url = `${config.baseUrl}/drive/v3/files?${params.toString()}`;
167
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${config.token}` } });
168
+ expect(res.status).toBe(200);
169
+ const data = await res.json();
170
+
171
+ const ids = data.files.map((f: { id: string }) => f.id);
172
+
173
+ // Should contain file2 (New), but NOT file1 (Old)
174
+ expect(ids).toContain(file2.id);
175
+ expect(ids).not.toContain(file1.id);
176
+ }, 60000);
177
+ });