@@ -703,6 +703,72 @@ const matchDynamicPath = (pattern, path) => {
703703 return { match : false } ;
704704} ;
705705
706+ const parseQueryString = ( queryString ) => {
707+ // Parse query string into key-value pairs
708+ const params = { } ;
709+ if ( ! queryString ) return params ;
710+
711+ const pairs = queryString . split ( '&' ) ;
712+ for ( const pair of pairs ) {
713+ const equalIndex = pair . indexOf ( '=' ) ;
714+ if ( equalIndex === - 1 ) {
715+ // No = sign, treat as key with empty value
716+ const key = decodeURIComponent ( pair ) ;
717+ if ( key ) {
718+ params [ key ] = '' ;
719+ }
720+ } else {
721+ const key = decodeURIComponent ( pair . substring ( 0 , equalIndex ) ) ;
722+ const value = decodeURIComponent ( pair . substring ( equalIndex + 1 ) ) ;
723+ if ( key ) {
724+ params [ key ] = value ;
725+ }
726+ }
727+ }
728+ return params ;
729+ } ;
730+
731+ const matchDynamicQuery = ( patternQuery , requestQuery ) => {
732+ // Match query strings with dynamic variables
733+ // Pattern: start_date={start}&end_date={end}
734+ // Request: start_date=test-value&end_date=test-value2
735+
736+ const patternParams = parseQueryString ( patternQuery ) ;
737+ const requestParams = parseQueryString ( requestQuery ) ;
738+
739+ // Check if all pattern keys exist in request
740+ for ( const [ key , patternValue ] of Object . entries ( patternParams ) ) {
741+ if ( ! ( key in requestParams ) ) {
742+ return { match : false } ;
743+ }
744+
745+ // If pattern value is a dynamic variable {var}, it matches any value
746+ if ( patternValue . startsWith ( '{' ) && patternValue . endsWith ( '}' ) ) {
747+ // This is a dynamic variable, it matches any value
748+ continue ;
749+ }
750+
751+ // Otherwise, values must match exactly
752+ if ( patternValue !== requestParams [ key ] ) {
753+ return { match : false } ;
754+ }
755+ }
756+
757+ // Check if request has extra params not in pattern (optional - you might want to allow this)
758+ // For now, we'll allow extra params in the request
759+
760+ // Extract dynamic parameter values
761+ const params = { } ;
762+ for ( const [ key , patternValue ] of Object . entries ( patternParams ) ) {
763+ if ( patternValue . startsWith ( '{' ) && patternValue . endsWith ( '}' ) ) {
764+ const paramName = patternValue . slice ( 1 , - 1 ) ;
765+ params [ paramName ] = requestParams [ key ] ;
766+ }
767+ }
768+
769+ return { match : true , params } ;
770+ } ;
771+
706772// Dynamic mock routing - this should be last to catch all routes
707773app . use ( ( req , res , next ) => {
708774 // Skip static files and management API routes
@@ -739,30 +805,56 @@ app.use((req, res, next) => {
739805 // If no exact match, try dynamic path matching (without query params for path matching)
740806 if ( ! endpoint ) {
741807 const pathWithoutQuery = requestPath . split ( '?' ) [ 0 ] ;
808+ const reqQuery = requestPath . includes ( '?' ) ? requestPath . split ( '?' ) [ 1 ] : '' ;
809+
742810 endpoint = endpoints . find ( ep => {
743811 if ( ep . method !== method ) return false ;
744812
745813 const epPathWithoutQuery = ep . path . split ( '?' ) [ 0 ] ;
814+ const epQuery = ep . path . includes ( '?' ) ? ep . path . split ( '?' ) [ 1 ] : '' ;
815+
816+ // Check if path has dynamic segments
817+ const pathHasDynamic = epPathWithoutQuery . includes ( '{' ) ;
818+
819+ // Match path part
820+ let pathMatch = false ;
821+ let pathParams = { } ;
746822
747- // Check if this endpoint has dynamic segments (contains {})
748- if ( epPathWithoutQuery . includes ( '{' ) ) {
823+ if ( pathHasDynamic ) {
749824 const matchResult = matchDynamicPath ( epPathWithoutQuery , pathWithoutQuery ) ;
750825 if ( matchResult . match ) {
751- // Check if query params also match
752- const epQuery = ep . path . includes ( '?' ) ? ep . path . split ( '?' ) [ 1 ] : '' ;
753- const reqQuery = requestPath . includes ( '?' ) ? requestPath . split ( '?' ) [ 1 ] : '' ;
754-
755- // If endpoint has query params, they must match exactly
756- if ( epQuery && epQuery !== reqQuery ) {
757- return false ;
758- }
759-
760- // Store the matched parameters in the request object for potential use
761- req . dynamicParams = matchResult . params ;
762- return true ;
826+ pathMatch = true ;
827+ pathParams = matchResult . params ;
828+ }
829+ } else if ( epPathWithoutQuery === pathWithoutQuery ) {
830+ pathMatch = true ;
831+ }
832+
833+ if ( ! pathMatch ) {
834+ return false ;
835+ }
836+
837+ // Match query part
838+ if ( epQuery && reqQuery ) {
839+ // Both have query params - match them
840+ const queryMatch = matchDynamicQuery ( epQuery , reqQuery ) ;
841+ if ( ! queryMatch . match ) {
842+ return false ;
763843 }
844+ // Merge query params with path params
845+ req . dynamicParams = { ...pathParams , ...queryMatch . params } ;
846+ } else if ( epQuery && ! reqQuery ) {
847+ // Endpoint expects query params but request doesn't have them
848+ return false ;
849+ } else if ( ! epQuery && reqQuery ) {
850+ // Endpoint doesn't have query params but request does - this is OK
851+ req . dynamicParams = pathParams ;
852+ } else {
853+ // Neither has query params
854+ req . dynamicParams = pathParams ;
764855 }
765- return false ;
856+
857+ return true ;
766858 } ) ;
767859 }
768860
0 commit comments