google-drive-mock 1.0.7 → 1.0.8

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/dist/index.js CHANGED
@@ -45,7 +45,7 @@ const createApp = (config = {}) => {
45
45
  next();
46
46
  }));
47
47
  app.use(express_1.default.json());
48
- app.use(express_1.default.text({ type: ['multipart/mixed', 'multipart/related'] }));
48
+ app.use(express_1.default.text({ type: ['multipart/mixed', 'multipart/related', 'text/*', 'application/xml'] }));
49
49
  // Batch Route
50
50
  app.post('/batch', batch_1.handleBatchRequest);
51
51
  app.post('/batch/drive/v3', batch_1.handleBatchRequest);
package/dist/routes/v2.js CHANGED
@@ -17,6 +17,19 @@ const createV2Router = (config) => {
17
17
  const newFile = store_1.driveStore.createFile(Object.assign(Object.assign({}, fileData), { name: name, mimeType: fileData.mimeType || "application/octet-stream", parents: fileData.parents || [] }));
18
18
  res.status(200).json((0, mappers_1.toV2File)(newFile));
19
19
  });
20
+ // V2 Generate IDs (Must come before /:fileId)
21
+ app.get('/drive/v2/files/generateIds', (req, res) => {
22
+ const count = parseInt(req.query.maxResults) || 10;
23
+ const ids = [];
24
+ for (let i = 0; i < count; i++) {
25
+ ids.push(Math.random().toString(36).substring(2, 15));
26
+ }
27
+ res.json({
28
+ kind: "drive#generatedIds",
29
+ ids: ids,
30
+ space: req.query.space || 'drive'
31
+ });
32
+ });
20
33
  // V2 Files: Get
21
34
  app.get('/drive/v2/files/:fileId', (req, res) => {
22
35
  const fileId = req.params.fileId;
@@ -33,6 +46,9 @@ const createV2Router = (config) => {
33
46
  res.setHeader('ETag', file.etag);
34
47
  }
35
48
  if (req.query.alt === 'media') {
49
+ if (file.mimeType) {
50
+ res.setHeader('Content-Type', file.mimeType);
51
+ }
36
52
  if (file.content === undefined) {
37
53
  res.send("");
38
54
  return;
@@ -47,6 +63,43 @@ const createV2Router = (config) => {
47
63
  }
48
64
  res.json((0, mappers_1.toV2File)(file));
49
65
  });
66
+ // V2 Export
67
+ app.get('/drive/v2/files/:fileId/export', (req, res) => {
68
+ const fileId = req.params.fileId;
69
+ if (typeof fileId !== 'string') {
70
+ res.status(400).send("Invalid file ID");
71
+ return;
72
+ }
73
+ const file = store_1.driveStore.getFile(fileId);
74
+ if (!file) {
75
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
76
+ return;
77
+ }
78
+ // Mock export: just return content. Real API validates mimeType compatibility.
79
+ res.send(file.content || "");
80
+ });
81
+ // V2 Watch
82
+ app.post('/drive/v2/files/:fileId/watch', (req, res) => {
83
+ const fileId = req.params.fileId;
84
+ if (typeof fileId !== 'string') {
85
+ res.status(400).send("Invalid file ID");
86
+ return;
87
+ }
88
+ const file = store_1.driveStore.getFile(fileId);
89
+ if (!file) {
90
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
91
+ return;
92
+ }
93
+ // Mock Channel response
94
+ res.json({
95
+ kind: "api#channel",
96
+ id: req.body.id || Math.random().toString(36).substring(7),
97
+ resourceId: fileId,
98
+ resourceUri: `${config.apiEndpoint}/drive/v2/files/${fileId}`,
99
+ token: req.body.token,
100
+ expiration: Date.now() + 3600000 // 1 hour
101
+ });
102
+ });
50
103
  // V2 Files: Update (PUT)
51
104
  app.put('/drive/v2/files/:fileId', (req, res) => {
52
105
  const fileId = req.params.fileId;
@@ -69,6 +122,25 @@ const createV2Router = (config) => {
69
122
  return;
70
123
  }
71
124
  }
125
+ // Handle addParents/removeParents
126
+ let parents = existingFile.parents || [];
127
+ const addParents = req.query.addParents;
128
+ const removeParents = req.query.removeParents;
129
+ if (addParents) {
130
+ const toAdd = addParents.split(',');
131
+ toAdd.forEach(id => {
132
+ if (!parents.includes(id))
133
+ parents.push(id);
134
+ });
135
+ }
136
+ if (removeParents) {
137
+ const toRemove = removeParents.split(',');
138
+ parents = parents.filter(id => !toRemove.includes(id));
139
+ }
140
+ // Merge parents into updates if they were modified
141
+ if (addParents || removeParents) {
142
+ updates.parents = parents;
143
+ }
72
144
  const updatedFile = store_1.driveStore.updateFile(fileId, updates);
73
145
  res.json((0, mappers_1.toV2File)(updatedFile));
74
146
  });
@@ -94,6 +166,25 @@ const createV2Router = (config) => {
94
166
  return;
95
167
  }
96
168
  }
169
+ // Handle addParents/removeParents
170
+ let parents = existingFile.parents || [];
171
+ const addParents = req.query.addParents;
172
+ const removeParents = req.query.removeParents;
173
+ if (addParents) {
174
+ const toAdd = addParents.split(',');
175
+ toAdd.forEach(id => {
176
+ if (!parents.includes(id))
177
+ parents.push(id);
178
+ });
179
+ }
180
+ if (removeParents) {
181
+ const toRemove = removeParents.split(',');
182
+ parents = parents.filter(id => !toRemove.includes(id));
183
+ }
184
+ // Merge parents into updates if they were modified
185
+ if (addParents || removeParents) {
186
+ updates.parents = parents;
187
+ }
97
188
  const updatedFile = store_1.driveStore.updateFile(fileId, updates);
98
189
  res.json((0, mappers_1.toV2File)(updatedFile));
99
190
  });
@@ -192,38 +283,12 @@ const createV2Router = (config) => {
192
283
  startPageToken: token
193
284
  });
194
285
  });
195
- // V2 Upload (Multipart)
196
- app.post('/upload/drive/v2/files', (req, res) => {
197
- const uploadType = req.query.uploadType;
198
- if (uploadType !== 'multipart') {
199
- res.status(400).json({ error: { code: 400, message: "Only uploadType=multipart is supported in this mock route" } });
200
- return;
201
- }
202
- const contentTypeHeader = req.headers['content-type'];
203
- const contentType = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader;
204
- if (!contentType || !contentType.includes('multipart/related')) {
205
- res.status(400).json({ error: { code: 400, message: "Content-Type must be multipart/related" } });
206
- return;
207
- }
208
- const boundaryMatch = contentType.match(/boundary=(.+)/);
209
- if (!boundaryMatch) {
210
- res.status(400).json({ error: { code: 400, message: "Multipart boundary missing" } });
211
- return;
212
- }
213
- let boundary = boundaryMatch[1];
214
- if (boundary.startsWith('"') && boundary.endsWith('"')) {
215
- boundary = boundary.substring(1, boundary.length - 1);
216
- }
217
- const rawBody = req.body;
218
- if (typeof rawBody !== 'string') {
219
- res.status(400).json({ error: { code: 400, message: "Body parsing failed" } });
220
- return;
221
- }
286
+ // Helper for multipart parsing
287
+ const parseMultipart = (rawBody, boundary) => {
222
288
  const parts = rawBody.split(`--${boundary}`);
223
289
  const validParts = parts.filter(p => p.trim() !== '' && p.trim() !== '--');
224
290
  if (validParts.length < 2) {
225
- res.status(400).json({ error: { code: 400, message: "Invalid multipart body" } });
226
- return;
291
+ return null;
227
292
  }
228
293
  const parsePart = (rawPart) => {
229
294
  let splitIndex = rawPart.indexOf('\r\n\r\n');
@@ -233,7 +298,6 @@ const createV2Router = (config) => {
233
298
  separatorLength = 2;
234
299
  }
235
300
  if (splitIndex === -1) {
236
- console.log('V2 Upload: Could not find header/body separator in part', rawPart.substring(0, 50));
237
301
  return null;
238
302
  }
239
303
  const headers = rawPart.substring(0, splitIndex).trim();
@@ -246,16 +310,14 @@ const createV2Router = (config) => {
246
310
  const metadataPart = parsePart(validParts[0]);
247
311
  const contentPart = parsePart(validParts[1]);
248
312
  if (!metadataPart || !contentPart) {
249
- res.status(400).json({ error: { code: 400, message: "Failed to parse parts" } });
250
- return;
313
+ return null;
251
314
  }
252
315
  let metadata;
253
316
  try {
254
317
  metadata = JSON.parse(metadataPart.body);
255
318
  }
256
319
  catch (_a) {
257
- res.status(400).json({ error: { code: 400, message: "Invalid JSON in metadata part" } });
258
- return;
320
+ return null;
259
321
  }
260
322
  let content;
261
323
  try {
@@ -264,10 +326,114 @@ const createV2Router = (config) => {
264
326
  catch (_b) {
265
327
  content = contentPart.body;
266
328
  }
267
- const fileData = (0, mappers_1.fromV2Update)(metadata);
268
- const name = fileData.name || metadata.title || "Untitled";
269
- const newFile = store_1.driveStore.createFile(Object.assign(Object.assign({}, fileData), { name: name, mimeType: fileData.mimeType || "application/octet-stream", parents: fileData.parents || [], content: content }));
270
- res.status(200).json((0, mappers_1.toV2File)(newFile));
329
+ return { metadata, content };
330
+ };
331
+ // V2 Upload (POST)
332
+ app.post('/upload/drive/v2/files', (req, res) => {
333
+ const uploadType = req.query.uploadType;
334
+ if (uploadType === 'media') {
335
+ const rawBody = req.body;
336
+ // For simple upload, metadata is default
337
+ const name = "Untitled";
338
+ const newFile = store_1.driveStore.createFile({
339
+ name: name,
340
+ mimeType: req.headers['content-type'] || "application/octet-stream",
341
+ parents: [],
342
+ content: rawBody
343
+ });
344
+ res.status(200).json((0, mappers_1.toV2File)(newFile));
345
+ return;
346
+ }
347
+ if (uploadType === 'multipart') {
348
+ const contentTypeHeader = req.headers['content-type'];
349
+ const contentType = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader;
350
+ if (!contentType || !contentType.includes('multipart/related')) {
351
+ res.status(400).json({ error: { code: 400, message: "Content-Type must be multipart/related" } });
352
+ return;
353
+ }
354
+ const boundaryMatch = contentType.match(/boundary=(.+)/);
355
+ if (!boundaryMatch) {
356
+ res.status(400).json({ error: { code: 400, message: "Multipart boundary missing" } });
357
+ return;
358
+ }
359
+ let boundary = boundaryMatch[1];
360
+ if (boundary.startsWith('"') && boundary.endsWith('"')) {
361
+ boundary = boundary.substring(1, boundary.length - 1);
362
+ }
363
+ const rawBody = req.body;
364
+ if (typeof rawBody !== 'string') {
365
+ res.status(400).json({ error: { code: 400, message: "Body parsing failed" } });
366
+ return;
367
+ }
368
+ const parsed = parseMultipart(rawBody, boundary);
369
+ if (!parsed) {
370
+ res.status(400).json({ error: { code: 400, message: "Invalid multipart body" } });
371
+ return;
372
+ }
373
+ const { metadata, content } = parsed;
374
+ const fileData = (0, mappers_1.fromV2Update)(metadata);
375
+ const name = fileData.name || metadata.title || "Untitled";
376
+ const newFile = store_1.driveStore.createFile(Object.assign(Object.assign({}, fileData), { name: name, mimeType: fileData.mimeType || "application/octet-stream", parents: fileData.parents || [], content: content }));
377
+ res.status(200).json((0, mappers_1.toV2File)(newFile));
378
+ return;
379
+ }
380
+ res.status(400).json({ error: { code: 400, message: "Invalid uploadType" } });
381
+ });
382
+ // V2 Upload (PUT)
383
+ app.put('/upload/drive/v2/files/:fileId', (req, res) => {
384
+ const fileId = req.params.fileId;
385
+ if (typeof fileId !== 'string') {
386
+ res.status(400).send("Invalid file ID");
387
+ return;
388
+ }
389
+ const existingFile = store_1.driveStore.getFile(fileId);
390
+ if (!existingFile) {
391
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
392
+ return;
393
+ }
394
+ const uploadType = req.query.uploadType;
395
+ if (uploadType === 'media') {
396
+ const rawBody = req.body;
397
+ const updatedFile = store_1.driveStore.updateFile(fileId, {
398
+ content: rawBody,
399
+ modifiedTime: new Date().toISOString()
400
+ });
401
+ res.status(200).json((0, mappers_1.toV2File)(updatedFile));
402
+ return;
403
+ }
404
+ if (uploadType === 'multipart') {
405
+ const contentTypeHeader = req.headers['content-type'];
406
+ const contentType = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader;
407
+ if (!contentType || !contentType.includes('multipart/related')) {
408
+ res.status(400).json({ error: { code: 400, message: "Content-Type must be multipart/related" } });
409
+ return;
410
+ }
411
+ const boundaryMatch = contentType.match(/boundary=(.+)/);
412
+ if (!boundaryMatch) {
413
+ res.status(400).json({ error: { code: 400, message: "Multipart boundary missing" } });
414
+ return;
415
+ }
416
+ let boundary = boundaryMatch[1];
417
+ if (boundary.startsWith('"') && boundary.endsWith('"')) {
418
+ boundary = boundary.substring(1, boundary.length - 1);
419
+ }
420
+ const rawBody = req.body;
421
+ if (typeof rawBody !== 'string') {
422
+ res.status(400).json({ error: { code: 400, message: "Body parsing failed" } });
423
+ return;
424
+ }
425
+ const parsed = parseMultipart(rawBody, boundary);
426
+ if (!parsed) {
427
+ res.status(400).json({ error: { code: 400, message: "Invalid multipart body" } });
428
+ return;
429
+ }
430
+ const { metadata, content } = parsed;
431
+ const fileData = (0, mappers_1.fromV2Update)(metadata);
432
+ const updatedFile = store_1.driveStore.updateFile(fileId, Object.assign(Object.assign({}, fileData), { content: content, modifiedTime: new Date().toISOString() }));
433
+ res.status(200).json((0, mappers_1.toV2File)(updatedFile));
434
+ return;
435
+ }
436
+ res.status(400).json({ error: { code: 400, message: "Invalid uploadType" } });
271
437
  });
272
438
  // V2 Trash
273
439
  app.post('/drive/v2/files/:fileId/trash', (req, res) => {
package/dist/routes/v3.js CHANGED
@@ -215,6 +215,32 @@ const createV3Router = () => {
215
215
  const newFile = store_1.driveStore.createFile(Object.assign(Object.assign({}, metadata), { content: content }));
216
216
  res.status(200).json(newFile);
217
217
  });
218
+ // Upload Files: Update (PATCH)
219
+ app.patch('/upload/drive/v3/files/:fileId', (req, res) => {
220
+ const fileId = req.params.fileId;
221
+ if (typeof fileId !== 'string') {
222
+ res.status(400).send("Invalid file ID");
223
+ return;
224
+ }
225
+ const existingFile = store_1.driveStore.getFile(fileId);
226
+ if (!existingFile) {
227
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
228
+ return;
229
+ }
230
+ const uploadType = req.query.uploadType;
231
+ if (uploadType === 'media') {
232
+ const rawBody = req.body;
233
+ // V3 update content via media upload
234
+ const updatedFile = store_1.driveStore.updateFile(fileId, {
235
+ content: rawBody,
236
+ modifiedTime: new Date().toISOString()
237
+ });
238
+ res.status(200).json(updatedFile);
239
+ return;
240
+ }
241
+ // Add multipart support if needed, but media is primary for now
242
+ res.status(400).json({ error: { code: 400, message: "Only uploadType=media is currently supported for V3 PATCH upload" } });
243
+ });
218
244
  // Files: Create (Standard)
219
245
  app.post('/drive/v3/files', (req, res) => {
220
246
  const body = req.body || {};
@@ -239,6 +265,22 @@ const createV3Router = () => {
239
265
  res.status(400).json({ error: { code: 400, message: "Invalid field selection: etag" } });
240
266
  return;
241
267
  }
268
+ if (req.query.alt === 'media') {
269
+ if (file.mimeType) {
270
+ res.setHeader('Content-Type', file.mimeType);
271
+ }
272
+ if (file.content === undefined) {
273
+ res.send("");
274
+ return;
275
+ }
276
+ if (typeof file.content === 'object') {
277
+ res.json(file.content);
278
+ }
279
+ else {
280
+ res.send(file.content);
281
+ }
282
+ return;
283
+ }
242
284
  res.json(file);
243
285
  });
244
286
  // Files: Update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-drive-mock",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Mock-Server that simulates being google-drive. Used for testing.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -36,7 +36,7 @@ const createApp = (config: AppConfig = {}) => {
36
36
  });
37
37
 
38
38
  app.use(express.json());
39
- app.use(express.text({ type: ['multipart/mixed', 'multipart/related'] }));
39
+ app.use(express.text({ type: ['multipart/mixed', 'multipart/related', 'text/*', 'application/xml'] }));
40
40
 
41
41
  // Batch Route
42
42
  app.post('/batch', handleBatchRequest);