google-drive-mock 1.0.5 → 1.0.6

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
@@ -17,9 +17,12 @@ const express_1 = __importDefault(require("express"));
17
17
  const cors_1 = __importDefault(require("cors"));
18
18
  const store_1 = require("./store");
19
19
  const batch_1 = require("./batch");
20
+ const mappers_1 = require("./mappers");
20
21
  const createApp = (config = {}) => {
21
22
  const app = (0, express_1.default)();
22
- app.use((0, cors_1.default)());
23
+ app.use((0, cors_1.default)({
24
+ exposedHeaders: ['ETag']
25
+ }));
23
26
  app.set('etag', false); // Disable default ETag generation to match Real API behavior
24
27
  app.use((req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
25
28
  if (config.serverLagBefore && config.serverLagBefore > 0) {
@@ -53,7 +56,8 @@ const createApp = (config = {}) => {
53
56
  // Auth Middleware
54
57
  const validTokens = ['valid-token', 'another-valid-token'];
55
58
  app.use((req, res, next) => {
56
- const authHeader = req.headers.authorization;
59
+ const authHeaderVal = req.headers.authorization;
60
+ const authHeader = Array.isArray(authHeaderVal) ? authHeaderVal[0] : authHeaderVal;
57
61
  if (!authHeader) {
58
62
  res.status(401).json({ error: { code: 401, message: "Unauthorized: No token provided" } });
59
63
  return;
@@ -206,7 +210,8 @@ const createApp = (config = {}) => {
206
210
  res.status(400).json({ error: { code: 400, message: "Only uploadType=multipart is supported in this mock route" } });
207
211
  return;
208
212
  }
209
- const contentType = req.headers['content-type'];
213
+ const contentTypeHeader = req.headers['content-type'];
214
+ const contentType = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader;
210
215
  if (!contentType || !contentType.includes('multipart/related')) {
211
216
  res.status(400).json({ error: { code: 400, message: "Content-Type must be multipart/related" } });
212
217
  return;
@@ -335,6 +340,12 @@ const createApp = (config = {}) => {
335
340
  res.status(404).json({ error: { code: 404, message: "File not found" } });
336
341
  return;
337
342
  }
343
+ // Parity: Real V3 API returns 400 if 'etag' is requested in fields
344
+ const fields = req.query.fields;
345
+ if (fields && (fields.includes('etag') || fields.includes('kind,etag'))) {
346
+ res.status(400).json({ error: { code: 400, message: "Invalid field selection: etag" } });
347
+ return;
348
+ }
338
349
  // Mock does not return ETag header because Real API (v3) does not return it by default/in this context.
339
350
  // res.setHeader('ETag', etag);
340
351
  // Real API also ignores If-None-Match if ETag is not supported?
@@ -410,6 +421,115 @@ const createApp = (config = {}) => {
410
421
  }
411
422
  res.status(204).send();
412
423
  });
424
+ // ==========================================
425
+ // Google Drive API V2 Routes
426
+ // ==========================================
427
+ // V2 Files: Create
428
+ app.post('/drive/v2/files', (req, res) => {
429
+ const v2Body = req.body || {};
430
+ const fileData = (0, mappers_1.fromV2Update)(v2Body);
431
+ // V2 typical defaults
432
+ const name = fileData.name || v2Body.title || "Untitled"; // Fallback if mapper missed it or explicit
433
+ const newFile = store_1.driveStore.createFile(Object.assign(Object.assign({}, fileData), { name: name, mimeType: fileData.mimeType || "application/octet-stream", parents: fileData.parents || [] }));
434
+ res.status(200).json((0, mappers_1.toV2File)(newFile));
435
+ });
436
+ // V2 Files: Get
437
+ app.get('/drive/v2/files/:fileId', (req, res) => {
438
+ const fileId = req.params.fileId;
439
+ if (typeof fileId !== 'string') {
440
+ res.status(400).send("Invalid file ID");
441
+ return;
442
+ }
443
+ const file = store_1.driveStore.getFile(fileId);
444
+ if (!file) {
445
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
446
+ return;
447
+ }
448
+ // V2 ETag handling - usually sends ETag header
449
+ if (file.etag) {
450
+ res.setHeader('ETag', file.etag);
451
+ }
452
+ res.json((0, mappers_1.toV2File)(file));
453
+ });
454
+ // V2 Files: Update (PUT)
455
+ app.put('/drive/v2/files/:fileId', (req, res) => {
456
+ const fileId = req.params.fileId;
457
+ if (typeof fileId !== 'string') {
458
+ res.status(400).send("Invalid file ID");
459
+ return;
460
+ }
461
+ const v2Body = req.body || {};
462
+ const updates = (0, mappers_1.fromV2Update)(v2Body);
463
+ const existingFile = store_1.driveStore.getFile(fileId);
464
+ if (!existingFile) {
465
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
466
+ return;
467
+ }
468
+ // Check for Precondition (If-Match)
469
+ const ifMatchHeader = req.headers['if-match'];
470
+ const ifMatch = Array.isArray(ifMatchHeader) ? ifMatchHeader[0] : ifMatchHeader;
471
+ if (ifMatch && ifMatch !== '*' && ifMatch !== existingFile.etag) {
472
+ // Also support quoted etag if user sends it
473
+ // Internal etag might be "version", validation needs exact match
474
+ if (ifMatch !== existingFile.etag && ifMatch !== `"${existingFile.etag}"`) {
475
+ res.status(412).json({ error: { code: 412, message: "Precondition Failed" } });
476
+ return;
477
+ }
478
+ }
479
+ const updatedFile = store_1.driveStore.updateFile(fileId, updates);
480
+ res.json((0, mappers_1.toV2File)(updatedFile));
481
+ });
482
+ // V2 Files: Patch (PATCH)
483
+ app.patch('/drive/v2/files/:fileId', (req, res) => {
484
+ const fileId = req.params.fileId;
485
+ if (typeof fileId !== 'string') {
486
+ res.status(400).send("Invalid file ID");
487
+ return;
488
+ }
489
+ const v2Body = req.body || {};
490
+ const updates = (0, mappers_1.fromV2Update)(v2Body);
491
+ const existingFile = store_1.driveStore.getFile(fileId);
492
+ if (!existingFile) {
493
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
494
+ return;
495
+ }
496
+ // Check for Precondition (If-Match)
497
+ const ifMatchHeader = req.headers['if-match'];
498
+ const ifMatch = Array.isArray(ifMatchHeader) ? ifMatchHeader[0] : ifMatchHeader;
499
+ if (ifMatch && ifMatch !== '*' && ifMatch !== existingFile.etag) {
500
+ if (ifMatch !== existingFile.etag && ifMatch !== `"${existingFile.etag}"`) {
501
+ res.status(412).json({ error: { code: 412, message: "Precondition Failed" } });
502
+ return;
503
+ }
504
+ }
505
+ const updatedFile = store_1.driveStore.updateFile(fileId, updates);
506
+ res.json((0, mappers_1.toV2File)(updatedFile));
507
+ });
508
+ // V2 Files: Delete
509
+ app.delete('/drive/v2/files/:fileId', (req, res) => {
510
+ const fileId = req.params.fileId;
511
+ if (typeof fileId !== 'string') {
512
+ res.status(400).send("Invalid file ID");
513
+ return;
514
+ }
515
+ const existingFile = store_1.driveStore.getFile(fileId);
516
+ // V2 specific: often returns 404 for not found, same as V3 check
517
+ if (!existingFile) {
518
+ res.status(404).json({ error: { code: 404, message: "File not found" } });
519
+ return;
520
+ }
521
+ // Check for Precondition (If-Match) - V2 respects this more often
522
+ const ifMatchHeader = req.headers['if-match'];
523
+ const ifMatch = Array.isArray(ifMatchHeader) ? ifMatchHeader[0] : ifMatchHeader;
524
+ if (ifMatch && ifMatch !== '*' && ifMatch !== existingFile.etag) {
525
+ if (ifMatch !== existingFile.etag && ifMatch !== `"${existingFile.etag}"`) {
526
+ res.status(412).json({ error: { code: 412, message: "Precondition Failed" } });
527
+ return;
528
+ }
529
+ }
530
+ store_1.driveStore.deleteFile(fileId);
531
+ res.status(204).send();
532
+ });
413
533
  return app;
414
534
  };
415
535
  exports.createApp = createApp;
@@ -0,0 +1,9 @@
1
+ import { DriveFile } from './store';
2
+ /**
3
+ * Maps an internal DriveFile (V3 format) to a V2 API File resource.
4
+ */
5
+ export declare function toV2File(file: DriveFile): Record<string, unknown>;
6
+ /**
7
+ * Maps a V2 API File Update/Insert body to a partial Internal DriveFile (V3 format).
8
+ */
9
+ export declare function fromV2Update(body: Record<string, unknown>): Partial<DriveFile>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toV2File = toV2File;
4
+ exports.fromV2Update = fromV2Update;
5
+ /**
6
+ * Maps an internal DriveFile (V3 format) to a V2 API File resource.
7
+ */
8
+ function toV2File(file) {
9
+ return {
10
+ kind: 'drive#file',
11
+ id: file.id,
12
+ etag: file.etag || `"${file.version}"`, // V2 uses etags frequently
13
+ selfLink: `http://localhost/drive/v2/files/${file.id}`, // Mock link
14
+ title: file.name,
15
+ mimeType: file.mimeType,
16
+ labels: {
17
+ starred: file.starred || false,
18
+ hidden: false,
19
+ trashed: file.trashed || false,
20
+ restricted: false,
21
+ viewed: true
22
+ },
23
+ createdDate: file.createdTime,
24
+ modifiedDate: file.modifiedTime,
25
+ parents: (file.parents || []).map(parentId => ({
26
+ kind: 'drive#parentReference',
27
+ id: parentId,
28
+ selfLink: `http://localhost/drive/v2/files/${parentId}`,
29
+ parentLink: `http://localhost/drive/v2/files/${parentId}`,
30
+ isRoot: false // Mock simplification
31
+ })),
32
+ version: file.version,
33
+ downloadUrl: `http://localhost/drive/v2/files/${file.id}?alt=media`
34
+ };
35
+ }
36
+ /**
37
+ * Maps a V2 API File Update/Insert body to a partial Internal DriveFile (V3 format).
38
+ */
39
+ function fromV2Update(body) {
40
+ const update = {};
41
+ if (typeof body.title === 'string')
42
+ update.name = body.title;
43
+ if (typeof body.mimeType === 'string')
44
+ update.mimeType = body.mimeType;
45
+ if (typeof body.modifiedDate === 'string')
46
+ update.modifiedTime = body.modifiedDate;
47
+ // Parents in V2 create are typically [{id: '...'}]
48
+ if (body.parents && Array.isArray(body.parents)) {
49
+ update.parents = body.parents
50
+ .map((p) => p.id)
51
+ .filter((id) => typeof id === 'string');
52
+ }
53
+ if (body.labels && typeof body.labels === 'object') {
54
+ const labels = body.labels;
55
+ if (typeof labels.starred === 'boolean')
56
+ update.starred = labels.starred;
57
+ if (typeof labels.trashed === 'boolean')
58
+ update.trashed = labels.trashed;
59
+ }
60
+ return update;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-drive-mock",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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",
@@ -10,7 +10,8 @@
10
10
  "dev": "ts-node src/index.ts",
11
11
  "test": "TEST_TARGET=mock vitest run",
12
12
  "test:slow": "LATENCY=20 vitest run",
13
- "test:browser": "start-server-and-test dev http://localhost:3000 'BROWSER_ENABLED=true vitest run --browser'",
13
+ "test:browser": "start-server-and-test dev http://localhost:3000 'TEST_TARGET=mock BROWSER_ENABLED=true vitest run --browser'",
14
+ "test:browser:real": "TEST_TARGET=real BROWSER_ENABLED=true vitest run --browser",
14
15
  "test:real": "TEST_TARGET=real vitest run",
15
16
  "example:login": "ts-node examples/serve-login.ts",
16
17
  "lint": "eslint .",