121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
# #region Test.DatasetReview.SemanticResolverEdge [C:3] [TYPE Module] [SEMANTICS test,semantic,resolver,fuzzy,match,edge]
|
|
# @BRIEF Edge-case coverage for semantic_resolver.py uncovered lines (149, 343, 347).
|
|
# @RELATION BINDS_TO -> [SemanticResolver]
|
|
# @TEST_EDGE: missing_mapping -> Returns empty when no mapping found
|
|
# @TEST_EDGE: invalid_type -> Non-string input handled gracefully
|
|
# @TEST_EDGE: empty_input -> Empty string returns no matches
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
from unittest.mock import patch
|
|
import pytest
|
|
|
|
|
|
class TestSemanticSourceResolverEdge:
|
|
"""Cover fuzzy match and row processing edge cases."""
|
|
|
|
@pytest.fixture
|
|
def resolver(self):
|
|
from src.services.dataset_review.semantic_resolver import SemanticSourceResolver
|
|
return SemanticSourceResolver()
|
|
|
|
def test_find_fuzzy_matches_with_empty_key(self, resolver):
|
|
"""Row with empty field_key is skipped (line 343)."""
|
|
field_name = "region"
|
|
rows = [
|
|
{"field_key": "", "field_name": "Empty"},
|
|
{"field_key": "region", "field_name": "Region"},
|
|
]
|
|
result = resolver._find_fuzzy_matches(field_name, rows)
|
|
assert len(result) >= 1
|
|
# The empty key row should be skipped, only region matches
|
|
|
|
def test_find_fuzzy_matches_low_score_skipped(self, resolver):
|
|
"""Row with score below 0.72 is skipped (line 347)."""
|
|
field_name = "region"
|
|
rows = [
|
|
{"field_key": "completely_different_name", "field_name": "Different"},
|
|
]
|
|
result = resolver._find_fuzzy_matches(field_name, rows)
|
|
assert len(result) == 0
|
|
|
|
def test_find_fuzzy_matches_good_score(self, resolver):
|
|
"""Row with score above 0.72 is included."""
|
|
# Use strings close enough to exceed 0.72 threshold
|
|
field_name = "salesregion"
|
|
rows = [
|
|
{"field_key": "sales_region", "field_name": "Sales Region"},
|
|
]
|
|
result = resolver._find_fuzzy_matches(field_name, rows)
|
|
assert len(result) >= 1
|
|
assert result[0]["score"] >= 0.72
|
|
|
|
def test_find_fuzzy_matches_top_3(self, resolver):
|
|
"""Only top 3 fuzzy matches returned."""
|
|
field_name = "sales_amount_value"
|
|
rows = [
|
|
{"field_key": f"sales_amount_{i}" if i < 5 else "unrelated", "field_name": f"Sales {i}"}
|
|
for i in range(10)
|
|
]
|
|
result = resolver._find_fuzzy_matches(field_name, rows)
|
|
assert len(result) <= 3
|
|
|
|
def test_resolve_from_dictionary_no_matches(self, resolver):
|
|
"""Field with no dictionary match produces unresolved entry."""
|
|
source_payload = {
|
|
"source_ref": "test_dict",
|
|
"rows": [
|
|
{"field_name": "Region"},
|
|
{"field_name": "Country"},
|
|
],
|
|
}
|
|
fields = [{"field_name": "nonexistent_field"}]
|
|
|
|
result = resolver.resolve_from_dictionary(source_payload, fields)
|
|
assert result.unresolved_fields == ["nonexistent_field"]
|
|
assert len(result.resolved_fields) == 1
|
|
assert result.resolved_fields[0]["status"] == "unresolved"
|
|
|
|
def test_normalize_dictionary_row(self, resolver):
|
|
"""_normalize_dictionary_row produces expected keys."""
|
|
row = {"field_name": "My Field", "verbose_name": "My Field Label"}
|
|
result = resolver._normalize_dictionary_row(row)
|
|
assert result["field_key"] == "myfield" # normalized: lowercase, no spaces
|
|
assert result["field_name"] == "My Field"
|
|
|
|
def test_resolve_from_dictionary_fuzzy_match(self, resolver):
|
|
"""Fuzzy match produces ranked candidates (coverage for line 149)."""
|
|
source_payload = {
|
|
"source_ref": "test_dict",
|
|
"rows": [
|
|
{"field_name": "Sales Amount"},
|
|
{"field_name": "Revenue"},
|
|
],
|
|
}
|
|
# Use a field name close enough to "sales amount"
|
|
fields = [{"field_name": "sales_amount"}]
|
|
|
|
result = resolver.resolve_from_dictionary(source_payload, fields)
|
|
assert len(result.resolved_fields) == 1
|
|
assert result.resolved_fields[0]["field_name"] == "sales_amount"
|
|
candidates = result.resolved_fields[0].get("candidates", [])
|
|
# Should have at least one candidate (exact or fuzzy)
|
|
assert len(candidates) >= 1
|
|
|
|
def test_resolve_from_dictionary_locked_field(self, resolver):
|
|
"""Locked field is preserved."""
|
|
source_payload = {
|
|
"source_ref": "test_dict",
|
|
"rows": [
|
|
{"field_name": "Region"},
|
|
],
|
|
}
|
|
fields = [{"field_name": "region", "is_locked": True}]
|
|
|
|
result = resolver.resolve_from_dictionary(source_payload, fields)
|
|
assert result.resolved_fields[0].get("is_locked") is True
|
|
# #endregion Test.DatasetReview.SemanticResolverEdge
|