weifuwu 0.2.2 → 0.2.4

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 +90 -5
  2. package/dist/compress.d.ts +6 -0
  3. package/dist/cookie.d.ts +12 -0
  4. package/dist/index.d.ts +21 -0
  5. package/dist/index.js +1486 -0
  6. package/dist/middleware.d.ts +21 -0
  7. package/dist/rate-limit.d.ts +8 -0
  8. package/dist/router.d.ts +55 -0
  9. package/dist/serve.d.ts +19 -0
  10. package/dist/static.d.ts +7 -0
  11. package/dist/tsx.d.ts +17 -0
  12. package/dist/types.d.ts +9 -0
  13. package/dist/upload.d.ts +14 -0
  14. package/dist/validate.d.ts +9 -0
  15. package/package.json +14 -2
  16. package/AGENTS.md +0 -105
  17. package/compress.ts +0 -69
  18. package/cookie.ts +0 -58
  19. package/index.ts +0 -21
  20. package/middleware.ts +0 -178
  21. package/rate-limit.ts +0 -68
  22. package/router.ts +0 -701
  23. package/serve.ts +0 -126
  24. package/static.ts +0 -113
  25. package/test/compress.test.ts +0 -106
  26. package/test/cookie.test.ts +0 -79
  27. package/test/fixtures/pages/about/page.tsx +0 -3
  28. package/test/fixtures/pages/blog/[slug]/load.ts +0 -3
  29. package/test/fixtures/pages/blog/[slug]/page.tsx +0 -3
  30. package/test/fixtures/pages/blog/[slug]/route.ts +0 -7
  31. package/test/fixtures/pages/blog/layout.tsx +0 -3
  32. package/test/fixtures/pages/layout.tsx +0 -12
  33. package/test/fixtures/pages/page.tsx +0 -3
  34. package/test/middleware.test.ts +0 -407
  35. package/test/rate-limit.test.ts +0 -94
  36. package/test/static.test.ts +0 -93
  37. package/test/tsx.test.ts +0 -285
  38. package/test/unode.test.ts +0 -401
  39. package/test/upload.test.ts +0 -130
  40. package/test/validate.test.ts +0 -133
  41. package/tsconfig.json +0 -13
  42. package/tsx.ts +0 -374
  43. package/types.ts +0 -23
  44. package/upload.ts +0 -101
  45. package/validate.ts +0 -88
@@ -1,407 +0,0 @@
1
- import { describe, it, mock, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { Router } from '../router.ts'
4
- import { serve } from '../serve.ts'
5
- import { auth, cors, logger } from '../middleware.ts'
6
-
7
- function handler(text = 'ok') {
8
- return () => new Response(text)
9
- }
10
-
11
- // ── Logger ────────────────────────────────────────────────────────────────────
12
-
13
- describe('logger', () => {
14
- it('logs method, path and status', async () => {
15
- const logs: string[] = []
16
- mock.method(console, 'log', (msg: string) => { logs.push(msg) })
17
-
18
- const r = new Router()
19
- .use(logger())
20
- .get('/hello', handler())
21
-
22
- await r.handler()(new Request('http://localhost/hello'), { params: {}, query: {} })
23
-
24
- assert.equal(logs.length, 1)
25
- assert.ok(logs[0]!.includes('GET'))
26
- assert.ok(logs[0]!.includes('/hello'))
27
- assert.ok(logs[0]!.includes('200'))
28
-
29
- mock.restoreAll()
30
- })
31
-
32
- it('combined format includes search params', async () => {
33
- const logs: string[] = []
34
- mock.method(console, 'log', (msg: string) => { logs.push(msg) })
35
-
36
- const r = new Router()
37
- .use(logger({ format: 'combined' }))
38
- .get('/search', handler())
39
-
40
- await r.handler()(new Request('http://localhost/search?q=test'), { params: {}, query: {} })
41
-
42
- assert.ok(logs[0]!.includes('?q=test'))
43
-
44
- mock.restoreAll()
45
- })
46
- })
47
-
48
- // ── CORS ───────────────────────────────────────────────────────────────────────
49
-
50
- describe('cors', () => {
51
- it('adds Access-Control-Allow-Origin: * by default', async () => {
52
- const r = new Router()
53
- .use(cors())
54
- .get('/data', handler())
55
-
56
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
57
- assert.equal(res.headers.get('Access-Control-Allow-Origin'), '*')
58
- })
59
-
60
- it('reflects request origin when in allowed list', async () => {
61
- const r = new Router()
62
- .use(cors({ origin: ['https://example.com', 'https://app.com'] }))
63
- .get('/data', handler())
64
-
65
- const res = await r.handler()(
66
- new Request('http://localhost/data', { headers: { origin: 'https://example.com' } }),
67
- { params: {}, query: {} },
68
- )
69
- assert.equal(res.headers.get('Access-Control-Allow-Origin'), 'https://example.com')
70
- })
71
-
72
- it('omits CORS headers for disallowed origin', async () => {
73
- const r = new Router()
74
- .use(cors({ origin: ['https://example.com'] }))
75
- .get('/data', handler())
76
-
77
- const res = await r.handler()(
78
- new Request('http://localhost/data', { headers: { origin: 'https://evil.com' } }),
79
- { params: {}, query: {} },
80
- )
81
- assert.equal(res.headers.get('Access-Control-Allow-Origin'), null)
82
- })
83
-
84
- it('handles OPTIONS preflight', async () => {
85
- const r = new Router()
86
- .use(cors({ origin: 'https://example.com', methods: ['GET', 'POST'], allowedHeaders: ['X-Custom'], maxAge: 3600 }))
87
- .get('/data', handler())
88
-
89
- const res = await r.handler()(
90
- new Request('http://localhost/data', { method: 'OPTIONS', headers: { origin: 'https://example.com' } }),
91
- { params: {}, query: {} },
92
- )
93
- assert.equal(res.status, 204)
94
- assert.equal(res.headers.get('Access-Control-Allow-Origin'), 'https://example.com')
95
- assert.equal(res.headers.get('Access-Control-Allow-Methods'), 'GET, POST')
96
- assert.equal(res.headers.get('Access-Control-Allow-Headers'), 'X-Custom')
97
- assert.equal(res.headers.get('Access-Control-Max-Age'), '3600')
98
- })
99
-
100
- it('sets Access-Control-Allow-Credentials', async () => {
101
- const r = new Router()
102
- .use(cors({ origin: 'https://example.com', credentials: true }))
103
- .get('/data', handler())
104
-
105
- const res = await r.handler()(
106
- new Request('http://localhost/data', { headers: { origin: 'https://example.com' } }),
107
- { params: {}, query: {} },
108
- )
109
- assert.equal(res.headers.get('Access-Control-Allow-Credentials'), 'true')
110
- })
111
-
112
- it('sets Access-Control-Expose-Headers', async () => {
113
- const r = new Router()
114
- .use(cors({ origin: '*', exposedHeaders: ['X-Total-Count', 'X-Page'] }))
115
- .get('/data', handler())
116
-
117
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
118
- assert.equal(res.headers.get('Access-Control-Expose-Headers'), 'X-Total-Count, X-Page')
119
- })
120
-
121
- it('sets Vary: Origin', async () => {
122
- const r = new Router()
123
- .use(cors({ origin: 'https://example.com' }))
124
- .get('/data', handler())
125
-
126
- const res = await r.handler()(
127
- new Request('http://localhost/data', { headers: { origin: 'https://example.com' } }),
128
- { params: {}, query: {} },
129
- )
130
- assert.equal(res.headers.get('Vary'), 'Origin')
131
- })
132
-
133
- it('uses dynamic origin function', async () => {
134
- const r = new Router()
135
- .use(cors({
136
- origin: (origin) => origin.endsWith('.trusted.com') ? origin : false,
137
- }))
138
- .get('/data', handler())
139
-
140
- const res1 = await r.handler()(
141
- new Request('http://localhost/data', { headers: { origin: 'https://app.trusted.com' } }),
142
- { params: {}, query: {} },
143
- )
144
- assert.equal(res1.headers.get('Access-Control-Allow-Origin'), 'https://app.trusted.com')
145
-
146
- const res2 = await r.handler()(
147
- new Request('http://localhost/data', { headers: { origin: 'https://evil.com' } }),
148
- { params: {}, query: {} },
149
- )
150
- assert.equal(res2.headers.get('Access-Control-Allow-Origin'), null)
151
- })
152
-
153
- it('returns 204 for OPTIONS without matching route', async () => {
154
- const r = new Router()
155
- .use(cors({ origin: '*' }))
156
- .get('/data', handler())
157
-
158
- const res = await r.handler()(
159
- new Request('http://localhost/other', { method: 'OPTIONS', headers: { origin: 'https://example.com' } }),
160
- { params: {}, query: {} },
161
- )
162
- assert.equal(res.status, 204)
163
- })
164
- })
165
-
166
- // ── Auth ───────────────────────────────────────────────────────────────────────
167
-
168
- function authHandler() {
169
- return (req: Request, ctx: { user?: unknown }) =>
170
- Response.json({ user: ctx.user })
171
- }
172
-
173
- describe('auth', () => {
174
- it('rejects missing Authorization header with 401', async () => {
175
- const r = new Router()
176
- .use(auth({ token: 'secret' }))
177
- .get('/data', handler())
178
-
179
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
180
- assert.equal(res.status, 401)
181
- })
182
-
183
- it('sets WWW-Authenticate header on 401', async () => {
184
- const r = new Router()
185
- .use(auth({ token: 'secret' }))
186
- .get('/data', handler())
187
-
188
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
189
- assert.equal(res.headers.get('WWW-Authenticate'), 'Bearer')
190
- })
191
-
192
- it('accepts valid Bearer token', async () => {
193
- const r = new Router()
194
- .use(auth({ token: 'secret' }))
195
- .get('/data', handler())
196
-
197
- const res = await r.handler()(
198
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer secret' } }),
199
- { params: {}, query: {} },
200
- )
201
- assert.equal(res.status, 200)
202
- })
203
-
204
- it('rejects invalid Bearer token with 403', async () => {
205
- const r = new Router()
206
- .use(auth({ token: 'secret' }))
207
- .get('/data', handler())
208
-
209
- const res = await r.handler()(
210
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer wrong' } }),
211
- { params: {}, query: {} },
212
- )
213
- assert.equal(res.status, 403)
214
- })
215
-
216
- it('supports custom header name (X-API-Key)', async () => {
217
- const r = new Router()
218
- .use(auth({ token: 'my-key', header: 'X-API-Key' }))
219
- .get('/data', handler())
220
-
221
- const res1 = await r.handler()(
222
- new Request('http://localhost/data', { headers: { 'X-API-Key': 'my-key' } }),
223
- { params: {}, query: {} },
224
- )
225
- assert.equal(res1.status, 200)
226
-
227
- const res2 = await r.handler()(
228
- new Request('http://localhost/data', { headers: { 'X-API-Key': 'wrong' } }),
229
- { params: {}, query: {} },
230
- )
231
- assert.equal(res2.status, 403)
232
- })
233
-
234
- it('does not set WWW-Authenticate for custom header', async () => {
235
- const r = new Router()
236
- .use(auth({ token: 'my-key', header: 'X-API-Key' }))
237
- .get('/data', handler())
238
-
239
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
240
- assert.equal(res.headers.get('WWW-Authenticate'), null)
241
- })
242
-
243
- it('verify returning boolean true passes', async () => {
244
- const r = new Router()
245
- .use(auth({
246
- verify: () => true,
247
- }))
248
- .get('/data', handler())
249
-
250
- const res = await r.handler()(
251
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer any' } }),
252
- { params: {}, query: {} },
253
- )
254
- assert.equal(res.status, 200)
255
- })
256
-
257
- it('verify returning boolean false rejects with 403', async () => {
258
- const r = new Router()
259
- .use(auth({
260
- verify: () => false,
261
- }))
262
- .get('/data', handler())
263
-
264
- const res = await r.handler()(
265
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer any' } }),
266
- { params: {}, query: {} },
267
- )
268
- assert.equal(res.status, 403)
269
- })
270
-
271
- it('verify returning object sets ctx.user', async () => {
272
- const r = new Router()
273
- .use(auth({
274
- verify: () => ({ sub: 'user-1', role: 'admin' }),
275
- }))
276
- .get('/admin', authHandler())
277
-
278
- const res = await r.handler()(
279
- new Request('http://localhost/admin', { headers: { Authorization: 'Bearer token' } }),
280
- { params: {}, query: {} },
281
- )
282
- const data = await res.json() as Record<string, unknown>
283
- assert.deepEqual(data.user, { sub: 'user-1', role: 'admin' })
284
- })
285
-
286
- it('verify returning null rejects with 403', async () => {
287
- const r = new Router()
288
- .use(auth({
289
- verify: () => null,
290
- }))
291
- .get('/data', handler())
292
-
293
- const res = await r.handler()(
294
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer any' } }),
295
- { params: {}, query: {} },
296
- )
297
- assert.equal(res.status, 403)
298
- })
299
-
300
- it('works as route-level middleware', async () => {
301
- const mw = auth({ verify: () => ({ sub: 'u1' }) })
302
-
303
- const r = new Router()
304
- .get('/admin', mw, (req, ctx) =>
305
- Response.json({ user: ctx.user }),
306
- )
307
-
308
- const res = await r.handler()(
309
- new Request('http://localhost/admin', { headers: { Authorization: 'Bearer token' } }),
310
- { params: {}, query: {} },
311
- )
312
- assert.equal(res.status, 200)
313
- const data = await res.json() as Record<string, unknown>
314
- assert.deepEqual(data.user, { sub: 'u1' })
315
- })
316
-
317
- it('route-level auth rejects without token', async () => {
318
- const mw = auth({ verify: () => ({ sub: 'u1' }) })
319
-
320
- const r = new Router()
321
- .get('/admin', mw, (req, ctx) => Response.json({ user: ctx.user }))
322
-
323
- const res = await r.handler()(
324
- new Request('http://localhost/admin'),
325
- { params: {}, query: {} },
326
- )
327
- assert.equal(res.status, 401)
328
- })
329
-
330
- // ── Proxy mode ──────────────────────────────────────────────────────────
331
-
332
- it('proxy: 2xx response passes auth', async () => {
333
- const proxy = serve(() => new Response(JSON.stringify({ sub: 'u1' }), {
334
- headers: { 'content-type': 'application/json' },
335
- }), { port: 0 })
336
- await proxy.ready
337
- const proxyUrl = `http://localhost:${proxy.port}/validate`
338
-
339
- const r = new Router()
340
- .get('/data', auth({ proxy: proxyUrl }), authHandler())
341
-
342
- const res = await r.handler()(
343
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer valid' } }),
344
- { params: {}, query: {} },
345
- )
346
- assert.equal(res.status, 200)
347
- const data = await res.json() as Record<string, unknown>
348
- assert.deepEqual(data.user, { sub: 'u1' })
349
- proxy.stop()
350
- })
351
-
352
- it('proxy: 4xx response rejects auth', async () => {
353
- const proxy = serve(() => new Response('Unauthorized', { status: 401 }), { port: 0 })
354
- await proxy.ready
355
- const proxyUrl = `http://localhost:${proxy.port}/validate`
356
-
357
- const r = new Router()
358
- .get('/data', auth({ proxy: proxyUrl }), handler())
359
-
360
- const res = await r.handler()(
361
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer bad' } }),
362
- { params: {}, query: {} },
363
- )
364
- assert.equal(res.status, 401)
365
- proxy.stop()
366
- })
367
-
368
- it('proxy forwards Authorization header for Bearer tokens', async () => {
369
- let receivedAuth: string | null = null
370
- const proxy = serve((req) => {
371
- receivedAuth = req.headers.get('Authorization')
372
- return new Response('ok')
373
- }, { port: 0 })
374
- await proxy.ready
375
- const proxyUrl = `http://localhost:${proxy.port}/validate`
376
-
377
- const r = new Router()
378
- .get('/data', auth({ proxy: proxyUrl }), handler())
379
-
380
- await r.handler()(
381
- new Request('http://localhost/data', { headers: { Authorization: 'Bearer mytoken' } }),
382
- { params: {}, query: {} },
383
- )
384
- assert.equal(receivedAuth, 'Bearer mytoken')
385
- proxy.stop()
386
- })
387
-
388
- it('proxy sends access_token query param for custom header', async () => {
389
- let receivedQuery = ''
390
- const proxy = serve((req) => {
391
- receivedQuery = new URL(req.url).search
392
- return new Response('ok')
393
- }, { port: 0 })
394
- await proxy.ready
395
- const proxyUrl = `http://localhost:${proxy.port}/validate`
396
-
397
- const r = new Router()
398
- .get('/data', auth({ proxy: proxyUrl, header: 'X-API-Key' }), handler())
399
-
400
- await r.handler()(
401
- new Request('http://localhost/data', { headers: { 'X-API-Key': 'my-key' } }),
402
- { params: {}, query: {} },
403
- )
404
- assert.ok(receivedQuery.includes('access_token=my-key'))
405
- proxy.stop()
406
- })
407
- })
@@ -1,94 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { Router } from '../router.ts'
4
- import { rateLimit } from '../rate-limit.ts'
5
-
6
- describe('rateLimit', () => {
7
- it('allows requests under the limit', async () => {
8
- const r = new Router()
9
- .use(rateLimit({ max: 5, window: 60_000 }))
10
- .get('/data', () => new Response('ok'))
11
-
12
- for (let i = 0; i < 5; i++) {
13
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
14
- assert.equal(res.status, 200)
15
- }
16
- })
17
-
18
- it('blocks requests exceeding the limit', async () => {
19
- const r = new Router()
20
- .use(rateLimit({ max: 3, window: 60_000 }))
21
- .get('/data', () => new Response('ok'))
22
-
23
- for (let i = 0; i < 3; i++) {
24
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
25
- assert.equal(res.status, 200)
26
- }
27
-
28
- const res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
29
- assert.equal(res.status, 429)
30
- })
31
-
32
- it('returns rate limit headers', async () => {
33
- const r = new Router()
34
- .use(rateLimit({ max: 2, window: 60_000 }))
35
- .get('/data', () => new Response('ok'))
36
-
37
- let res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
38
- assert.equal(res.headers.get('X-RateLimit-Remaining'), '1')
39
-
40
- res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
41
- assert.equal(res.headers.get('X-RateLimit-Remaining'), '0')
42
-
43
- res = await r.handler()(new Request('http://localhost/data'), { params: {}, query: {} })
44
- assert.equal(res.status, 429)
45
- assert.equal(res.headers.get('X-RateLimit-Limit'), '2')
46
- assert.equal(res.headers.get('X-RateLimit-Remaining'), '0')
47
- assert.ok(res.headers.get('X-RateLimit-Reset'))
48
- assert.ok(res.headers.get('Retry-After'))
49
- })
50
-
51
- it('uses custom key function', async () => {
52
- const keys: string[] = []
53
- const r = new Router()
54
- .use(rateLimit({
55
- max: 1,
56
- window: 60_000,
57
- key: (req) => {
58
- const key = req.headers.get('x-api-key') ?? 'anonymous'
59
- keys.push(key)
60
- return key
61
- },
62
- }))
63
- .get('/data', () => new Response('ok'))
64
-
65
- const req1 = new Request('http://localhost/data', { headers: { 'x-api-key': 'alice' } })
66
- const res1 = await r.handler()(req1, { params: {}, query: {} })
67
- assert.equal(res1.status, 200)
68
-
69
- const res2 = await r.handler()(req1, { params: {}, query: {} })
70
- assert.equal(res2.status, 429)
71
-
72
- const req2 = new Request('http://localhost/data', { headers: { 'x-api-key': 'bob' } })
73
- const res3 = await r.handler()(req2, { params: {}, query: {} })
74
- assert.equal(res3.status, 200)
75
- })
76
-
77
- it('resets after window expires', { timeout: 2000 }, async () => {
78
- const r = new Router()
79
- .use(rateLimit({ max: 1, window: 100 }))
80
- .get('/data', () => new Response('ok'))
81
-
82
- const req = new Request('http://localhost/data')
83
- const res1 = await r.handler()(req, { params: {}, query: {} })
84
- assert.equal(res1.status, 200)
85
-
86
- const res2 = await r.handler()(req, { params: {}, query: {} })
87
- assert.equal(res2.status, 429)
88
-
89
- await new Promise((r) => setTimeout(r, 150))
90
-
91
- const res3 = await r.handler()(req, { params: {}, query: {} })
92
- assert.equal(res3.status, 200)
93
- })
94
- })
@@ -1,93 +0,0 @@
1
- import { describe, it, before, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { writeFile, mkdir, rm } from 'node:fs/promises'
4
- import { resolve } from 'node:path'
5
- import { Router } from '../router.ts'
6
- import { serve } from '../serve.ts'
7
- import { serveStatic } from '../static.ts'
8
-
9
- const tmpDir = resolve(import.meta.dirname, '../.test-static')
10
-
11
- before(async () => {
12
- await mkdir(tmpDir, { recursive: true })
13
- await writeFile(resolve(tmpDir, 'hello.txt'), 'Hello, World!')
14
- await writeFile(resolve(tmpDir, 'index.html'), '<h1>Index</h1>')
15
- await writeFile(resolve(tmpDir, 'script.js'), 'console.log(1)')
16
- await mkdir(resolve(tmpDir, 'sub'), { recursive: true })
17
- await writeFile(resolve(tmpDir, 'sub', 'deep.txt'), 'deep')
18
- })
19
-
20
- after(async () => {
21
- await rm(tmpDir, { recursive: true, force: true })
22
- })
23
-
24
- describe('serveStatic', () => {
25
- it('serves a file', async () => {
26
- const r = new Router().get('/files/*', serveStatic(tmpDir))
27
- const res = await r.handler()(new Request('http://localhost/files/hello.txt'), { params: {}, query: {} })
28
- assert.equal(res.status, 200)
29
- assert.equal(await res.text(), 'Hello, World!')
30
- })
31
-
32
- it('sets Content-Type based on extension', async () => {
33
- const r = new Router().get('/files/*', serveStatic(tmpDir))
34
- const res = await r.handler()(new Request('http://localhost/files/script.js'), { params: {}, query: {} })
35
- assert.ok(res.headers.get('Content-Type')?.includes('javascript'))
36
- })
37
-
38
- it('sets ETag header', async () => {
39
- const r = new Router().get('/files/*', serveStatic(tmpDir))
40
- const res = await r.handler()(new Request('http://localhost/files/hello.txt'), { params: {}, query: {} })
41
- assert.ok(res.headers.get('ETag'))
42
- })
43
-
44
- it('returns 304 on matching ETag', async () => {
45
- const r = new Router().get('/files/*', serveStatic(tmpDir))
46
- const res1 = await r.handler()(new Request('http://localhost/files/hello.txt'), { params: {}, query: {} })
47
- const etag = res1.headers.get('ETag')
48
- const res2 = await r.handler()(
49
- new Request('http://localhost/files/hello.txt', { headers: { 'if-none-match': etag! } }),
50
- { params: {}, query: {} },
51
- )
52
- assert.equal(res2.status, 304)
53
- })
54
-
55
- it('serves index.html for directory', async () => {
56
- const r = new Router().get('/files/*', serveStatic(tmpDir))
57
- const res = await r.handler()(new Request('http://localhost/files/'), { params: {}, query: {} })
58
- assert.equal(res.status, 200)
59
- assert.equal(await res.text(), '<h1>Index</h1>')
60
- })
61
-
62
- it('returns 404 for missing file', async () => {
63
- const r = new Router().get('/files/*', serveStatic(tmpDir))
64
- const res = await r.handler()(new Request('http://localhost/files/nope.txt'), { params: {}, query: {} })
65
- assert.equal(res.status, 404)
66
- })
67
-
68
- it('blocks directory traversal via wildcard param', async () => {
69
- const handler = serveStatic(tmpDir)
70
- const res = await handler(
71
- new Request('http://localhost/ignored'),
72
- { params: { '*': '../../package.json' }, query: {} },
73
- )
74
- assert.equal(res.status, 403)
75
- })
76
-
77
- it('supports nested paths', async () => {
78
- const r = new Router().get('/files/*', serveStatic(tmpDir))
79
- const res = await r.handler()(new Request('http://localhost/files/sub/deep.txt'), { params: {}, query: {} })
80
- assert.equal(res.status, 200)
81
- assert.equal(await res.text(), 'deep')
82
- })
83
-
84
- it('works with serve() end-to-end', async () => {
85
- const r = new Router().get('/static/*', serveStatic(tmpDir))
86
- const server = serve(r.handler(), { port: 0 })
87
- await server.ready
88
- const res = await fetch(`http://localhost:${server.port}/static/hello.txt`)
89
- assert.equal(res.status, 200)
90
- assert.equal(await res.text(), 'Hello, World!')
91
- server.stop()
92
- })
93
- })