Coverage for tests/test_utils.py: 100%
48 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""Tests for utils module."""
3from copy import deepcopy
5import pytest
7from data_api.utils.schema import url_corrector
8from data_api.utils.search import fuzzy_matches
11class TestUrlCorrector:
12 """Tests for url_corrector function."""
14 async def test_url_corrector_none_input(self):
15 """Test that None input returns None."""
16 result = url_corrector(None)
17 assert result is None
19 async def test_url_corrector_double_slash_prefix(self):
20 """Test URL starting with //."""
21 result = url_corrector("//example.com/path")
22 assert result == "https://example.com/path"
24 async def test_url_corrector_valid_http(self):
25 """Test valid http URL is unchanged."""
26 url = "http://example.com/path"
27 result = url_corrector(url)
28 assert result == url
30 async def test_url_corrector_valid_https(self):
31 """Test valid https URL is unchanged."""
32 url = "https://example.com/path"
33 result = url_corrector(url)
34 assert result == url
36 async def test_url_corrector_invalid_protocol(self):
37 """Test URL with invalid protocol gets corrected."""
38 result = url_corrector("ftp://example.com/path")
39 assert result == "https://example.com/path"
41 async def test_url_corrector_strips_whitespace(self):
42 """Test that whitespace is stripped."""
43 result = url_corrector(" https://example.com/path ")
44 assert result == "https://example.com/path"
46 async def test_url_corrector_no_protocol(self):
47 """Test URL without protocol is unchanged."""
48 url = "example.com/path"
49 result = url_corrector(url)
50 assert result == url
53@pytest.mark.parametrize("threshold", [60, 80])
54def test_fuzzy_matches_rank_stably_without_mutating_input(monkeypatch, threshold):
55 items = [
56 {"title": "below"},
57 {"title": "threshold"},
58 {"title": "best", "id": 1},
59 {"title": "best", "id": 2},
60 ]
61 original = deepcopy(items)
62 scores = {"below": threshold - 1, "threshold": threshold, "best": 100}
63 monkeypatch.setattr(
64 "data_api.utils.search.fuzz.partial_ratio", lambda query, value: scores[value]
65 )
66 assert fuzzy_matches(items, "query", "title", threshold) == [items[2], items[3], items[1]]
67 assert items == original
70@pytest.mark.parametrize("metadata", [{}, {"title": None}, {"title": ""}])
71def test_fuzzy_matches_normalizes_absent_titles(monkeypatch, metadata):
72 calls = []
74 def score(query, value):
75 assert isinstance(value, str)
76 calls.append(value)
77 return 100 if value == query else 0
79 monkeypatch.setattr("data_api.utils.search.fuzz.partial_ratio", score)
80 match = {"title": "Notice"}
81 assert fuzzy_matches([metadata, match], "Notice", "title", 80) == [match]
82 assert calls == ["", "Notice"]