Skip to content

Commit f63f79a

Browse files
committed
test: pin resolveSrcset and per-tag URL resolution contracts
resolveSrcset and resolveDocumentURLs both report ~80%/86% line coverage, but the previous tests only exercised one srcset shape and one happy-path mix. Pin the remaining contracts so future changes to base.ResolveReference, the srcsetURLRE regex, or the resource-selector list cannot silently rewrite URLs in unexpected ways. resolveSrcset (direct calls): - single "URL Nx" entry resolves relative to base - comma-separated multi-entry mix (relative / absolute / cross-origin) - "w" width descriptor variant alongside "x" density - empty input returns "" (signal used by SetAttr skip) - uppercase host folds to lowercase resolveDocumentURLs: - per-tag check across <a>, <img>, <source>, <video>, <audio>, <iframe> with no <base> element present - protocol-relative iframe ("//host/path") preserved verbatim Line coverage of resolveSrcset stays at 80% because the remaining lines are unreachable defenses (regex guarantees 4 capture groups; url.Parse would only fail on inputs the regex itself rejected first).
1 parent a060c9d commit f63f79a

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

‎url_test.go‎

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package readability
22

33
import (
4+
"net/url"
45
"strings"
56
"testing"
67

@@ -46,3 +47,116 @@ func TestResolveDocumentURLsKeepsFragmentWithoutBaseElement(t *testing.T) {
4647
t.Fatalf("fragment href = %q", got)
4748
}
4849
}
50+
51+
// TestResolveSrcsetSingleEntry pins the simplest srcset: one URL with a
52+
// density descriptor. Verifies the URL is resolved relative to base and
53+
// the descriptor is preserved verbatim.
54+
func TestResolveSrcsetSingleEntry(t *testing.T) {
55+
base, _ := url.Parse("https://example.com/articles/")
56+
got := resolveSrcset("photo.jpg 2x", base)
57+
if got != "https://example.com/articles/photo.jpg 2x" {
58+
t.Fatalf("got %q", got)
59+
}
60+
}
61+
62+
// TestResolveSrcsetMultipleEntries verifies the comma-separated path:
63+
// each candidate URL must be resolved independently against base, and
64+
// commas plus inter-entry whitespace must round-trip unchanged.
65+
func TestResolveSrcsetMultipleEntries(t *testing.T) {
66+
base, _ := url.Parse("https://example.com/a/b/")
67+
got := resolveSrcset("small.jpg 1x, /big.jpg 2x, https://cdn.example.com/huge.jpg 3x", base)
68+
want := "https://example.com/a/b/small.jpg 1x, https://example.com/big.jpg 2x, https://cdn.example.com/huge.jpg 3x"
69+
if got != want {
70+
t.Fatalf("got %q\nwant %q", got, want)
71+
}
72+
}
73+
74+
// TestResolveSrcsetWidthDescriptor exercises the "w" descriptor variant
75+
// (width hint) in addition to "x" (density).
76+
func TestResolveSrcsetWidthDescriptor(t *testing.T) {
77+
base, _ := url.Parse("https://example.com/")
78+
got := resolveSrcset("a.jpg 320w, b.jpg 640w", base)
79+
if got != "https://example.com/a.jpg 320w, https://example.com/b.jpg 640w" {
80+
t.Fatalf("got %q", got)
81+
}
82+
}
83+
84+
// TestResolveSrcsetEmpty confirms the empty-input fast path returns
85+
// empty without panicking — resolveDocumentURLs uses that signal to skip
86+
// SetAttr.
87+
func TestResolveSrcsetEmpty(t *testing.T) {
88+
base, _ := url.Parse("https://example.com/")
89+
if got := resolveSrcset("", base); got != "" {
90+
t.Fatalf("empty input should return empty, got %q", got)
91+
}
92+
}
93+
94+
// TestResolveSrcsetUppercaseHostLowercased mirrors the host-folding
95+
// behavior already applied to <a href> / <img src>: srcset URLs land in
96+
// the same lowercase canonical form so deduplication and comparison are
97+
// consistent across the document.
98+
func TestResolveSrcsetUppercaseHostLowercased(t *testing.T) {
99+
base, _ := url.Parse("https://example.com/")
100+
got := resolveSrcset("https://CDN.Example.COM/p.jpg 2x", base)
101+
if got != "https://cdn.example.com/p.jpg 2x" {
102+
t.Fatalf("host should be lowercased, got %q", got)
103+
}
104+
}
105+
106+
// TestResolveDocumentURLsResolvesAllResourceAttrs confirms every
107+
// resource selector listed in resolveDocumentURLs (a/img/source/video/
108+
// audio/iframe) gets its URL resolved when no <base> element is present.
109+
// The previous tests focused on srcset / fragments / base href; this one
110+
// nails down the per-tag coverage.
111+
func TestResolveDocumentURLsResolvesAllResourceAttrs(t *testing.T) {
112+
doc, err := goquery.NewDocumentFromReader(strings.NewReader(`
113+
<html><body>
114+
<a href="story.html">story</a>
115+
<img src="image.png">
116+
<picture><source src="alt.webp"></picture>
117+
<video src="movie.mp4"></video>
118+
<audio src="podcast.mp3"></audio>
119+
<iframe src="https://player.example/embed"></iframe>
120+
</body></html>`))
121+
if err != nil {
122+
t.Fatal(err)
123+
}
124+
resolveDocumentURLs(doc, "https://example.com/articles/page.html")
125+
126+
checks := map[string]string{
127+
"a": "https://example.com/articles/story.html",
128+
"img": "https://example.com/articles/image.png",
129+
"source": "https://example.com/articles/alt.webp",
130+
"video": "https://example.com/articles/movie.mp4",
131+
"audio": "https://example.com/articles/podcast.mp3",
132+
// iframe absolute URLs are left as-is, host lowercased.
133+
"iframe": "https://player.example/embed",
134+
}
135+
for sel, want := range checks {
136+
var got string
137+
if sel == "a" {
138+
got = attr(doc.Find(sel).First(), "href")
139+
} else {
140+
got = attr(doc.Find(sel).First(), "src")
141+
}
142+
if got != want {
143+
t.Fatalf("%s: got %q, want %q", sel, got, want)
144+
}
145+
}
146+
}
147+
148+
// TestResolveDocumentURLsPreservesRelativeIframeProtocol pins the
149+
// "//host/path" iframe special-case: protocol-relative iframe sources
150+
// must NOT be rewritten to absolute, matching upstream behavior that
151+
// preserves embed providers' protocol-agility.
152+
func TestResolveDocumentURLsPreservesRelativeIframeProtocol(t *testing.T) {
153+
doc, err := goquery.NewDocumentFromReader(strings.NewReader(
154+
`<iframe src="//player.example/embed/abc"></iframe>`))
155+
if err != nil {
156+
t.Fatal(err)
157+
}
158+
resolveDocumentURLs(doc, "https://example.com/")
159+
if got := attr(doc.Find("iframe").First(), "src"); got != "//player.example/embed/abc" {
160+
t.Fatalf("iframe src should remain protocol-relative, got %q", got)
161+
}
162+
}

0 commit comments

Comments
 (0)