From 1cba1af3ca574e9ff46260ee171cfe408d878f09 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 25 Aug 2026 15:40:46 +0200 Subject: [PATCH] test(normalizer): fix the escaped-path base case failing on windows TestNormalizer_CanonicalBase/escaped_path_rendering expected normalizeBase("%2F") to be "file:///". A base carrying no scheme is anchored to the working directory, so on windows it comes out as "file:///d:", or whichever drive the tests run from. The case moves out of the table into TestNormalizer_EscapedPathRendering, which compares normalizeBase("%2F") against normalizeBase("/") rather than a literal: both spell the root and both are anchored the same way, on either platform. It also asserts what the defect actually was, that the result is neither "file://%2F" nor something parseURL rejects. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Frederic BIDON --- normalizer_canonical_test.go | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/normalizer_canonical_test.go b/normalizer_canonical_test.go index 53a60ab..e8ac098 100644 --- a/normalizer_canonical_test.go +++ b/normalizer_canonical_test.go @@ -173,13 +173,6 @@ func TestNormalizer_CanonicalBase(t *testing.T) { base: "https://example.com/base/spec.json#/definitions/x", expected: "https://example.com/base/spec.json", }, - { - name: "escaped path rendering", - rule: `"%2F" decodes to "/", and the escaped form has no leading slash to render, ` + - `so it used to come out as the authority of "file://%2F"`, - base: "%2F", - expected: "file:///", - }, } for _, test := range tests { @@ -199,6 +192,34 @@ func TestNormalizer_CanonicalBase(t *testing.T) { } } +// TestNormalizer_EscapedPathRendering covers the rendering defect that produced an unparseable +// base: normalizeBase("%2F") returned "file://%2F". +// +// url.Parse reads "%2F" as the path "/" spelled "%2F", and url.URL.String renders RawPath +// whenever it still decodes to Path - here a spelling with no leading slash, so what is left +// reads as an authority. forgetSpelling drops RawPath once Path has been rewritten. +// +// The expectation is written against normalizeBase("/") rather than a literal, because a +// relative base is anchored to the working directory: "file:///" on unix, "file:///c:" or +// whichever drive the tests run from on windows. +func TestNormalizer_EscapedPathRendering(t *testing.T) { + t.Parallel() + + const escapedSlash = "%2F" + + canonical := normalizeBase(escapedSlash) + assert.EqualT(t, normalizeBase("/"), canonical, "the escaped spelling of the root must normalize like the plain one") + assert.NotEqualT(t, "file://"+escapedSlash, canonical, "the escaped path was rendered as an authority") + + _, err := parseURL(canonical) + require.NoErrorf(t, err, "normalizing %q yielded %q, which does not parse", escapedSlash, canonical) + + ref := MustCreateRef(canonical) + assert.EqualTf(t, canonical, ref.String(), + "%q is not a fixpoint of jsonreference's canonicalization", canonical) + assert.EqualTf(t, canonical, normalizeBase(canonical), "normalizing %q again changed it", canonical) +} + // TestNormalizer_EscapedSlashIsDecoded records a rule that is not ours to change here. // // jsonreference clears url.URL.RawPath, so "%2F" comes back as a separator. RFC 3986 ยง6.2.2.2