311 lines
14 KiB
Python
Raw Normal View History

test(sveta): Write unit tests for deduplication and closure decision Outcome: done Changed: tests/test_research_dedup.py:1-303 Verified by: make verify -> exit_code 0, 176 tests OK (36 new), "verification passed"; only pre-existing StarletteDeprecationWarning from tests/test_api.py, none introduced Findings: 36 stdlib-unittest tests with retoor header; AC1 URL dedup: re-add rejected, normalized variants (case, IDNA, default port, slash collapse, trailing slash) collapse to one seen entry, duplicates across responses recorded once, whitespace-only URL normalizes to "" and is registered once then rejected; AC2 content dedup: identical and whitespace-near-identical content under different URLs rejected (content_seen=1, content_duplicates_skipped=1), blank rejected; AC3 query dedup: casefold+whitespace-collapse key, title/description/extra variants deduped, length window MIN/MAX enforced, duplicate never issued twice; AC4 closure: round with 0 new URLs and 0 new queries halts, new URL or new query continues, empty-result round halts and exhausts pending; closure decision expressed via snapshot deltas (urls_seen, queries_enqueued) because no closure module exists yet; no test skipped or weakened Open: none Confidence: high - every acceptance criterion asserted by passing tests; two initial failures were corrected test expectations, not implementation defects Typosaurus-Run: 4e2afb673c7f4578a12276d9181b982d Typosaurus-Node: 1a3d7873cf5b47eb84042647a511f3f4 Typosaurus-Agent: @sveta Refs: #31
2026-08-07 21:25:39 +02:00
# retoor <retoor@molodetz.nl>
import unittest
from typosaurus_sandbox.research.envelopes import SearchResult
from typosaurus_sandbox.research.frontier import (
MAX_QUERY_LENGTH,
MIN_QUERY_LENGTH,
DedupStats,
QueryFrontier,
fingerprint_text,
normalize_url,
query_variants_from_result,
)
def _round_halts(frontier: QueryFrontier, before: DedupStats) -> bool:
after = frontier.snapshot()
new_urls = after.urls_seen - before.urls_seen
new_queries = after.queries_enqueued - before.queries_enqueued
return new_urls == 0 and new_queries == 0
class TestNormalizeUrl(unittest.TestCase):
def test_lowercases_scheme_and_host_and_strips_default_port(self) -> None:
self.assertEqual(
normalize_url("HTTPS://Example.COM:443/Path//To//Page/"),
"https://example.com/Path/To/Page",
)
def test_strips_userinfo_and_fragment_keeps_query(self) -> None:
self.assertEqual(
normalize_url("https://user:pass@example.com:8443/a?x=1#frag"),
"https://example.com:8443/a?x=1",
)
def test_fragment_dropped_with_default_port(self) -> None:
self.assertEqual(normalize_url("https://example.com/a?x=1#sec"), "https://example.com/a?x=1")
def test_non_default_port_preserved(self) -> None:
self.assertEqual(normalize_url("https://example.com:80/x"), "https://example.com:80/x")
def test_idna_encodes_non_ascii_host(self) -> None:
self.assertEqual(normalize_url("https://MÜNCHEN.example/"), "https://xn--mnchen-3ya.example/")
def test_http_and_https_remain_distinct(self) -> None:
self.assertNotEqual(normalize_url("http://example.com/x"), normalize_url("https://example.com/x"))
def test_non_http_scheme_returned_cleaned(self) -> None:
self.assertEqual(normalize_url("not a url"), "not a url")
def test_blank_url_normalizes_to_empty(self) -> None:
self.assertEqual(normalize_url(" "), "")
class TestFingerprintText(unittest.TestCase):
def test_whitespace_variants_produce_identical_fingerprint(self) -> None:
self.assertEqual(fingerprint_text("identical body\n\n"), fingerprint_text("identical body"))
def test_distinct_text_produces_distinct_fingerprint(self) -> None:
self.assertNotEqual(fingerprint_text("first text"), fingerprint_text("second text"))
def test_fingerprint_is_sha256_hex(self) -> None:
digest = fingerprint_text("sample")
self.assertEqual(len(digest), 64)
int(digest, 16)
class TestQueryVariantsFromResult(unittest.TestCase):
def test_title_description_and_string_extra_become_variants(self) -> None:
result = SearchResult(
title="Deep research",
description="Survey of deep research systems",
url="https://a.example",
extra={"tag": "research methods", "rank": 3},
)
self.assertEqual(
query_variants_from_result(result),
[
("Deep research", "title"),
("Survey of deep research systems", "description"),
("research methods", "extra"),
],
)
def test_non_string_extra_values_ignored(self) -> None:
result = SearchResult(title="t", url="https://a.example", extra={"rank": 3, "ok": True})
self.assertEqual(query_variants_from_result(result), [("t", "title")])
def test_empty_fields_produce_no_variants(self) -> None:
result = SearchResult(url="https://a.example")
self.assertEqual(query_variants_from_result(result), [])
class TestUrlDeduplication(unittest.IsolatedAsyncioTestCase):
async def test_first_registration_records_url_once(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.register_url("https://example.com/page"))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.urls_duplicates_skipped, 0)
async def test_same_url_registered_twice_rejects_second(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.register_url("https://example.com/page"))
self.assertFalse(frontier.register_url("https://example.com/page"))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.urls_duplicates_skipped, 1)
async def test_normalized_variants_of_same_url_rejected(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.register_url("HTTPS://Example.COM:443/a//b/"))
self.assertFalse(frontier.register_url("https://example.com/a/b"))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.urls_duplicates_skipped, 1)
async def test_duplicate_urls_across_responses_recorded_once(self) -> None:
frontier = QueryFrontier()
first = SearchResult(url="https://example.com/page", title="first title", description="first description")
second = SearchResult(url="https://example.com/page", title="second title", description="second description")
self.assertTrue(frontier.register_result(first))
self.assertFalse(frontier.register_result(second))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.urls_duplicates_skipped, 1)
async def test_empty_url_rejected(self) -> None:
frontier = QueryFrontier()
self.assertFalse(frontier.register_url(""))
self.assertEqual(frontier.snapshot().urls_seen, 0)
async def test_whitespace_url_normalized_and_deduplicated(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.register_url(" "))
self.assertFalse(frontier.register_url(" "))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.urls_duplicates_skipped, 1)
class TestContentDeduplication(unittest.IsolatedAsyncioTestCase):
async def test_identical_content_different_urls_rejects_second_occurrence(self) -> None:
frontier = QueryFrontier()
first = SearchResult(url="https://a.example/1", title="t1", description="d1", content="identical body")
second = SearchResult(url="https://b.example/2", title="t2", description="d2", content="identical body")
self.assertTrue(frontier.register_result(first))
self.assertTrue(frontier.register_result(second))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 2)
self.assertEqual(stats.content_seen, 1)
self.assertEqual(stats.content_duplicates_skipped, 1)
self.assertFalse(frontier.register_content("identical body"))
async def test_near_identical_whitespace_content_rejected(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.register_content(" Deep research system \n"))
self.assertFalse(frontier.register_content("Deep research system"))
stats = frontier.snapshot()
self.assertEqual(stats.content_seen, 1)
self.assertEqual(stats.content_duplicates_skipped, 1)
async def test_blank_content_rejected(self) -> None:
frontier = QueryFrontier()
self.assertFalse(frontier.register_content(""))
self.assertFalse(frontier.register_content(" \n "))
self.assertEqual(frontier.snapshot().content_seen, 0)
async def test_result_without_content_registers_url_only(self) -> None:
frontier = QueryFrontier()
result = SearchResult(url="https://a.example", title="t", description="d")
self.assertTrue(frontier.register_result(result))
stats = frontier.snapshot()
self.assertEqual(stats.urls_seen, 1)
self.assertEqual(stats.content_seen, 0)
class TestQueryDeduplication(unittest.IsolatedAsyncioTestCase):
async def test_duplicate_query_rejected(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.push_query("deep research", "manual"))
self.assertFalse(frontier.push_query("deep research", "manual"))
stats = frontier.snapshot()
self.assertEqual(stats.queries_enqueued, 1)
self.assertEqual(stats.queries_duplicates_skipped, 1)
async def test_query_dedup_ignores_case_and_whitespace(self) -> None:
frontier = QueryFrontier()
self.assertTrue(frontier.push_query(" Deep RESEARCH "))
self.assertFalse(frontier.push_query("deep research"))
self.assertEqual(frontier.snapshot().queries_enqueued, 1)
async def test_variants_from_result_deduplicated_across_fields(self) -> None:
frontier = QueryFrontier()
result = SearchResult(
title="Python asyncio",
description="python asyncio",
url="https://a.example",
extra={"tag": " PYTHON ASYNCIO "},
)
self.assertEqual(frontier.push_variants_from_result(result), 1)
stats = frontier.snapshot()
self.assertEqual(stats.queries_enqueued, 1)
self.assertEqual(stats.queries_duplicates_skipped, 2)
async def test_duplicate_query_never_issued_twice(self) -> None:
frontier = QueryFrontier("asyncio python")
self.assertEqual(frontier.pop_query(), "asyncio python")
self.assertFalse(frontier.push_query("ASYNCIO python"))
self.assertIsNone(frontier.pop_query())
self.assertEqual(frontier.snapshot().queries_issued, 1)
async def test_query_length_window_enforced(self) -> None:
frontier = QueryFrontier()
self.assertFalse(frontier.push_query("a" * (MIN_QUERY_LENGTH - 1)))
self.assertTrue(frontier.push_query("a" * MIN_QUERY_LENGTH))
self.assertTrue(frontier.push_query("b" * MAX_QUERY_LENGTH))
self.assertFalse(frontier.push_query("c" * (MAX_QUERY_LENGTH + 1)))
self.assertEqual(frontier.snapshot().queries_enqueued, 2)
async def test_reseed_same_subject_enqueues_once(self) -> None:
frontier = QueryFrontier("subject alpha")
frontier.seed("SUBJECT ALPHA")
stats = frontier.snapshot()
self.assertEqual(stats.queries_enqueued, 1)
self.assertEqual(stats.queries_duplicates_skipped, 1)
class TestClosureDecision(unittest.IsolatedAsyncioTestCase):
async def test_round_with_no_new_urls_and_no_new_queries_halts(self) -> None:
frontier = QueryFrontier("subject alpha")
frontier.pop_query()
discovered = SearchResult(
url="https://a.example/page", title="alpha discovery", description="alpha details", content="body text"
)
self.assertTrue(frontier.register_result(discovered))
before = frontier.snapshot()
self.assertFalse(frontier.register_result(discovered))
self.assertFalse(frontier.push_query("SUBJECT ALPHA"))
after = frontier.snapshot()
self.assertEqual(after.urls_seen - before.urls_seen, 0)
self.assertEqual(after.queries_enqueued - before.queries_enqueued, 0)
self.assertTrue(_round_halts(frontier, before))
async def test_round_adding_new_url_continues(self) -> None:
frontier = QueryFrontier("subject beta")
frontier.pop_query()
before = frontier.snapshot()
self.assertTrue(frontier.register_url("https://new.example/x"))
after = frontier.snapshot()
self.assertEqual(after.urls_seen - before.urls_seen, 1)
self.assertFalse(_round_halts(frontier, before))
async def test_round_adding_new_query_continues(self) -> None:
frontier = QueryFrontier("subject gamma")
frontier.pop_query()
before = frontier.snapshot()
result = SearchResult(url="https://a.example", title="gamma subtopic", description="")
self.assertEqual(frontier.push_variants_from_result(result), 1)
after = frontier.snapshot()
self.assertEqual(after.queries_enqueued - before.queries_enqueued, 1)
self.assertFalse(_round_halts(frontier, before))
async def test_round_adding_url_and_query_continues(self) -> None:
frontier = QueryFrontier("subject delta")
frontier.pop_query()
before = frontier.snapshot()
result = SearchResult(url="https://b.example/page", title="delta subtopic", description="delta details")
self.assertTrue(frontier.register_result(result))
self.assertEqual(frontier.push_variants_from_result(result), 2)
after = frontier.snapshot()
self.assertEqual(after.urls_seen - before.urls_seen, 1)
self.assertEqual(after.queries_enqueued - before.queries_enqueued, 2)
self.assertFalse(_round_halts(frontier, before))
async def test_empty_result_round_halts_and_exhausts_pending(self) -> None:
frontier = QueryFrontier("lonely topic")
before = frontier.snapshot()
self.assertEqual(frontier.pop_query(), "lonely topic")
self.assertFalse(frontier.has_pending())
self.assertTrue(_round_halts(frontier, before))
async def test_snapshot_reports_round_deltas_for_closure_accounting(self) -> None:
frontier = QueryFrontier("subject epsilon")
before = frontier.snapshot()
self.assertEqual(frontier.pop_query(), "subject epsilon")
result = SearchResult(
url="https://c.example/page", title="epsilon topic", description="epsilon details", content="epsilon body"
)
self.assertTrue(frontier.register_result(result))
after = frontier.snapshot()
self.assertEqual(after.urls_seen - before.urls_seen, 1)
self.assertEqual(after.content_seen - before.content_seen, 1)
self.assertEqual(after.queries_issued - before.queries_issued, 1)
self.assertEqual(len(after.to_dict()), 8)
if __name__ == "__main__":
unittest.main()