from __future__ import annotations import sys import os import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from comment_target_resolver import ( _build_char_map, _normalize_for_match, _find_all, _resolve_target_in_paragraph, resolve_single_target, ResolveResult, ) from models import ParagraphText class TestCharMap: def test_basic(self): cm = _build_char_map("Hello World") assert cm.normalized == "HelloWorld" assert cm.orig_offsets == [0, 1, 2, 3, 4, 6, 7, 8, 9, 10] def test_nbsp_stripped(self): cm = _build_char_map("Hello\u00a0World") assert cm.normalized == "HelloWorld" def test_nbsp_offsets(self): cm = _build_char_map("A\u00a0B") assert cm.normalized == "AB" assert cm.orig_offsets == [0, 2] def test_nbsp(self): cm = _build_char_map("Hello\u00a0World") assert cm.normalized == "HelloWorld" def test_smart_quotes(self): cm = _build_char_map("\u201cHello\u201d") assert cm.normalized == '"Hello"' def test_em_dash(self): cm = _build_char_map("A\u2014B") assert cm.normalized == "A-B" class TestNormalize: def test_missing_spaces(self): norm = _normalize_for_match("the Sellershall") assert norm == "theSellershall" def test_nbsp_normalized(self): norm = _normalize_for_match("Hello\u00a0World") assert norm == "HelloWorld" def test_spaces_stripped(self): norm = _normalize_for_match("The Seller shall") assert norm == "TheSellershall" class TestFindAll: def test_single(self): assert _find_all("abc", "b") == [1] def test_multiple(self): assert _find_all("ababa", "a") == [0, 2, 4] def test_none(self): assert _find_all("abc", "z") == [] class TestResolveTargetInParagraph: def test_exact_match(self): result = _resolve_target_in_paragraph( "The Seller shall deliver the goods promptly.", "shall deliver the goods", "", "", ) assert result is not None assert result.strategy == "exact" assert result.confidence == 1.0 assert result.target_text == "shall deliver the goods" def test_exact_match_with_anchors(self): result = _resolve_target_in_paragraph( "The Seller shall deliver the goods promptly.", "shall deliver the goods", "Seller ", " promptly", ) assert result is not None assert result.strategy == "exact" assert result.was_resolved is False assert result.before_anchor == "Seller " assert result.after_anchor == " promptly" def test_missing_spaces(self): result = _resolve_target_in_paragraph( "The Seller shall not be liable for any loss of revenue.", "the Sellershall not be liable for anyloss of revenue", "", "", ) assert result is not None assert result.strategy == "whitespace_normalized" assert "Seller shall not be liable for any loss of revenue" in result.target_text def test_missing_spaces_around_parens(self): result = _resolve_target_in_paragraph( "ABC Construction Ltd (The Buyer)", "ABC Construction Ltd(TheBuyer)", "", "", ) assert result is not None assert result.strategy == "whitespace_normalized" assert result.target_text == "ABC Construction Ltd (The Buyer)" def test_missing_space_after_punctuation(self): result = _resolve_target_in_paragraph( "from the factory. This warranty extends only to", "factory.This warrantyextendsonly to", "", "", ) assert result is not None assert result.strategy == "whitespace_normalized" assert "factory. This warranty extends only" in result.target_text def test_nbsp_vs_space(self): result = _resolve_target_in_paragraph( "The\u00a0materials remain the sole property of the Seller", "The materials remain the sole property ofthe Seller", "", "", ) assert result is not None assert result.strategy == "whitespace_normalized" def test_smart_quotes(self): result = _resolve_target_in_paragraph( 'He said \u201cno liability\u201d for damages.', 'He said "no liability" for damages', "", "", ) assert result is not None assert result.strategy == "whitespace_normalized" def test_em_dash(self): result = _resolve_target_in_paragraph( "Payment \u2014 if delayed \u2014 incurs interest.", "Payment - if delayed - incurs interest.", "", "", ) assert result is not None def test_duplicate_with_anchors(self): para = "The Seller shall deliver. The Seller shall deliver promptly." result = _resolve_target_in_paragraph( para, "The Sellershall deliver", "The Seller", "promptly", ) assert result is not None assert result.strategy in ("anchor_normalized", "whitespace_normalized") def test_no_match_returns_none(self): result = _resolve_target_in_paragraph( "Completely unrelated text here.", "The Seller shall not be liable", "", "", ) assert result is None def test_sentence_fallback(self): para = "The Seller reserves all rights under this clause. The Buyer shall pay within 30 days. Force majeure does not apply." result = _resolve_target_in_paragraph( para, "The Sellerreserves all rightsunder this clause", "", "", ) assert result is not None assert result.strategy in ("whitespace_normalized", "sentence_fallback") assert "The Seller reserves all rights under this clause" in result.target_text def test_sentence_fallback_low_confidence_fails(self): para = "The Seller reserves all rights. The Buyer shall pay. Force majeure does not apply." result = _resolve_target_in_paragraph( para, "completely different and unrelated gibberish text", "", "", ) assert result is None class TestResolveSingleTarget: def test_with_paragraph_list(self): paragraphs = [ ParagraphText(id="P_0001", text="The Seller shall deliver the goods."), ParagraphText(id="P_0002", text="The Buyer shall pay the price."), ] result = resolve_single_target( paragraphs, "P_0001", "the Sellershall deliver the goods", "", "", ) assert result.strategy == "whitespace_normalized" assert result.paragraph_id == "P_0001" assert "The Seller shall deliver the goods" in result.target_text def test_missing_paragraph(self): paragraphs = [ ParagraphText(id="P_0001", text="Some text."), ] result = resolve_single_target( paragraphs, "P_9999", "Some text", "", "", ) assert result.strategy == "not_resolved" assert result.confidence == 0.0 def test_paragraph_fallback(self): paragraphs = [ ParagraphText(id="P_0001", text="Short paragraph here."), ] result = resolve_single_target( paragraphs, "P_0001", "totally unrelated", "", "", allow_paragraph_fallback=True, ) assert result.strategy == "paragraph_fallback" def test_paragraph_fallback_disabled(self): paragraphs = [ ParagraphText(id="P_0001", text="Short paragraph here."), ] result = resolve_single_target( paragraphs, "P_0001", "totally unrelated", "", "", allow_paragraph_fallback=False, ) assert result.strategy == "not_resolved" assert result.confidence == 0.0 if __name__ == "__main__": pytest.main([__file__, "-v"])