7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
263 lines
9.4 KiB
Python
263 lines
9.4 KiB
Python
"""
|
|
Tests for utilities/url_utils.py
|
|
|
|
Tests cover:
|
|
- URL normalization
|
|
- Scheme handling
|
|
- Private IP detection
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
|
|
class TestNormalizeUrl:
|
|
"""Tests for normalize_url function."""
|
|
|
|
def test_normalize_url_with_http_scheme(self):
|
|
"""Test URL with http:// scheme is returned unchanged."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("http://example.com")
|
|
assert result == "http://example.com"
|
|
|
|
def test_normalize_url_with_https_scheme(self):
|
|
"""Test URL with https:// scheme is returned unchanged."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("https://example.com")
|
|
assert result == "https://example.com"
|
|
|
|
def test_normalize_url_with_http_scheme_and_port(self):
|
|
"""Test URL with http:// scheme and port is returned unchanged."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("http://example.com:8080")
|
|
assert result == "http://example.com:8080"
|
|
|
|
def test_normalize_url_empty_raises_error(self):
|
|
"""Test that empty URL raises ValueError."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
with pytest.raises(ValueError) as exc_info:
|
|
normalize_url("")
|
|
assert "empty" in str(exc_info.value).lower()
|
|
|
|
def test_normalize_url_strips_whitespace(self):
|
|
"""Test that whitespace is stripped."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url(" http://example.com ")
|
|
assert result == "http://example.com"
|
|
|
|
def test_normalize_url_malformed_http_colon(self):
|
|
"""Test URL with malformed http: (missing //) is fixed."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("http:example.com")
|
|
assert result == "http://example.com"
|
|
|
|
def test_normalize_url_malformed_https_colon(self):
|
|
"""Test URL with malformed https: (missing //) is fixed."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("https:example.com")
|
|
assert result == "https://example.com"
|
|
|
|
def test_normalize_url_localhost_gets_http(self):
|
|
"""Test that localhost gets http:// scheme."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
with patch(
|
|
"local_deep_research.utilities.url_utils.is_private_ip",
|
|
return_value=True,
|
|
):
|
|
result = normalize_url("localhost:8080")
|
|
assert result == "http://localhost:8080"
|
|
|
|
def test_normalize_url_external_gets_https(self):
|
|
"""Test that external hosts get https:// scheme."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
with patch(
|
|
"local_deep_research.utilities.url_utils.is_private_ip",
|
|
return_value=False,
|
|
):
|
|
result = normalize_url("example.com:443")
|
|
assert result == "https://example.com:443"
|
|
|
|
def test_normalize_url_double_slash_prefix(self):
|
|
"""Test URL starting with // has prefix removed."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
with patch(
|
|
"local_deep_research.utilities.url_utils.is_private_ip",
|
|
return_value=False,
|
|
):
|
|
result = normalize_url("//example.com")
|
|
assert result == "https://example.com"
|
|
|
|
def test_normalize_url_127_0_0_1(self):
|
|
"""Test that 127.0.0.1 gets http:// scheme."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
with patch(
|
|
"local_deep_research.utilities.url_utils.is_private_ip",
|
|
return_value=True,
|
|
):
|
|
result = normalize_url("127.0.0.1:11434")
|
|
assert result == "http://127.0.0.1:11434"
|
|
|
|
def test_normalize_url_preserves_path(self):
|
|
"""Test that URL path is preserved."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("http://example.com/api/v1/search")
|
|
assert result == "http://example.com/api/v1/search"
|
|
|
|
def test_normalize_url_preserves_query_string(self):
|
|
"""Test that query string is preserved."""
|
|
from local_deep_research.utilities.url_utils import normalize_url
|
|
|
|
result = normalize_url("http://example.com?q=test&page=1")
|
|
assert result == "http://example.com?q=test&page=1"
|
|
|
|
|
|
class TestCanonicalUrlKey:
|
|
"""Tests for canonical_url_key — dedup key generation, NOT for display."""
|
|
|
|
def setup_method(self):
|
|
# lru_cache persists across tests; clear to keep each assertion
|
|
# independent of prior test inputs.
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
canonical_url_key.cache_clear()
|
|
|
|
def test_empty_returns_empty(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert canonical_url_key("") == ""
|
|
|
|
def test_lowercases_scheme_and_host_preserves_path_case(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("HTTPS://EXAMPLE.COM/Foo/Bar")
|
|
== "https://example.com/Foo/Bar"
|
|
)
|
|
|
|
def test_strips_fragment(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("https://example.com/page#section")
|
|
== "https://example.com/page"
|
|
)
|
|
|
|
def test_trailing_slash_normalized(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert canonical_url_key("https://example.com/p") == canonical_url_key(
|
|
"https://example.com/p/"
|
|
)
|
|
# Root path '/' is preserved (not stripped to empty).
|
|
assert canonical_url_key("https://example.com/").endswith("/")
|
|
|
|
def test_strips_utm_and_common_trackers(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
url = (
|
|
"https://example.com/p?"
|
|
"utm_source=x&UTM_Medium=y&utm_campaign=z&"
|
|
"fbclid=a&gclid=b&msclkid=c&yclid=d&dclid=e&gad_source=f&"
|
|
"mc_eid=g&mc_cid=h&ref_src=i&igshid=j&_ga=k&_gl=l"
|
|
)
|
|
assert canonical_url_key(url) == "https://example.com/p"
|
|
|
|
def test_keeps_non_tracking_query_params(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
# q, ref (GitHub branch), v (YouTube id), page, id are content-bearing.
|
|
assert (
|
|
canonical_url_key("https://github.com/o/r?ref=main&utm_source=x")
|
|
== "https://github.com/o/r?ref=main"
|
|
)
|
|
assert (
|
|
canonical_url_key(
|
|
"https://www.youtube.com/watch?v=abc123&utm_source=x"
|
|
)
|
|
== "https://www.youtube.com/watch?v=abc123"
|
|
)
|
|
assert (
|
|
canonical_url_key("https://example.com/p?q=hello&page=2")
|
|
== "https://example.com/p?q=hello&page=2"
|
|
)
|
|
|
|
def test_strips_userinfo(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("https://user:pass@example.com/p")
|
|
== "https://example.com/p"
|
|
)
|
|
# Userinfo without colon.
|
|
assert (
|
|
canonical_url_key("https://user@example.com/p")
|
|
== "https://example.com/p"
|
|
)
|
|
|
|
def test_strips_default_ports(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("https://example.com:443/p")
|
|
== "https://example.com/p"
|
|
)
|
|
assert (
|
|
canonical_url_key("http://example.com:80/p")
|
|
== "http://example.com/p"
|
|
)
|
|
|
|
def test_preserves_nondefault_port(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("https://example.com:8443/p")
|
|
== "https://example.com:8443/p"
|
|
)
|
|
|
|
def test_ipv6_host_preserved(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key("https://[::1]:8443/page")
|
|
== "https://[::1]:8443/page"
|
|
)
|
|
|
|
def test_invalid_url_returned_stripped(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert canonical_url_key(" not a url ") == "not a url"
|
|
|
|
def test_mailto_returns_stripped(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert canonical_url_key("mailto:foo@bar.com") == "mailto:foo@bar.com"
|
|
|
|
def test_protocol_relative_returns_stripped(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
# Protocol-relative URL has no scheme — canonicalization is ambiguous,
|
|
# so we fall back to the stripped input.
|
|
assert canonical_url_key("//example.com/path") == "//example.com/path"
|
|
|
|
def test_combined_normalization(self):
|
|
from local_deep_research.utilities.url_utils import canonical_url_key
|
|
|
|
assert (
|
|
canonical_url_key(
|
|
"HTTPS://User:Pass@Example.COM:443/Path/?utm_source=x&q=1#frag"
|
|
)
|
|
== "https://example.com/Path?q=1"
|
|
)
|