koa-classic-server 2.3.0 → 2.5.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.
- package/README.md +92 -7
- package/__tests__/hideExtension.test.js +550 -0
- package/__tests__/publicWwwTest/hideext-test/about/index.html +1 -0
- package/__tests__/publicWwwTest/hideext-test/about.EJS +1 -0
- package/__tests__/publicWwwTest/hideext-test/about.ejs +2 -0
- package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +2 -0
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +1 -0
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +1 -0
- package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +1 -0
- package/__tests__/publicWwwTest/hideext-test/index.ejs +2 -0
- package/__tests__/publicWwwTest/hideext-test/photo.txt +1 -0
- package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +1 -0
- package/__tests__/publicWwwTest/hideext-test/style.css +1 -0
- package/__tests__/symlink.test.js +438 -0
- package/docs/CHANGELOG.md +87 -0
- package/docs/DOCUMENTATION.md +53 -5
- package/index.cjs +186 -23
- package/package.json +1 -1
- package/.vscode/OLD_launch.json +0 -26
- package/.vscode/launch.json +0 -41
- package/.vscode/settings.json +0 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// TEST FOR hideExtension OPTION
|
|
4
|
+
// This test verifies that the hideExtension option works correctly:
|
|
5
|
+
// - Clean URL resolution (URL without extension → file with extension)
|
|
6
|
+
// - Redirect from URL with extension to clean URL
|
|
7
|
+
// - Query string preservation
|
|
8
|
+
// - Conflict resolution (directory vs file, extensionless vs extension)
|
|
9
|
+
// - Input validation
|
|
10
|
+
// - Interaction with existing options (urlsReserved, useOriginalUrl, template)
|
|
11
|
+
//
|
|
12
|
+
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
const supertest = require('supertest');
|
|
15
|
+
const koaClassicServer = require('../index.cjs');
|
|
16
|
+
const Koa = require('koa');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const ejs = require('ejs');
|
|
20
|
+
|
|
21
|
+
const rootDir = path.join(__dirname, 'publicWwwTest', 'hideext-test');
|
|
22
|
+
|
|
23
|
+
describe('hideExtension option tests', () => {
|
|
24
|
+
|
|
25
|
+
// ==========================================
|
|
26
|
+
// Clean URL Resolution
|
|
27
|
+
// ==========================================
|
|
28
|
+
describe('Clean URL resolution', () => {
|
|
29
|
+
let app, server, request;
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
app = new Koa();
|
|
33
|
+
app.use(koaClassicServer(rootDir, {
|
|
34
|
+
showDirContents: true,
|
|
35
|
+
index: ['index.ejs'],
|
|
36
|
+
hideExtension: { ext: '.ejs' },
|
|
37
|
+
template: {
|
|
38
|
+
ext: ['ejs'],
|
|
39
|
+
render: async (ctx, next, filePath) => {
|
|
40
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
41
|
+
ctx.type = 'text/html';
|
|
42
|
+
ctx.body = content;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
server = app.listen();
|
|
47
|
+
request = supertest(server);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterAll(() => { server.close(); });
|
|
51
|
+
|
|
52
|
+
test('/about serves about.ejs', async () => {
|
|
53
|
+
const response = await request.get('/about');
|
|
54
|
+
expect(response.status).toBe(200);
|
|
55
|
+
expect(response.text).toContain('About Page');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('/blog/articolo serves blog/articolo.ejs (multi-level path)', async () => {
|
|
59
|
+
const response = await request.get('/blog/articolo');
|
|
60
|
+
expect(response.status).toBe(200);
|
|
61
|
+
expect(response.text).toContain('Blog Article');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('/style.css serves style.css (no interference with other extensions)', async () => {
|
|
65
|
+
const response = await request.get('/style.css');
|
|
66
|
+
expect(response.status).toBe(200);
|
|
67
|
+
expect(response.text).toContain('body { color: red; }');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('/ serves the index file via existing index flow', async () => {
|
|
71
|
+
const response = await request.get('/');
|
|
72
|
+
expect(response.status).toBe(200);
|
|
73
|
+
expect(response.text).toContain('Home Page');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ==========================================
|
|
78
|
+
// Redirect URL with extension → clean URL
|
|
79
|
+
// ==========================================
|
|
80
|
+
describe('Redirect URL with extension to clean URL', () => {
|
|
81
|
+
let app, server, request;
|
|
82
|
+
|
|
83
|
+
beforeAll(() => {
|
|
84
|
+
app = new Koa();
|
|
85
|
+
app.use(koaClassicServer(rootDir, {
|
|
86
|
+
showDirContents: true,
|
|
87
|
+
index: ['index.ejs'],
|
|
88
|
+
hideExtension: { ext: '.ejs' },
|
|
89
|
+
template: {
|
|
90
|
+
ext: ['ejs'],
|
|
91
|
+
render: async (ctx, next, filePath) => {
|
|
92
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
93
|
+
ctx.type = 'text/html';
|
|
94
|
+
ctx.body = content;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
server = app.listen();
|
|
99
|
+
request = supertest(server);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterAll(() => { server.close(); });
|
|
103
|
+
|
|
104
|
+
test('/about.ejs → redirect 301 to /about', async () => {
|
|
105
|
+
const response = await request.get('/about.ejs');
|
|
106
|
+
expect(response.status).toBe(301);
|
|
107
|
+
expect(response.headers.location).toBe('/about');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('/blog/articolo.ejs → redirect 301 to /blog/articolo', async () => {
|
|
111
|
+
const response = await request.get('/blog/articolo.ejs');
|
|
112
|
+
expect(response.status).toBe(301);
|
|
113
|
+
expect(response.headers.location).toBe('/blog/articolo');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('/about.ejs?lang=it → redirect 301 to /about?lang=it (preserves query string)', async () => {
|
|
117
|
+
const response = await request.get('/about.ejs?lang=it');
|
|
118
|
+
expect(response.status).toBe(301);
|
|
119
|
+
expect(response.headers.location).toBe('/about?lang=it');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('/index.ejs → redirect to /', async () => {
|
|
123
|
+
const response = await request.get('/index.ejs');
|
|
124
|
+
expect(response.status).toBe(301);
|
|
125
|
+
expect(response.headers.location).toBe('/');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('/sezione/index.ejs → redirect to /sezione/', async () => {
|
|
129
|
+
const response = await request.get('/sezione/index.ejs');
|
|
130
|
+
expect(response.status).toBe(301);
|
|
131
|
+
expect(response.headers.location).toBe('/sezione/');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ==========================================
|
|
136
|
+
// Custom redirect code (302)
|
|
137
|
+
// ==========================================
|
|
138
|
+
describe('Custom redirect code', () => {
|
|
139
|
+
let app, server, request;
|
|
140
|
+
|
|
141
|
+
beforeAll(() => {
|
|
142
|
+
app = new Koa();
|
|
143
|
+
app.use(koaClassicServer(rootDir, {
|
|
144
|
+
showDirContents: true,
|
|
145
|
+
index: ['index.ejs'],
|
|
146
|
+
hideExtension: { ext: '.ejs', redirect: 302 }
|
|
147
|
+
}));
|
|
148
|
+
server = app.listen();
|
|
149
|
+
request = supertest(server);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterAll(() => { server.close(); });
|
|
153
|
+
|
|
154
|
+
test('/about.ejs → redirect 302 to /about', async () => {
|
|
155
|
+
const response = await request.get('/about.ejs');
|
|
156
|
+
expect(response.status).toBe(302);
|
|
157
|
+
expect(response.headers.location).toBe('/about');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ==========================================
|
|
162
|
+
// Directory/file conflict (showDirContents: true)
|
|
163
|
+
// ==========================================
|
|
164
|
+
describe('Directory/file conflict with showDirContents: true', () => {
|
|
165
|
+
let app, server, request;
|
|
166
|
+
|
|
167
|
+
beforeAll(() => {
|
|
168
|
+
app = new Koa();
|
|
169
|
+
app.use(koaClassicServer(rootDir, {
|
|
170
|
+
showDirContents: true,
|
|
171
|
+
index: ['index.html'],
|
|
172
|
+
hideExtension: { ext: '.ejs' },
|
|
173
|
+
template: {
|
|
174
|
+
ext: ['ejs'],
|
|
175
|
+
render: async (ctx, next, filePath) => {
|
|
176
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
177
|
+
ctx.type = 'text/html';
|
|
178
|
+
ctx.body = content;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
182
|
+
server = app.listen();
|
|
183
|
+
request = supertest(server);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
afterAll(() => { server.close(); });
|
|
187
|
+
|
|
188
|
+
test('/about serves about.ejs (file wins over directory)', async () => {
|
|
189
|
+
const response = await request.get('/about');
|
|
190
|
+
expect(response.status).toBe(200);
|
|
191
|
+
expect(response.text).toContain('About Page');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('/about/ shows the directory index or directory contents', async () => {
|
|
195
|
+
const response = await request.get('/about/');
|
|
196
|
+
expect(response.status).toBe(200);
|
|
197
|
+
// The about/ directory has index.html, so it's served as the index file
|
|
198
|
+
expect(response.text).toContain('About Directory Index');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ==========================================
|
|
203
|
+
// Trailing slash without directory → 404
|
|
204
|
+
// ==========================================
|
|
205
|
+
describe('Trailing slash without directory', () => {
|
|
206
|
+
let app, server, request;
|
|
207
|
+
|
|
208
|
+
beforeAll(() => {
|
|
209
|
+
app = new Koa();
|
|
210
|
+
app.use(koaClassicServer(rootDir, {
|
|
211
|
+
showDirContents: true,
|
|
212
|
+
hideExtension: { ext: '.ejs' }
|
|
213
|
+
}));
|
|
214
|
+
server = app.listen();
|
|
215
|
+
request = supertest(server);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
afterAll(() => { server.close(); });
|
|
219
|
+
|
|
220
|
+
test('/nonexistent/ returns 404', async () => {
|
|
221
|
+
const response = await request.get('/nonexistent/');
|
|
222
|
+
expect(response.status).toBe(404);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ==========================================
|
|
227
|
+
// File without extension vs .ejs conflict
|
|
228
|
+
// ==========================================
|
|
229
|
+
describe('File without extension vs .ejs conflict', () => {
|
|
230
|
+
let app, server, request;
|
|
231
|
+
|
|
232
|
+
beforeAll(() => {
|
|
233
|
+
app = new Koa();
|
|
234
|
+
app.use(koaClassicServer(rootDir, {
|
|
235
|
+
showDirContents: true,
|
|
236
|
+
hideExtension: { ext: '.ejs' },
|
|
237
|
+
template: {
|
|
238
|
+
ext: ['ejs'],
|
|
239
|
+
render: async (ctx, next, filePath) => {
|
|
240
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
241
|
+
ctx.type = 'text/html';
|
|
242
|
+
ctx.body = content;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}));
|
|
246
|
+
server = app.listen();
|
|
247
|
+
request = supertest(server);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
afterAll(() => { server.close(); });
|
|
251
|
+
|
|
252
|
+
test('/conflict-test/pagina serves pagina.ejs (ejs wins over extensionless file)', async () => {
|
|
253
|
+
const response = await request.get('/conflict-test/pagina');
|
|
254
|
+
expect(response.status).toBe(200);
|
|
255
|
+
expect(response.text).toContain('Pagina EJS');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ==========================================
|
|
260
|
+
// Interaction with urlsReserved
|
|
261
|
+
// ==========================================
|
|
262
|
+
describe('Interaction with urlsReserved', () => {
|
|
263
|
+
let app, server, request;
|
|
264
|
+
|
|
265
|
+
beforeAll(() => {
|
|
266
|
+
app = new Koa();
|
|
267
|
+
|
|
268
|
+
// Add a "next" middleware to catch reserved URLs
|
|
269
|
+
app.use(async (ctx, next) => {
|
|
270
|
+
await next();
|
|
271
|
+
if (ctx.status === 404 && ctx._passedToNext) {
|
|
272
|
+
ctx.status = 200;
|
|
273
|
+
ctx.body = 'RESERVED';
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const middleware = koaClassicServer(rootDir, {
|
|
278
|
+
showDirContents: true,
|
|
279
|
+
index: ['index.ejs'],
|
|
280
|
+
urlsReserved: ['/blog'],
|
|
281
|
+
hideExtension: { ext: '.ejs' },
|
|
282
|
+
template: {
|
|
283
|
+
ext: ['ejs'],
|
|
284
|
+
render: async (ctx, next, filePath) => {
|
|
285
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
286
|
+
ctx.type = 'text/html';
|
|
287
|
+
ctx.body = content;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Wrap middleware to track next() calls
|
|
293
|
+
app.use(async (ctx, next) => {
|
|
294
|
+
const originalNext = next;
|
|
295
|
+
await middleware(ctx, async () => {
|
|
296
|
+
ctx._passedToNext = true;
|
|
297
|
+
await originalNext();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
server = app.listen();
|
|
302
|
+
request = supertest(server);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
afterAll(() => { server.close(); });
|
|
306
|
+
|
|
307
|
+
test('/blog is reserved (passed to next middleware)', async () => {
|
|
308
|
+
const response = await request.get('/blog');
|
|
309
|
+
// blog is a directory and is reserved, so it passes to next
|
|
310
|
+
expect(response.text).toBe('RESERVED');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('/about still resolves about.ejs normally', async () => {
|
|
314
|
+
const response = await request.get('/about');
|
|
315
|
+
expect(response.status).toBe(200);
|
|
316
|
+
expect(response.text).toContain('About Page');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ==========================================
|
|
321
|
+
// Interaction with useOriginalUrl
|
|
322
|
+
// ==========================================
|
|
323
|
+
describe('Interaction with useOriginalUrl', () => {
|
|
324
|
+
let app, server, request;
|
|
325
|
+
|
|
326
|
+
beforeAll(() => {
|
|
327
|
+
app = new Koa();
|
|
328
|
+
|
|
329
|
+
// i18n middleware that rewrites URLs
|
|
330
|
+
app.use(async (ctx, next) => {
|
|
331
|
+
if (ctx.path.match(/^\/it\//)) {
|
|
332
|
+
ctx.url = ctx.path.replace(/^\/it/, '');
|
|
333
|
+
}
|
|
334
|
+
await next();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
app.use(koaClassicServer(rootDir, {
|
|
338
|
+
showDirContents: true,
|
|
339
|
+
useOriginalUrl: false,
|
|
340
|
+
hideExtension: { ext: '.ejs' },
|
|
341
|
+
template: {
|
|
342
|
+
ext: ['ejs'],
|
|
343
|
+
render: async (ctx, next, filePath) => {
|
|
344
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
345
|
+
ctx.type = 'text/html';
|
|
346
|
+
ctx.body = content;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}));
|
|
350
|
+
|
|
351
|
+
server = app.listen();
|
|
352
|
+
request = supertest(server);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
afterAll(() => { server.close(); });
|
|
356
|
+
|
|
357
|
+
test('redirect uses ctx.originalUrl (preserves /it/ prefix)', async () => {
|
|
358
|
+
const response = await request.get('/it/about.ejs');
|
|
359
|
+
expect(response.status).toBe(301);
|
|
360
|
+
// Redirect should use originalUrl: /it/about (not /about)
|
|
361
|
+
expect(response.headers.location).toBe('/it/about');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('clean URL resolves through rewritten URL', async () => {
|
|
365
|
+
const response = await request.get('/it/about');
|
|
366
|
+
expect(response.status).toBe(200);
|
|
367
|
+
expect(response.text).toContain('About Page');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ==========================================
|
|
372
|
+
// Case-sensitive extension matching
|
|
373
|
+
// ==========================================
|
|
374
|
+
describe('Case-sensitive extension matching', () => {
|
|
375
|
+
let app, server, request;
|
|
376
|
+
|
|
377
|
+
beforeAll(() => {
|
|
378
|
+
app = new Koa();
|
|
379
|
+
app.use(koaClassicServer(rootDir, {
|
|
380
|
+
showDirContents: true,
|
|
381
|
+
hideExtension: { ext: '.ejs' }
|
|
382
|
+
}));
|
|
383
|
+
server = app.listen();
|
|
384
|
+
request = supertest(server);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
afterAll(() => { server.close(); });
|
|
388
|
+
|
|
389
|
+
test('/about.EJS is not handled by hideExtension (case-sensitive)', async () => {
|
|
390
|
+
const response = await request.get('/about.EJS');
|
|
391
|
+
// The file about.EJS exists, should be served normally (not redirected)
|
|
392
|
+
expect(response.status).toBe(200);
|
|
393
|
+
// Should NOT be a redirect
|
|
394
|
+
expect(response.status).not.toBe(301);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// ==========================================
|
|
399
|
+
// URLs with different extensions (no interference)
|
|
400
|
+
// ==========================================
|
|
401
|
+
describe('URLs with different extensions', () => {
|
|
402
|
+
let app, server, request;
|
|
403
|
+
|
|
404
|
+
beforeAll(() => {
|
|
405
|
+
app = new Koa();
|
|
406
|
+
app.use(koaClassicServer(rootDir, {
|
|
407
|
+
showDirContents: true,
|
|
408
|
+
hideExtension: { ext: '.ejs' }
|
|
409
|
+
}));
|
|
410
|
+
server = app.listen();
|
|
411
|
+
request = supertest(server);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
afterAll(() => { server.close(); });
|
|
415
|
+
|
|
416
|
+
test('/file.ejs.bak is not interfered with', async () => {
|
|
417
|
+
const response = await request.get('/file.ejs.bak');
|
|
418
|
+
// .bak is the extension, not .ejs - should be served normally
|
|
419
|
+
expect(response.status).toBe(200);
|
|
420
|
+
expect(response.status).not.toBe(301);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('/photo.txt is not interfered with', async () => {
|
|
424
|
+
const response = await request.get('/photo.txt');
|
|
425
|
+
expect(response.status).toBe(200);
|
|
426
|
+
expect(response.status).not.toBe(301);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('/style.css is not interfered with', async () => {
|
|
430
|
+
const response = await request.get('/style.css');
|
|
431
|
+
expect(response.status).toBe(200);
|
|
432
|
+
expect(response.status).not.toBe(301);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// ==========================================
|
|
437
|
+
// Template engine integration
|
|
438
|
+
// ==========================================
|
|
439
|
+
describe('Template engine integration', () => {
|
|
440
|
+
let app, server, request;
|
|
441
|
+
|
|
442
|
+
beforeAll(() => {
|
|
443
|
+
app = new Koa();
|
|
444
|
+
app.use(koaClassicServer(rootDir, {
|
|
445
|
+
showDirContents: true,
|
|
446
|
+
index: ['index.ejs'],
|
|
447
|
+
hideExtension: { ext: '.ejs' },
|
|
448
|
+
template: {
|
|
449
|
+
ext: ['ejs'],
|
|
450
|
+
render: async (ctx, next, filePath) => {
|
|
451
|
+
const templateContent = await fs.promises.readFile(filePath, 'utf-8');
|
|
452
|
+
const html = ejs.render(templateContent, { title: 'Test Title' });
|
|
453
|
+
ctx.type = 'text/html';
|
|
454
|
+
ctx.body = html;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}));
|
|
458
|
+
server = app.listen();
|
|
459
|
+
request = supertest(server);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
afterAll(() => { server.close(); });
|
|
463
|
+
|
|
464
|
+
test('file resolved via hideExtension passes correctly to template engine', async () => {
|
|
465
|
+
const response = await request.get('/about');
|
|
466
|
+
expect(response.status).toBe(200);
|
|
467
|
+
expect(response.type).toBe('text/html');
|
|
468
|
+
expect(response.text).toContain('About Page');
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// ==========================================
|
|
473
|
+
// Input Validation
|
|
474
|
+
// ==========================================
|
|
475
|
+
describe('Input validation', () => {
|
|
476
|
+
|
|
477
|
+
test('hideExtension: true → throws Error', () => {
|
|
478
|
+
expect(() => {
|
|
479
|
+
koaClassicServer(rootDir, {
|
|
480
|
+
hideExtension: true
|
|
481
|
+
});
|
|
482
|
+
}).toThrow();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test('hideExtension: {} → throws Error (missing ext)', () => {
|
|
486
|
+
expect(() => {
|
|
487
|
+
koaClassicServer(rootDir, {
|
|
488
|
+
hideExtension: {}
|
|
489
|
+
});
|
|
490
|
+
}).toThrow();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test('hideExtension: { ext: "" } → throws Error (empty ext)', () => {
|
|
494
|
+
expect(() => {
|
|
495
|
+
koaClassicServer(rootDir, {
|
|
496
|
+
hideExtension: { ext: '' }
|
|
497
|
+
});
|
|
498
|
+
}).toThrow();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('hideExtension: { ext: "ejs" } → warning + normalizes to ".ejs"', () => {
|
|
502
|
+
const originalWarn = console.warn;
|
|
503
|
+
const warnings = [];
|
|
504
|
+
console.warn = (...args) => { warnings.push(args); };
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const middleware = koaClassicServer(rootDir, {
|
|
508
|
+
hideExtension: { ext: 'ejs' }
|
|
509
|
+
});
|
|
510
|
+
// Should not throw
|
|
511
|
+
expect(middleware).toBeDefined();
|
|
512
|
+
// Should have issued a warning
|
|
513
|
+
expect(warnings.length).toBeGreaterThan(0);
|
|
514
|
+
expect(warnings[0][1]).toContain('hideExtension.ext should start with a dot');
|
|
515
|
+
} finally {
|
|
516
|
+
console.warn = originalWarn;
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test('hideExtension: { ext: ".ejs", redirect: "abc" } → throws Error (redirect not numeric)', () => {
|
|
521
|
+
expect(() => {
|
|
522
|
+
koaClassicServer(rootDir, {
|
|
523
|
+
hideExtension: { ext: '.ejs', redirect: 'abc' }
|
|
524
|
+
});
|
|
525
|
+
}).toThrow();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test('hideExtension: { ext: ".ejs" } → valid, default redirect 301', () => {
|
|
529
|
+
expect(() => {
|
|
530
|
+
koaClassicServer(rootDir, {
|
|
531
|
+
hideExtension: { ext: '.ejs' }
|
|
532
|
+
});
|
|
533
|
+
}).not.toThrow();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test('hideExtension: { ext: ".ejs", redirect: 302 } → valid', () => {
|
|
537
|
+
expect(() => {
|
|
538
|
+
koaClassicServer(rootDir, {
|
|
539
|
+
hideExtension: { ext: '.ejs', redirect: 302 }
|
|
540
|
+
});
|
|
541
|
+
}).not.toThrow();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('hideExtension: undefined → feature disabled (no error)', () => {
|
|
545
|
+
expect(() => {
|
|
546
|
+
koaClassicServer(rootDir, {});
|
|
547
|
+
}).not.toThrow();
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>About Directory Index</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>About UPPERCASE</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
plain pagina file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>Pagina EJS</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
backup file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This is a photo description file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>Sezione Index</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
body { color: red; }
|