|
1 | 1 | import { test, beforeEach } from 'node:test'; |
2 | 2 | import assert from 'node:assert/strict'; |
3 | 3 | import { applyOptions } from '../src/config.js'; |
4 | | -import { normalizeUrl } from '../src/util/url.js'; |
| 4 | +import { normalizeUrl, cacheKeyUrl } from '../src/util/url.js'; |
5 | 5 |
|
6 | 6 | beforeEach(() => applyOptions({})); |
7 | 7 |
|
@@ -40,3 +40,39 @@ test('an explicit empty allowlist drops all params regardless of global policy', |
40 | 40 | applyOptions({ url: { queryParams: ['*'] } }); |
41 | 41 | assert.equal(normalizeUrl('https://x.com/a?page=2&CN=foo', false, []), 'https://x.com/a'); |
42 | 42 | }); |
| 43 | + |
| 44 | +// cacheKeyUrl presents the cache-key form of an ALREADY-NORMALIZED url: it decodes `:` so |
| 45 | +// keys read like the canonical form (`?CN=Gender:Mens`). Pure transform — it must NOT |
| 46 | +// re-normalize or drop params (serving already applied the route/global allowlist). |
| 47 | +test('cacheKeyUrl decodes ":" and preserves the (already-normalized) query verbatim', () => { |
| 48 | + assert.equal( |
| 49 | + cacheKeyUrl('https://www.kohls.com/catalog/mens-clothing.jsp?CN=Gender%3AMens+Department%3AClothing'), |
| 50 | + 'https://www.kohls.com/catalog/mens-clothing.jsp?CN=Gender:Mens+Department:Clothing' |
| 51 | + ); |
| 52 | +}); |
| 53 | + |
| 54 | +test('cacheKeyUrl does not drop params (no allowlist re-applied)', () => { |
| 55 | + // even a param the global allowlist would drop is preserved — the caller already normalized |
| 56 | + assert.equal(cacheKeyUrl('https://x.com/a?CN=x%3Ay&foo=1'), 'https://x.com/a?CN=x:y&foo=1'); |
| 57 | +}); |
| 58 | + |
| 59 | +test('cacheKeyUrl decodes safe query sub-delimiters (: , @) but leaves separators encoded', () => { |
| 60 | + assert.equal(cacheKeyUrl('https://x.com/a?CN=A%3AB%2CC%40D'), 'https://x.com/a?CN=A:B,C@D'); |
| 61 | + // %26 (&), %3D (=), %3B (;) stay encoded — decoding them could shift how the URL parses. |
| 62 | + assert.equal(cacheKeyUrl('https://x.com/a?q=a%26b%3Dc%3Bd'), 'https://x.com/a?q=a%26b%3Dc%3Bd'); |
| 63 | +}); |
| 64 | + |
| 65 | +test('cacheKeyUrl accepts a URL object and never throws', () => { |
| 66 | + assert.equal(cacheKeyUrl(new URL('https://x.com/a?CN=x%3Ay')), 'https://x.com/a?CN=x:y'); |
| 67 | + assert.doesNotThrow(() => cacheKeyUrl('::::not a url')); |
| 68 | +}); |
| 69 | + |
| 70 | +// The encoded/decoded COLLAPSE comes from normalizeUrl (sort re-encodes to %3A) then |
| 71 | +// cacheKeyUrl (decode) — the pipeline the serving + scheduling paths use. |
| 72 | +test('normalizeUrl -> cacheKeyUrl collapses encoded and decoded to one key', () => { |
| 73 | + applyOptions({ url: { queryParams: ['*'] } }); |
| 74 | + const enc = cacheKeyUrl(normalizeUrl('https://x.com/a?CN=Gender%3AMens')); |
| 75 | + const dec = cacheKeyUrl(normalizeUrl('https://x.com/a?CN=Gender:Mens')); |
| 76 | + assert.equal(enc, dec); |
| 77 | + assert.equal(enc, 'https://x.com/a?CN=Gender:Mens'); |
| 78 | +}); |
0 commit comments