# #region Test.ContextAwarePromptBuilder [C:2] [TYPE Module] [SEMANTICS test,prompt,builder,context] # @BRIEF Tests for prompt_builder.py — ContextAwarePromptBuilder. # @RELATION BINDS_TO -> [ContextAwarePromptBuilder] import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src")) import pytest from unittest.mock import MagicMock from src.plugins.translate.prompt_builder import ContextAwarePromptBuilder class TestRenderEntry: """ContextAwarePromptBuilder.render_entry.""" def test_basic_entry_dict(self): """Render a dict entry.""" entry = {"source_term": "hello", "target_term": "bonjour"} line = ContextAwarePromptBuilder.render_entry(entry) assert line == '"hello" -> "bonjour"' def test_basic_entry_object(self): """Render an object entry.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = False entry.context_data = None entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry) assert line == '"hello" -> "bonjour"' def test_with_context(self): """Entry with context data.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = {"table": "users", "column": "name"} entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry) assert "table=users" in line assert "column=name" in line assert "context:" in line def test_with_context_dict_str(self): """Entry with context_data as JSON string.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = '{"table": "users"}' entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry) assert "table=users" in line def test_with_context_invalid_json(self): """Entry with context_data as non-JSON string.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = "plain string context" entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry) assert "plain string context" in line def test_with_context_string_direct(self): """Entry with context_data as string (not dict, not valid JSON).""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = "some raw context" entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry, priority=True) assert "PRIORITY" in line def test_usage_notes(self): """Entry with usage notes.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = False entry.context_data = None entry.usage_notes = "Formal greeting" line = ContextAwarePromptBuilder.render_entry(entry) assert "Usage: Formal greeting" in line def test_priority(self): """Priority entry gets prefix.""" entry = {"source_term": "hello", "target_term": "bonjour"} line = ContextAwarePromptBuilder.render_entry(entry, priority=True) assert line.startswith("# PRIORITY") def test_context_truncation(self): """Very long context is truncated.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = {"long": "x" * 3000} entry.usage_notes = None line = ContextAwarePromptBuilder.render_entry(entry) assert len(line) < 4000 assert "...[truncated]" in line def test_context_items_dict_access(self): """Context items from dict-type entry.""" entry = { "source_term": "hello", "target_term": "bonjour", "has_context": True, "context_data": {"key": "value"}, "usage_notes": None, } line = ContextAwarePromptBuilder.render_entry(entry) assert "key=value" in line def test_context_data_json_array(self): """Context_data as JSON array string — parsed as non-dict (line 69).""" entry = { "source_term": "hello", "target_term": "bonjour", "has_context": True, "context_data": '["val1", "val2"]', "usage_notes": None, } line = ContextAwarePromptBuilder.render_entry(entry) # Should use raw string since parsed JSON is not a dict assert '["val1", "val2"]' in line class TestComputeContextSimilarity: """ContextAwarePromptBuilder.compute_context_similarity.""" def test_exact_match(self): """Identical contexts => 1.0.""" ctx = {"a": "hello", "b": "world"} sim = ContextAwarePromptBuilder.compute_context_similarity(ctx, ctx) assert sim == 1.0 def test_no_match(self): """Disjoint contexts => 0.0.""" ctx1 = {"a": "hello"} ctx2 = {"b": "world"} sim = ContextAwarePromptBuilder.compute_context_similarity(ctx1, ctx2) assert sim == 0.0 def test_partial_match(self): """Partial overlap yields value between 0 and 1.""" ctx1 = {"a": "hello", "b": "world"} ctx2 = {"a": "hello", "c": "foo"} sim = ContextAwarePromptBuilder.compute_context_similarity(ctx1, ctx2) # intersection = {"hello"}, union = {"hello", "world", "foo"} => 1/3 assert 0.33 < sim < 0.34 def test_none_entry_context(self): """None entry_context => 0.0.""" sim = ContextAwarePromptBuilder.compute_context_similarity(None, {"a": "b"}) assert sim == 0.0 def test_none_row_context(self): """None row_context => 0.0.""" sim = ContextAwarePromptBuilder.compute_context_similarity({"a": "b"}, None) assert sim == 0.0 def test_empty_values(self): """Context with all None values => 0.0.""" ctx1 = {"a": None, "b": None} ctx2 = {"a": "hello"} sim = ContextAwarePromptBuilder.compute_context_similarity(ctx1, ctx2) assert sim == 0.0 def test_case_insensitive(self): """Values are lowercased.""" ctx1 = {"a": "Hello"} ctx2 = {"a": "hello"} sim = ContextAwarePromptBuilder.compute_context_similarity(ctx1, ctx2) assert sim == 1.0 class TestBuildContextEntries: """ContextAwarePromptBuilder.build_context_entries.""" def test_basic_build(self): """Build list of rendered entries sorted by priority.""" entries = [ {"source_term": "hello", "target_term": "bonjour", "has_context": False, "context_data": None, "usage_notes": None}, {"source_term": "world", "target_term": "monde", "has_context": True, "context_data": {"table": "users"}, "usage_notes": None}, ] result = ContextAwarePromptBuilder.build_context_entries(entries) assert len(result) == 2 def test_priority_by_similarity(self): """Entries matching row_context get priority prefix.""" entries = [ {"source_term": "hello", "target_term": "bonjour", "has_context": True, "context_data": {"table": "users"}, "usage_notes": None}, {"source_term": "world", "target_term": "monde", "has_context": True, "context_data": {"table": "orders"}, "usage_notes": None}, ] row_context = {"table": "users"} result = ContextAwarePromptBuilder.build_context_entries(entries, row_context) # First entry matches and gets priority assert "# PRIORITY" in result[0] assert "# PRIORITY" not in result[1] def test_no_row_context(self): """No row_context => no priority.""" entries = [ {"source_term": "hello", "target_term": "bonjour", "has_context": False, "context_data": None, "usage_notes": None}, ] result = ContextAwarePromptBuilder.build_context_entries(entries) assert len(result) == 1 assert "# PRIORITY" not in result[0] def test_empty_entries(self): """Empty entries returns empty list.""" result = ContextAwarePromptBuilder.build_context_entries([]) assert result == [] def test_object_entries(self): """Entries as objects work too.""" entry = MagicMock() entry.source_term = "hello" entry.target_term = "bonjour" entry.has_context = True entry.context_data = {"table": "users"} entry.usage_notes = None result = ContextAwarePromptBuilder.build_context_entries([entry], {"table": "users"}) assert len(result) == 1 assert "# PRIORITY" in result[0] # #endregion Test.ContextAwarePromptBuilder