11import {
22 buildSearchString ,
3+ createError ,
34 freezeObject ,
45 generateHash ,
56 hashCode ,
@@ -8,11 +9,13 @@ import {
89 isPromise ,
910 isReferenceLike ,
1011 isUrl ,
12+ isUrlObject ,
1113 isPath ,
1214 isWhitelistedUrl ,
1315 listAllCombinations ,
1416 listIncrementalCombinations ,
1517 mergeObjects ,
18+ parseUrl ,
1619 portValid ,
1720 splitUri ,
1821 stringJoin ,
@@ -57,12 +60,66 @@ describe('buildSearchString', () => {
5760 lorem : 'ipsum dolor'
5861 } ,
5962 expected : 'lorem=ipsum+dolor'
63+ } ,
64+ {
65+ description : 'prefix with base that has query' ,
66+ values : {
67+ lorem : 'ipsum'
68+ } ,
69+ options : {
70+ prefix : true ,
71+ base : 'patternfly://docs?version=1'
72+ } ,
73+ expected : '&lorem=ipsum'
6074 }
6175 ] ) ( 'should build a search string, $description' , ( { values, options, expected } ) => {
6276 expect ( buildSearchString ( values , options || { } ) ) . toBe ( expected ) ;
6377 } ) ;
6478} ) ;
6579
80+ describe ( 'createError' , ( ) => {
81+ it . each ( [
82+ {
83+ description : 'use an explicit message and metadata' ,
84+ message : 'Custom error message' ,
85+ metadata : { code : 404 , details : 'Not found' } ,
86+ expectedMessage : 'Custom error message' ,
87+ expectedMetadata : { code : 404 , details : 'Not found' }
88+ } ,
89+ {
90+ description : 'use an Error object as message' ,
91+ message : new Error ( 'Original error message' ) ,
92+ metadata : { path : '/tmp' } ,
93+ expectedMessage : 'Original error message' ,
94+ expectedMetadata : { path : '/tmp' }
95+ } ,
96+ {
97+ description : 'use cause message when message is undefined' ,
98+ message : undefined ,
99+ options : { cause : new Error ( 'Cause error' ) } ,
100+ metadata : { retry : true } ,
101+ expectedMessage : 'Cause error' ,
102+ expectedMetadata : { retry : true }
103+ } ,
104+ {
105+ description : 'use fallback message when nothing else is provided' ,
106+ message : undefined ,
107+ metadata : { } ,
108+ expectedMessage : 'An error occurred' ,
109+ expectedMetadata : { }
110+ }
111+ ] ) ( 'should create an error, $description' , ( { message, options, metadata, expectedMessage, expectedMetadata } : any ) => {
112+ const err = createError ( message , options || { } , metadata ) ;
113+
114+ expect ( err ) . toBeInstanceOf ( Error ) ;
115+ expect ( err . message ) . toBe ( expectedMessage ) ;
116+ Object . entries ( expectedMetadata ) . forEach ( ( [ key , value ] ) => {
117+ expect ( ( err as any ) [ key ] ) . toBe ( value ) ;
118+ } ) ;
119+ expect ( err . cause ) . toBe ( options ?. cause ) ;
120+ } ) ;
121+ } ) ;
122+
66123describe ( 'freezeObject' , ( ) => {
67124 it . each ( [
68125 {
@@ -463,6 +520,55 @@ describe('isPromise', () => {
463520 } ) ;
464521} ) ;
465522
523+ describe ( 'isUrlObject' , ( ) => {
524+ it . each ( [
525+ {
526+ description : 'valid URL object with no protocol restrictions' ,
527+ obj : new URL ( 'https://patternfly.org' ) ,
528+ options : { } ,
529+ expected : true
530+ } ,
531+ {
532+ description : 'valid URL object with matching allowed protocol' ,
533+ obj : new URL ( 'patternfly://docs' ) ,
534+ options : { allowedProtocols : [ 'patternfly' ] } ,
535+ expected : true
536+ } ,
537+ {
538+ description : 'valid URL object with non-matching allowed protocol' ,
539+ obj : new URL ( 'http://patternfly.org' ) ,
540+ options : { allowedProtocols : [ 'https' ] } ,
541+ expected : false
542+ } ,
543+ {
544+ description : 'invalid input (string instead of URL object)' ,
545+ obj : 'https://patternfly.org' ,
546+ options : { } ,
547+ expected : false
548+ } ,
549+ {
550+ description : 'invalid input, null' ,
551+ obj : null ,
552+ options : { } ,
553+ expected : false
554+ } ,
555+ {
556+ description : 'invalid input, undefined' ,
557+ obj : undefined ,
558+ options : { } ,
559+ expected : false
560+ } ,
561+ {
562+ description : 'invalid input, empty string' ,
563+ obj : ' ' ,
564+ options : { } ,
565+ expected : false
566+ }
567+ ] ) ( 'should return $expected for $description' , ( { obj, options, expected } ) => {
568+ expect ( isUrlObject ( obj , options ) ) . toBe ( expected ) ;
569+ } ) ;
570+ } ) ;
571+
466572describe ( 'isPlainObject' , ( ) => {
467573 it . each ( [
468574 {
@@ -729,6 +835,11 @@ describe('isWhitelistedUrl', () => {
729835 description : 'invalid URL string' ,
730836 url : 'not-a-url' ,
731837 expected : false
838+ } ,
839+ {
840+ description : 'reject encoded slash in pathname' ,
841+ url : 'https://github.com/patternfly%2Fother-repo' ,
842+ expected : false
732843 }
733844 ] ) ( 'should confirm if a URL is whitelisted, $description' , ( { url, expected } ) => {
734845 const whitelist = [
@@ -950,6 +1061,84 @@ describe('portValid', () => {
9501061 } ) ;
9511062} ) ;
9521063
1064+ describe ( 'parseUrl' , ( ) => {
1065+ it . each ( [
1066+ {
1067+ description : 'absolute URI without prefix' ,
1068+ uri : 'patternfly://docs/button?v=1' ,
1069+ options : { isStrict : false } ,
1070+ expected : {
1071+ protocol : 'patternfly:' ,
1072+ hostname : 'docs' ,
1073+ path : 'button' ,
1074+ params : { v : '1' }
1075+ }
1076+ } ,
1077+ {
1078+ description : 'relative URI with prefix' ,
1079+ uri : 'docs/button' ,
1080+ options : { prefix : 'patternfly' , isStrict : false } ,
1081+ expected : {
1082+ protocol : 'patternfly:' ,
1083+ hostname : 'docs' ,
1084+ path : 'button' ,
1085+ params : { }
1086+ }
1087+ } ,
1088+ {
1089+ description : 'absolute URI with ignored prefix' ,
1090+ uri : 'https://google.com/search?q=test' ,
1091+ options : { prefix : 'patternfly://' } ,
1092+ expected : {
1093+ protocol : 'https:' ,
1094+ hostname : 'google.com' ,
1095+ path : 'search' ,
1096+ params : { q : 'test' }
1097+ }
1098+ } ,
1099+ {
1100+ description : 'invalid URI' ,
1101+ uri : 'not a uri' ,
1102+ expected : undefined
1103+ } ,
1104+ {
1105+ description : 'relative URI without prefix' ,
1106+ uri : 'docs/button' ,
1107+ expected : undefined
1108+ } ,
1109+ {
1110+ description : 'absolute URI without hostname' ,
1111+ uri : 'patternfly:docs/button' ,
1112+ options : { isStrict : false } ,
1113+ expected : {
1114+ protocol : 'patternfly:' ,
1115+ hostname : '' ,
1116+ path : 'docs/button' ,
1117+ params : { }
1118+ }
1119+ } ,
1120+ {
1121+ description : 'pass through a malformed percent-encoding in absolute URI' ,
1122+ uri : 'https://patternfly.org/docs/bad%zzpath' ,
1123+ options : { } ,
1124+ expected : {
1125+ protocol : 'https:' ,
1126+ hostname : 'patternfly.org' ,
1127+ path : 'docs/bad%zzpath' ,
1128+ params : { }
1129+ }
1130+ } ,
1131+ {
1132+ description : 'return undefined for malformed percent-encoding in prefixed path' ,
1133+ uri : 'docs/bad%zzpath' ,
1134+ options : { prefix : 'patternfly' } ,
1135+ expected : undefined
1136+ }
1137+ ] ) ( 'should parse a URI, $description' , ( { uri, options, expected } ) => {
1138+ expect ( parseUrl ( uri , options ) ) . toEqual ( expected ) ;
1139+ } ) ;
1140+ } ) ;
1141+
9531142describe ( 'splitUri' , ( ) => {
9541143 it . each ( [
9551144 {
0 commit comments