tests/test_content_dedup.py
Rama main · réplica de lectura del repositorio original.
"""
Tests for `src.pipeline.content_dedup`.
- sources with identical content are removed except for one.
- with mirror subdomains the main domain wins.
- sources with too little content (< 200 characters) stay unchanged.
- the original order is kept among the surviving sources.
- URLs in the content do not disturb duplicate detection.
"""
import unittest
from dataclasses import dataclass
from src.pipeline.content_dedup import dedupe_sources_by_content
@dataclass
class _Src:
"""Minimal duck type for SourceDocument (only url + content)."""
url: str
content: str
class TestDedupeSourcesByContent(unittest.TestCase):
def test_no_duplicates_keeps_all(self):
sources = [
_Src("https://a.com", "Content A " * 100),
_Src("https://b.com", "Content B " * 100),
_Src("https://c.com", "Content C " * 100),
]
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 3)
self.assertEqual(removed, 0)
def test_identical_content_collapses(self):
same_text = "Identischer Inhalt " * 100
sources = [
_Src("https://a.com", same_text),
_Src("https://b.com", same_text),
]
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 1)
self.assertEqual(removed, 1)
def test_mirror_subdomain_main_wins(self):
"""The main domain is preferred over a subdomain."""
same_text = "Uni-Inhalt über Forschung " * 100
sources = [
_Src("https://cms.example.edu/page", same_text),
_Src("https://example.edu/page", same_text),
_Src("https://committees.example.edu/page", same_text),
]
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 1)
# The shortest URL (= main domain) wins
self.assertEqual(kept[0].url, "https://example.edu/page")
self.assertEqual(removed, 2)
def test_short_content_not_deduped(self):
"""Short content (< 200 characters) is not de-duplicated.
Safe default: two short stub pages could have the same text by
chance; we do not want to collapse them by mistake.
"""
short = "Kurz."
sources = [
_Src("https://a.com", short),
_Src("https://b.com", short),
]
kept, removed = dedupe_sources_by_content(sources)
# both stay
self.assertEqual(len(kept), 2)
self.assertEqual(removed, 0)
def test_urls_in_content_dont_prevent_dedup(self):
"""URLs in the content differ between mirrors — they must not prevent
duplicate detection."""
# Both have identical main content, but different URLs
content_a = (
"Forschung über KI. Mehr unter https://cms.example.edu/details "
"und im Hauptartikel. " * 50
)
content_b = (
"Forschung über KI. Mehr unter https://committees.example.edu/details "
"und im Hauptartikel. " * 50
)
sources = [
_Src("https://cms.example.edu/page", content_a),
_Src("https://committees.example.edu/page", content_b),
]
kept, removed = dedupe_sources_by_content(sources)
# Identified as a duplicate (URL stripped from the content)
self.assertEqual(len(kept), 1)
self.assertEqual(removed, 1)
def test_whitespace_normalized(self):
"""Different whitespace must not count as a difference."""
a = "Inhalt mit viel whitespace " * 50
b = "Inhalt mit viel whitespace " * 50
sources = [
_Src("https://a.com", a),
_Src("https://b.com", b),
]
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 1)
def test_case_insensitive(self):
a = "Forschung Über KI " * 50
b = "FORSCHUNG ÜBER KI " * 50
sources = [
_Src("https://a.com", a),
_Src("https://b.com", b),
]
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 1)
def test_preserves_original_order(self):
"""Surviving sources keep their relative original order."""
unique = "Einzigartiger Inhalt " * 50
same = "Doppelter Inhalt " * 50
sources = [
_Src("https://x.com", unique + " 1"),
_Src("https://aaa.com", same),
_Src("https://y.com", unique + " 2"),
_Src("https://b.com", same), # mirror of aaa
_Src("https://z.com", unique + " 3"),
]
kept, removed = dedupe_sources_by_content(sources)
# 4 sources expected (3 unique + 1 of the same mirrors)
self.assertEqual(len(kept), 4)
self.assertEqual(removed, 1)
# the order of the unique sources is kept
urls = [s.url for s in kept]
self.assertEqual(urls.index("https://x.com"), 0)
self.assertLess(urls.index("https://x.com"), urls.index("https://y.com"))
self.assertLess(urls.index("https://y.com"), urls.index("https://z.com"))
def test_empty_input(self):
kept, removed = dedupe_sources_by_content([])
self.assertEqual(kept, [])
self.assertEqual(removed, 0)
def test_none_content_handled(self):
"""A source with None content is not treated as a duplicate."""
sources = [
_Src("https://a.com", None),
_Src("https://b.com", "Content B " * 50),
]
# should not crash
kept, removed = dedupe_sources_by_content(sources)
self.assertEqual(len(kept), 2)
if __name__ == "__main__":
unittest.main()