koa-classic-server 2.6.1 → 3.0.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 (57) hide show
  1. package/CLAUDE.md +101 -0
  2. package/README.md +564 -591
  3. package/__tests__/benchmark-results-v3.0.0.txt +372 -0
  4. package/__tests__/benchmark.js +1 -1
  5. package/__tests__/caching-headers.test.js +30 -30
  6. package/__tests__/compression-fixtures/data.json +1 -0
  7. package/__tests__/compression-fixtures/large.txt +1 -0
  8. package/__tests__/compression-fixtures/small.txt +1 -0
  9. package/__tests__/compression.test.js +284 -0
  10. package/__tests__/customTest/serversToLoad.util.js +5 -5
  11. package/__tests__/demo-regex-index.js +4 -4
  12. package/__tests__/deprecation-warnings.test.js +71 -183
  13. package/__tests__/directory-sorting-links.test.js +1 -1
  14. package/__tests__/dt-unknown.test.js +39 -28
  15. package/__tests__/ejs.test.js +1 -1
  16. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  17. package/__tests__/hidden-fixtures/.env +2 -0
  18. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  19. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  20. package/__tests__/hidden-fixtures/data.key +1 -0
  21. package/__tests__/hidden-fixtures/file.secret +1 -0
  22. package/__tests__/hidden-fixtures/index.html +1 -0
  23. package/__tests__/hidden-fixtures/normal.txt +1 -0
  24. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  25. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  26. package/__tests__/hidden-option.test.js +407 -0
  27. package/__tests__/hideExtension.test.js +70 -13
  28. package/__tests__/index-option.test.js +18 -16
  29. package/__tests__/index.test.js +14 -10
  30. package/__tests__/listing.test.js +437 -0
  31. package/__tests__/logger.test.js +232 -0
  32. package/__tests__/range-fixtures/sample.txt +1 -0
  33. package/__tests__/range.test.js +223 -0
  34. package/__tests__/security-headers.test.js +165 -0
  35. package/__tests__/security.test.js +148 -162
  36. package/__tests__/server-cache-fixtures/large.txt +1 -0
  37. package/__tests__/server-cache-fixtures/small.txt +1 -0
  38. package/__tests__/server-cache.test.js +594 -0
  39. package/__tests__/symlink.test.js +18 -15
  40. package/__tests__/template-timeout.test.js +321 -0
  41. package/docs/ACTION_PLAN.md +293 -0
  42. package/docs/CHANGELOG.md +289 -0
  43. package/docs/CODE_REVIEW.md +2 -0
  44. package/docs/DOCUMENTATION.md +259 -32
  45. package/docs/EXAMPLES_INDEX_OPTION.md +3 -3
  46. package/docs/FLOW_DIAGRAM.md +15 -13
  47. package/docs/INDEX_OPTION_PRIORITY.md +2 -2
  48. package/docs/OPTIMIZATION_HTTP_CACHING.md +2 -0
  49. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  50. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  51. package/docs/security_improvement_for_V3.md +421 -0
  52. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +5 -5
  53. package/docs/template-engine/esempi-incrementali.js +1 -1
  54. package/eslint.config.mjs +17 -0
  55. package/index.cjs +1507 -429
  56. package/index.mjs +1 -5
  57. package/package.json +9 -1
@@ -0,0 +1,284 @@
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, 'compression-fixtures');
7
+
8
+ // Fixtures:
9
+ // large.txt — 2000 bytes of 'A' (text/plain, exceeds 1KB threshold)
10
+ // small.txt — 4 bytes of 'tiny' (text/plain, below 1KB threshold)
11
+ // data.json — 16 bytes '{"key":"value"}\n' (application/json, below threshold)
12
+
13
+ function createApp(opts = {}) {
14
+ const app = new Koa();
15
+ app.use(koaClassicServer(root, { dirListing: { enabled: false }, ...opts }));
16
+ return app.listen();
17
+ }
18
+
19
+ // ─── Default behaviour (compression enabled, serverCache enabled) ─────────────
20
+
21
+ describe('Compression — default: br preferred', () => {
22
+ let server;
23
+ beforeAll(() => { server = createApp(); });
24
+ afterAll(() => server.close());
25
+
26
+ test('large.txt with Accept-Encoding: br → brotli compressed', async () => {
27
+ const res = await supertest(server)
28
+ .get('/large.txt')
29
+ .set('Accept-Encoding', 'br');
30
+ expect(res.status).toBe(200);
31
+ expect(res.headers['content-encoding']).toBe('br');
32
+ expect(res.headers['vary']).toBe('Accept-Encoding');
33
+ });
34
+
35
+ test('large.txt with Accept-Encoding: gzip → gzip compressed, body decompressed by supertest', async () => {
36
+ const res = await supertest(server)
37
+ .get('/large.txt')
38
+ .set('Accept-Encoding', 'gzip');
39
+ expect(res.status).toBe(200);
40
+ expect(res.headers['content-encoding']).toBe('gzip');
41
+ expect(res.headers['vary']).toBe('Accept-Encoding');
42
+ // supertest auto-decompresses gzip — res.text is the original content
43
+ expect(res.text).toBe('A'.repeat(2000));
44
+ });
45
+
46
+ test('large.txt with Accept-Encoding: br,gzip → br preferred (higher priority)', async () => {
47
+ const res = await supertest(server)
48
+ .get('/large.txt')
49
+ .set('Accept-Encoding', 'br, gzip');
50
+ expect(res.headers['content-encoding']).toBe('br');
51
+ });
52
+
53
+ // Use 'identity' to prevent supertest's default Accept-Encoding from triggering compression
54
+ test('large.txt with Accept-Encoding: identity → uncompressed', async () => {
55
+ const res = await supertest(server)
56
+ .get('/large.txt')
57
+ .set('Accept-Encoding', 'identity');
58
+ expect(res.status).toBe(200);
59
+ expect(res.headers['content-encoding']).toBeUndefined();
60
+ expect(res.headers['vary']).toBeUndefined();
61
+ expect(res.text).toBe('A'.repeat(2000));
62
+ });
63
+
64
+ test('Content-Length present on compressed response (serverCache)', async () => {
65
+ const res = await supertest(server)
66
+ .get('/large.txt')
67
+ .set('Accept-Encoding', 'gzip');
68
+ expect(res.headers['content-length']).toBeDefined();
69
+ expect(Number(res.headers['content-length'])).toBeGreaterThan(0);
70
+ });
71
+
72
+ test('Compressed Content-Length is smaller than original file size', async () => {
73
+ const res = await supertest(server)
74
+ .get('/large.txt')
75
+ .set('Accept-Encoding', 'gzip');
76
+ expect(Number(res.headers['content-length'])).toBeLessThan(2000);
77
+ });
78
+ });
79
+
80
+ // ─── Threshold: files below threshold are served uncompressed ────────────────
81
+
82
+ describe('Compression — threshold (default 1024 bytes)', () => {
83
+ let server;
84
+ beforeAll(() => { server = createApp(); });
85
+ afterAll(() => server.close());
86
+
87
+ test('small.txt (4 bytes) below threshold → no compression', async () => {
88
+ const res = await supertest(server)
89
+ .get('/small.txt')
90
+ .set('Accept-Encoding', 'br, gzip');
91
+ expect(res.status).toBe(200);
92
+ expect(res.headers['content-encoding']).toBeUndefined();
93
+ expect(res.text).toBe('tiny');
94
+ });
95
+
96
+ test('large.txt (2000 bytes) above threshold → compressed', async () => {
97
+ const res = await supertest(server)
98
+ .get('/large.txt')
99
+ .set('Accept-Encoding', 'gzip');
100
+ expect(res.headers['content-encoding']).toBe('gzip');
101
+ });
102
+
103
+ test('minFileSize: false → compress regardless of size', async () => {
104
+ const s = createApp({ compression: { minFileSize: false } });
105
+ const res = await supertest(s)
106
+ .get('/small.txt')
107
+ .set('Accept-Encoding', 'gzip');
108
+ s.close();
109
+ expect(res.headers['content-encoding']).toBe('gzip');
110
+ // supertest auto-decompresses gzip
111
+ expect(res.text).toBe('tiny');
112
+ });
113
+ });
114
+
115
+ // ─── compression: false shorthand ────────────────────────────────────────────
116
+
117
+ describe('Compression — disabled', () => {
118
+ let server;
119
+ beforeAll(() => { server = createApp({ compression: false }); });
120
+ afterAll(() => server.close());
121
+
122
+ test('compression: false → no compression on any file', async () => {
123
+ const res = await supertest(server)
124
+ .get('/large.txt')
125
+ .set('Accept-Encoding', 'br, gzip');
126
+ expect(res.status).toBe(200);
127
+ expect(res.headers['content-encoding']).toBeUndefined();
128
+ expect(res.text).toBe('A'.repeat(2000));
129
+ });
130
+ });
131
+
132
+ // ─── encodings configuration ──────────────────────────────────────────────────
133
+
134
+ describe('Compression — encodings configuration', () => {
135
+ test('encodings: [gzip] → no brotli even if client prefers br', async () => {
136
+ const s = createApp({ compression: { encodings: ['gzip'] } });
137
+ const res = await supertest(s)
138
+ .get('/large.txt')
139
+ .set('Accept-Encoding', 'br, gzip');
140
+ s.close();
141
+ expect(res.headers['content-encoding']).toBe('gzip');
142
+ });
143
+
144
+ test('encodings: [] → no compression', async () => {
145
+ const s = createApp({ compression: { encodings: [] } });
146
+ const res = await supertest(s)
147
+ .get('/large.txt')
148
+ .set('Accept-Encoding', 'br, gzip');
149
+ s.close();
150
+ expect(res.headers['content-encoding']).toBeUndefined();
151
+ });
152
+ });
153
+
154
+ // ─── mimeTypes configuration ──────────────────────────────────────────────────
155
+
156
+ describe('Compression — mimeTypes configuration', () => {
157
+ test('custom mimeTypes replaces default list', async () => {
158
+ // Only compress application/json; text/plain should not be compressed
159
+ const s = createApp({ compression: { mimeTypes: ['application/json'], threshold: false } });
160
+
161
+ const resTxt = await supertest(s)
162
+ .get('/large.txt')
163
+ .set('Accept-Encoding', 'gzip');
164
+ expect(resTxt.headers['content-encoding']).toBeUndefined();
165
+
166
+ s.close();
167
+ });
168
+ });
169
+
170
+ // ─── ETag encoding-specific ───────────────────────────────────────────────────
171
+
172
+ describe('Compression — encoding-specific ETag', () => {
173
+ let server;
174
+ beforeAll(() => { server = createApp({ browserCacheEnabled: true }); });
175
+ afterAll(() => server.close());
176
+
177
+ test('ETag for br response has -br suffix', async () => {
178
+ const res = await supertest(server)
179
+ .get('/large.txt')
180
+ .set('Accept-Encoding', 'br');
181
+ expect(res.headers['etag']).toMatch(/-br"$/);
182
+ });
183
+
184
+ test('ETag for gzip response has -gz suffix', async () => {
185
+ const res = await supertest(server)
186
+ .get('/large.txt')
187
+ .set('Accept-Encoding', 'gzip');
188
+ expect(res.headers['etag']).toMatch(/-gz"$/);
189
+ });
190
+
191
+ test('ETag for uncompressed response has no suffix', async () => {
192
+ // Use 'identity' to prevent supertest's default Accept-Encoding from triggering compression
193
+ const res = await supertest(server)
194
+ .get('/large.txt')
195
+ .set('Accept-Encoding', 'identity');
196
+ expect(res.headers['etag']).not.toMatch(/-(br|gz)"$/);
197
+ });
198
+
199
+ test('304 returned when If-None-Match matches encoding-specific ETag', async () => {
200
+ const first = await supertest(server)
201
+ .get('/large.txt')
202
+ .set('Accept-Encoding', 'gzip');
203
+ const etag = first.headers['etag'];
204
+
205
+ const second = await supertest(server)
206
+ .get('/large.txt')
207
+ .set('Accept-Encoding', 'gzip')
208
+ .set('If-None-Match', etag);
209
+ expect(second.status).toBe(304);
210
+ });
211
+
212
+ test('304 NOT returned when ETag suffix differs (br ETag sent with gzip request)', async () => {
213
+ const brRes = await supertest(server)
214
+ .get('/large.txt')
215
+ .set('Accept-Encoding', 'br');
216
+ const brEtag = brRes.headers['etag'];
217
+
218
+ // Send the br ETag but request gzip → ETag mismatch → 200
219
+ const gzipRes = await supertest(server)
220
+ .get('/large.txt')
221
+ .set('Accept-Encoding', 'gzip')
222
+ .set('If-None-Match', brEtag);
223
+ expect(gzipRes.status).toBe(200);
224
+ });
225
+ });
226
+
227
+ // ─── serverCache.compressedFile disabled (streaming mode) ────────────────────
228
+
229
+ describe('Compression — serverCache.compressedFile disabled (streaming)', () => {
230
+ let server;
231
+ beforeAll(() => {
232
+ server = createApp({ serverCache: { compressedFile: { enabled: false } } });
233
+ });
234
+ afterAll(() => server.close());
235
+
236
+ test('streaming: Content-Encoding set but no Content-Length', async () => {
237
+ const res = await supertest(server)
238
+ .get('/large.txt')
239
+ .set('Accept-Encoding', 'gzip');
240
+ expect(res.headers['content-encoding']).toBe('gzip');
241
+ // Streaming compressed responses use Transfer-Encoding: chunked → no Content-Length
242
+ expect(res.headers['content-length']).toBeUndefined();
243
+ });
244
+
245
+ test('streaming: response body is correctly decompressed', async () => {
246
+ const res = await supertest(server)
247
+ .get('/large.txt')
248
+ .set('Accept-Encoding', 'gzip');
249
+ // supertest auto-decompresses gzip — res.text is the original content
250
+ expect(res.text).toBe('A'.repeat(2000));
251
+ });
252
+ });
253
+
254
+ // ─── Compression does not apply to Range requests ────────────────────────────
255
+
256
+ describe('Compression — no compression on Range requests (HTTP 206)', () => {
257
+ let server;
258
+ beforeAll(() => { server = createApp(); });
259
+ afterAll(() => server.close());
260
+
261
+ test('Range request is served uncompressed even with Accept-Encoding', async () => {
262
+ const res = await supertest(server)
263
+ .get('/large.txt')
264
+ .set('Range', 'bytes=0-9')
265
+ .set('Accept-Encoding', 'br, gzip');
266
+ expect(res.status).toBe(206);
267
+ expect(res.headers['content-encoding']).toBeUndefined();
268
+ expect(res.text).toBe('A'.repeat(10));
269
+ });
270
+ });
271
+
272
+ // ─── V3 migration guard ───────────────────────────────────────────────────────
273
+
274
+ describe('Compression — V3 migration guard (old name throws helpful error)', () => {
275
+ test('compression.minSize throws with hint pointing to minFileSize', () => {
276
+ expect(() => koaClassicServer(root, { compression: { minSize: 2048 } }))
277
+ .toThrow(/options\.compression\.minSize was renamed[\s\S]*compression: \{ minFileSize: 2048 \}/);
278
+ });
279
+
280
+ test('compression.minSize === false also throws (legacy "no minimum" sentinel)', () => {
281
+ expect(() => koaClassicServer(root, { compression: { minSize: false } }))
282
+ .toThrow(/options\.compression\.minSize was renamed/);
283
+ });
284
+ });
@@ -20,7 +20,7 @@ const configurations = [
20
20
  options: {
21
21
  //urlPrefix: '/',
22
22
  method: ['GET'],
23
- showDirContents: true,
23
+ dirListing: { enabled: true },
24
24
  },
25
25
  },
26
26
  {
@@ -31,8 +31,8 @@ const configurations = [
31
31
  options: {
32
32
  urlPrefix: '/public',
33
33
  method: ['GET'],
34
- showDirContents: true,
35
- index: 'index.html',
34
+ dirListing: { enabled: true },
35
+ index: ['index.html'],
36
36
  },
37
37
  },
38
38
  {
@@ -43,7 +43,7 @@ const configurations = [
43
43
  options: {
44
44
  urlPrefix: '/public',
45
45
  method: ['GET'],
46
- showDirContents: true,
46
+ dirListing: { enabled: true },
47
47
  },
48
48
  },
49
49
  {
@@ -54,7 +54,7 @@ const configurations = [
54
54
  options: {
55
55
  urlPrefix: '',
56
56
  method: ['GET'],
57
- showDirContents: true,
57
+ dirListing: { enabled: true },
58
58
  urlsReserved : Array('/percorso_riservato', '/percorso riservato con spazi')
59
59
  },
60
60
  },
@@ -48,7 +48,7 @@ console.log(' Aspettativa: Matcha INDEX.HTML o Index.Html');
48
48
  const app1 = new Koa();
49
49
  app1.use(koaClassicServer(testDir, {
50
50
  index: [/index\.html/i],
51
- showDirContents: true
51
+ dirListing: { enabled: true }
52
52
  }));
53
53
  const server1 = app1.listen(3001);
54
54
  console.log(' ✓ Server avviato su http://localhost:3001\n');
@@ -63,7 +63,7 @@ console.log(' Aspettativa: Matcha INDEX.HTML, Index.Html, index.htm');
63
63
  const app2 = new Koa();
64
64
  app2.use(koaClassicServer(testDir, {
65
65
  index: [/index\.(html|htm)/i],
66
- showDirContents: true
66
+ dirListing: { enabled: true }
67
67
  }));
68
68
  const server2 = app2.listen(3002);
69
69
  console.log(' ✓ Server avviato su http://localhost:3002\n');
@@ -81,7 +81,7 @@ app3.use(koaClassicServer(testDir, {
81
81
  /index\.(html|htm)/i,
82
82
  /default\.html/i
83
83
  ],
84
- showDirContents: true
84
+ dirListing: { enabled: true }
85
85
  }));
86
86
  const server3 = app3.listen(3003);
87
87
  console.log(' ✓ Server avviato su http://localhost:3003\n');
@@ -100,7 +100,7 @@ app4.use(koaClassicServer(testDir, {
100
100
  /INDEX\.HTML/i, // Case-insensitive fallback
101
101
  /default\.html/i // Default fallback
102
102
  ],
103
- showDirContents: true
103
+ dirListing: { enabled: true }
104
104
  }));
105
105
  const server4 = app4.listen(3004);
106
106
  console.log(' ✓ Server avviato su http://localhost:3004\n');
@@ -1,217 +1,105 @@
1
1
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
2
  //
3
- // TEST FOR DEPRECATED OPTION NAMES (enableCaching, cacheMaxAge)
4
- // This test verifies backward compatibility and deprecation warnings
3
+ // TEST FOR REMOVED OPTION NAMES (enableCaching, cacheMaxAge)
4
+ // These options were removed in v3.0.0 passing them must throw an Error.
5
5
  //
6
6
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7
7
 
8
- const supertest = require('supertest');
9
8
  const koaClassicServer = require('../index.cjs');
10
9
  const Koa = require('koa');
11
10
  const path = require('path');
12
11
 
13
12
  const rootDir = path.join(__dirname, 'publicWwwTest');
14
13
 
15
- describe('Deprecated option names (backward compatibility)', () => {
14
+ describe('Removed option names (v3.0.0 breaking changes)', () => {
16
15
 
17
- describe('Using deprecated enableCaching option', () => {
18
- let app;
19
- let server;
20
- let consoleWarnSpy;
21
-
22
- beforeAll(() => {
23
- // Spy on console.warn to capture deprecation warnings
24
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
25
-
26
- app = new Koa();
27
-
28
- // Use deprecated option name
29
- app.use(koaClassicServer(rootDir, {
30
- enableCaching: true // DEPRECATED: should use browserCacheEnabled
31
- }));
32
-
33
- server = app.listen();
34
- });
35
-
36
- afterAll(() => {
37
- server.close();
38
- consoleWarnSpy.mockRestore();
39
- });
40
-
41
- test('should display deprecation warning for enableCaching', () => {
42
- expect(consoleWarnSpy).toHaveBeenCalled();
43
- const warningMessage = consoleWarnSpy.mock.calls[0][1];
44
- expect(warningMessage).toContain('DEPRECATION WARNING');
45
- expect(warningMessage).toContain('enableCaching');
46
- expect(warningMessage).toContain('browserCacheEnabled');
47
- });
48
-
49
- test('should still work with deprecated option', async () => {
50
- const response = await supertest(server).get('/test-page.html');
51
-
52
- // Should return the file
53
- expect(response.status).toBe(200);
54
-
55
- // Should have caching headers (because enableCaching: true was set)
56
- expect(response.headers['etag']).toBeDefined();
57
- expect(response.headers['last-modified']).toBeDefined();
58
- expect(response.headers['cache-control']).toContain('public');
59
- });
16
+ test('should throw when "enableCaching" is passed', () => {
17
+ expect(() => {
18
+ const app = new Koa();
19
+ app.use(koaClassicServer(rootDir, { enableCaching: true }));
20
+ }).toThrow('"enableCaching" option was removed in v3.0.0');
60
21
  });
61
22
 
62
- describe('Using deprecated cacheMaxAge option', () => {
63
- let app;
64
- let server;
65
- let consoleWarnSpy;
66
-
67
- beforeAll(() => {
68
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
69
-
70
- app = new Koa();
71
-
72
- // Use deprecated option name
73
- app.use(koaClassicServer(rootDir, {
74
- enableCaching: true, // Also deprecated
75
- cacheMaxAge: 7200 // DEPRECATED: should use browserCacheMaxAge
76
- }));
77
-
78
- server = app.listen();
79
- });
80
-
81
- afterAll(() => {
82
- server.close();
83
- consoleWarnSpy.mockRestore();
84
- });
85
-
86
- test('should display deprecation warning for cacheMaxAge', () => {
87
- const warningCalls = consoleWarnSpy.mock.calls;
88
- const cacheMaxAgeWarning = warningCalls.find(call =>
89
- call[1] && call[1].includes('cacheMaxAge')
90
- );
91
-
92
- expect(cacheMaxAgeWarning).toBeDefined();
93
- expect(cacheMaxAgeWarning[1]).toContain('DEPRECATION WARNING');
94
- expect(cacheMaxAgeWarning[1]).toContain('browserCacheMaxAge');
95
- });
96
-
97
- test('should use the deprecated cacheMaxAge value', async () => {
98
- const response = await supertest(server).get('/test-page.html');
99
-
100
- expect(response.status).toBe(200);
101
- expect(response.headers['cache-control']).toContain('max-age=7200');
102
- });
23
+ test('should throw when "cacheMaxAge" is passed', () => {
24
+ expect(() => {
25
+ const app = new Koa();
26
+ app.use(koaClassicServer(rootDir, { cacheMaxAge: 3600 }));
27
+ }).toThrow('"cacheMaxAge" option was removed in v3.0.0');
103
28
  });
104
29
 
105
- describe('Using new option names (no warnings)', () => {
106
- let app;
107
- let server;
108
- let consoleWarnSpy;
109
-
110
- beforeAll(() => {
111
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
112
-
113
- app = new Koa();
114
-
115
- // Use NEW option names
116
- app.use(koaClassicServer(rootDir, {
117
- browserCacheEnabled: true,
118
- browserCacheMaxAge: 3600
119
- }));
120
-
121
- server = app.listen();
122
- });
123
-
124
- afterAll(() => {
125
- server.close();
126
- consoleWarnSpy.mockRestore();
127
- });
128
-
129
- test('should NOT display deprecation warnings', () => {
130
- // Filter out warnings from other tests (like index option deprecation)
131
- const cachingWarnings = consoleWarnSpy.mock.calls.filter(call =>
132
- call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
133
- );
134
-
135
- expect(cachingWarnings.length).toBe(0);
136
- });
137
-
138
- test('should work correctly with new option names', async () => {
139
- const response = await supertest(server).get('/test-page.html');
140
-
141
- expect(response.status).toBe(200);
142
- expect(response.headers['etag']).toBeDefined();
143
- expect(response.headers['cache-control']).toContain('max-age=3600');
144
- });
30
+ test('should throw even when new option is also provided alongside "cacheMaxAge"', () => {
31
+ expect(() => {
32
+ const app = new Koa();
33
+ app.use(koaClassicServer(rootDir, { cacheMaxAge: 3600, browserCacheMaxAge: 3600 }));
34
+ }).toThrow('"cacheMaxAge" option was removed in v3.0.0');
145
35
  });
146
36
 
147
- describe('Using both old and new names (new takes precedence)', () => {
148
- let app;
149
- let server;
150
- let consoleWarnSpy;
151
-
152
- beforeAll(() => {
153
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
154
-
155
- app = new Koa();
37
+ test('should throw even when new option is also provided alongside "enableCaching"', () => {
38
+ expect(() => {
39
+ const app = new Koa();
40
+ app.use(koaClassicServer(rootDir, { enableCaching: false, browserCacheEnabled: false }));
41
+ }).toThrow('"enableCaching" option was removed in v3.0.0');
42
+ });
43
+ });
156
44
 
157
- // Use BOTH old and new names - new should take precedence
158
- app.use(koaClassicServer(rootDir, {
159
- enableCaching: true, // OLD (deprecated)
160
- browserCacheEnabled: false, // NEW (should take precedence)
161
- cacheMaxAge: 7200, // OLD (deprecated)
162
- browserCacheMaxAge: 9999 // NEW (should take precedence)
163
- }));
45
+ describe('New option names (browserCacheEnabled / browserCacheMaxAge)', () => {
46
+ const supertest = require('supertest');
47
+ let app;
48
+ let server;
49
+ let consoleWarnSpy;
164
50
 
165
- server = app.listen();
166
- });
51
+ beforeAll(() => {
52
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
167
53
 
168
- afterAll(() => {
169
- server.close();
170
- consoleWarnSpy.mockRestore();
171
- });
54
+ app = new Koa();
55
+ app.use(koaClassicServer(rootDir, {
56
+ browserCacheEnabled: true,
57
+ browserCacheMaxAge: 3600
58
+ }));
172
59
 
173
- test('should NOT display warnings when new names are also provided', () => {
174
- // When both old and new names are provided, no warning should be shown
175
- const cachingWarnings = consoleWarnSpy.mock.calls.filter(call =>
176
- call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
177
- );
60
+ server = app.listen();
61
+ });
178
62
 
179
- expect(cachingWarnings.length).toBe(0);
180
- });
63
+ afterAll(() => {
64
+ server.close();
65
+ consoleWarnSpy.mockRestore();
66
+ });
181
67
 
182
- test('new option values should take precedence over old ones', async () => {
183
- const response = await supertest(server).get('/test-page.html');
68
+ test('should not warn about removed options', () => {
69
+ const removedWarnings = consoleWarnSpy.mock.calls.filter(call =>
70
+ call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
71
+ );
72
+ expect(removedWarnings.length).toBe(0);
73
+ });
184
74
 
185
- expect(response.status).toBe(200);
75
+ test('should work correctly with new option names', async () => {
76
+ const response = await supertest(server).get('/test-page.html');
186
77
 
187
- // browserCacheEnabled: false should take precedence over enableCaching: true
188
- // So there should be NO caching headers
189
- expect(response.headers['etag']).toBeUndefined();
190
- expect(response.headers['cache-control']).toContain('no-cache');
191
- });
78
+ expect(response.status).toBe(200);
79
+ expect(response.headers['etag']).toBeDefined();
80
+ expect(response.headers['cache-control']).toContain('max-age=3600');
192
81
  });
82
+ });
193
83
 
194
- describe('Default behavior (no caching options specified)', () => {
195
- let app;
196
- let server;
197
-
198
- beforeAll(() => {
199
- app = new Koa();
200
- app.use(koaClassicServer(rootDir));
201
- server = app.listen();
202
- });
84
+ describe('Default behavior (no caching options specified)', () => {
85
+ const supertest = require('supertest');
86
+ let app;
87
+ let server;
203
88
 
204
- afterAll(() => {
205
- server.close();
206
- });
89
+ beforeAll(() => {
90
+ app = new Koa();
91
+ app.use(koaClassicServer(rootDir));
92
+ server = app.listen();
93
+ });
207
94
 
208
- test('should default to browserCacheEnabled: false', async () => {
209
- const response = await supertest(server).get('/test-page.html');
95
+ afterAll(() => {
96
+ server.close();
97
+ });
210
98
 
211
- expect(response.status).toBe(200);
99
+ test('should default to browserCacheEnabled: false', async () => {
100
+ const response = await supertest(server).get('/test-page.html');
212
101
 
213
- // Default is caching disabled
214
- expect(response.headers['cache-control']).toContain('no-cache');
215
- });
102
+ expect(response.status).toBe(200);
103
+ expect(response.headers['cache-control']).toContain('no-cache');
216
104
  });
217
105
  });
@@ -13,7 +13,7 @@ describe('Directory Sorting Links Bug Tests', () => {
13
13
 
14
14
  app.use(koaClassicServer(publicDir, {
15
15
  method: ['GET'],
16
- showDirContents: true
16
+ dirListing: { enabled: true }
17
17
  }));
18
18
 
19
19
  server = app.listen();