koa-classic-server 2.6.0 → 3.0.0-alpha.0

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.
Files changed (40) hide show
  1. package/README.md +68 -10
  2. package/__tests__/caching-headers.test.js +30 -30
  3. package/__tests__/compression-fixtures/data.json +1 -0
  4. package/__tests__/compression-fixtures/large.txt +1 -0
  5. package/__tests__/compression-fixtures/small.txt +1 -0
  6. package/__tests__/compression.test.js +270 -0
  7. package/__tests__/customTest/serversToLoad.util.js +1 -1
  8. package/__tests__/deprecation-warnings.test.js +71 -183
  9. package/__tests__/dt-unknown.test.js +635 -0
  10. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  11. package/__tests__/hidden-fixtures/.env +2 -0
  12. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  13. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  14. package/__tests__/hidden-fixtures/data.key +1 -0
  15. package/__tests__/hidden-fixtures/file.secret +1 -0
  16. package/__tests__/hidden-fixtures/index.html +1 -0
  17. package/__tests__/hidden-fixtures/normal.txt +1 -0
  18. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  19. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  20. package/__tests__/hidden-option.test.js +422 -0
  21. package/__tests__/index-option.test.js +18 -16
  22. package/__tests__/index.test.js +8 -4
  23. package/__tests__/range-fixtures/sample.txt +1 -0
  24. package/__tests__/range.test.js +223 -0
  25. package/__tests__/security-headers.test.js +153 -0
  26. package/__tests__/security.test.js +145 -159
  27. package/__tests__/server-cache-fixtures/large.txt +1 -0
  28. package/__tests__/server-cache-fixtures/small.txt +1 -0
  29. package/__tests__/server-cache.test.js +423 -0
  30. package/__tests__/symlink.test.js +8 -5
  31. package/docs/ACTION_PLAN.md +293 -0
  32. package/docs/CHANGELOG.md +118 -0
  33. package/docs/EXAMPLES_INDEX_OPTION.md +2 -2
  34. package/docs/FLOW_DIAGRAM.md +13 -13
  35. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  36. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  37. package/eslint.config.mjs +17 -0
  38. package/index.cjs +1114 -378
  39. package/index.mjs +1 -5
  40. package/package.json +4 -1
@@ -0,0 +1,422 @@
1
+ const supertest = require('supertest');
2
+ const koaClassicServer = require('../index.cjs');
3
+ const Koa = require('koa');
4
+ const path = require('path');
5
+
6
+ const root = path.join(__dirname, 'hidden-fixtures');
7
+
8
+ function createApp(hiddenOpts) {
9
+ const app = new Koa();
10
+ app.use(koaClassicServer(root, { showDirContents: true, hidden: hiddenOpts }));
11
+ return app.listen();
12
+ }
13
+
14
+ // ─── Implicit default warning ─────────────────────────────────────────────────
15
+ // This describe block MUST be first: the warning flag is module-level and fires
16
+ // only once per Jest worker process. Jest isolates each test FILE into its own
17
+ // module registry, so the flag starts as false when this file loads.
18
+
19
+ describe('hidden option — implicit default warning', () => {
20
+ let warnSpy;
21
+
22
+ beforeAll(() => {
23
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
24
+ });
25
+
26
+ afterAll(() => {
27
+ warnSpy.mockRestore();
28
+ });
29
+
30
+ test('warns when dotFiles.default and dotDirs.default are not explicitly set', () => {
31
+ warnSpy.mockClear();
32
+ koaClassicServer(root, {}); // no hidden config at all — both defaults implicit
33
+ const warnings = warnSpy.mock.calls.filter(c =>
34
+ c[1] && c[1].includes('dotFiles') && c[1].includes('dotDirs')
35
+ );
36
+ expect(warnings.length).toBe(1);
37
+ expect(warnings[0][1]).toContain('hidden.dotFiles.default');
38
+ expect(warnings[0][1]).toContain('v3.0.0');
39
+ });
40
+
41
+ test('does not warn again on subsequent calls (once per process)', () => {
42
+ warnSpy.mockClear();
43
+ koaClassicServer(root, {}); // flag already set from the test above
44
+ const warnings = warnSpy.mock.calls.filter(c =>
45
+ c[1] && c[1].includes('dotFiles')
46
+ );
47
+ expect(warnings.length).toBe(0);
48
+ });
49
+
50
+ test('no warning when both dotFiles.default and dotDirs.default are explicitly set', () => {
51
+ // Use jest.isolateModules to obtain a fresh copy of index.cjs whose flag is false,
52
+ // then verify that an explicit configuration emits no warning.
53
+ let fresh;
54
+ jest.isolateModules(() => {
55
+ fresh = require('../index.cjs');
56
+ });
57
+ warnSpy.mockClear();
58
+ fresh(root, {
59
+ hidden: {
60
+ dotFiles: { default: 'hidden' },
61
+ dotDirs: { default: 'visible' },
62
+ }
63
+ });
64
+ const warnings = warnSpy.mock.calls.filter(c =>
65
+ c[1] && c[1].includes('dotFiles')
66
+ );
67
+ expect(warnings.length).toBe(0);
68
+ });
69
+ });
70
+
71
+ // ─── Option validation ────────────────────────────────────────────────────────
72
+
73
+ describe('hidden option — validation', () => {
74
+ test('throws when dotFiles.default is not "hidden" or "visible"', () => {
75
+ expect(() =>
76
+ koaClassicServer(root, { hidden: { dotFiles: { default: 'yes' } } })
77
+ ).toThrow(/hidden\.dotFiles\.default must be "hidden" or "visible"/);
78
+ });
79
+
80
+ test('throws when dotDirs.default is not "hidden" or "visible"', () => {
81
+ expect(() =>
82
+ koaClassicServer(root, { hidden: { dotDirs: { default: 'yes' } } })
83
+ ).toThrow(/hidden\.dotDirs\.default must be "hidden" or "visible"/);
84
+ });
85
+
86
+ test('does not throw when hidden option is omitted', () => {
87
+ expect(() => koaClassicServer(root, {})).not.toThrow();
88
+ });
89
+
90
+ test('does not throw when hidden is a valid object', () => {
91
+ expect(() =>
92
+ koaClassicServer(root, {
93
+ hidden: {
94
+ dotFiles: { default: 'hidden', whitelist: ['.well-known'], blacklist: ['.env'] },
95
+ dotDirs: { default: 'visible', blacklist: [/^\.git/] },
96
+ alwaysHide: ['*.secret', /\.key$/]
97
+ }
98
+ })
99
+ ).not.toThrow();
100
+ });
101
+ });
102
+
103
+ // ─── dotFiles default: 'hidden' (system default) ─────────────────────────────
104
+
105
+ describe('dotFiles — default hidden (system default)', () => {
106
+ let server;
107
+ beforeAll(() => { server = createApp(undefined); });
108
+ afterAll(() => server.close());
109
+
110
+ test('GET /.env returns 404', async () => {
111
+ const res = await supertest(server).get('/.env');
112
+ expect(res.status).toBe(404);
113
+ });
114
+
115
+ test('GET /.gitignore returns 404', async () => {
116
+ const res = await supertest(server).get('/.gitignore');
117
+ expect(res.status).toBe(404);
118
+ });
119
+
120
+ test('GET /subdir/.env returns 404 (dot-file in subdirectory)', async () => {
121
+ const res = await supertest(server).get('/subdir/.env');
122
+ expect(res.status).toBe(404);
123
+ });
124
+
125
+ test('directory listing does not include .env', async () => {
126
+ const res = await supertest(server).get('/');
127
+ expect(res.status).toBe(200);
128
+ expect(res.text).not.toContain('.env');
129
+ });
130
+
131
+ test('directory listing does not include .gitignore', async () => {
132
+ const res = await supertest(server).get('/');
133
+ expect(res.text).not.toContain('.gitignore');
134
+ });
135
+
136
+ test('regular files remain accessible', async () => {
137
+ const res = await supertest(server).get('/normal.txt');
138
+ expect(res.status).toBe(200);
139
+ });
140
+ });
141
+
142
+ // ─── dotFiles default: 'visible' ─────────────────────────────────────────────
143
+
144
+ describe('dotFiles — default visible', () => {
145
+ let server;
146
+ beforeAll(() => {
147
+ server = createApp({ dotFiles: { default: 'visible' } });
148
+ });
149
+ afterAll(() => server.close());
150
+
151
+ test('GET /.env returns 200 when dotFiles.default is "visible"', async () => {
152
+ const res = await supertest(server).get('/.env');
153
+ expect(res.status).toBe(200);
154
+ });
155
+
156
+ test('directory listing includes .env when dotFiles.default is "visible"', async () => {
157
+ const res = await supertest(server).get('/');
158
+ expect(res.text).toContain('.env');
159
+ });
160
+ });
161
+
162
+ // ─── dotFiles whitelist ───────────────────────────────────────────────────────
163
+
164
+ describe('dotFiles — whitelist exceptions', () => {
165
+ let server;
166
+ beforeAll(() => {
167
+ server = createApp({
168
+ dotFiles: { default: 'hidden', whitelist: ['.well-known'] },
169
+ dotDirs: { default: 'hidden', whitelist: ['.well-known'] }
170
+ });
171
+ });
172
+ afterAll(() => server.close());
173
+
174
+ test('GET /.well-known/ is accessible (whitelisted dir)', async () => {
175
+ const res = await supertest(server).get('/.well-known/');
176
+ expect(res.status).toBe(200);
177
+ });
178
+
179
+ test('GET /.well-known/acme-challenge.txt is accessible (inside whitelisted dir)', async () => {
180
+ const res = await supertest(server).get('/.well-known/acme-challenge.txt');
181
+ expect(res.status).toBe(200);
182
+ });
183
+
184
+ test('.well-known appears in root listing', async () => {
185
+ const res = await supertest(server).get('/');
186
+ expect(res.text).toContain('.well-known');
187
+ });
188
+
189
+ test('GET /.env still returns 404 (not whitelisted)', async () => {
190
+ const res = await supertest(server).get('/.env');
191
+ expect(res.status).toBe(404);
192
+ });
193
+
194
+ test('whitelist with RegExp: /^\\.public/ matches .public-assets', async () => {
195
+ // This tests the regex matching logic; .public-assets doesn't exist so we
196
+ // expect 404 from "not found", not from "hidden" — indirectly confirms regex is not blocking
197
+ const server2 = createApp({
198
+ dotFiles: { default: 'hidden', whitelist: [/^\.public/] }
199
+ });
200
+ // .env is not matched by /^\.public/ so it stays hidden
201
+ const res = await supertest(server2).get('/.env');
202
+ expect(res.status).toBe(404);
203
+ server2.close();
204
+ });
205
+ });
206
+
207
+ // ─── dotFiles blacklist ───────────────────────────────────────────────────────
208
+
209
+ describe('dotFiles — blacklist', () => {
210
+ let server;
211
+ beforeAll(() => {
212
+ server = createApp({
213
+ dotFiles: { default: 'visible', blacklist: ['.env'] }
214
+ });
215
+ });
216
+ afterAll(() => server.close());
217
+
218
+ test('GET /.env returns 404 (blacklisted even with default: visible)', async () => {
219
+ const res = await supertest(server).get('/.env');
220
+ expect(res.status).toBe(404);
221
+ });
222
+
223
+ test('GET /.gitignore returns 200 (visible, not blacklisted)', async () => {
224
+ const res = await supertest(server).get('/.gitignore');
225
+ expect(res.status).toBe(200);
226
+ });
227
+
228
+ test('.env not in listing when blacklisted', async () => {
229
+ const res = await supertest(server).get('/');
230
+ expect(res.text).not.toContain('.env');
231
+ });
232
+ });
233
+
234
+ // ─── blacklist beats whitelist ────────────────────────────────────────────────
235
+
236
+ describe('dotFiles — blacklist beats whitelist', () => {
237
+ let server;
238
+ beforeAll(() => {
239
+ server = createApp({
240
+ dotFiles: { default: 'visible', whitelist: ['.env'], blacklist: ['.env'] }
241
+ });
242
+ });
243
+ afterAll(() => server.close());
244
+
245
+ test('GET /.env returns 404 (blacklist wins over whitelist)', async () => {
246
+ const res = await supertest(server).get('/.env');
247
+ expect(res.status).toBe(404);
248
+ });
249
+ });
250
+
251
+ // ─── dotDirs default: 'visible' (system default) ─────────────────────────────
252
+
253
+ describe('dotDirs — default visible (system default)', () => {
254
+ let server;
255
+ beforeAll(() => { server = createApp(undefined); });
256
+ afterAll(() => server.close());
257
+
258
+ test('.dot-dir appears in root listing by default', async () => {
259
+ const res = await supertest(server).get('/');
260
+ expect(res.text).toContain('.dot-dir');
261
+ });
262
+
263
+ test('GET /.dot-dir/ returns 200 (directory listing) by default', async () => {
264
+ const res = await supertest(server).get('/.dot-dir/');
265
+ expect(res.status).toBe(200);
266
+ });
267
+
268
+ test('GET /.dot-dir/inside.txt returns 200 by default', async () => {
269
+ const res = await supertest(server).get('/.dot-dir/inside.txt');
270
+ expect(res.status).toBe(200);
271
+ });
272
+ });
273
+
274
+ // ─── dotDirs blacklist ────────────────────────────────────────────────────────
275
+
276
+ describe('dotDirs — blacklist', () => {
277
+ let server;
278
+ beforeAll(() => {
279
+ server = createApp({ dotDirs: { blacklist: ['.dot-dir'] } });
280
+ });
281
+ afterAll(() => server.close());
282
+
283
+ test('GET /.dot-dir/ returns 404 (blacklisted dir)', async () => {
284
+ const res = await supertest(server).get('/.dot-dir/');
285
+ expect(res.status).toBe(404);
286
+ });
287
+
288
+ test('GET /.dot-dir/inside.txt returns 404 (inside blocked dir)', async () => {
289
+ const res = await supertest(server).get('/.dot-dir/inside.txt');
290
+ expect(res.status).toBe(404);
291
+ });
292
+
293
+ test('.dot-dir not in root listing when blacklisted', async () => {
294
+ const res = await supertest(server).get('/');
295
+ expect(res.text).not.toContain('.dot-dir');
296
+ });
297
+ });
298
+
299
+ // ─── dotDirs blacklist with RegExp ────────────────────────────────────────────
300
+
301
+ describe('dotDirs — blacklist with RegExp', () => {
302
+ let server;
303
+ beforeAll(() => {
304
+ server = createApp({ dotDirs: { blacklist: [/^\.dot/] } });
305
+ });
306
+ afterAll(() => server.close());
307
+
308
+ test('GET /.dot-dir/ returns 404 (RegExp blacklist match)', async () => {
309
+ const res = await supertest(server).get('/.dot-dir/');
310
+ expect(res.status).toBe(404);
311
+ });
312
+
313
+ test('.well-known accessible (does not match /^\\.dot/)', async () => {
314
+ const res = await supertest(server).get('/.well-known/');
315
+ expect(res.status).toBe(200);
316
+ });
317
+ });
318
+
319
+ // ─── alwaysHide ───────────────────────────────────────────────────────────────
320
+
321
+ describe('alwaysHide — glob and regex patterns', () => {
322
+ let server;
323
+ beforeAll(() => {
324
+ server = createApp({
325
+ dotFiles: { default: 'visible' }, // dot-files visible so we can test alwaysHide separately
326
+ alwaysHide: ['*.secret', /\.key$/]
327
+ });
328
+ });
329
+ afterAll(() => server.close());
330
+
331
+ test('GET /file.secret returns 404 (glob *.secret)', async () => {
332
+ const res = await supertest(server).get('/file.secret');
333
+ expect(res.status).toBe(404);
334
+ });
335
+
336
+ test('GET /data.key returns 404 (regex /\\.key$/)', async () => {
337
+ const res = await supertest(server).get('/data.key');
338
+ expect(res.status).toBe(404);
339
+ });
340
+
341
+ test('file.secret not in directory listing', async () => {
342
+ const res = await supertest(server).get('/');
343
+ expect(res.text).not.toContain('file.secret');
344
+ });
345
+
346
+ test('data.key not in directory listing', async () => {
347
+ const res = await supertest(server).get('/');
348
+ expect(res.text).not.toContain('data.key');
349
+ });
350
+
351
+ test('regular files remain accessible', async () => {
352
+ const res = await supertest(server).get('/normal.txt');
353
+ expect(res.status).toBe(200);
354
+ });
355
+ });
356
+
357
+ // ─── alwaysHide — path-anchored patterns ─────────────────────────────────────
358
+
359
+ describe('alwaysHide — path-anchored glob (config/secrets/**)', () => {
360
+ let server;
361
+ beforeAll(() => {
362
+ server = createApp({ alwaysHide: ['config/secrets/**'] });
363
+ });
364
+ afterAll(() => server.close());
365
+
366
+ test('GET /config/secrets/password.txt returns 404', async () => {
367
+ const res = await supertest(server).get('/config/secrets/password.txt');
368
+ expect(res.status).toBe(404);
369
+ });
370
+
371
+ test('password.txt not in /config/secrets/ listing', async () => {
372
+ const res = await supertest(server).get('/config/secrets/');
373
+ expect(res.status).toBe(200);
374
+ expect(res.text).not.toContain('password.txt');
375
+ });
376
+
377
+ test('GET /normal.txt remains accessible (not under config/secrets/)', async () => {
378
+ const res = await supertest(server).get('/normal.txt');
379
+ expect(res.status).toBe(200);
380
+ });
381
+ });
382
+
383
+ // ─── alwaysHide secondary to whitelist ───────────────────────────────────────
384
+
385
+ describe('alwaysHide — secondary to dotFiles whitelist', () => {
386
+ let server;
387
+ beforeAll(() => {
388
+ server = createApp({
389
+ dotFiles: { default: 'hidden', whitelist: ['.env'] },
390
+ alwaysHide: ['.env'] // both alwaysHide and whitelist target .env
391
+ });
392
+ });
393
+ afterAll(() => server.close());
394
+
395
+ test('GET /.env returns 200 (whitelist wins over alwaysHide)', async () => {
396
+ const res = await supertest(server).get('/.env');
397
+ expect(res.status).toBe(200);
398
+ });
399
+ });
400
+
401
+ // ─── deep tree: dot-files hidden at any depth ─────────────────────────────────
402
+
403
+ describe('hidden entries at any depth in directory tree', () => {
404
+ let server;
405
+ beforeAll(() => { server = createApp(undefined); });
406
+ afterAll(() => server.close());
407
+
408
+ test('GET /subdir/.env returns 404 (dot-file hidden at any depth)', async () => {
409
+ const res = await supertest(server).get('/subdir/.env');
410
+ expect(res.status).toBe(404);
411
+ });
412
+
413
+ test('/subdir/.env not in /subdir/ listing', async () => {
414
+ const res = await supertest(server).get('/subdir/');
415
+ expect(res.text).not.toContain('.env');
416
+ });
417
+
418
+ test('GET /subdir/regular.txt returns 200 (regular file accessible)', async () => {
419
+ const res = await supertest(server).get('/subdir/regular.txt');
420
+ expect(res.status).toBe(200);
421
+ });
422
+ });
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Enhanced Index Option Tests
3
3
  *
4
- * Tests for the new index option that supports:
5
- * - String (backward compatible)
4
+ * Tests for the index option that supports:
6
5
  * - Array of strings
7
6
  * - Array of RegExp
8
7
  * - Mixed array (strings + RegExp)
9
8
  * - Priority handling (first match wins)
9
+ *
10
+ * v3.0.0: string format removed — passing a non-empty string throws an Error.
10
11
  */
11
12
 
12
13
  const Koa = require('koa');
@@ -39,21 +40,20 @@ describe('Enhanced Index Option Tests', () => {
39
40
  }
40
41
  });
41
42
 
42
- describe('Backward Compatibility - String index', () => {
43
- test('String: "index.html" should work as before', async () => {
44
- // Create index.html
45
- fs.writeFileSync(path.join(tempDir, 'index.html'), '<h1>Index HTML</h1>');
46
-
47
- app = new Koa();
48
- app.use(koaClassicServer(tempDir, { index: 'index.html' }));
49
- server = app.listen();
43
+ describe('String index removed in v3.0.0', () => {
44
+ test('Non-empty string should throw an Error', () => {
45
+ expect(() => {
46
+ new Koa().use(koaClassicServer(tempDir, { index: 'index.html' }));
47
+ }).toThrow('"index" option no longer accepts a string in v3.0.0');
48
+ });
50
49
 
51
- const res = await supertest(server).get('/');
52
- expect(res.status).toBe(200);
53
- expect(res.text).toContain('Index HTML');
50
+ test('Throw message should include migration hint', () => {
51
+ expect(() => {
52
+ new Koa().use(koaClassicServer(tempDir, { index: 'default.htm' }));
53
+ }).toThrow('index: ["default.htm"]');
54
54
  });
55
55
 
56
- test('Empty string should show directory listing', async () => {
56
+ test('Empty string should show directory listing (no throw)', async () => {
57
57
  fs.writeFileSync(path.join(tempDir, 'test.txt'), 'test');
58
58
 
59
59
  app = new Koa();
@@ -281,7 +281,8 @@ describe('Enhanced Index Option Tests', () => {
281
281
 
282
282
  const res = await supertest(server).get('/');
283
283
  expect(res.status).toBe(200);
284
- expect(res.text).toContain('EJS content');
284
+ const body = res.text !== undefined ? res.text : res.body.toString('utf8');
285
+ expect(body).toContain('EJS content');
285
286
  });
286
287
  });
287
288
 
@@ -362,7 +363,8 @@ describe('Enhanced Index Option Tests', () => {
362
363
 
363
364
  const res = await supertest(server).get('/');
364
365
  expect(res.status).toBe(200);
365
- expect(res.text).toContain('pug content');
366
+ const body = res.text !== undefined ? res.text : res.body.toString('utf8');
367
+ expect(body).toContain('pug content');
366
368
  });
367
369
 
368
370
  test('Case-insensitive filesystem (Windows-like): matches any case', async () => {
@@ -115,7 +115,7 @@ describe(` koaClassicServer options1: ${JSON.stringify(options1)}`, () => {
115
115
  const options2 = {
116
116
  method: ['GET'],
117
117
  showDirContents: false,
118
- index: 'index.html',
118
+ index: ['index.html'],
119
119
  };
120
120
 
121
121
  describe(` koaClassicServer options2: ${JSON.stringify(options2)}`, () => {
@@ -213,7 +213,7 @@ function getFilesRecursivelySync(dir) {
213
213
  results = results.concat(getFilesRecursivelySync(fullPath));
214
214
  } else if (entry.isFile()) {
215
215
  // Se l'entry è un file, lo aggiungiamo all'array dei risultati
216
- const mimeType = mime.lookup(entry.name) || 'false';//false --> mimetype non riconosciuto , cosi lo trasmette il server , da approfondire
216
+ const mimeType = mime.lookup(entry.name) || 'application/octet-stream'; // fallback for unknown MIME types
217
217
  entry.type = 'file';
218
218
  entry.mimeType = mimeType;
219
219
  results.push(entry);
@@ -263,7 +263,10 @@ function testAllPathByFileList(filesAndDirArray, getServer, options) {
263
263
  expect(res.status).toBe(200);
264
264
  const content = fs.readFileSync(entry.fullPath, 'utf8');
265
265
  expect(res.type).toBe(entry.mimeType);
266
- expect(res.text).toBe(content);
266
+ // For binary content-types (e.g. application/octet-stream), supertest populates
267
+ // res.body (Buffer) instead of res.text — normalise to string for comparison
268
+ const responseBody = res.text !== undefined ? res.text : res.body.toString('utf8');
269
+ expect(responseBody).toBe(content);
267
270
  } else {//è una directory
268
271
  if( options.showDirContents === false ){
269
272
  // FIX: Quando directory listing è disabilitato, restituisce 404
@@ -328,7 +331,8 @@ function testAllPathByFileList(filesAndDirArray, getServer, options) {
328
331
  if (entry.type === 'file') {
329
332
  const content = fs.readFileSync(entry.fullPath, 'utf8');
330
333
  expect(res.type).toBe(entry.mimeType);
331
- expect(res.text).toBe(content);
334
+ const responseBody = res.text !== undefined ? res.text : res.body.toString('utf8');
335
+ expect(responseBody).toBe(content);
332
336
  } else {
333
337
  expect(res.type).toBe('text/html');
334
338
  expect(res.text).toContain('<!DOCTYPE html>');
@@ -0,0 +1 @@
1
+ 0123456789abcdefghij