"""Unit tests for macro parser.""" import pytest from markitect.prompts.templates.parser import MacroParser, MacroParsingError from markitect.prompts.templates.models import MacroKind class TestMacroParser: """Tests for MacroParser.""" def setup_method(self): """Setup parser for each test.""" self.parser = MacroParser() def test_parse_required_macro(self): """Test parsing required macro.""" content = "Some text {{require:glossary}} more text" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].kind == MacroKind.REQUIRED assert macros[0].target == "glossary" assert macros[0].parameters == {} def test_parse_optional_macro(self): """Test parsing optional macro.""" content = "Text {{optional:constraints}}" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].kind == MacroKind.OPTIONAL assert macros[0].target == "constraints" def test_parse_generate_macro(self): """Test parsing generate macro.""" content = "{{generate:examples}}" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].kind == MacroKind.GENERATE assert macros[0].target == "examples" def test_parse_macro_with_parameters(self): """Test parsing macro with parameters.""" content = "{{generate:code|language=python|framework=fastapi}}" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].target == "code" assert macros[0].parameters == { "language": "python", "framework": "fastapi", } def test_parse_multiple_macros(self): """Test parsing multiple macros.""" content = """ {{require:glossary}} Some text here {{optional:notes}} {{generate:examples|lang=python}} """ macros = self.parser.parse(content) assert len(macros) == 3 assert macros[0].kind == MacroKind.REQUIRED assert macros[1].kind == MacroKind.OPTIONAL assert macros[2].kind == MacroKind.GENERATE def test_parse_with_line_numbers(self): """Test that line numbers are recorded.""" content = """Line 1 {{require:dep1}} Line 3 {{optional:dep2}} """ macros = self.parser.parse(content) assert macros[0].line_number == 2 assert macros[1].line_number == 4 def test_parse_case_insensitive(self): """Test macro kind is case insensitive.""" content = "{{REQUIRE:test}} {{Optional:test2}} {{Generate:test3}}" macros = self.parser.parse(content) assert len(macros) == 3 assert all(m.kind in MacroKind for m in macros) def test_parse_alias_gen_for_generate(self): """Test 'gen' alias for 'generate'.""" content = "{{gen:test}}" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].kind == MacroKind.GENERATE def test_parse_alias_required_for_require(self): """Test 'required' alias for 'require'.""" content = "{{required:test}}" macros = self.parser.parse(content) assert len(macros) == 1 assert macros[0].kind == MacroKind.REQUIRED def test_parse_preserves_raw_text(self): """Test raw text is preserved.""" content = "{{require:test|a=1}}" macros = self.parser.parse(content) assert macros[0].raw_text == "{{require:test|a=1}}" def test_parse_empty_content(self): """Test parsing empty content.""" macros = self.parser.parse("") assert macros == [] def test_parse_no_macros(self): """Test parsing content without macros.""" content = "Just regular text without any macros" macros = self.parser.parse(content) assert macros == [] def test_parse_invalid_macro_kind_raises_error(self): """Test invalid macro kind raises error.""" content = "{{invalid:target}}" with pytest.raises(MacroParsingError, match="Invalid macro kind"): self.parser.parse(content) def test_parse_empty_target_raises_error(self): """Test empty target raises error.""" content = "{{require:}}" with pytest.raises(MacroParsingError, match="target cannot be empty"): self.parser.parse(content) def test_find_macro_positions(self): """Test finding macro positions.""" content = "Start {{require:dep1}} middle {{optional:dep2}} end" positions = self.parser.find_macro_positions(content) assert len(positions) == 2 assert content[positions[0][0]:positions[0][1]] == "{{require:dep1}}" assert content[positions[1][0]:positions[1][1]] == "{{optional:dep2}}" def test_count_macros(self): """Test macro counting.""" content = """ {{require:dep1}} {{require:dep2}} {{optional:dep3}} {{generate:gen1}} """ counts = self.parser.count_macros(content) assert counts['required'] == 2 assert counts['optional'] == 1 assert counts['generate'] == 1 def test_has_macros_true(self): """Test has_macros returns True when macros present.""" content = "Text {{require:test}} text" assert self.parser.has_macros(content) is True def test_has_macros_false(self): """Test has_macros returns False when no macros.""" content = "Just plain text" assert self.parser.has_macros(content) is False def test_parse_macro_with_spaces_in_target(self): """Test target with spaces is trimmed.""" content = "{{require: my-artifact }}" macros = self.parser.parse(content) assert macros[0].target == "my-artifact" def test_parse_parameter_with_spaces(self): """Test parameters with spaces are trimmed.""" content = "{{generate:test| key = value }}" macros = self.parser.parse(content) assert macros[0].parameters == {"key": "value"}