|
| 1 | +import { |
| 2 | + addParam, addParams, removeParam, parseLinkHeader, isMaybeSecure, portMatch, parse, stringify |
| 3 | +} from '@shell/utils/url'; |
| 4 | + |
| 5 | +describe('fx: addParam', () => { |
| 6 | + it('should add a query parameter to a URL without existing params', () => { |
| 7 | + expect(addParam('https://example.com/path', 'foo', 'bar')).toStrictEqual('https://example.com/path?foo=bar'); |
| 8 | + }); |
| 9 | + |
| 10 | + it('should append a query parameter to a URL with existing params', () => { |
| 11 | + expect(addParam('https://example.com/path?a=1', 'b', '2')).toStrictEqual('https://example.com/path?a=1&b=2'); |
| 12 | + }); |
| 13 | + |
| 14 | + it('should encode special characters in key and value', () => { |
| 15 | + expect(addParam('https://example.com', 'my key', 'hello world')).toStrictEqual('https://example.com?my%20key=hello%20world'); |
| 16 | + }); |
| 17 | + |
| 18 | + it('should add multiple values from an array', () => { |
| 19 | + expect(addParam('https://example.com', 'tag', ['a', 'b'])).toStrictEqual('https://example.com?tag=a&tag=b'); |
| 20 | + }); |
| 21 | + |
| 22 | + it('should add a key-only param when value is null', () => { |
| 23 | + expect(addParam('https://example.com', 'flag', null)).toStrictEqual('https://example.com?flag'); |
| 24 | + }); |
| 25 | + |
| 26 | + it('should handle an array with a null value', () => { |
| 27 | + expect(addParam('https://example.com', 'flag', [null])).toStrictEqual('https://example.com?flag'); |
| 28 | + }); |
| 29 | + |
| 30 | + it('should add a param with an empty string value', () => { |
| 31 | + expect(addParam('https://example.com', 'key', '')).toStrictEqual('https://example.com?key='); |
| 32 | + }); |
| 33 | + |
| 34 | + it('should add a duplicate key as an additional param', () => { |
| 35 | + expect(addParam('https://example.com?a=1', 'a', '2')).toStrictEqual('https://example.com?a=1&a=2'); |
| 36 | + }); |
| 37 | +}); |
| 38 | + |
| 39 | +describe('fx: addParams', () => { |
| 40 | + it('should add multiple parameters to a URL', () => { |
| 41 | + expect(addParams('https://example.com', { a: '1', b: '2' })).toStrictEqual('https://example.com?a=1&b=2'); |
| 42 | + }); |
| 43 | + |
| 44 | + it('should return the URL unchanged if params is empty', () => { |
| 45 | + expect(addParams('https://example.com', {})).toStrictEqual('https://example.com'); |
| 46 | + }); |
| 47 | + |
| 48 | + it('should return the URL unchanged if params is null', () => { |
| 49 | + expect(addParams('https://example.com', null)).toStrictEqual('https://example.com'); |
| 50 | + }); |
| 51 | + |
| 52 | + it('should return the URL unchanged if params is a non-object value', () => { |
| 53 | + expect(addParams('https://example.com', 'not-an-object')).toStrictEqual('https://example.com'); |
| 54 | + }); |
| 55 | +}); |
| 56 | + |
| 57 | +describe('fx: removeParam', () => { |
| 58 | + it('should remove a query parameter from a URL', () => { |
| 59 | + expect(removeParam('https://example.com?foo=bar&baz=qux', 'foo')).toStrictEqual('https://example.com/?baz=qux'); |
| 60 | + }); |
| 61 | + |
| 62 | + it('should return a normalized URL if the param does not exist', () => { |
| 63 | + expect(removeParam('https://example.com?a=1', 'nonexistent')).toStrictEqual('https://example.com/?a=1'); |
| 64 | + }); |
| 65 | + |
| 66 | + it('should remove the only query parameter', () => { |
| 67 | + expect(removeParam('https://example.com?only=param', 'only')).toStrictEqual('https://example.com/'); |
| 68 | + }); |
| 69 | + |
| 70 | + it('should normalize a key-only query parameter to key= (parser treats it as empty value)', () => { |
| 71 | + expect(removeParam('https://example.com?flag', 'flag')).toStrictEqual('https://example.com/?flag='); |
| 72 | + }); |
| 73 | +}); |
| 74 | + |
| 75 | +describe('fx: parseLinkHeader', () => { |
| 76 | + it('should parse a single link header entry', () => { |
| 77 | + expect(parseLinkHeader('<https://example.com/page2>; rel="next"')).toStrictEqual({ next: 'https://example.com/page2' }); |
| 78 | + }); |
| 79 | + |
| 80 | + it('should parse multiple link header entries', () => { |
| 81 | + const header = '<https://example.com/page2>; rel="next", <https://example.com/page1>; rel="prev"'; |
| 82 | + |
| 83 | + expect(parseLinkHeader(header)).toStrictEqual({ |
| 84 | + next: 'https://example.com/page2', |
| 85 | + prev: 'https://example.com/page1', |
| 86 | + }); |
| 87 | + }); |
| 88 | + |
| 89 | + it('should return an empty object for an empty string', () => { |
| 90 | + expect(parseLinkHeader('')).toStrictEqual({}); |
| 91 | + }); |
| 92 | + |
| 93 | + it('should return an empty object for a malformed header', () => { |
| 94 | + expect(parseLinkHeader('not a valid link header')).toStrictEqual({}); |
| 95 | + }); |
| 96 | + |
| 97 | + it('should lowercase the rel value', () => { |
| 98 | + expect(parseLinkHeader('<https://example.com>; rel="Next"')).toStrictEqual({ next: 'https://example.com' }); |
| 99 | + }); |
| 100 | +}); |
| 101 | + |
| 102 | +describe('fx: portMatch', () => { |
| 103 | + it.each([ |
| 104 | + { |
| 105 | + ports: [443], equals: [443, 8443], endsWith: [], expected: true, desc: 'port is in the equals list' |
| 106 | + }, |
| 107 | + { |
| 108 | + ports: [8080], equals: [443, 8443], endsWith: ['443'], expected: false, desc: 'port is not in equals or endsWith lists' |
| 109 | + }, |
| 110 | + { |
| 111 | + ports: [8443], equals: [], endsWith: ['443'], expected: true, desc: 'port string ends with the given suffix' |
| 112 | + }, |
| 113 | + { |
| 114 | + ports: [443], equals: [], endsWith: ['443'], expected: false, desc: 'port equals the suffix exactly (endsWith excludes exact match)' |
| 115 | + }, |
| 116 | + { |
| 117 | + ports: [], equals: [443], endsWith: ['443'], expected: false, desc: 'ports array is empty' |
| 118 | + }, |
| 119 | + { |
| 120 | + ports: [80, 443], equals: [443], endsWith: [], expected: true, desc: 'any port in the array matches equals' |
| 121 | + }, |
| 122 | + { |
| 123 | + ports: [18443], equals: [], endsWith: ['443'], expected: true, desc: 'multi-digit port ending with suffix' |
| 124 | + }, |
| 125 | + ])('should return $expected when $desc', ({ |
| 126 | + ports, equals, endsWith, expected |
| 127 | + }) => { |
| 128 | + expect(portMatch(ports, equals, endsWith)).toBe(expected); |
| 129 | + }); |
| 130 | +}); |
| 131 | + |
| 132 | +describe('fx: isMaybeSecure', () => { |
| 133 | + it.each([ |
| 134 | + { |
| 135 | + port: 80, proto: 'https', expected: true, desc: 'https protocol' |
| 136 | + }, |
| 137 | + { |
| 138 | + port: 80, proto: 'HTTPS', expected: true, desc: 'HTTPS protocol (case-insensitive)' |
| 139 | + }, |
| 140 | + { |
| 141 | + port: 443, proto: 'http', expected: true, desc: 'port 443' |
| 142 | + }, |
| 143 | + { |
| 144 | + port: 8443, proto: 'http', expected: true, desc: 'port 8443' |
| 145 | + }, |
| 146 | + { |
| 147 | + port: 18443, proto: 'http', expected: true, desc: 'port 18443 (endsWith 443)' |
| 148 | + }, |
| 149 | + { |
| 150 | + port: 80, proto: 'http', expected: false, desc: 'http on non-secure port' |
| 151 | + }, |
| 152 | + ])('should return $expected for $desc', ({ port, proto, expected }) => { |
| 153 | + expect(isMaybeSecure(port, proto)).toBe(expected); |
| 154 | + }); |
| 155 | +}); |
| 156 | + |
| 157 | +describe('fx: parse', () => { |
| 158 | + it('should parse a simple URL', () => { |
| 159 | + const result = parse('https://example.com/path?foo=bar'); |
| 160 | + |
| 161 | + expect(result.protocol).toStrictEqual('https'); |
| 162 | + expect(result.host).toStrictEqual('example.com'); |
| 163 | + expect(result.path).toStrictEqual('/path'); |
| 164 | + expect(result.query).toStrictEqual({ foo: 'bar' }); |
| 165 | + }); |
| 166 | + |
| 167 | + it('should parse a URL with port', () => { |
| 168 | + const result = parse('https://example.com:8080/'); |
| 169 | + |
| 170 | + expect(result.host).toStrictEqual('example.com'); |
| 171 | + expect(result.port).toStrictEqual('8080'); |
| 172 | + }); |
| 173 | + |
| 174 | + it('should parse a URL with user credentials', () => { |
| 175 | + const result = parse('https://user:pass@example.com/'); |
| 176 | + |
| 177 | + expect(result.user).toStrictEqual('user'); |
| 178 | + expect(result.password).toStrictEqual('pass'); |
| 179 | + }); |
| 180 | + |
| 181 | + it('should parse a URL with anchor', () => { |
| 182 | + const result = parse('https://example.com/page#section1'); |
| 183 | + |
| 184 | + expect(result.anchor).toStrictEqual('section1'); |
| 185 | + }); |
| 186 | + |
| 187 | + it('should parse a URL with multiple query params', () => { |
| 188 | + expect(parse('https://example.com?a=1&b=2').query).toStrictEqual({ a: '1', b: '2' }); |
| 189 | + }); |
| 190 | + |
| 191 | + it('should parse a URL with user only (no password)', () => { |
| 192 | + const result = parse('https://admin@example.com/'); |
| 193 | + |
| 194 | + expect(result.user).toStrictEqual('admin'); |
| 195 | + expect(result.password).toStrictEqual(''); |
| 196 | + }); |
| 197 | + |
| 198 | + it('should set empty strings for missing optional fields', () => { |
| 199 | + const result = parse('https://example.com/path'); |
| 200 | + |
| 201 | + expect(result.port).toStrictEqual(''); |
| 202 | + expect(result.anchor).toStrictEqual(''); |
| 203 | + expect(result.user).toStrictEqual(''); |
| 204 | + expect(result.password).toStrictEqual(''); |
| 205 | + }); |
| 206 | +}); |
| 207 | + |
| 208 | +describe('fx: stringify', () => { |
| 209 | + it('should reconstruct a simple URL', () => { |
| 210 | + expect(stringify(parse('https://example.com/path'))).toStrictEqual('https://example.com/path'); |
| 211 | + }); |
| 212 | + |
| 213 | + it('should include user and password when both present', () => { |
| 214 | + expect(stringify(parse('https://user:pass@example.com/'))).toStrictEqual('https://user:pass@example.com/'); |
| 215 | + }); |
| 216 | + |
| 217 | + it('should include user only when password is absent', () => { |
| 218 | + expect(stringify(parse('https://admin@example.com/'))).toStrictEqual('https://admin@example.com/'); |
| 219 | + }); |
| 220 | + |
| 221 | + it('should include port when present', () => { |
| 222 | + expect(stringify(parse('https://example.com:9090/'))).toStrictEqual('https://example.com:9090/'); |
| 223 | + }); |
| 224 | + |
| 225 | + it('should include anchor when present', () => { |
| 226 | + expect(stringify(parse('https://example.com/page#section'))).toStrictEqual('https://example.com/page#section'); |
| 227 | + }); |
| 228 | + |
| 229 | + it('should default path to / when path is empty', () => { |
| 230 | + const parsed = parse('https://example.com'); |
| 231 | + |
| 232 | + parsed.path = ''; |
| 233 | + |
| 234 | + expect(stringify(parsed)).toStrictEqual('https://example.com/'); |
| 235 | + }); |
| 236 | + |
| 237 | + it('should include query parameters', () => { |
| 238 | + expect(stringify(parse('https://example.com/path?a=1&b=2'))).toStrictEqual('https://example.com/path?a=1&b=2'); |
| 239 | + }); |
| 240 | + |
| 241 | + it('should round-trip a complex URL', () => { |
| 242 | + const url = 'https://user:pass@example.com:8080/some/path?key=value&other=test#anchor'; |
| 243 | + |
| 244 | + expect(stringify(parse(url))).toStrictEqual(url); |
| 245 | + }); |
| 246 | +}); |
0 commit comments