""" Tests für Deduplication Module. """ import pytest from alerts_agent.processing.dedup import ( compute_simhash, hamming_distance, are_similar, find_duplicates, exact_url_duplicates, ) from alerts_agent.models.alert_item import AlertItem class TestSimHash: """Tests für SimHash Berechnung.""" def test_compute_simhash_returns_hex(self): """Test SimHash gibt Hex-String zurück.""" text = "Dies ist ein Test für SimHash Berechnung" result = compute_simhash(text) assert isinstance(result, str) assert len(result) == 16 # Prüfe dass es gültiges Hex ist int(result, 16) def test_empty_text_returns_zeros(self): """Test leerer Text gibt Null-Hash.""" assert compute_simhash("") == "0" * 16 assert compute_simhash(None) == "0" * 16 def test_identical_texts_same_hash(self): """Test identische Texte haben gleichen Hash.""" text = "Inklusion in bayerischen Schulen wird verstärkt" hash1 = compute_simhash(text) hash2 = compute_simhash(text) assert hash1 == hash2 def test_similar_texts_similar_hash(self): """Test ähnliche Texte haben ähnlichen Hash.""" text1 = "Inklusion in bayerischen Schulen wird verstärkt" text2 = "Inklusion in bayerischen Schulen wurde verstärkt" hash1 = compute_simhash(text1) hash2 = compute_simhash(text2) # Ähnliche Texte sollten geringe Hamming-Distanz haben distance = hamming_distance(hash1, hash2) assert distance < 20 # Relativ ähnlich def test_different_texts_different_hash(self): """Test verschiedene Texte haben verschiedenen Hash.""" text1 = "Inklusion in bayerischen Schulen" text2 = "Fußball Bundesliga Spieltag" hash1 = compute_simhash(text1) hash2 = compute_simhash(text2) assert hash1 != hash2 def test_stopwords_ignored(self): """Test Stoppwörter werden ignoriert.""" text1 = "Die neue Regelung für Inklusion" text2 = "Eine neue Regelung für die Inklusion" hash1 = compute_simhash(text1) hash2 = compute_simhash(text2) # Trotz unterschiedlicher Stoppwörter ähnlich distance = hamming_distance(hash1, hash2) assert distance < 10 class TestHammingDistance: """Tests für Hamming-Distanz.""" def test_identical_hashes_zero_distance(self): """Test identische Hashes haben Distanz 0.""" hash1 = "abcdef0123456789" hash2 = "abcdef0123456789" assert hamming_distance(hash1, hash2) == 0 def test_completely_different_max_distance(self): """Test komplett verschiedene Hashes haben max Distanz.""" hash1 = "0000000000000000" hash2 = "ffffffffffffffff" assert hamming_distance(hash1, hash2) == 64 def test_one_bit_difference(self): """Test ein Bit Unterschied.""" hash1 = "0000000000000000" hash2 = "0000000000000001" assert hamming_distance(hash1, hash2) == 1 def test_invalid_hash_returns_max(self): """Test ungültiger Hash gibt maximale Distanz.""" assert hamming_distance("", "abc") == 64 assert hamming_distance("invalid", "abc") == 64 def test_symmetric(self): """Test Hamming-Distanz ist symmetrisch.""" hash1 = "abcd1234abcd1234" hash2 = "1234abcd1234abcd" assert hamming_distance(hash1, hash2) == hamming_distance(hash2, hash1) class TestAreSimilar: """Tests für Ähnlichkeitsprüfung.""" def test_identical_are_similar(self): """Test identische Hashes sind ähnlich.""" hash1 = "abcdef0123456789" assert are_similar(hash1, hash1) def test_threshold_respected(self): """Test Schwellenwert wird respektiert.""" hash1 = "0000000000000000" hash2 = "0000000000000003" # 2 Bits unterschiedlich assert are_similar(hash1, hash2, threshold=5) assert are_similar(hash1, hash2, threshold=2) assert not are_similar(hash1, hash2, threshold=1) class TestFindDuplicates: """Tests für Duplikat-Erkennung.""" def test_no_duplicates(self): """Test keine Duplikate wenn alle verschieden.""" items = [ AlertItem(title="Unique 1", url="https://example.com/1"), AlertItem(title="Unique 2", url="https://example.com/2"), ] # Setze verschiedene Hashes items[0].content_hash = "0000000000000000" items[1].content_hash = "ffffffffffffffff" duplicates = find_duplicates(items) assert len(duplicates) == 0 def test_finds_duplicates(self): """Test findet Duplikate mit ähnlichen Hashes.""" items = [ AlertItem(title="Original", url="https://example.com/1"), AlertItem(title="Duplicate", url="https://example.com/2"), AlertItem(title="Different", url="https://example.com/3"), ] # Setze ähnliche Hashes für die ersten beiden items[0].content_hash = "0000000000000000" items[1].content_hash = "0000000000000001" # 1 Bit unterschiedlich items[2].content_hash = "ffffffffffffffff" # Komplett anders duplicates = find_duplicates(items, threshold=3) # Beide sollten im gleichen Cluster sein assert len(duplicates) == 2 assert duplicates[items[0].id] == duplicates[items[1].id] def test_empty_list(self): """Test leere Liste.""" duplicates = find_duplicates([]) assert len(duplicates) == 0 def test_items_without_hash_skipped(self): """Test Items ohne Hash werden übersprungen.""" items = [ AlertItem(title="No Hash", url="https://example.com/1"), ] # content_hash bleibt None duplicates = find_duplicates(items) assert len(duplicates) == 0 class TestExactUrlDuplicates: """Tests für exakte URL Duplikate.""" def test_finds_exact_duplicates(self): """Test findet exakte URL Duplikate.""" items = [ AlertItem(title="First", url="https://example.com/article"), AlertItem(title="Second", url="https://example.com/article"), # Duplikat AlertItem(title="Third", url="https://example.com/other"), ] duplicates = exact_url_duplicates(items) assert len(duplicates) == 1 assert items[1].id in duplicates assert items[0].id not in duplicates # Original, nicht Duplikat def test_no_duplicates(self): """Test keine Duplikate bei verschiedenen URLs.""" items = [ AlertItem(title="First", url="https://example.com/1"), AlertItem(title="Second", url="https://example.com/2"), ] duplicates = exact_url_duplicates(items) assert len(duplicates) == 0 def test_multiple_duplicates(self): """Test mehrere Duplikate der gleichen URL.""" items = [ AlertItem(title="First", url="https://example.com/same"), AlertItem(title="Second", url="https://example.com/same"), AlertItem(title="Third", url="https://example.com/same"), ] duplicates = exact_url_duplicates(items) # Zweites und drittes sollten als Duplikate markiert sein assert len(duplicates) == 2 assert items[0].id not in duplicates assert items[1].id in duplicates assert items[2].id in duplicates