google-drive-mock 1.0.9 → 1.0.10

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/mappers.js CHANGED
@@ -30,6 +30,8 @@ function toV2File(file) {
30
30
  isRoot: false // Mock simplification
31
31
  })),
32
32
  version: file.version,
33
+ fileSize: file.size,
34
+ md5Checksum: file.md5Checksum,
33
35
  downloadUrl: `http://localhost/drive/v2/files/${file.id}?alt=media`
34
36
  };
35
37
  }
package/dist/store.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface DriveFile {
9
9
  trashed: boolean;
10
10
  createdTime: string;
11
11
  modifiedTime: string;
12
+ size: string;
13
+ md5Checksum: string;
12
14
  [key: string]: unknown;
13
15
  }
14
16
  export interface DriveChange {
@@ -38,6 +40,7 @@ export declare class DriveStore {
38
40
  private files;
39
41
  private changes;
40
42
  constructor();
43
+ private calculateStats;
41
44
  createFile(file: Partial<DriveFile> & {
42
45
  name: string;
43
46
  }): DriveFile;
package/dist/store.js CHANGED
@@ -1,18 +1,71 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.driveStore = exports.DriveStore = void 0;
37
+ const crypto = __importStar(require("crypto"));
4
38
  class DriveStore {
5
39
  constructor() {
6
40
  this.files = new Map();
7
41
  this.changes = [];
8
42
  }
43
+ calculateStats(content) {
44
+ let buffer;
45
+ if (typeof content === 'string') {
46
+ buffer = Buffer.from(content);
47
+ }
48
+ else if (content === undefined || content === null) {
49
+ buffer = Buffer.from('');
50
+ }
51
+ else {
52
+ buffer = Buffer.from(JSON.stringify(content));
53
+ }
54
+ return {
55
+ size: String(buffer.length),
56
+ md5Checksum: crypto.createHash('md5').update(buffer).digest('hex')
57
+ };
58
+ }
9
59
  createFile(file) {
10
60
  if (!file.name) {
11
61
  throw new Error("File name is required");
12
62
  }
13
63
  const id = file.id || Math.random().toString(36).substring(7);
14
64
  const now = new Date().toISOString();
15
- const newFile = Object.assign(Object.assign({ kind: "drive#file", mimeType: "application/octet-stream", trashed: false, createdTime: now, modifiedTime: now }, file), { id, version: 1, etag: "1" });
65
+ const stats = this.calculateStats(file.content);
66
+ const newFile = Object.assign(Object.assign({ kind: "drive#file", mimeType: "application/octet-stream", trashed: false, createdTime: now, modifiedTime: now }, file), { id, version: 1, etag: "1",
67
+ // Ensure calculated stats override provided ones
68
+ size: stats.size, md5Checksum: stats.md5Checksum });
16
69
  this.files.set(id, newFile);
17
70
  this.addChange(newFile);
18
71
  return newFile;
@@ -21,9 +74,14 @@ class DriveStore {
21
74
  const file = this.files.get(id);
22
75
  if (!file)
23
76
  return null;
77
+ // If content is being updated, recalculate stats
78
+ let statsUpdates = {};
79
+ if (updates.content !== undefined) {
80
+ statsUpdates = this.calculateStats(updates.content);
81
+ }
24
82
  // Merge updates and increment version
25
83
  const newVersion = file.version + 1;
26
- const updatedFile = Object.assign(Object.assign(Object.assign({}, file), updates), { version: newVersion, etag: String(newVersion), modifiedTime: new Date().toISOString() });
84
+ const updatedFile = Object.assign(Object.assign(Object.assign(Object.assign({}, file), updates), statsUpdates), { version: newVersion, etag: String(newVersion), modifiedTime: new Date().toISOString() });
27
85
  this.files.set(id, updatedFile);
28
86
  this.addChange(updatedFile);
29
87
  return updatedFile;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-drive-mock",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
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",
@@ -12,7 +12,7 @@
12
12
  "test:slow": "LATENCY=20 vitest run",
13
13
  "test:browser": "start-server-and-test dev http://localhost:3000 'TEST_TARGET=mock BROWSER_ENABLED=true vitest run --browser --no-file-parallelism'",
14
14
  "test:browser:real": "TEST_TARGET=real BROWSER_ENABLED=true vitest run --browser",
15
- "test:real": "TEST_TARGET=real vitest run",
15
+ "test:real": "ts-node scripts/check-token.ts && TEST_TARGET=real vitest run",
16
16
  "example:login": "ts-node examples/serve-login.ts",
17
17
  "lint": "eslint .",
18
18
  "lint:fix": "eslint . --fix"
@@ -0,0 +1,75 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as https from 'https';
4
+
5
+ // Load .ENV manually to avoid devDependency issues if dotenv isn't available in this context,
6
+ // though project seems to use it.
7
+ const envPath = path.resolve(__dirname, '../.ENV');
8
+ if (fs.existsSync(envPath)) {
9
+ const envContent = fs.readFileSync(envPath, 'utf8');
10
+ envContent.split('\n').forEach(line => {
11
+ const match = line.match(/^([^=]+)=(.*)$/);
12
+ if (match) {
13
+ const key = match[1].trim();
14
+ const value = match[2].trim().replace(/^['"]|['"]$/g, ''); // strip quotes
15
+ if (!process.env[key]) {
16
+ process.env[key] = value;
17
+ }
18
+ }
19
+ });
20
+ }
21
+
22
+ const token = process.env.GDRIVE_TOKEN;
23
+
24
+ if (!token) {
25
+ console.error('❌ Error: GDRIVE_TOKEN not found in environment or .ENV file.');
26
+ process.exit(1);
27
+ }
28
+
29
+ // Simple check only if running real tests (though script is likely invoked specifically for that)
30
+ // The user asked to run this BEFORE test:real.
31
+
32
+ console.log('🔄 Verifying GDRIVE_TOKEN...');
33
+
34
+ const options = {
35
+ hostname: 'www.googleapis.com',
36
+ path: '/drive/v3/about?fields=user',
37
+ method: 'GET',
38
+ headers: {
39
+ 'Authorization': `Bearer ${token}`,
40
+ 'User-Agent': 'node-script'
41
+ }
42
+ };
43
+
44
+ const req = https.request(options, (res) => {
45
+ let data = '';
46
+
47
+ res.on('data', (chunk) => {
48
+ data += chunk;
49
+ });
50
+
51
+ res.on('end', () => {
52
+ if (res.statusCode === 200) {
53
+ try {
54
+ const body = JSON.parse(data);
55
+ console.log(`✅ Token is valid. User: ${body.user?.emailAddress || 'Unknown'}`);
56
+ process.exit(0);
57
+ } catch (e: unknown) {
58
+ const msg = e instanceof Error ? e.message : String(e);
59
+ console.error('❌ Error parsing response:', msg);
60
+ process.exit(1);
61
+ }
62
+ } else {
63
+ console.error(`❌ Token verification failed. Tell the human to update the .ENV file with a valid token. Status: ${res.statusCode}`);
64
+ console.error('Response:', data);
65
+ process.exit(1);
66
+ }
67
+ });
68
+ });
69
+
70
+ req.on('error', (e) => {
71
+ console.error(`❌ Request error: ${e.message}`);
72
+ process.exit(1);
73
+ });
74
+
75
+ req.end();
package/src/mappers.ts CHANGED
@@ -28,6 +28,8 @@ export function toV2File(file: DriveFile): Record<string, unknown> {
28
28
  isRoot: false // Mock simplification
29
29
  })),
30
30
  version: file.version,
31
+ fileSize: file.size,
32
+ md5Checksum: file.md5Checksum,
31
33
  downloadUrl: `http://localhost/drive/v2/files/${file.id}?alt=media`
32
34
  };
33
35
  }
package/src/store.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import * as crypto from 'crypto';
2
+
1
3
  export interface DriveFile {
2
4
  id: string;
3
5
  name: string;
@@ -9,6 +11,8 @@ export interface DriveFile {
9
11
  trashed: boolean;
10
12
  createdTime: string;
11
13
  modifiedTime: string;
14
+ size: string;
15
+ md5Checksum: string;
12
16
  [key: string]: unknown;
13
17
  }
14
18
 
@@ -46,22 +50,45 @@ export class DriveStore {
46
50
  this.changes = [];
47
51
  }
48
52
 
53
+ private calculateStats(content: unknown): { size: string, md5Checksum: string } {
54
+ let buffer: Buffer;
55
+ if (typeof content === 'string') {
56
+ buffer = Buffer.from(content);
57
+ } else if (content === undefined || content === null) {
58
+ buffer = Buffer.from('');
59
+ } else {
60
+ buffer = Buffer.from(JSON.stringify(content));
61
+ }
62
+
63
+ return {
64
+ size: String(buffer.length),
65
+ md5Checksum: crypto.createHash('md5').update(buffer).digest('hex')
66
+ };
67
+ }
68
+
49
69
  createFile(file: Partial<DriveFile> & { name: string }): DriveFile {
50
70
  if (!file.name) {
51
71
  throw new Error("File name is required");
52
72
  }
53
73
  const id = file.id || Math.random().toString(36).substring(7);
54
74
  const now = new Date().toISOString();
75
+
76
+ const stats = this.calculateStats(file.content);
77
+
55
78
  const newFile: DriveFile = {
56
79
  kind: "drive#file",
57
80
  mimeType: "application/octet-stream",
58
81
  trashed: false,
59
82
  createdTime: now,
60
83
  modifiedTime: now,
84
+
61
85
  ...file,
62
86
  id,
63
87
  version: 1, // Initialize version
64
88
  etag: "1", // Initialize etag
89
+ // Ensure calculated stats override provided ones
90
+ size: stats.size,
91
+ md5Checksum: stats.md5Checksum
65
92
  };
66
93
 
67
94
  this.files.set(id, newFile);
@@ -73,11 +100,18 @@ export class DriveStore {
73
100
  const file = this.files.get(id);
74
101
  if (!file) return null;
75
102
 
103
+ // If content is being updated, recalculate stats
104
+ let statsUpdates = {};
105
+ if (updates.content !== undefined) {
106
+ statsUpdates = this.calculateStats(updates.content);
107
+ }
108
+
76
109
  // Merge updates and increment version
77
110
  const newVersion = file.version + 1;
78
111
  const updatedFile = {
79
112
  ...file,
80
113
  ...updates,
114
+ ...statsUpdates,
81
115
  version: newVersion,
82
116
  etag: String(newVersion),
83
117
  modifiedTime: new Date().toISOString()
@@ -54,7 +54,7 @@ describe('MIME Type Handling', () => {
54
54
  expect(await getRes.json()).toEqual(content);
55
55
  });
56
56
 
57
- it('should update MIME type and return new Content-Type header', async () => {
57
+ it('should update MIME type and return new Content-Type header', { timeout: 10000 }, async () => {
58
58
  // Create as text/plain
59
59
  const createRes = await fetch(`${config.baseUrl}/drive/v3/files`, {
60
60
  method: 'POST',
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { getTestConfig, TestConfig } from './config';
3
+
4
+ describe('Missing Fields Support (Size & MD5)', () => {
5
+ let config: TestConfig;
6
+
7
+ beforeAll(async () => {
8
+ config = await getTestConfig();
9
+ });
10
+
11
+ afterAll(() => {
12
+ if (config) config.stop();
13
+ });
14
+
15
+ it('should return size and md5Checksum in V3 GET', async () => {
16
+ const content = JSON.stringify({ foo: 'bar' });
17
+ const metadata = { name: 'V3 Size Test', mimeType: 'application/json' };
18
+
19
+ const boundary = 'foo_bar_baz';
20
+ const delimiter = `\r\n--${boundary}\r\n`;
21
+ const closeDelim = `\r\n--${boundary}--`;
22
+
23
+ const body = delimiter +
24
+ 'Content-Type: application/json\r\n\r\n' +
25
+ JSON.stringify(metadata) +
26
+ delimiter +
27
+ 'Content-Type: application/json\r\n\r\n' +
28
+ content +
29
+ closeDelim;
30
+
31
+ const createRes = await fetch(`${config.baseUrl}/upload/drive/v3/files?uploadType=multipart`, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Authorization': `Bearer ${config.token}`,
35
+ 'Content-Type': `multipart/related; boundary="${boundary}"`
36
+ },
37
+ body: body
38
+ });
39
+
40
+ expect(createRes.status).toBe(200);
41
+ const file = await createRes.json();
42
+
43
+ const fields = 'id,name,size,md5Checksum';
44
+ const getRes = await fetch(`${config.baseUrl}/drive/v3/files/${file.id}?fields=${fields}`, {
45
+ headers: { 'Authorization': `Bearer ${config.token}` }
46
+ });
47
+ expect(getRes.status).toBe(200);
48
+ const getFile = await getRes.json();
49
+
50
+ expect(getFile.size).toBeDefined();
51
+ // size should be string in V3
52
+ expect(String(getFile.size)).toBe(String(content.length));
53
+ expect(getFile.md5Checksum).toBeDefined();
54
+ });
55
+
56
+ it('should return fileSize and md5Checksum in V2 GET', async () => {
57
+ const content = 'V2 Content';
58
+
59
+ // Use simple upload for V2 to ensure content and metadata
60
+ const uploadRes = await fetch(`${config.baseUrl}/upload/drive/v2/files?uploadType=media`, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Authorization': `Bearer ${config.token}`,
64
+ 'Content-Type': 'text/plain'
65
+ },
66
+ body: content
67
+ });
68
+ expect(uploadRes.status).toBe(200);
69
+ const file = await uploadRes.json();
70
+
71
+ expect(file.fileSize).toBeDefined(); // V2 uses fileSize
72
+ expect(Number(file.fileSize)).toBe(content.length);
73
+ expect(file.md5Checksum).toBeDefined();
74
+
75
+ // Verify GET
76
+ const getRes = await fetch(`${config.baseUrl}/drive/v2/files/${file.id}`, {
77
+ headers: { 'Authorization': `Bearer ${config.token}` }
78
+ });
79
+ const getFile = await getRes.json();
80
+ expect(getFile.fileSize).toBeDefined();
81
+ expect(Number(getFile.fileSize)).toBe(content.length);
82
+ expect(getFile.md5Checksum).toBeDefined();
83
+ });
84
+
85
+ it('should return correct size and md5Checksum for EMPTY file', async () => {
86
+ const content = '';
87
+ const md5Empty = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 of empty string
88
+
89
+ // Upload empty file via V2 media upload
90
+ const uploadRes = await fetch(`${config.baseUrl}/upload/drive/v2/files?uploadType=media`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Authorization': `Bearer ${config.token}`,
94
+ 'Content-Type': 'text/plain'
95
+ },
96
+ body: content
97
+ });
98
+ expect(uploadRes.status).toBe(200);
99
+ const file = await uploadRes.json();
100
+
101
+ // Check response
102
+ expect(file.fileSize).toBeDefined();
103
+ expect(Number(file.fileSize)).toBe(0);
104
+ expect(file.md5Checksum).toBe(md5Empty);
105
+
106
+ // Verify GET V2
107
+ const getV2 = await fetch(`${config.baseUrl}/drive/v2/files/${file.id}`, {
108
+ headers: { 'Authorization': `Bearer ${config.token}` }
109
+ });
110
+ const v2File = await getV2.json();
111
+ expect(Number(v2File.fileSize)).toBe(0);
112
+ expect(v2File.md5Checksum).toBe(md5Empty);
113
+
114
+ // Verify GET V3
115
+ const fields = 'id,name,size,md5Checksum';
116
+ const getV3 = await fetch(`${config.baseUrl}/drive/v3/files/${file.id}?fields=${fields}`, {
117
+ headers: { 'Authorization': `Bearer ${config.token}` }
118
+ });
119
+ const v3File = await getV3.json();
120
+ expect(Number(v3File.size)).toBe(0);
121
+ expect(v3File.md5Checksum).toBe(md5Empty);
122
+ });
123
+ });