@@ -12,6 +12,9 @@ import {
1212 shouldIncludeResource ,
1313 filterResources ,
1414 extractRootApiName ,
15+ isWildcardPattern ,
16+ wildcardToRegex ,
17+ wildcardMatch ,
1518} from '../../../src/services/filter-service.js' ;
1619
1720describe ( 'filter-service' , ( ) => {
@@ -325,4 +328,259 @@ describe('filter-service', () => {
325328 expect ( extractRootApiName ( '' ) ) . toBe ( '' ) ;
326329 } ) ;
327330 } ) ;
331+
332+ describe ( 'isWildcardPattern' , ( ) => {
333+ it ( 'should detect * wildcard' , ( ) => {
334+ expect ( isWildcardPattern ( '*-test' ) ) . toBe ( true ) ;
335+ expect ( isWildcardPattern ( 'prod-*' ) ) . toBe ( true ) ;
336+ expect ( isWildcardPattern ( '*' ) ) . toBe ( true ) ;
337+ } ) ;
338+
339+ it ( 'should detect ? wildcard' , ( ) => {
340+ expect ( isWildcardPattern ( 'api-v?' ) ) . toBe ( true ) ;
341+ expect ( isWildcardPattern ( '?-test' ) ) . toBe ( true ) ;
342+ } ) ;
343+
344+ it ( 'should return false for exact names' , ( ) => {
345+ expect ( isWildcardPattern ( 'my-api' ) ) . toBe ( false ) ;
346+ expect ( isWildcardPattern ( 'prod-api-v2' ) ) . toBe ( false ) ;
347+ } ) ;
348+ } ) ;
349+
350+ describe ( 'wildcardMatch' , ( ) => {
351+ it ( 'should match * against any characters' , ( ) => {
352+ expect ( wildcardMatch ( 'prod-*' , 'prod-api' ) ) . toBe ( true ) ;
353+ expect ( wildcardMatch ( 'prod-*' , 'prod-' ) ) . toBe ( true ) ;
354+ expect ( wildcardMatch ( 'prod-*' , 'dev-api' ) ) . toBe ( false ) ;
355+ } ) ;
356+
357+ it ( 'should match ? against a single character' , ( ) => {
358+ expect ( wildcardMatch ( 'api-v?' , 'api-v1' ) ) . toBe ( true ) ;
359+ expect ( wildcardMatch ( 'api-v?' , 'api-v2' ) ) . toBe ( true ) ;
360+ expect ( wildcardMatch ( 'api-v?' , 'api-v10' ) ) . toBe ( false ) ;
361+ expect ( wildcardMatch ( 'api-v?' , 'api-v' ) ) . toBe ( false ) ;
362+ } ) ;
363+
364+ it ( 'should treat dots in patterns as literal dots, not regex any-char' , ( ) => {
365+ // "myapi.*" should match "myapi.test" but NOT "myapixtest"
366+ expect ( wildcardMatch ( 'myapi.*' , 'myapi.test' ) ) . toBe ( true ) ;
367+ expect ( wildcardMatch ( 'myapi.*' , 'myapi.v2' ) ) . toBe ( true ) ;
368+ expect ( wildcardMatch ( 'myapi.*' , 'myapixtest' ) ) . toBe ( false ) ;
369+ expect ( wildcardMatch ( 'myapi.*' , 'myapi-test' ) ) . toBe ( false ) ;
370+ } ) ;
371+
372+ it ( 'should handle names with dots when matching exact segments' , ( ) => {
373+ // "*.test" should match "echo.test" but not "echo-test"
374+ expect ( wildcardMatch ( '*.test' , 'echo.test' ) ) . toBe ( true ) ;
375+ expect ( wildcardMatch ( '*.test' , 'petstore.test' ) ) . toBe ( true ) ;
376+ expect ( wildcardMatch ( '*.test' , 'echo-test' ) ) . toBe ( false ) ;
377+ expect ( wildcardMatch ( '*.test' , 'echoXtest' ) ) . toBe ( false ) ;
378+ } ) ;
379+
380+ it ( 'should handle names with multiple dots' , ( ) => {
381+ expect ( wildcardMatch ( 'api.v1.*' , 'api.v1.test' ) ) . toBe ( true ) ;
382+ expect ( wildcardMatch ( 'api.v1.*' , 'api.v1.prod' ) ) . toBe ( true ) ;
383+ expect ( wildcardMatch ( 'api.v1.*' , 'api.v2.test' ) ) . toBe ( false ) ;
384+ expect ( wildcardMatch ( '*.v1.*' , 'api.v1.test' ) ) . toBe ( true ) ;
385+ expect ( wildcardMatch ( '*.v1.*' , 'svc.v1.prod' ) ) . toBe ( true ) ;
386+ } ) ;
387+
388+ it ( 'should treat other regex special chars as literals' , ( ) => {
389+ // Names with parentheses, brackets, plus, etc.
390+ expect ( wildcardMatch ( 'api(v1)*' , 'api(v1)-test' ) ) . toBe ( true ) ;
391+ expect ( wildcardMatch ( 'api[1]*' , 'api[1]-prod' ) ) . toBe ( true ) ;
392+ expect ( wildcardMatch ( 'api+v1*' , 'api+v1-test' ) ) . toBe ( true ) ;
393+ } ) ;
394+
395+ it ( 'should be case-insensitive' , ( ) => {
396+ expect ( wildcardMatch ( 'Prod-*' , 'prod-api' ) ) . toBe ( true ) ;
397+ expect ( wildcardMatch ( 'Prod-*' , 'PROD-API' ) ) . toBe ( true ) ;
398+ } ) ;
399+ } ) ;
400+
401+ describe ( 'wildcardToRegex' , ( ) => {
402+ it ( 'should produce a regex that anchors to start and end' , ( ) => {
403+ const regex = wildcardToRegex ( 'prod-*' ) ;
404+ expect ( regex . test ( 'prod-api' ) ) . toBe ( true ) ;
405+ expect ( regex . test ( 'xxprod-api' ) ) . toBe ( false ) ;
406+ } ) ;
407+
408+ it ( 'should escape regex special characters' , ( ) => {
409+ const regex = wildcardToRegex ( 'api.v1.*' ) ;
410+ expect ( regex . test ( 'api.v1.test' ) ) . toBe ( true ) ;
411+ expect ( regex . test ( 'apixv1xtest' ) ) . toBe ( false ) ;
412+ } ) ;
413+ } ) ;
414+
415+ describe ( 'wildcard pattern matching in shouldIncludeResource' , ( ) => {
416+ it ( 'should match APIs ending with a suffix using *-suffix pattern' , ( ) => {
417+ const filter : FilterConfig = { apis : [ '*-test' ] } ;
418+ const included : ResourceDescriptor = {
419+ type : ResourceType . Api ,
420+ nameParts : [ 'echo-test' ] ,
421+ } ;
422+ const excluded : ResourceDescriptor = {
423+ type : ResourceType . Api ,
424+ nameParts : [ 'echo-prod' ] ,
425+ } ;
426+ expect ( shouldIncludeResource ( included , filter ) ) . toBe ( true ) ;
427+ expect ( shouldIncludeResource ( excluded , filter ) ) . toBe ( false ) ;
428+ } ) ;
429+
430+ it ( 'should match APIs starting with a prefix using prefix-* pattern' , ( ) => {
431+ const filter : FilterConfig = { apis : [ 'prod-*' ] } ;
432+ const included : ResourceDescriptor = {
433+ type : ResourceType . Api ,
434+ nameParts : [ 'prod-users-api' ] ,
435+ } ;
436+ const excluded : ResourceDescriptor = {
437+ type : ResourceType . Api ,
438+ nameParts : [ 'dev-users-api' ] ,
439+ } ;
440+ expect ( shouldIncludeResource ( included , filter ) ) . toBe ( true ) ;
441+ expect ( shouldIncludeResource ( excluded , filter ) ) . toBe ( false ) ;
442+ } ) ;
443+
444+ it ( 'should match APIs containing a substring using *-substr-* pattern' , ( ) => {
445+ const filter : FilterConfig = { apis : [ '*-internal-*' ] } ;
446+ const included : ResourceDescriptor = {
447+ type : ResourceType . Api ,
448+ nameParts : [ 'company-internal-users' ] ,
449+ } ;
450+ const excluded : ResourceDescriptor = {
451+ type : ResourceType . Api ,
452+ nameParts : [ 'company-external-users' ] ,
453+ } ;
454+ expect ( shouldIncludeResource ( included , filter ) ) . toBe ( true ) ;
455+ expect ( shouldIncludeResource ( excluded , filter ) ) . toBe ( false ) ;
456+ } ) ;
457+
458+ it ( 'should match all resources with * wildcard' , ( ) => {
459+ const filter : FilterConfig = { apis : [ '*' ] } ;
460+ const descriptor : ResourceDescriptor = {
461+ type : ResourceType . Api ,
462+ nameParts : [ 'any-api-name' ] ,
463+ } ;
464+ expect ( shouldIncludeResource ( descriptor , filter ) ) . toBe ( true ) ;
465+ } ) ;
466+
467+ it ( 'should support ? for single character matching' , ( ) => {
468+ const filter : FilterConfig = { apis : [ 'api-v?' ] } ;
469+ const v1 : ResourceDescriptor = {
470+ type : ResourceType . Api ,
471+ nameParts : [ 'api-v1' ] ,
472+ } ;
473+ const v2 : ResourceDescriptor = {
474+ type : ResourceType . Api ,
475+ nameParts : [ 'api-v2' ] ,
476+ } ;
477+ const v10 : ResourceDescriptor = {
478+ type : ResourceType . Api ,
479+ nameParts : [ 'api-v10' ] ,
480+ } ;
481+ expect ( shouldIncludeResource ( v1 , filter ) ) . toBe ( true ) ;
482+ expect ( shouldIncludeResource ( v2 , filter ) ) . toBe ( true ) ;
483+ expect ( shouldIncludeResource ( v10 , filter ) ) . toBe ( false ) ;
484+ } ) ;
485+
486+ it ( 'should support mixing exact names and wildcard patterns' , ( ) => {
487+ const filter : FilterConfig = { apis : [ 'echo-api' , 'prod-*' ] } ;
488+ const exact : ResourceDescriptor = {
489+ type : ResourceType . Api ,
490+ nameParts : [ 'echo-api' ] ,
491+ } ;
492+ const pattern : ResourceDescriptor = {
493+ type : ResourceType . Api ,
494+ nameParts : [ 'prod-users' ] ,
495+ } ;
496+ const neither : ResourceDescriptor = {
497+ type : ResourceType . Api ,
498+ nameParts : [ 'dev-users' ] ,
499+ } ;
500+ expect ( shouldIncludeResource ( exact , filter ) ) . toBe ( true ) ;
501+ expect ( shouldIncludeResource ( pattern , filter ) ) . toBe ( true ) ;
502+ expect ( shouldIncludeResource ( neither , filter ) ) . toBe ( false ) ;
503+ } ) ;
504+
505+ it ( 'should apply wildcard matching case-insensitively' , ( ) => {
506+ const filter : FilterConfig = { apis : [ 'Prod-*' ] } ;
507+ const descriptor : ResourceDescriptor = {
508+ type : ResourceType . Api ,
509+ nameParts : [ 'PROD-Users-Api' ] ,
510+ } ;
511+ expect ( shouldIncludeResource ( descriptor , filter ) ) . toBe ( true ) ;
512+ } ) ;
513+
514+ it ( 'should apply wildcard matching to non-API resource types' , ( ) => {
515+ const filter : FilterConfig = {
516+ backends : [ 'backend-*-prod' ] ,
517+ products : [ 'test-*' ] ,
518+ namedValues : [ '*-secret' ] ,
519+ } ;
520+ const backend : ResourceDescriptor = {
521+ type : ResourceType . Backend ,
522+ nameParts : [ 'backend-users-prod' ] ,
523+ } ;
524+ const product : ResourceDescriptor = {
525+ type : ResourceType . Product ,
526+ nameParts : [ 'test-starter' ] ,
527+ } ;
528+ const namedValue : ResourceDescriptor = {
529+ type : ResourceType . NamedValue ,
530+ nameParts : [ 'db-connection-secret' ] ,
531+ } ;
532+ const excludedBackend : ResourceDescriptor = {
533+ type : ResourceType . Backend ,
534+ nameParts : [ 'backend-users-dev' ] ,
535+ } ;
536+ expect ( shouldIncludeResource ( backend , filter ) ) . toBe ( true ) ;
537+ expect ( shouldIncludeResource ( product , filter ) ) . toBe ( true ) ;
538+ expect ( shouldIncludeResource ( namedValue , filter ) ) . toBe ( true ) ;
539+ expect ( shouldIncludeResource ( excludedBackend , filter ) ) . toBe ( false ) ;
540+ } ) ;
541+
542+ it ( 'should apply wildcard matching to API revisions by root name' , ( ) => {
543+ const filter : FilterConfig = { apis : [ 'prod-*' ] } ;
544+ const descriptor : ResourceDescriptor = {
545+ type : ResourceType . Api ,
546+ nameParts : [ 'prod-users;rev=3' ] ,
547+ } ;
548+ expect ( shouldIncludeResource ( descriptor , filter ) ) . toBe ( true ) ;
549+ } ) ;
550+
551+ it ( 'should apply wildcard matching to child resources via parent name' , ( ) => {
552+ const filter : FilterConfig = { apis : [ 'prod-*' ] } ;
553+ const apiPolicy : ResourceDescriptor = {
554+ type : ResourceType . ApiPolicy ,
555+ nameParts : [ 'prod-users' ] ,
556+ } ;
557+ const excludedPolicy : ResourceDescriptor = {
558+ type : ResourceType . ApiPolicy ,
559+ nameParts : [ 'dev-users' ] ,
560+ } ;
561+ expect ( shouldIncludeResource ( apiPolicy , filter ) ) . toBe ( true ) ;
562+ expect ( shouldIncludeResource ( excludedPolicy , filter ) ) . toBe ( false ) ;
563+ } ) ;
564+
565+ it ( 'should apply wildcard matching in apiSubFilters operations' , ( ) => {
566+ const filter : FilterConfig = {
567+ apis : [ 'my-api' ] ,
568+ apiSubFilters : {
569+ 'my-api' : {
570+ operations : [ 'get-*' ] ,
571+ } ,
572+ } ,
573+ } ;
574+ const included : ResourceDescriptor = {
575+ type : ResourceType . ApiOperation ,
576+ nameParts : [ 'my-api' , 'get-users' ] ,
577+ } ;
578+ const excluded : ResourceDescriptor = {
579+ type : ResourceType . ApiOperation ,
580+ nameParts : [ 'my-api' , 'post-users' ] ,
581+ } ;
582+ expect ( shouldIncludeResource ( included , filter ) ) . toBe ( true ) ;
583+ expect ( shouldIncludeResource ( excluded , filter ) ) . toBe ( false ) ;
584+ } ) ;
585+ } ) ;
328586} ) ;
0 commit comments