From 10527aa1c045b239f1e1409997745d04d5aa8f47 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 00:13:39 +0000 Subject: [PATCH 1/3] Add tests for internal/urlutil package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds comprehensive test coverage for the urlutil package which had 0% coverage. The new test file covers: - ExtractURLDomains: empty/no-URL strings, http/https schemes, case-insensitive scheme matching, hostname lowercasing, URL components (path, query, fragment, port, userinfo), trailing punctuation stripping (comma, period, semicolon, exclamation, parentheses, brackets, quotes), deduplication, sorted output, and error paths (invalid percent-encoding, empty hostname after parsing). - ExtractURLDomainsFromValue: string, map[string]any, []any, and []map[string]any inputs including nested structures, unmatched types (int, bool, nil), deduplication across values, and sorted output. Coverage: 0% → 97.8% (50 test cases) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/urlutil/domains_test.go | 340 +++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 internal/urlutil/domains_test.go diff --git a/internal/urlutil/domains_test.go b/internal/urlutil/domains_test.go new file mode 100644 index 000000000..eacc944bc --- /dev/null +++ b/internal/urlutil/domains_test.go @@ -0,0 +1,340 @@ +package urlutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractURLDomains(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + want []string + }{ + { + name: "empty string returns nil", + input: "", + want: nil, + }, + { + name: "no URLs returns nil", + input: "this has no URLs at all", + want: nil, + }, + { + name: "single http URL", + input: "http://example.com", + want: []string{"example.com"}, + }, + { + name: "single https URL", + input: "https://example.com", + want: []string{"example.com"}, + }, + { + name: "URL with path", + input: "https://example.com/some/path", + want: []string{"example.com"}, + }, + { + name: "URL with query string", + input: "https://example.com/search?q=test&page=1", + want: []string{"example.com"}, + }, + { + name: "URL with port number", + input: "https://example.com:8080/path", + want: []string{"example.com"}, + }, + { + name: "hostname is lowercased", + input: "https://EXAMPLE.COM/path", + want: []string{"example.com"}, + }, + { + name: "scheme is case-insensitive (uppercase)", + input: "HTTPS://example.com", + want: []string{"example.com"}, + }, + { + name: "scheme is case-insensitive (mixed case)", + input: "Https://example.com", + want: []string{"example.com"}, + }, + { + name: "trailing comma stripped", + input: "See https://example.com, for details.", + want: []string{"example.com"}, + }, + { + name: "trailing period stripped", + input: "Visit https://example.com.", + want: []string{"example.com"}, + }, + { + name: "trailing semicolon stripped", + input: "URL: https://example.com; end", + want: []string{"example.com"}, + }, + { + name: "trailing exclamation stripped", + input: "Go to https://example.com!", + want: []string{"example.com"}, + }, + { + name: "trailing closing parenthesis stripped", + input: "Link (https://example.com)", + want: []string{"example.com"}, + }, + { + name: "trailing closing bracket stripped", + input: "[https://example.com]", + want: []string{"example.com"}, + }, + { + name: "trailing closing brace stripped", + input: "{https://example.com}", + want: []string{"example.com"}, + }, + { + name: "trailing double quote stripped", + input: `href="https://example.com"`, + want: []string{"example.com"}, + }, + { + name: "multiple different domains returns sorted list", + input: "https://zebra.com and https://alpha.com and https://middle.com", + want: []string{"alpha.com", "middle.com", "zebra.com"}, + }, + { + name: "duplicate domains are deduplicated", + input: "https://example.com/page1 and https://example.com/page2", + want: []string{"example.com"}, + }, + { + name: "URL embedded in markdown link", + input: "[Click here](https://example.com/page)", + want: []string{"example.com"}, + }, + { + name: "URL embedded in HTML anchor", + input: `text`, + want: []string{"example.com"}, + }, + { + name: "subdomain is preserved", + input: "https://api.github.com/repos", + want: []string{"api.github.com"}, + }, + { + name: "multiple subdomains preserved as distinct domains", + input: "https://api.example.com and https://www.example.com", + want: []string{"api.example.com", "www.example.com"}, + }, + { + name: "URL with fragment", + input: "https://example.com/page#section", + want: []string{"example.com"}, + }, + { + name: "URL with user info", + input: "https://user@example.com/path", + want: []string{"example.com"}, + }, + { + name: "URL with IPv4 address", + input: "https://192.168.1.1/path", + want: []string{"192.168.1.1"}, + }, + { + // https://user@ has an empty hostname (userinfo only); url.Parse succeeds but + // Hostname() returns "". The empty host branch is hit and domainSet stays empty. + name: "URL with userinfo but no hostname returns nil", + input: "https://user@", + want: nil, + }, + { + // Invalid percent-encoding causes url.Parse to return a *url.Error; the URL is + // skipped and the function returns nil when no valid hosts remain. + name: "URL with invalid percent-encoding is skipped", + input: "https://example.com/%GG", + want: nil, + }, + { + // Only the parseable URL contributes when mixed with an invalid one. + name: "mix of valid and invalid percent-encoding URLs", + input: "https://valid.com/path https://example.com/%ZZ", + want: []string{"valid.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := ExtractURLDomains(tt.input) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestExtractURLDomainsFromValue(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value any + want []string + }{ + { + name: "nil value returns nil", + value: nil, + want: nil, + }, + { + name: "integer value (unmatched type) returns nil", + value: 42, + want: nil, + }, + { + name: "boolean value (unmatched type) returns nil", + value: true, + want: nil, + }, + { + name: "string with no URLs returns nil", + value: "no urls here", + want: nil, + }, + { + name: "string with one URL returns domain", + value: "check https://example.com for info", + want: []string{"example.com"}, + }, + { + name: "string with multiple URLs returns sorted unique domains", + value: "https://beta.com and https://alpha.com", + want: []string{"alpha.com", "beta.com"}, + }, + { + name: "map[string]any with URL values", + value: map[string]any{ + "url": "https://example.com/api", + }, + want: []string{"example.com"}, + }, + { + name: "map[string]any with multiple URL values", + value: map[string]any{ + "url1": "https://alpha.com", + "url2": "https://beta.com", + }, + want: []string{"alpha.com", "beta.com"}, + }, + { + name: "map[string]any with duplicate domains deduplicates", + value: map[string]any{ + "url1": "https://example.com/page1", + "url2": "https://example.com/page2", + }, + want: []string{"example.com"}, + }, + { + name: "map[string]any with non-URL string value returns nil", + value: map[string]any{ + "name": "no url here", + }, + want: nil, + }, + { + name: "map[string]any with integer value is ignored", + value: map[string]any{ + "count": 42, + }, + want: nil, + }, + { + name: "[]any with URL strings", + value: []any{ + "https://example.com", + "https://other.com", + }, + want: []string{"example.com", "other.com"}, + }, + { + name: "[]any with mixed types, only strings processed", + value: []any{ + "https://example.com", + 42, + true, + "no-url", + }, + want: []string{"example.com"}, + }, + { + name: "[]any empty slice returns nil", + value: []any{}, + want: nil, + }, + { + name: "[]map[string]any with URL values", + value: []map[string]any{ + {"url": "https://alpha.com"}, + {"url": "https://beta.com"}, + }, + want: []string{"alpha.com", "beta.com"}, + }, + { + name: "[]map[string]any with duplicate domains deduplicates", + value: []map[string]any{ + {"url": "https://example.com/page1"}, + {"url": "https://example.com/page2"}, + }, + want: []string{"example.com"}, + }, + { + name: "nested map[string]any with nested map", + value: map[string]any{ + "outer": map[string]any{ + "inner": "https://nested.example.com", + }, + }, + want: []string{"nested.example.com"}, + }, + { + name: "[]any containing maps", + value: []any{ + map[string]any{"href": "https://first.com"}, + map[string]any{"href": "https://second.com"}, + }, + want: []string{"first.com", "second.com"}, + }, + { + name: "cross-type deduplication: same domain in string and map", + value: map[string]any{ + "link1": "https://example.com/a", + "link2": "https://example.com/b", + }, + want: []string{"example.com"}, + }, + { + name: "result is sorted alphabetically", + value: []any{ + "https://zoo.com", + "https://apple.com", + "https://mango.com", + }, + want: []string{"apple.com", "mango.com", "zoo.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := ExtractURLDomainsFromValue(tt.value) + assert.Equal(t, tt.want, got) + }) + } +} From 3beb2db82c8536f26d4e4b7c6a1ee9b09a20e0a2 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 24 Jun 2026 07:32:53 -0700 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- internal/urlutil/domains_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/urlutil/domains_test.go b/internal/urlutil/domains_test.go index eacc944bc..dce522d18 100644 --- a/internal/urlutil/domains_test.go +++ b/internal/urlutil/domains_test.go @@ -79,6 +79,11 @@ func TestExtractURLDomains(t *testing.T) { input: "URL: https://example.com; end", want: []string{"example.com"}, }, + { + name: "trailing colon stripped", + input: "URL: https://example.com: end", + want: []string{"example.com"}, + }, { name: "trailing exclamation stripped", input: "Go to https://example.com!", From 3e22d2c15bce3f1a96d16b73dc171f62e98006de Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 24 Jun 2026 07:33:02 -0700 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- internal/urlutil/domains_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/urlutil/domains_test.go b/internal/urlutil/domains_test.go index dce522d18..87cfc3bbb 100644 --- a/internal/urlutil/domains_test.go +++ b/internal/urlutil/domains_test.go @@ -317,7 +317,7 @@ func TestExtractURLDomainsFromValue(t *testing.T) { want: []string{"first.com", "second.com"}, }, { - name: "cross-type deduplication: same domain in string and map", + name: "deduplication: same domain in multiple map values", value: map[string]any{ "link1": "https://example.com/a", "link2": "https://example.com/b",