"""Tests for hokusai.prompt — prompt resolution and placeholder substitution.""" from __future__ import annotations from pathlib import Path from hokusai.prompt import ( extract_placeholder_files, resolve_prompt, substitute_placeholders, ) class TestSubstitutePlaceholders: """Test placeholder substitution in prompt text.""" def test_no_placeholders(self, project_dir: Path) -> None: assert substitute_placeholders("Hello world", project_dir) == "Hello world" def test_single_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "name.txt").write_text("World") result = substitute_placeholders("Hello {name.txt}", project_dir) assert result == "Hello World" def test_multiple_placeholders(self, project_dir: Path) -> None: _ = (project_dir / "first.txt").write_text("Alice") _ = (project_dir / "last.txt").write_text("Smith") result = substitute_placeholders("Dear {first.txt} {last.txt}", project_dir) assert result == "Dear Alice Smith" def test_escaped_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "name.txt").write_text("World") result = substitute_placeholders("Hello \\{name.txt}", project_dir) assert result == "Hello {name.txt}" def test_double_escaped_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "name.txt").write_text("World") result = substitute_placeholders("Hello \\\\{name.txt}", project_dir) assert result == "Hello \\World" def test_triple_escaped_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "name.txt").write_text("World") result = substitute_placeholders("Hello \\\\\\{name.txt}", project_dir) assert result == "Hello \\{name.txt}" def test_nine_backslashes_before_placeholder(self, project_dir: Path) -> None: """Nine backslashes (odd) → 4 literal backslashes + literal {file.txt}.""" _ = (project_dir / "file.txt").write_text("content") # 9 backslashes before {file.txt}: odd → escaped, halve to 4 result = substitute_placeholders( "\\\\\\\\\\\\\\\\\\" + "{file.txt}", project_dir ) assert result == "\\\\\\\\{file.txt}" def test_placeholder_at_start(self, project_dir: Path) -> None: _ = (project_dir / "greeting.txt").write_text("Hi there") result = substitute_placeholders("{greeting.txt}!", project_dir) assert result == "Hi there!" def test_placeholder_with_multiline_content(self, project_dir: Path) -> None: _ = (project_dir / "block.txt").write_text("line1\nline2\nline3") result = substitute_placeholders("Start: {block.txt} :End", project_dir) assert result == "Start: line1\nline2\nline3 :End" class TestExtractPlaceholderFiles: """Test extraction of placeholder filenames from prompts.""" def test_no_placeholders(self) -> None: assert extract_placeholder_files("Hello world") == [] def test_single_placeholder(self) -> None: assert extract_placeholder_files("Hello {name.txt}") == ["name.txt"] def test_multiple_placeholders(self) -> None: result = extract_placeholder_files("{a.txt} and {b.txt}") assert result == ["a.txt", "b.txt"] def test_escaped_placeholder_ignored(self) -> None: assert extract_placeholder_files("\\{name.txt}") == [] def test_double_escaped_included(self) -> None: assert extract_placeholder_files("\\\\{name.txt}") == ["name.txt"] def test_triple_escaped_ignored(self) -> None: assert extract_placeholder_files("\\\\\\{name.txt}") == [] class TestResolvePrompt: """Test full prompt resolution (file loading + placeholder substitution).""" def test_inline_prompt_no_placeholders(self, project_dir: Path) -> None: assert resolve_prompt("Just a string", project_dir) == "Just a string" def test_file_prompt(self, project_dir: Path) -> None: _ = (project_dir / "prompt.txt").write_text("From file") result = resolve_prompt("prompt.txt", project_dir) assert result == "From file" def test_nonexistent_file_treated_as_inline(self, project_dir: Path) -> None: result = resolve_prompt("no_such_file.txt", project_dir) assert result == "no_such_file.txt" def test_inline_with_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "style.txt").write_text("impressionist") result = resolve_prompt("Paint in {style.txt} style", project_dir) assert result == "Paint in impressionist style" def test_multiline_prompt_with_placeholder(self, project_dir: Path) -> None: _ = (project_dir / "detail.txt").write_text("vivid colours") result = resolve_prompt("First line\nWith {detail.txt}", project_dir) assert result == "First line\nWith vivid colours" def test_file_prompt_no_placeholder_processing(self, project_dir: Path) -> None: """When the prompt is a file path, the file is loaded verbatim.""" _ = (project_dir / "prompt.txt").write_text("Literal {not_a_file.txt}") result = resolve_prompt("prompt.txt", project_dir) assert result == "Literal {not_a_file.txt}"