koa-classic-server 2.0.0 → 2.1.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 (45) hide show
  1. package/README.md +31 -17
  2. package/__tests__/directory-sorting-links.test.js +135 -0
  3. package/__tests__/ejs.test.js +299 -0
  4. package/__tests__/performance.test.js +75 -6
  5. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +56 -0
  6. package/__tests__/publicWwwTest/ejs-templates/index.ejs +30 -0
  7. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +13 -0
  8. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +28 -0
  9. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +26 -0
  10. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +16 -0
  11. package/{scripts → __tests__}/setup-benchmark.js +1 -1
  12. package/docs/CODE_REVIEW.md +298 -0
  13. package/docs/FLOW_DIAGRAM.md +952 -0
  14. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +1734 -0
  15. package/docs/template-engine/esempi-incrementali.js +192 -0
  16. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +12 -0
  17. package/docs/template-engine/examples/esempio2-una-variabile.ejs +11 -0
  18. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +15 -0
  19. package/docs/template-engine/examples/esempio4-condizionale.ejs +18 -0
  20. package/docs/template-engine/examples/esempio5-loop.ejs +18 -0
  21. package/docs/template-engine/examples/index-esempi.html +181 -0
  22. package/docs/template-engine/examples/index.html +40 -0
  23. package/docs/template-engine/examples/test.ejs +64 -0
  24. package/index.cjs +186 -35
  25. package/package.json +9 -6
  26. package/CREATE_RELEASE.sh +0 -53
  27. package/publish-to-npm.sh +0 -65
  28. /package/{benchmark-results-baseline-v1.2.0.txt → __tests__/benchmark-results-baseline-v1.2.0.txt} +0 -0
  29. /package/{benchmark-results-optimized-v2.0.0.txt → __tests__/benchmark-results-optimized-v2.0.0.txt} +0 -0
  30. /package/{benchmark.js → __tests__/benchmark.js} +0 -0
  31. /package/{customTest → __tests__/customTest}/README.md +0 -0
  32. /package/{customTest → __tests__/customTest}/loadConfig.util.js +0 -0
  33. /package/{customTest → __tests__/customTest}/serversToLoad.util.js +0 -0
  34. /package/{demo-regex-index.js → __tests__/demo-regex-index.js} +0 -0
  35. /package/{test-regex-quick.js → __tests__/test-regex-quick.js} +0 -0
  36. /package/{BENCHMARKS.md → docs/BENCHMARKS.md} +0 -0
  37. /package/{CHANGELOG.md → docs/CHANGELOG.md} +0 -0
  38. /package/{DEBUG_REPORT.md → docs/DEBUG_REPORT.md} +0 -0
  39. /package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +0 -0
  40. /package/{EXAMPLES_INDEX_OPTION.md → docs/EXAMPLES_INDEX_OPTION.md} +0 -0
  41. /package/{INDEX_OPTION_PRIORITY.md → docs/INDEX_OPTION_PRIORITY.md} +0 -0
  42. /package/{OPTIMIZATION_HTTP_CACHING.md → docs/OPTIMIZATION_HTTP_CACHING.md} +0 -0
  43. /package/{PERFORMANCE_ANALYSIS.md → docs/PERFORMANCE_ANALYSIS.md} +0 -0
  44. /package/{PERFORMANCE_COMPARISON.md → docs/PERFORMANCE_COMPARISON.md} +0 -0
  45. /package/{noteExports.md → docs/noteExports.md} +0 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/koa-classic-server.svg)](https://www.npmjs.com/package/koa-classic-server)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Tests](https://img.shields.io/badge/tests-71%20passing-brightgreen.svg)]()
7
+ [![Tests](https://img.shields.io/badge/tests-146%20passing-brightgreen.svg)]()
8
8
 
9
9
  ## ⚠️ Version 1.2.0 - Critical Security Update
10
10
 
@@ -17,9 +17,9 @@ Version 1.2.0 includes **critical security fixes** for path traversal vulnerabil
17
17
  ✅ **Template Error Handling** - No more server crashes
18
18
  ✅ **XSS Protection** - HTML escaping in directory listings
19
19
  ✅ **Race Condition Fixes** - Robust file access
20
- ✅ **71 Tests Passing** - Comprehensive test coverage
20
+ ✅ **146 Tests Passing** - Comprehensive test coverage including EJS integration
21
21
 
22
- [See full changelog](./CHANGELOG.md)
22
+ [See full changelog](./docs/CHANGELOG.md)
23
23
 
24
24
  ## Features
25
25
 
@@ -32,7 +32,7 @@ koa-classic-server is a middleware for serving static files from a directory wit
32
32
  - 🎨 **Template Engine Support** - Integrate EJS, Pug, Handlebars, etc.
33
33
  - 🔒 **Security** - Path traversal protection, XSS prevention
34
34
  - ⚙️ **Configurable** - URL prefixes, reserved paths, index files
35
- - 🧪 **Well-Tested** - 71 tests with security coverage
35
+ - 🧪 **Well-Tested** - 146 tests with comprehensive coverage (security, EJS, performance)
36
36
  - 📦 **Dual Module Support** - CommonJS and ES Modules
37
37
 
38
38
  ## Installation
@@ -110,24 +110,36 @@ app.listen(3000);
110
110
  const Koa = require('koa');
111
111
  const koaClassicServer = require('koa-classic-server');
112
112
  const ejs = require('ejs');
113
+ const fs = require('fs').promises;
113
114
 
114
115
  const app = new Koa();
115
116
 
116
117
  app.use(koaClassicServer(__dirname + '/views', {
117
118
  template: {
119
+ ext: ['ejs'], // File extensions to process
118
120
  render: async (ctx, next, filePath) => {
119
- ctx.body = await ejs.renderFile(filePath, {
121
+ // Read template file
122
+ const templateContent = await fs.readFile(filePath, 'utf-8');
123
+
124
+ // Render with data
125
+ const html = ejs.render(templateContent, {
120
126
  title: 'My App',
121
- user: ctx.state.user
127
+ user: ctx.state.user || { name: 'Guest' },
128
+ path: ctx.path,
129
+ timestamp: new Date().toISOString()
122
130
  });
123
- },
124
- ext: ['ejs', 'html']
131
+
132
+ ctx.type = 'text/html';
133
+ ctx.body = html;
134
+ }
125
135
  }
126
136
  }));
127
137
 
128
138
  app.listen(3000);
129
139
  ```
130
140
 
141
+ See **[Template Engine Guide](./docs/template-engine/TEMPLATE_ENGINE_GUIDE.md)** for comprehensive template engine documentation with progressive examples.
142
+
131
143
  ## API
132
144
 
133
145
  ### koaClassicServer(rootDir, options)
@@ -282,11 +294,14 @@ See [CHANGELOG.md](./CHANGELOG.md) for detailed information.
282
294
 
283
295
  For complete documentation with all features, examples, troubleshooting, and best practices, see:
284
296
 
285
- - **[DOCUMENTATION.md](./DOCUMENTATION.md)** - Complete API reference and usage guide
286
- - **[INDEX_OPTION_PRIORITY.md](./INDEX_OPTION_PRIORITY.md)** - Detailed priority behavior for `index` option (string, array, RegExp)
287
- - **[EXAMPLES_INDEX_OPTION.md](./EXAMPLES_INDEX_OPTION.md)** - 10 practical examples of `index` option with RegExp
288
- - **[PERFORMANCE_ANALYSIS.md](./PERFORMANCE_ANALYSIS.md)** - Performance optimization analysis
289
- - **[PERFORMANCE_COMPARISON.md](./PERFORMANCE_COMPARISON.md)** - Before/after performance benchmarks
297
+ - **[DOCUMENTATION.md](./docs/DOCUMENTATION.md)** - Complete API reference and usage guide
298
+ - **[FLOW_DIAGRAM.md](./docs/FLOW_DIAGRAM.md)** - Visual flow diagrams and code execution paths
299
+ - **[TEMPLATE_ENGINE_GUIDE.md](./docs/template-engine/TEMPLATE_ENGINE_GUIDE.md)** - Complete guide to template engine integration (EJS, Pug, Handlebars, Nunjucks)
300
+ - **[INDEX_OPTION_PRIORITY.md](./docs/INDEX_OPTION_PRIORITY.md)** - Detailed priority behavior for `index` option (string, array, RegExp)
301
+ - **[EXAMPLES_INDEX_OPTION.md](./docs/EXAMPLES_INDEX_OPTION.md)** - 10 practical examples of `index` option with RegExp
302
+ - **[PERFORMANCE_ANALYSIS.md](./docs/PERFORMANCE_ANALYSIS.md)** - Performance optimization analysis
303
+ - **[PERFORMANCE_COMPARISON.md](./docs/PERFORMANCE_COMPARISON.md)** - Before/after performance benchmarks
304
+ - **[CODE_REVIEW.md](./docs/CODE_REVIEW.md)** - Code quality analysis and review
290
305
 
291
306
  ## Contributing
292
307
 
@@ -295,9 +310,8 @@ Contributions are welcome! Please feel free to submit a Pull Request.
295
310
  ## Known Limitations
296
311
 
297
312
  - Reserved URLs only work for first-level directories
298
- - Single index file name (no fallback array)
299
313
 
300
- See [DEBUG_REPORT.md](./DEBUG_REPORT.md) for technical details.
314
+ See [DEBUG_REPORT.md](./docs/DEBUG_REPORT.md) for technical details.
301
315
 
302
316
  ## License
303
317
 
@@ -313,8 +327,8 @@ See [CHANGELOG.md](./CHANGELOG.md)
313
327
 
314
328
  ## Links
315
329
 
316
- - [Full Documentation](./DOCUMENTATION.md)
317
- - [Debug Report](./DEBUG_REPORT.md)
330
+ - [Full Documentation](./docs/DOCUMENTATION.md)
331
+ - [Debug Report](./docs/DEBUG_REPORT.md)
318
332
  - [Changelog](./CHANGELOG.md)
319
333
  - [Repository](https://github.com/italopaesano/koa-classic-server)
320
334
  - [npm Package](https://www.npmjs.com/package/koa-classic-server)
@@ -0,0 +1,135 @@
1
+ const Koa = require('koa');
2
+ const request = require('supertest');
3
+ const koaClassicServer = require('../index.cjs');
4
+ const path = require('path');
5
+
6
+ describe('Directory Sorting Links Bug Tests', () => {
7
+ let app;
8
+ let server;
9
+
10
+ beforeAll(() => {
11
+ app = new Koa();
12
+ const publicDir = path.join(__dirname, 'publicWwwTest');
13
+
14
+ app.use(koaClassicServer(publicDir, {
15
+ method: ['GET'],
16
+ showDirContents: true
17
+ }));
18
+
19
+ server = app.listen();
20
+ });
21
+
22
+ afterAll(() => {
23
+ server.close();
24
+ });
25
+
26
+ describe('Bug: Links with query parameters in path', () => {
27
+ test('File links should not contain sort query parameters in path', async () => {
28
+ const response = await request(server)
29
+ .get('/?sort=name&order=asc')
30
+ .expect(200);
31
+
32
+ console.log('Testing for bug...');
33
+ console.log('Looking for malformed URLs like: ?sort=name&order=asc/');
34
+
35
+ // The bug creates malformed URLs like: href="http://localhost:3000/?sort=name&order=asc/test.txt"
36
+ // Should NOT contain query params before slash
37
+ expect(response.text).not.toContain('?sort=name&order=asc/');
38
+
39
+ // Also check specific pattern
40
+ const malformedPattern = /href="[^"]*\?sort=[^"]*\//;
41
+ expect(response.text).not.toMatch(malformedPattern);
42
+ });
43
+
44
+ test('Directory links should not contain query parameters in path', async () => {
45
+ const response = await request(server)
46
+ .get('/?sort=size&order=desc')
47
+ .expect(200);
48
+
49
+ // Bug would create: href="http://localhost/?sort=size&order=desc/cartella"
50
+ expect(response.text).not.toContain('?sort=size&order=desc/');
51
+
52
+ // Check for malformed href pattern
53
+ const malformedPattern = /href="[^"]*\?sort=[^"]*\//;
54
+ expect(response.text).not.toMatch(malformedPattern);
55
+ });
56
+ });
57
+
58
+ describe('Functional: Navigation after sorting', () => {
59
+ test('Should enter folder after sorting by name', async () => {
60
+ // Load root with sorting
61
+ const rootResponse = await request(server)
62
+ .get('/?sort=name&order=asc')
63
+ .expect(200);
64
+
65
+ expect(rootResponse.text).toContain('cartella');
66
+
67
+ // Enter cartella folder
68
+ const folderResponse = await request(server)
69
+ .get('/cartella')
70
+ .expect(200);
71
+
72
+ expect(folderResponse.text).toContain('sottocartella');
73
+ });
74
+
75
+ test('Should navigate subfolders after sorting by size', async () => {
76
+ await request(server)
77
+ .get('/?sort=size&order=desc')
78
+ .expect(200);
79
+
80
+ // Enter cartella
81
+ const folderResponse = await request(server)
82
+ .get('/cartella')
83
+ .expect(200);
84
+
85
+ expect(folderResponse.text).toContain('sottocartella');
86
+
87
+ // Enter sottocartella
88
+ const subfolderResponse = await request(server)
89
+ .get('/cartella/sottocartella')
90
+ .expect(200);
91
+
92
+ expect(subfolderResponse.text).toContain('ciao.html');
93
+ });
94
+
95
+ test('Should access file after sorting by type', async () => {
96
+ await request(server)
97
+ .get('/?sort=type&order=asc')
98
+ .expect(200);
99
+
100
+ // Access file
101
+ const fileResponse = await request(server)
102
+ .get('/test.txt')
103
+ .expect(200);
104
+
105
+ expect(fileResponse.text).toContain('hello world');
106
+ });
107
+ });
108
+
109
+ describe('Regression: Sort links still work', () => {
110
+ test('Sort header links should work', async () => {
111
+ const response = await request(server)
112
+ .get('/')
113
+ .expect(200);
114
+
115
+ // Sort links SHOULD have query parameters
116
+ expect(response.text).toContain('?sort=name');
117
+ expect(response.text).toContain('?sort=type');
118
+ expect(response.text).toContain('?sort=size');
119
+ });
120
+
121
+ test('Sort indicators should toggle', async () => {
122
+ const response1 = await request(server)
123
+ .get('/?sort=name&order=asc')
124
+ .expect(200);
125
+
126
+ expect(response1.text).toContain('↑');
127
+
128
+ const response2 = await request(server)
129
+ .get('/?sort=name&order=desc')
130
+ .expect(200);
131
+
132
+ expect(response2.text).toContain('↓');
133
+ });
134
+ });
135
+ });
@@ -0,0 +1,299 @@
1
+ const Koa = require('koa');
2
+ const koaClassicServer = require('../index.cjs');
3
+ const supertest = require('supertest');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const ejs = require('ejs');
7
+
8
+ describe('EJS Template Engine Integration Tests', () => {
9
+ let app;
10
+ let server;
11
+ let request;
12
+
13
+ beforeAll(() => {
14
+ app = new Koa();
15
+
16
+ const rootDir = path.join(__dirname, 'publicWwwTest');
17
+
18
+ // Configure koaClassicServer with EJS template support
19
+ app.use(
20
+ koaClassicServer(rootDir, {
21
+ method: ['GET'],
22
+ showDirContents: true,
23
+ template: {
24
+ ext: ['ejs'],
25
+ render: async (ctx, next, filePath) => {
26
+ // Read the template file
27
+ const templateContent = await fs.promises.readFile(filePath, 'utf-8');
28
+
29
+ // Prepare data for different templates
30
+ const data = getTemplateData(filePath);
31
+
32
+ // Render with EJS
33
+ const html = ejs.render(templateContent, data);
34
+
35
+ ctx.type = 'text/html';
36
+ ctx.body = html;
37
+ }
38
+ }
39
+ })
40
+ );
41
+
42
+ server = app.listen();
43
+ request = supertest(server);
44
+ });
45
+
46
+ afterAll(() => {
47
+ if (server) {
48
+ server.close();
49
+ }
50
+ });
51
+
52
+ // Helper function to provide data for each template
53
+ function getTemplateData(filePath) {
54
+ const basename = path.basename(filePath, '.ejs');
55
+
56
+ const dataMap = {
57
+ 'simple': {
58
+ title: 'Simple EJS Test',
59
+ heading: 'Hello from EJS',
60
+ message: 'This is a simple template test',
61
+ timestamp: '2025-11-18'
62
+ },
63
+ 'with-loop': {
64
+ items: [
65
+ { name: 'Item 1', value: 100 },
66
+ { name: 'Item 2', value: 200 },
67
+ { name: 'Item 3', value: 300 }
68
+ ]
69
+ },
70
+ 'with-conditional': {
71
+ isLoggedIn: true,
72
+ username: 'TestUser',
73
+ role: 'admin',
74
+ notifications: 5
75
+ },
76
+ 'with-escaping': {
77
+ userInput: '<script>alert("XSS")</script>',
78
+ htmlContent: '<strong>Bold text</strong>',
79
+ safeText: 'Plain & safe text',
80
+ allowedHtml: '<em>Emphasized text</em>'
81
+ },
82
+ 'complex': {
83
+ pageTitle: 'E-Commerce Products',
84
+ user: {
85
+ name: 'Mario Rossi'
86
+ },
87
+ products: [
88
+ {
89
+ id: 1,
90
+ name: 'Laptop',
91
+ description: 'High-performance laptop',
92
+ price: 999.99,
93
+ discount: 10,
94
+ inStock: true
95
+ },
96
+ {
97
+ id: 2,
98
+ name: 'Mouse',
99
+ description: 'Wireless mouse',
100
+ price: 29.99,
101
+ discount: 0,
102
+ inStock: true
103
+ },
104
+ {
105
+ id: 3,
106
+ name: 'Keyboard',
107
+ description: 'Mechanical keyboard',
108
+ price: 149.99,
109
+ discount: 15,
110
+ inStock: false
111
+ }
112
+ ]
113
+ },
114
+ 'testEjs': {
115
+ // For the existing test file
116
+ }
117
+ };
118
+
119
+ return dataMap[basename] || {};
120
+ }
121
+
122
+ describe('Simple Template - simple.ejs', () => {
123
+ test('Should render simple template with variables', async () => {
124
+ const response = await request.get('/ejs-templates/simple.ejs');
125
+
126
+ expect(response.status).toBe(200);
127
+ expect(response.type).toBe('text/html');
128
+ expect(response.text).toContain('Simple EJS Test');
129
+ expect(response.text).toContain('Hello from EJS');
130
+ expect(response.text).toContain('This is a simple template test');
131
+ expect(response.text).toContain('2025-11-18');
132
+ });
133
+ });
134
+
135
+ describe('Loop Template - with-loop.ejs', () => {
136
+ test('Should render template with forEach loop', async () => {
137
+ const response = await request.get('/ejs-templates/with-loop.ejs');
138
+
139
+ expect(response.status).toBe(200);
140
+ expect(response.text).toContain('Lista di Items');
141
+ expect(response.text).toContain('Item 1 - 100');
142
+ expect(response.text).toContain('Item 2 - 200');
143
+ expect(response.text).toContain('Item 3 - 300');
144
+ expect(response.text).toContain('Totale items: 3');
145
+ expect(response.text).toContain('data-index="0"');
146
+ expect(response.text).toContain('data-index="1"');
147
+ expect(response.text).toContain('data-index="2"');
148
+ });
149
+ });
150
+
151
+ describe('Conditional Template - with-conditional.ejs', () => {
152
+ test('Should render template with if/else conditionals (logged in)', async () => {
153
+ const response = await request.get('/ejs-templates/with-conditional.ejs');
154
+
155
+ expect(response.status).toBe(200);
156
+ expect(response.text).toContain('Benvenuto, TestUser!');
157
+ expect(response.text).toContain('Ruolo: admin');
158
+ expect(response.text).toContain('Hai 5 nuove notifiche');
159
+ // Should NOT contain guest panel
160
+ expect(response.text).not.toContain('Benvenuto, ospite!');
161
+ });
162
+ });
163
+
164
+ describe('Escaping Template - with-escaping.ejs', () => {
165
+ test('Should properly escape HTML in <%= %> tags', async () => {
166
+ const response = await request.get('/ejs-templates/with-escaping.ejs');
167
+
168
+ expect(response.status).toBe(200);
169
+
170
+ // HTML should be escaped (safe)
171
+ // Note: EJS uses &#34; instead of &quot; for quotes
172
+ expect(response.text).toContain('&lt;script&gt;alert(&#34;XSS&#34;)&lt;/script&gt;');
173
+
174
+ // HTML should NOT be escaped (unsafe - unescaped output)
175
+ expect(response.text).toContain('<strong>Bold text</strong>');
176
+
177
+ // Safe text with & should be escaped
178
+ expect(response.text).toContain('Plain &amp; safe text');
179
+
180
+ // Allowed HTML should be rendered
181
+ expect(response.text).toContain('<em>Emphasized text</em>');
182
+ });
183
+
184
+ test('Should protect against XSS attacks', async () => {
185
+ const response = await request.get('/ejs-templates/with-escaping.ejs');
186
+
187
+ // The escaped section should not contain executable script tags
188
+ const escapedSection = response.text.match(/<div class="escaped">[\s\S]*?<\/div>/)[0];
189
+ expect(escapedSection).not.toContain('<script>');
190
+ expect(escapedSection).toContain('&lt;script&gt;');
191
+ });
192
+ });
193
+
194
+ describe('Complex Template - complex.ejs', () => {
195
+ test('Should render complex template with all features', async () => {
196
+ const response = await request.get('/ejs-templates/complex.ejs');
197
+
198
+ expect(response.status).toBe(200);
199
+
200
+ // Page title
201
+ expect(response.text).toContain('E-Commerce Products');
202
+
203
+ // User greeting
204
+ expect(response.text).toContain('Ciao, Mario Rossi!');
205
+
206
+ // Products list
207
+ expect(response.text).toContain('Laptop');
208
+ expect(response.text).toContain('High-performance laptop');
209
+ expect(response.text).toContain('€999.99');
210
+
211
+ // Discount calculation
212
+ expect(response.text).toContain('Sconto: 10%');
213
+ expect(response.text).toContain('€899.99'); // 999.99 - 10%
214
+
215
+ // Mouse without discount
216
+ expect(response.text).toContain('Mouse');
217
+ expect(response.text).toContain('€29.99');
218
+
219
+ // Keyboard out of stock
220
+ expect(response.text).toContain('Keyboard');
221
+ expect(response.text).toContain('Non disponibile');
222
+
223
+ // Total count
224
+ expect(response.text).toContain('Totale prodotti: 3');
225
+ });
226
+
227
+ test('Should have correct product data attributes', async () => {
228
+ const response = await request.get('/ejs-templates/complex.ejs');
229
+
230
+ expect(response.text).toContain('data-id="1"');
231
+ expect(response.text).toContain('data-id="2"');
232
+ expect(response.text).toContain('data-id="3"');
233
+ });
234
+
235
+ test('Should show "Add to cart" button only for in-stock items', async () => {
236
+ const response = await request.get('/ejs-templates/complex.ejs');
237
+
238
+ // Count "Aggiungi al carrello" buttons - should be 2 (Laptop and Mouse)
239
+ const buttonMatches = response.text.match(/Aggiungi al carrello/g);
240
+ expect(buttonMatches).toHaveLength(2);
241
+
242
+ // Count "Non disponibile" - should be 1 (Keyboard)
243
+ const outOfStockMatches = response.text.match(/Non disponibile/g);
244
+ expect(outOfStockMatches).toHaveLength(1);
245
+ });
246
+ });
247
+
248
+ describe('Index Template - index.ejs', () => {
249
+ test('Should render index page with links', async () => {
250
+ const response = await request.get('/ejs-templates/index.ejs');
251
+
252
+ expect(response.status).toBe(200);
253
+ expect(response.text).toContain('Test Templates EJS');
254
+ expect(response.text).toContain('simple.ejs');
255
+ expect(response.text).toContain('with-loop.ejs');
256
+ expect(response.text).toContain('with-conditional.ejs');
257
+ expect(response.text).toContain('with-escaping.ejs');
258
+ expect(response.text).toContain('complex.ejs');
259
+ });
260
+ });
261
+
262
+ describe('Existing EJS file - testEjs.ejs', () => {
263
+ test('Should render existing testEjs.ejs file', async () => {
264
+ const response = await request.get('/cartella/sottocartella/provaEjs/testEjs.ejs');
265
+
266
+ expect(response.status).toBe(200);
267
+ expect(response.type).toBe('text/html');
268
+ expect(response.text).toContain('<h1>hello world</h1>');
269
+ });
270
+ });
271
+
272
+ describe('Error Handling', () => {
273
+ test('Should return 404 for non-existent .ejs file', async () => {
274
+ const response = await request.get('/ejs-templates/non-existent.ejs');
275
+
276
+ expect(response.status).toBe(404);
277
+ });
278
+ });
279
+
280
+ describe('Performance', () => {
281
+ test('Should render complex template in reasonable time', async () => {
282
+ const start = Date.now();
283
+ const response = await request.get('/ejs-templates/complex.ejs');
284
+ const duration = Date.now() - start;
285
+
286
+ expect(response.status).toBe(200);
287
+ expect(duration).toBeLessThan(100); // Should render in less than 100ms
288
+ });
289
+
290
+ test('Should render simple template very quickly', async () => {
291
+ const start = Date.now();
292
+ const response = await request.get('/ejs-templates/simple.ejs');
293
+ const duration = Date.now() - start;
294
+
295
+ expect(response.status).toBe(200);
296
+ expect(duration).toBeLessThan(50); // Should render in less than 50ms
297
+ });
298
+ });
299
+ });
@@ -17,15 +17,84 @@ const koaClassicServer = require('../index.cjs');
17
17
  const path = require('path');
18
18
  const fs = require('fs');
19
19
 
20
- const BENCHMARK_DIR = path.join(__dirname, '../benchmark-data');
20
+ const BENCHMARK_DIR = path.join(__dirname, 'benchmark-data');
21
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);
22
+ // Auto-setup benchmark data if not exists
23
+ function setupBenchmarkData() {
24
+ if (fs.existsSync(BENCHMARK_DIR)) {
25
+ return; // Data already exists
26
+ }
27
+
28
+ console.log('\n🔧 Setting up benchmark data automatically...\n');
29
+
30
+ // Create benchmark directory
31
+ fs.mkdirSync(BENCHMARK_DIR, { recursive: true });
32
+
33
+ // Create small files (1KB each)
34
+ console.log(' Creating 100 small files (1KB each)...');
35
+ const smallDir = path.join(BENCHMARK_DIR, 'small-files');
36
+ fs.mkdirSync(smallDir);
37
+ for (let i = 1; i <= 100; i++) {
38
+ const content = 'X'.repeat(1024);
39
+ fs.writeFileSync(path.join(smallDir, `file-${i}.txt`), content);
40
+ }
41
+
42
+ // Create medium files (100KB each)
43
+ console.log(' Creating 50 medium files (100KB each)...');
44
+ const mediumDir = path.join(BENCHMARK_DIR, 'medium-files');
45
+ fs.mkdirSync(mediumDir);
46
+ for (let i = 1; i <= 50; i++) {
47
+ const content = 'X'.repeat(100 * 1024);
48
+ fs.writeFileSync(path.join(mediumDir, `file-${i}.txt`), content);
49
+ }
50
+
51
+ // Create large files (1MB each)
52
+ console.log(' Creating 10 large files (1MB each)...');
53
+ const largeDir = path.join(BENCHMARK_DIR, 'large-files');
54
+ fs.mkdirSync(largeDir);
55
+ for (let i = 1; i <= 10; i++) {
56
+ const content = 'X'.repeat(1024 * 1024);
57
+ fs.writeFileSync(path.join(largeDir, `file-${i}.txt`), content);
58
+ }
59
+
60
+ // Create directory with 1000 files
61
+ console.log(' Creating directory with 1000 files...');
62
+ const largeDirListing = path.join(BENCHMARK_DIR, 'large-directory');
63
+ fs.mkdirSync(largeDirListing);
64
+ for (let i = 1; i <= 1000; i++) {
65
+ const content = `File number ${i}\n`;
66
+ fs.writeFileSync(path.join(largeDirListing, `item-${String(i).padStart(4, '0')}.txt`), content);
67
+ }
68
+
69
+ // Create directory with 10,000 files
70
+ console.log(' Creating directory with 10,000 files (this may take a moment)...');
71
+ const veryLargeDirListing = path.join(BENCHMARK_DIR, 'very-large-directory');
72
+ fs.mkdirSync(veryLargeDirListing);
73
+ for (let i = 1; i <= 10000; i++) {
74
+ const content = `File number ${i}\n`;
75
+ fs.writeFileSync(path.join(veryLargeDirListing, `item-${String(i).padStart(5, '0')}.txt`), content);
76
+ }
77
+
78
+ // Create HTML file for caching test
79
+ const htmlContent = `<!DOCTYPE html>
80
+ <html>
81
+ <head>
82
+ <meta charset="UTF-8">
83
+ <title>Benchmark Test</title>
84
+ </head>
85
+ <body>
86
+ <h1>Benchmark Test Page</h1>
87
+ <p>${'Lorem ipsum dolor sit amet. '.repeat(100)}</p>
88
+ </body>
89
+ </html>`;
90
+ fs.writeFileSync(path.join(BENCHMARK_DIR, 'test.html'), htmlContent);
91
+
92
+ console.log('\n✅ Benchmark data setup complete!\n');
27
93
  }
28
94
 
95
+ // Setup benchmark data automatically if needed
96
+ setupBenchmarkData();
97
+
29
98
  // Helper to measure execution time
30
99
  function measureTime(fn) {
31
100
  const start = process.hrtime.bigint();