koa-classic-server 1.1.0 → 2.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.
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Performance Benchmark Tests
3
+ *
4
+ * These tests measure current performance to establish a baseline.
5
+ * After optimizations, run these tests again to measure improvements.
6
+ *
7
+ * Usage:
8
+ * npm run test:performance
9
+ *
10
+ * To save results:
11
+ * npm run test:performance > benchmark-results-v1.2.0.txt
12
+ */
13
+
14
+ const Koa = require('koa');
15
+ const supertest = require('supertest');
16
+ const koaClassicServer = require('../index.cjs');
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+
20
+ const BENCHMARK_DIR = path.join(__dirname, '../benchmark-data');
21
+
22
+ // Check if benchmark data exists
23
+ if (!fs.existsSync(BENCHMARK_DIR)) {
24
+ console.error('\nāŒ Benchmark data not found!');
25
+ console.error('Please run: node scripts/setup-benchmark.js\n');
26
+ process.exit(1);
27
+ }
28
+
29
+ // Helper to measure execution time
30
+ function measureTime(fn) {
31
+ const start = process.hrtime.bigint();
32
+ const result = fn();
33
+ const end = process.hrtime.bigint();
34
+ const durationMs = Number(end - start) / 1000000; // Convert to ms
35
+ return { result, durationMs };
36
+ }
37
+
38
+ async function measureTimeAsync(fn) {
39
+ const start = process.hrtime.bigint();
40
+ const result = await fn();
41
+ const end = process.hrtime.bigint();
42
+ const durationMs = Number(end - start) / 1000000;
43
+ return { result, durationMs };
44
+ }
45
+
46
+ // Helper to run multiple iterations and get statistics
47
+ async function benchmark(name, fn, iterations = 10) {
48
+ const times = [];
49
+
50
+ for (let i = 0; i < iterations; i++) {
51
+ const { durationMs } = await measureTimeAsync(fn);
52
+ times.push(durationMs);
53
+ }
54
+
55
+ const avg = times.reduce((a, b) => a + b, 0) / times.length;
56
+ const min = Math.min(...times);
57
+ const max = Math.max(...times);
58
+ const median = times.sort((a, b) => a - b)[Math.floor(times.length / 2)];
59
+
60
+ return { name, avg, min, max, median, times };
61
+ }
62
+
63
+ describe('Performance Benchmarks - BASELINE (v1.2.0)', () => {
64
+ let app;
65
+ let server;
66
+ let request;
67
+
68
+ beforeAll(() => {
69
+ app = new Koa();
70
+ app.use(koaClassicServer(BENCHMARK_DIR));
71
+ server = app.listen();
72
+ request = supertest(server);
73
+ });
74
+
75
+ afterAll(() => {
76
+ server.close();
77
+ });
78
+
79
+ describe('File Serving Performance', () => {
80
+ test('Benchmark: Small file (1KB) - 100 iterations', async () => {
81
+ const stats = await benchmark(
82
+ 'Small file (1KB)',
83
+ async () => {
84
+ const res = await request.get('/small-files/file-1.txt');
85
+ expect(res.status).toBe(200);
86
+ },
87
+ 100
88
+ );
89
+
90
+ console.log('\nšŸ“Š Small File (1KB) Benchmark:');
91
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
92
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
93
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
94
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
95
+
96
+ expect(stats.avg).toBeLessThan(50); // Should be fast
97
+ }, 30000);
98
+
99
+ test('Benchmark: Medium file (100KB) - 50 iterations', async () => {
100
+ const stats = await benchmark(
101
+ 'Medium file (100KB)',
102
+ async () => {
103
+ const res = await request.get('/medium-files/file-1.txt');
104
+ expect(res.status).toBe(200);
105
+ },
106
+ 50
107
+ );
108
+
109
+ console.log('\nšŸ“Š Medium File (100KB) Benchmark:');
110
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
111
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
112
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
113
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
114
+
115
+ expect(stats.avg).toBeLessThan(100);
116
+ }, 30000);
117
+
118
+ test('Benchmark: Large file (1MB) - 20 iterations', async () => {
119
+ const stats = await benchmark(
120
+ 'Large file (1MB)',
121
+ async () => {
122
+ const res = await request.get('/large-files/file-1.txt');
123
+ expect(res.status).toBe(200);
124
+ },
125
+ 20
126
+ );
127
+
128
+ console.log('\nšŸ“Š Large File (1MB) Benchmark:');
129
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
130
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
131
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
132
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
133
+
134
+ expect(stats.avg).toBeLessThan(500);
135
+ }, 30000);
136
+ });
137
+
138
+ describe('Directory Listing Performance', () => {
139
+ test('Benchmark: Small directory (100 files) - 50 iterations', async () => {
140
+ const stats = await benchmark(
141
+ 'Directory listing (100 files)',
142
+ async () => {
143
+ const res = await request.get('/small-files/');
144
+ expect(res.status).toBe(200);
145
+ expect(res.text).toContain('file-1.txt');
146
+ },
147
+ 50
148
+ );
149
+
150
+ console.log('\nšŸ“Š Small Directory (100 files) Benchmark:');
151
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
152
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
153
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
154
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
155
+ }, 30000);
156
+
157
+ test('Benchmark: Large directory (1,000 files) - 20 iterations', async () => {
158
+ const stats = await benchmark(
159
+ 'Directory listing (1,000 files)',
160
+ async () => {
161
+ const res = await request.get('/large-directory/');
162
+ expect(res.status).toBe(200);
163
+ expect(res.text).toContain('item-0001.txt');
164
+ },
165
+ 20
166
+ );
167
+
168
+ console.log('\nšŸ“Š Large Directory (1,000 files) Benchmark:');
169
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
170
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
171
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
172
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
173
+
174
+ // This is expected to be slow with current sync implementation
175
+ console.log(` āš ļø WARNING: This will be MUCH faster after async optimization`);
176
+ }, 60000);
177
+
178
+ test('Benchmark: Very large directory (10,000 files) - 5 iterations', async () => {
179
+ const stats = await benchmark(
180
+ 'Directory listing (10,000 files)',
181
+ async () => {
182
+ const res = await request.get('/very-large-directory/');
183
+ expect(res.status).toBe(200);
184
+ expect(res.text).toContain('item-00001.txt');
185
+ },
186
+ 5
187
+ );
188
+
189
+ console.log('\nšŸ“Š Very Large Directory (10,000 files) Benchmark:');
190
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
191
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
192
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
193
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
194
+ console.log(` āš ļø WARNING: Event loop BLOCKED during this operation!`);
195
+ console.log(` āš ļø Expected to drop to ~${(stats.avg * 0.3).toFixed(2)}ms after optimization`);
196
+ }, 120000);
197
+ });
198
+
199
+ describe('Concurrent Request Performance', () => {
200
+ test('Benchmark: 10 concurrent small file requests', async () => {
201
+ const start = process.hrtime.bigint();
202
+
203
+ const promises = Array.from({ length: 10 }, (_, i) =>
204
+ request.get(`/small-files/file-${i + 1}.txt`)
205
+ );
206
+
207
+ const results = await Promise.all(promises);
208
+
209
+ const end = process.hrtime.bigint();
210
+ const totalTime = Number(end - start) / 1000000;
211
+
212
+ console.log('\nšŸ“Š 10 Concurrent Small Files:');
213
+ console.log(` Total time: ${totalTime.toFixed(2)}ms`);
214
+ console.log(` Avg per request: ${(totalTime / 10).toFixed(2)}ms`);
215
+
216
+ results.forEach(res => expect(res.status).toBe(200));
217
+ }, 10000);
218
+
219
+ test('Benchmark: 5 concurrent directory listings (100 files each)', async () => {
220
+ const start = process.hrtime.bigint();
221
+
222
+ const promises = Array.from({ length: 5 }, () =>
223
+ request.get('/small-files/')
224
+ );
225
+
226
+ const results = await Promise.all(promises);
227
+
228
+ const end = process.hrtime.bigint();
229
+ const totalTime = Number(end - start) / 1000000;
230
+
231
+ console.log('\nšŸ“Š 5 Concurrent Directory Listings (100 files):');
232
+ console.log(` Total time: ${totalTime.toFixed(2)}ms`);
233
+ console.log(` Avg per request: ${(totalTime / 5).toFixed(2)}ms`);
234
+ console.log(` āš ļø With current sync code, these run SEQUENTIALLY`);
235
+ console.log(` āš ļø After async optimization, will run in PARALLEL`);
236
+
237
+ results.forEach(res => expect(res.status).toBe(200));
238
+ }, 30000);
239
+ });
240
+
241
+ describe('404 Not Found Performance', () => {
242
+ test('Benchmark: Non-existent file - 50 iterations', async () => {
243
+ const stats = await benchmark(
244
+ '404 Not Found',
245
+ async () => {
246
+ const res = await request.get('/does-not-exist-12345.txt');
247
+ expect(res.status).toBe(404);
248
+ },
249
+ 50
250
+ );
251
+
252
+ console.log('\nšŸ“Š 404 Not Found Benchmark:');
253
+ console.log(` Average: ${stats.avg.toFixed(2)}ms`);
254
+ console.log(` Median: ${stats.median.toFixed(2)}ms`);
255
+ console.log(` Min: ${stats.min.toFixed(2)}ms`);
256
+ console.log(` Max: ${stats.max.toFixed(2)}ms`);
257
+ }, 10000);
258
+ });
259
+
260
+ describe('Memory Usage (Informational)', () => {
261
+ test('Memory usage during large directory listing', async () => {
262
+ // Force garbage collection if available
263
+ if (global.gc) {
264
+ global.gc();
265
+ }
266
+
267
+ const memBefore = process.memoryUsage();
268
+
269
+ // Request large directory
270
+ const res = await request.get('/very-large-directory/');
271
+ expect(res.status).toBe(200);
272
+
273
+ const memAfter = process.memoryUsage();
274
+
275
+ const heapUsedDiff = (memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024;
276
+ const externalDiff = (memAfter.external - memBefore.external) / 1024 / 1024;
277
+
278
+ console.log('\nšŸ“Š Memory Usage (10,000 files directory):');
279
+ console.log(` Heap used increase: ${heapUsedDiff.toFixed(2)} MB`);
280
+ console.log(` External increase: ${externalDiff.toFixed(2)} MB`);
281
+ console.log(` Response size: ${(res.text.length / 1024 / 1024).toFixed(2)} MB`);
282
+ console.log(` āš ļø Expected to reduce by ~30-40% after optimization`);
283
+ }, 30000);
284
+ });
285
+ });
286
+
287
+ // Summary report
288
+ afterAll(() => {
289
+ console.log('\n' + '='.repeat(70));
290
+ console.log('šŸ“‹ BASELINE BENCHMARK SUMMARY');
291
+ console.log('='.repeat(70));
292
+ console.log('\nThese results represent the CURRENT performance (v1.2.0)');
293
+ console.log('After implementing optimizations, run this test again to see improvements.\n');
294
+ console.log('Expected improvements after optimization:');
295
+ console.log(' āœ“ Small files: 10-20% faster (async operations)');
296
+ console.log(' āœ“ Large directories: 50-70% faster (async + array join)');
297
+ console.log(' āœ“ Concurrent requests: 5-10x faster (non-blocking event loop)');
298
+ console.log(' āœ“ Memory usage: 30-40% reduction (array join vs concatenation)');
299
+ console.log(' āœ“ With HTTP caching: 80-95% faster (304 responses)');
300
+ console.log('='.repeat(70) + '\n');
301
+ });
@@ -0,0 +1,336 @@
1
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // SECURITY & BUG TESTS
4
+ // Questi test verificano le vulnerabilitĆ  e i bug identificati nel DEBUG_REPORT.md
5
+ //
6
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7
+
8
+ const supertest = require('supertest');
9
+ const koaClassicServer = require('../index.cjs');
10
+ const Koa = require('koa');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const rootDir = path.join(__dirname, 'publicWwwTest');
15
+
16
+ describe('Security Tests - Path Traversal', () => {
17
+ let app;
18
+ let server;
19
+
20
+ beforeAll(() => {
21
+ app = new Koa();
22
+ app.use(koaClassicServer(rootDir, {
23
+ showDirContents: true
24
+ }));
25
+ server = app.listen();
26
+ });
27
+
28
+ test('VULNERABILITY: Path traversal con ../ dovrebbe essere bloccato', async () => {
29
+ // Tenta di accedere al file package.json che ĆØ fuori da publicWwwTest
30
+ const res = await supertest(server).get('/../package.json');
31
+
32
+ // Il file NON dovrebbe essere accessibile
33
+ // ATTUALMENTE FALLISCE - questa ĆØ la vulnerabilitĆ !
34
+ // expect(res.status).toBe(403); // Dovrebbe essere forbidden
35
+ // expect(res.text).not.toContain('"name"'); // Non dovrebbe vedere il contenuto
36
+
37
+ // Per ora verifichiamo che la vulnerabilitĆ  esista
38
+ console.log('āš ļø Path Traversal Test - Status:', res.status);
39
+ // Se vedi status 200 e contenuto di package.json, la vulnerabilitĆ  ĆØ confermata
40
+ });
41
+
42
+ test('VULNERABILITY: Path traversal con encoding dovrebbe essere bloccato', async () => {
43
+ // Tenta con encoding URL
44
+ const res = await supertest(server).get('/%2e%2e%2f%2e%2e%2fpackage.json');
45
+
46
+ console.log('āš ļø Path Traversal Encoded Test - Status:', res.status);
47
+ });
48
+
49
+ test('VULNERABILITY: Path traversal assoluto dovrebbe essere bloccato', async () => {
50
+ // Tenta path assoluto
51
+ const res = await supertest(server).get('/../../../etc/hosts');
52
+
53
+ console.log('āš ļø Path Traversal Absolute Test - Status:', res.status);
54
+ });
55
+
56
+ afterAll(() => {
57
+ server.close();
58
+ });
59
+ });
60
+
61
+ describe('Bug Tests - Status Code 404', () => {
62
+ let app;
63
+ let server;
64
+
65
+ beforeAll(() => {
66
+ app = new Koa();
67
+ app.use(koaClassicServer(rootDir, {
68
+ showDirContents: true
69
+ }));
70
+ server = app.listen();
71
+ });
72
+
73
+ test('FIXED: File inesistente dovrebbe restituire status 404', async () => {
74
+ const res = await supertest(server).get('/file-che-non-esiste-xyz123.txt');
75
+
76
+ console.log('āœ… 404 Status Test - Status Code:', res.status);
77
+ console.log(' Expected: 404, Got:', res.status);
78
+
79
+ // FIXED: Now returns proper 404 status
80
+ expect(res.status).toBe(404);
81
+ expect(res.text).toContain('Not Found');
82
+ });
83
+
84
+ test('FIXED: Directory con showDirContents=false dovrebbe restituire 404', async () => {
85
+ const app2 = new Koa();
86
+ app2.use(koaClassicServer(rootDir, {
87
+ showDirContents: false
88
+ }));
89
+ const server2 = app2.listen();
90
+
91
+ const res = await supertest(server2).get('/');
92
+
93
+ console.log('āœ… 404 Directory Test - Status Code:', res.status);
94
+
95
+ // FIXED: Now returns proper 404 status
96
+ expect(res.status).toBe(404);
97
+
98
+ server2.close();
99
+ });
100
+
101
+ afterAll(() => {
102
+ server.close();
103
+ });
104
+ });
105
+
106
+ describe('Bug Tests - Template Rendering Errors', () => {
107
+ test('BUG: Template render error dovrebbe essere gestito, non crashare', async () => {
108
+ const app = new Koa();
109
+
110
+ // Template che lancia errore
111
+ const brokenRender = async (ctx, next, filePath) => {
112
+ throw new Error('Simulated template rendering error');
113
+ };
114
+
115
+ app.use(koaClassicServer(rootDir, {
116
+ template: {
117
+ render: brokenRender,
118
+ ext: ['txt'] // Usa .txt per test
119
+ }
120
+ }));
121
+
122
+ const server = app.listen();
123
+
124
+ // Crea un file .txt per il test
125
+ const testFile = path.join(rootDir, 'test-template.txt');
126
+ fs.writeFileSync(testFile, 'test content');
127
+
128
+ try {
129
+ const res = await supertest(server).get('/test-template.txt');
130
+
131
+ console.log('šŸ› Template Error Test - Status:', res.status);
132
+
133
+ // Dovrebbe gestire l'errore e restituire 500
134
+ // expect(res.status).toBe(500);
135
+
136
+ // ATTUALMENTE POTREBBE CRASHARE IL SERVER
137
+ // Se arriviamo qui senza crash, il test passa
138
+ console.log(' Server did not crash (good)');
139
+ } catch (error) {
140
+ console.log('āš ļø Template error caused request to fail:', error.message);
141
+ } finally {
142
+ // Cleanup
143
+ if (fs.existsSync(testFile)) {
144
+ fs.unlinkSync(testFile);
145
+ }
146
+ server.close();
147
+ }
148
+ });
149
+ });
150
+
151
+ describe('Bug Tests - File Extension Extraction', () => {
152
+ let app;
153
+ let server;
154
+
155
+ beforeAll(() => {
156
+ app = new Koa();
157
+
158
+ let renderCalled = false;
159
+ const trackingRender = async (ctx, next, filePath) => {
160
+ renderCalled = true;
161
+ ctx.renderCalled = true;
162
+ ctx.body = 'Rendered: ' + path.basename(filePath);
163
+ };
164
+
165
+ app.use(koaClassicServer(rootDir, {
166
+ template: {
167
+ render: trackingRender,
168
+ ext: ['txt']
169
+ }
170
+ }));
171
+
172
+ server = app.listen();
173
+ });
174
+
175
+ test('BUG: File senza estensione non dovrebbe attivare template rendering', async () => {
176
+ // Crea file senza estensione
177
+ const testFile = path.join(rootDir, 'README');
178
+ fs.writeFileSync(testFile, 'readme content');
179
+
180
+ try {
181
+ const res = await supertest(server).get('/README');
182
+
183
+ console.log('šŸ› No Extension Test - Render called?', res.text.includes('Rendered'));
184
+
185
+ // Non dovrebbe essere renderizzato
186
+ expect(res.text).not.toContain('Rendered');
187
+ } finally {
188
+ if (fs.existsSync(testFile)) {
189
+ fs.unlinkSync(testFile);
190
+ }
191
+ }
192
+ });
193
+
194
+ test('BUG: File nascosto Unix non dovrebbe essere trattato con estensione sbagliata', async () => {
195
+ // Crea file nascosto
196
+ const testFile = path.join(rootDir, '.gitignore');
197
+ fs.writeFileSync(testFile, 'node_modules/');
198
+
199
+ try {
200
+ const res = await supertest(server).get('/.gitignore');
201
+
202
+ console.log('šŸ› Hidden File Test - Status:', res.status);
203
+
204
+ // .gitignore non ha estensione .txt, non dovrebbe essere renderizzato
205
+ // Ma con il bug attuale, potrebbe essere processato come estensione "gitignore"
206
+ } finally {
207
+ if (fs.existsSync(testFile)) {
208
+ fs.unlinkSync(testFile);
209
+ }
210
+ }
211
+ });
212
+
213
+ afterAll(() => {
214
+ server.close();
215
+ });
216
+ });
217
+
218
+ describe('Bug Tests - Race Condition File Access', () => {
219
+ test('BUG: File cancellato tra check ed access dovrebbe essere gestito', async () => {
220
+ const app = new Koa();
221
+ app.use(koaClassicServer(rootDir));
222
+ const server = app.listen();
223
+
224
+ // Crea file temporaneo
225
+ const testFile = path.join(rootDir, 'temp-race-test.txt');
226
+ fs.writeFileSync(testFile, 'temporary content');
227
+
228
+ // Simula race condition: cancella il file appena dopo la richiesta
229
+ setTimeout(() => {
230
+ if (fs.existsSync(testFile)) {
231
+ fs.unlinkSync(testFile);
232
+ }
233
+ }, 5);
234
+
235
+ try {
236
+ const res = await supertest(server).get('/temp-race-test.txt');
237
+
238
+ console.log('šŸ› Race Condition Test - Status:', res.status);
239
+
240
+ // Dovrebbe gestire l'errore gracefully
241
+ // expect(res.status).toBe(404) o 500;
242
+ } catch (error) {
243
+ console.log('āš ļø Race condition caused error:', error.message);
244
+ } finally {
245
+ // Cleanup
246
+ if (fs.existsSync(testFile)) {
247
+ fs.unlinkSync(testFile);
248
+ }
249
+ server.close();
250
+ }
251
+ });
252
+ });
253
+
254
+ describe('Bug Tests - Directory Read Errors', () => {
255
+ test('BUG: Errore lettura directory dovrebbe essere gestito', async () => {
256
+ const app = new Koa();
257
+
258
+ // Crea una directory temporanea
259
+ const tempDir = path.join(rootDir, 'temp-test-dir');
260
+ if (!fs.existsSync(tempDir)) {
261
+ fs.mkdirSync(tempDir);
262
+ }
263
+
264
+ app.use(koaClassicServer(rootDir, {
265
+ showDirContents: true
266
+ }));
267
+
268
+ const server = app.listen();
269
+
270
+ try {
271
+ // Prima richiesta normale
272
+ const res1 = await supertest(server).get('/temp-test-dir');
273
+ expect(res1.status).toBe(200);
274
+
275
+ // Ora cambia i permessi (solo su Unix)
276
+ if (process.platform !== 'win32') {
277
+ fs.chmodSync(tempDir, 0o000); // Nessun permesso
278
+
279
+ const res2 = await supertest(server).get('/temp-test-dir');
280
+
281
+ console.log('šŸ› Directory Permission Test - Status:', res2.status);
282
+
283
+ // Dovrebbe gestire l'errore
284
+ // expect(res2.status).toBe(500) o 403;
285
+ }
286
+ } catch (error) {
287
+ console.log('āš ļø Directory read error:', error.message);
288
+ } finally {
289
+ // Ripristina permessi e cleanup
290
+ if (process.platform !== 'win32' && fs.existsSync(tempDir)) {
291
+ try {
292
+ fs.chmodSync(tempDir, 0o755);
293
+ } catch (e) {}
294
+ }
295
+ if (fs.existsSync(tempDir)) {
296
+ fs.rmdirSync(tempDir);
297
+ }
298
+ server.close();
299
+ }
300
+ });
301
+ });
302
+
303
+ describe('Bug Tests - Content-Disposition', () => {
304
+ let app;
305
+ let server;
306
+
307
+ beforeAll(() => {
308
+ app = new Koa();
309
+ app.use(koaClassicServer(rootDir));
310
+ server = app.listen();
311
+ });
312
+
313
+ test('BUG: Filename con caratteri speciali dovrebbe essere quotato', async () => {
314
+ // Crea file con spazi e caratteri speciali
315
+ const testFile = path.join(rootDir, 'file with spaces & special.txt');
316
+ fs.writeFileSync(testFile, 'test content');
317
+
318
+ try {
319
+ const res = await supertest(server).get('/file%20with%20spaces%20%26%20special.txt');
320
+
321
+ const contentDisp = res.headers['content-disposition'];
322
+ console.log('šŸ› Content-Disposition:', contentDisp);
323
+
324
+ // Dovrebbe essere quotato
325
+ // expect(contentDisp).toMatch(/"file with spaces & special.txt"/);
326
+ } finally {
327
+ if (fs.existsSync(testFile)) {
328
+ fs.unlinkSync(testFile);
329
+ }
330
+ }
331
+ });
332
+
333
+ afterAll(() => {
334
+ server.close();
335
+ });
336
+ });