@@ -265,10 +265,10 @@ describe('when excludedPaths is handled', () => {
265265 builderMock . query = queryMock ;
266266
267267 beforeEach ( ( ) => {
268+ vi . clearAllMocks ( ) ;
268269 mocks . generateBaseQueryMock . mockResolvedValue ( builderMock ) ;
269270 queryMock . and . mockReturnValue ( queryMock ) ;
270271
271- // Setup successful flow for count and exec
272272 const queryClonedMock = mock < PageQuery > ( ) ;
273273 queryMock . clone . mockReturnValue ( queryClonedMock ) ;
274274 queryClonedMock . count . mockResolvedValue ( 0 ) ;
@@ -279,45 +279,63 @@ describe('when excludedPaths is handled', () => {
279279 mocks . getToppageViewersCountMock . mockResolvedValue ( 0 ) ;
280280 } ) ;
281281
282- it ( 'does not add path exclusion conditions when excludedPaths is empty' , async ( ) => {
283- // setup
282+ it ( 'adds a sanitized regex exclusion condition with non-capturing groups' , async ( ) => {
284283 const reqMock = mock < IListPagesRequest > ( ) ;
285284 reqMock . query = { pagePath } ;
286285 const resMock = mock < Response > ( ) ;
287286 resMock . status . mockReturnValue ( mock < Response > ( ) ) ;
288287
289- // excludedPaths is empty
290- const handler = listPages ( { excludedPaths : [ ] } ) ;
288+ // Includes a path with meta-characters to test sanitization
289+ const excludedPaths = [ '/user' , 'tmp.bak' ] ;
290+ const handler = listPages ( { excludedPaths } ) ;
291291 await handler ( reqMock , resMock ) ;
292292
293- // query.and should NOT be called with a $not regex for paths
294- expect ( queryMock . and ) . not . toHaveBeenCalledWith (
295- expect . arrayContaining ( [
296- expect . objectContaining ( {
297- path : expect . objectContaining ( { $not : expect . any ( RegExp ) } ) ,
298- } ) ,
299- ] ) ,
300- ) ;
293+ // Matches the updated logic: non-capturing groups and escaped dots
294+ const expectedRegex = / ^ \/ (?: u s e r | t m p \. b a k ) (?: \/ | $ ) / ;
295+ expect ( queryMock . and ) . toHaveBeenCalledWith ( [
296+ {
297+ path : { $not : expectedRegex } ,
298+ } ,
299+ ] ) ;
301300 } ) ;
302301
303- it ( 'adds a regex exclusion condition when excludedPaths is specified' , async ( ) => {
304- // setup
302+ it ( 'filters out empty paths to prevent invalid regex or broad exclusion' , async ( ) => {
305303 const reqMock = mock < IListPagesRequest > ( ) ;
306304 reqMock . query = { pagePath } ;
307305 const resMock = mock < Response > ( ) ;
308306 resMock . status . mockReturnValue ( mock < Response > ( ) ) ;
309307
310- // excludedPaths provided
311- const excludedPaths = [ '/user ' , '/tmp ' ] ;
308+ // Input contains a slash (which becomes empty after cleaning) and an empty string
309+ const excludedPaths = [ '/' , '' , '/valid '] ;
312310 const handler = listPages ( { excludedPaths } ) ;
313311 await handler ( reqMock , resMock ) ;
314312
315- // check if the logic generates the correct regex: ^\/(user|tmp)(\/|$)
316- const expectedRegex = / ^ \/ ( u s e r | t m p ) ( \/ | $ ) / ;
313+ // The regex should only contain 'valid'
314+ const expectedRegex = / ^ \/ (?: v a l i d ) (?: \/ | $ ) / ;
315+
317316 expect ( queryMock . and ) . toHaveBeenCalledWith ( [
318317 {
319318 path : { $not : expectedRegex } ,
320319 } ,
321320 ] ) ;
322321 } ) ;
322+
323+ it ( 'does not call query.and if all provided excludedPaths are empty strings' , async ( ) => {
324+ const reqMock = mock < IListPagesRequest > ( ) ;
325+ reqMock . query = { pagePath } ;
326+ const resMock = mock < Response > ( ) ;
327+ resMock . status . mockReturnValue ( mock < Response > ( ) ) ;
328+
329+ const handler = listPages ( { excludedPaths : [ '/' , '' ] } ) ;
330+ await handler ( reqMock , resMock ) ;
331+
332+ // Should behave like the empty array case
333+ expect ( queryMock . and ) . not . toHaveBeenCalledWith (
334+ expect . arrayContaining ( [
335+ expect . objectContaining ( {
336+ path : expect . objectContaining ( { $not : expect . any ( RegExp ) } ) ,
337+ } ) ,
338+ ] ) ,
339+ ) ;
340+ } ) ;
323341} ) ;
0 commit comments