google-drive-mock 1.0.8 → 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/routes/v2.js CHANGED
@@ -391,6 +391,14 @@ const createV2Router = (config) => {
391
391
  res.status(404).json({ error: { code: 404, message: "File not found" } });
392
392
  return;
393
393
  }
394
+ const ifMatchHeader = req.headers['if-match'];
395
+ const ifMatch = Array.isArray(ifMatchHeader) ? ifMatchHeader[0] : ifMatchHeader;
396
+ if (ifMatch && ifMatch !== '*' && ifMatch !== existingFile.etag) {
397
+ if (ifMatch !== existingFile.etag && ifMatch !== `"${existingFile.etag}"`) {
398
+ res.status(412).json({ error: { code: 412, message: "Precondition Failed" } });
399
+ return;
400
+ }
401
+ }
394
402
  const uploadType = req.query.uploadType;
395
403
  if (uploadType === 'media') {
396
404
  const rawBody = req.body;
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.8",
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/routes/v2.ts CHANGED
@@ -454,6 +454,15 @@ export const createV2Router = (config: AppConfig) => {
454
454
  return;
455
455
  }
456
456
 
457
+ const ifMatchHeader = req.headers['if-match'];
458
+ const ifMatch = Array.isArray(ifMatchHeader) ? ifMatchHeader[0] : ifMatchHeader;
459
+ if (ifMatch && ifMatch !== '*' && ifMatch !== existingFile.etag) {
460
+ if (ifMatch !== existingFile.etag && ifMatch !== `"${existingFile.etag}"`) {
461
+ res.status(412).json({ error: { code: 412, message: "Precondition Failed" } });
462
+ return;
463
+ }
464
+ }
465
+
457
466
  const uploadType = req.query.uploadType as string;
458
467
 
459
468
  if (uploadType === 'media') {
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
+ });
@@ -139,4 +139,103 @@ describe('V2 Upload Features', () => {
139
139
  });
140
140
  expect(await contentRes.text()).toBe(newContent);
141
141
  });
142
+ it('should respect If-Match header in V2 media upload (PUT)', async () => {
143
+ // 1. Create file
144
+ const createRes = await fetch(`${config.baseUrl}/drive/v2/files`, {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Authorization': `Bearer ${config.token}`,
148
+ 'Content-Type': 'application/json'
149
+ },
150
+ body: JSON.stringify({ title: 'ETag Media Test', mimeType: 'text/plain' })
151
+ });
152
+ const file = await createRes.json();
153
+ const fileId = file.id;
154
+ const etag = file.etag;
155
+
156
+ const invalidEtag = etag ? etag.replace(/.$/, '0') : '"wrong-etag"';
157
+
158
+ // 2. Update with WRONG ETag
159
+ const wrongEtagRes = await fetch(`${config.baseUrl}/upload/drive/v2/files/${fileId}?uploadType=media`, {
160
+ method: 'PUT',
161
+ headers: {
162
+ 'Authorization': `Bearer ${config.token}`,
163
+ 'Content-Type': 'text/plain',
164
+ 'If-Match': invalidEtag
165
+ },
166
+ body: 'Should Fail'
167
+ });
168
+ expect(wrongEtagRes.status).toBe(412);
169
+
170
+ // 3. Update with CORRECT ETag
171
+ const correctEtagRes = await fetch(`${config.baseUrl}/upload/drive/v2/files/${fileId}?uploadType=media`, {
172
+ method: 'PUT',
173
+ headers: {
174
+ 'Authorization': `Bearer ${config.token}`,
175
+ 'Content-Type': 'text/plain',
176
+ 'If-Match': etag
177
+ },
178
+ body: 'Updated with ETag'
179
+ });
180
+ expect(correctEtagRes.status).toBe(200);
181
+
182
+ // Verify Content
183
+ const contentRes = await fetch(`${config.baseUrl}/drive/v2/files/${fileId}?alt=media`, {
184
+ headers: { 'Authorization': `Bearer ${config.token}` }
185
+ });
186
+ expect(await contentRes.text()).toBe('Updated with ETag');
187
+ });
188
+
189
+ it('should respect If-Match header in V2 multipart upload (PUT)', async () => {
190
+ // 1. Create file
191
+ const createRes = await fetch(`${config.baseUrl}/drive/v2/files`, {
192
+ method: 'POST',
193
+ headers: {
194
+ 'Authorization': `Bearer ${config.token}`,
195
+ 'Content-Type': 'application/json'
196
+ },
197
+ body: JSON.stringify({ title: 'ETag Multipart Test', mimeType: 'text/plain' })
198
+ });
199
+ const file = await createRes.json();
200
+ const fileId = file.id;
201
+ const etag = file.etag;
202
+
203
+ const boundary = 'foo_bar_baz';
204
+ const delimiter = `\r\n--${boundary}\r\n`;
205
+ const closeDelim = `\r\n--${boundary}--`;
206
+ const body = delimiter +
207
+ 'Content-Type: application/json\r\n\r\n' +
208
+ JSON.stringify({ title: 'Updated Title' }) +
209
+ delimiter +
210
+ 'Content-Type: text/plain\r\n\r\n' +
211
+ 'Multipart Update' +
212
+ closeDelim;
213
+
214
+ const invalidEtag = etag ? etag.replace(/.$/, '0') : '"wrong-etag"';
215
+
216
+ // 2. Update with WRONG ETag
217
+ const wrongEtagRes = await fetch(`${config.baseUrl}/upload/drive/v2/files/${fileId}?uploadType=multipart`, {
218
+ method: 'PUT',
219
+ headers: {
220
+ 'Authorization': `Bearer ${config.token}`,
221
+ 'Content-Type': `multipart/related; boundary="${boundary}"`,
222
+ 'If-Match': invalidEtag
223
+ },
224
+ body: body
225
+ });
226
+ expect(wrongEtagRes.status).toBe(412);
227
+
228
+ // 3. Update with CORRECT ETag
229
+ const correctEtagRes = await fetch(`${config.baseUrl}/upload/drive/v2/files/${fileId}?uploadType=multipart`, {
230
+ method: 'PUT',
231
+ headers: {
232
+ 'Authorization': `Bearer ${config.token}`,
233
+ 'Content-Type': `multipart/related; boundary="${boundary}"`,
234
+ 'If-Match': etag
235
+ },
236
+ body: body
237
+ });
238
+ expect(correctEtagRes.status).toBe(200);
239
+ expect((await correctEtagRes.json()).title).toBe('Updated Title');
240
+ });
142
241
  });