@@ -6,7 +6,8 @@ import { rollup } from 'rollup'
66import { build } from 'tsdown'
77import tsdownConfig from '../tsdown.config.ts'
88import { resolveModuleExportNames } from '@nuxt/kit/internal'
9- import { createEvent } from 'h3'
9+ import fc from 'fast-check'
10+ import { createApp , createEvent , createRouter , eventHandler , toPlainHandler } from 'h3'
1011import type { H3Event } from 'h3'
1112import * as shipped from '../../nuxt/src/server/index.ts'
1213import type { RequestEvent } from '../../nuxt/src/server/index.ts'
@@ -133,6 +134,70 @@ describe('the shape of what it reads off an h3 v1 event', () => {
133134 expect ( delegate . getRouterParams ( e ) ) . toEqual ( { id : 'a b' } )
134135 } )
135136
137+ describe . each ( [ '/' , '/base/' ] ) ( 'router params routed by h3 under %s' , ( base ) => {
138+ const router = createRouter ( { preemptive : true } )
139+ for ( const route of [ '/api/auth/**:slug' , '/api/users/:id' , '/api/users/:id/**:rest' , '/api/items/*/x' ] ) {
140+ router . use ( route , eventHandler ( event => ( {
141+ matched : event . context . params ,
142+ params : delegate . getRouterParams ( event ) ,
143+ decoded : delegate . getRouterParams ( event , { decode : true } ) ,
144+ } ) ) )
145+ }
146+ const app = createApp ( )
147+ app . use ( base , router . handler )
148+ const handle = toPlainHandler ( app )
149+
150+ async function request ( path : string ) {
151+ const response = await handle ( { method : 'GET' , path : base . slice ( 0 , - 1 ) + path , headers : { } } )
152+ expect ( response . status ) . toBe ( 200 )
153+ return JSON . parse ( response . body as string ) as Record < 'matched' | 'params' | 'decoded' , Record < string , string > >
154+ }
155+
156+ it . for ( [
157+ [ '/api/auth/private%3Fignored/public' , { slug : 'private' } ] ,
158+ [ '/api/auth/private%3fignored/public' , { slug : 'private' } ] ,
159+ [ '/api/auth/a/b%3F/c' , { slug : 'a/b' } ] ,
160+ [ '/api/auth/a/%3Fb/c' , { slug : 'a' } ] ,
161+ [ '/api/auth/a%3Fb?c=%3F/d' , { slug : 'a' } ] ,
162+ [ '/api/auth/a%253Fb/c' , { slug : 'a%253Fb/c' } ] ,
163+ [ '/api/auth/a%23b/c' , { slug : 'a%23b/c' } ] ,
164+ [ '/api/auth/a%2Fb/c' , { slug : 'a%2Fb/c' } ] ,
165+ [ '/api/auth/a%2F%3Fb/c' , { slug : 'a%2F' } ] ,
166+ [ '/api/auth/caf%C3%A9/' , { slug : 'caf%C3%A9' } ] ,
167+ [ '/api/auth/a//' , { slug : 'a/' } ] ,
168+ [ '/api/auth/a%20b?x=1' , { slug : 'a%20b' } ] ,
169+ [ '/api/users/a%3Fb' , { id : 'a' } ] ,
170+ [ '/api/users/a%20b/' , { id : 'a%20b' } ] ,
171+ [ '/api/users/a%20b/c%3F/d' , { id : 'a%20b' , rest : 'c' } ] ,
172+ [ '/api/users/a%3F/b/c' , { id : 'a' } ] ,
173+ [ '/api/items/a%2Fb/x' , { _0 : 'a%2Fb' } ] ,
174+ [ '/api/items/a%23b/x?y' , { _0 : 'a%23b' } ] ,
175+ ] as const ) ( 'reads %s as h3 matched it' , async ( [ path , params ] ) => {
176+ const { matched, params : encoded , decoded } = await request ( path )
177+ expect ( encoded ) . toEqual ( params )
178+ for ( const key in matched ) {
179+ expect ( decoded [ key ] ) . toBe ( shipped . getRouterParam ( { context : { params : matched } } , key , { decode : true } ) )
180+ }
181+ } )
182+
183+ it ( 'reads the same params h3 matched for any path' , async ( ) => {
184+ const segment = fc . array ( fc . constantFrom ( 'a' , 'é' , '%20' , '%3F' , '%3f' , '%23' , '%2F' , '%25' , '%253F' , '%2525' , '%C3%A9' , '?' , '#' , '' ) , { maxLength : 4 } ) . map ( parts => parts . join ( '' ) )
185+ const prefix = fc . constantFrom ( '/api/auth/' , '/api/users/' , '/api/items/' )
186+ await fc . assert ( fc . asyncProperty ( prefix , fc . array ( segment , { minLength : 1 , maxLength : 4 } ) , async ( prefix , segments ) => {
187+ const path = prefix + segments . join ( '/' )
188+ const response = await handle ( { method : 'GET' , path : base . slice ( 0 , - 1 ) + path , headers : { } } )
189+ if ( response . status !== 200 ) {
190+ return
191+ }
192+ const { matched, params, decoded } = JSON . parse ( response . body as string )
193+ expect ( decoded ) . toEqual ( shipped . getRouterParams ( { context : { params : matched } } , { decode : true } ) )
194+ for ( const value of Object . values ( params ) ) {
195+ expect ( path ) . toContain ( value )
196+ }
197+ } ) )
198+ } )
199+ } )
200+
136201 it ( 'reads the client IP from the socket, and the first forwarded hop only when opted in' , ( ) => {
137202 const e = event ( '/' , { headers : { 'x-forwarded-for' : '203.0.113.1, 10.0.0.1' } , ip : '198.51.100.7' } )
138203 expect ( delegate . getRequestIP ( e ) ) . toBe ( '198.51.100.7' )
0 commit comments