132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
"""Tests for SystemPrompter."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
import pytest
|
|
from backend.managers.system_prompter import SystemPrompter, MAX_FILE_CHARS # MAX_FILE_CHARS: character limit before content is truncated
|
|
|
|
|
|
class TestSystemPrompterBasePrompt:
|
|
"""Tests for generate_prompt() without file context."""
|
|
|
|
def test_returns_non_empty_string(self):
|
|
prompt = SystemPrompter.generate_prompt()
|
|
assert isinstance(prompt, str)
|
|
assert len(prompt) > 0
|
|
|
|
def test_describes_code_assistant(self):
|
|
prompt = SystemPrompter.generate_prompt()
|
|
assert "code assistant" in prompt.lower()
|
|
|
|
def test_contains_no_file_xml_tag(self):
|
|
prompt = SystemPrompter.generate_prompt()
|
|
assert "<file" not in prompt
|
|
assert "<code>" not in prompt
|
|
|
|
def test_none_equals_no_argument(self):
|
|
assert SystemPrompter.generate_prompt(file_context=None) == SystemPrompter.generate_prompt()
|
|
|
|
def test_empty_dict_behaves_like_none(self):
|
|
# {} is falsy in Python, so the implementation treats it the same as None (no file context).
|
|
assert SystemPrompter.generate_prompt(file_context={}) == SystemPrompter.generate_prompt()
|
|
|
|
|
|
class TestSystemPrompterWithFileContext:
|
|
"""Tests for generate_prompt() with file_context provided."""
|
|
|
|
def test_includes_filename(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "main.py", "content": ""})
|
|
assert "main.py" in prompt
|
|
|
|
def test_includes_file_content(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "app.py", "content": "x = 42"})
|
|
assert "x = 42" in prompt
|
|
|
|
def test_uses_xml_file_tag(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": "pass"})
|
|
assert "<file" in prompt
|
|
|
|
def test_uses_xml_code_tag(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": "pass"})
|
|
assert "<code>" in prompt
|
|
|
|
def test_with_context_is_longer_than_base(self):
|
|
base = SystemPrompter.generate_prompt()
|
|
with_ctx = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": "x=1"})
|
|
assert len(with_ctx) > len(base)
|
|
|
|
def test_missing_name_key_uses_unknown(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"content": "some code"})
|
|
assert "unknown" in prompt
|
|
|
|
def test_missing_content_key_does_not_raise(self):
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "empty.py"})
|
|
assert "empty.py" in prompt
|
|
|
|
|
|
class TestSystemPrompterTruncation:
|
|
"""Tests for file content truncation."""
|
|
|
|
def test_large_file_is_truncated(self):
|
|
large = "a" * (MAX_FILE_CHARS + 500)
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "big.py", "content": large})
|
|
assert "[truncated]" in prompt
|
|
|
|
def test_small_file_is_not_truncated(self):
|
|
content = "print('hello')"
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "small.py", "content": content})
|
|
assert "[truncated]" not in prompt
|
|
assert content in prompt
|
|
|
|
def test_file_exactly_at_limit_is_not_truncated(self):
|
|
content = "x" * MAX_FILE_CHARS
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": content})
|
|
assert "[truncated]" not in prompt
|
|
|
|
def test_file_one_over_limit_is_truncated(self):
|
|
content = "x" * (MAX_FILE_CHARS + 1)
|
|
prompt = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": content})
|
|
assert "[truncated]" in prompt
|
|
|
|
|
|
class TestSystemPrompterSpecialCharacters:
|
|
"""Tests that XML special characters in file content are handled without breaking the prompt."""
|
|
|
|
def test_less_than_in_content_does_not_break_prompt(self):
|
|
prompt = SystemPrompter.generate_prompt(
|
|
file_context={"name": "f.py", "content": "if x < 10:"}
|
|
)
|
|
assert "if x < 10:" in prompt
|
|
|
|
def test_greater_than_in_content_does_not_break_prompt(self):
|
|
prompt = SystemPrompter.generate_prompt(
|
|
file_context={"name": "f.py", "content": "if x > 0:"}
|
|
)
|
|
assert "if x > 0:" in prompt
|
|
|
|
def test_ampersand_in_content_does_not_break_prompt(self):
|
|
prompt = SystemPrompter.generate_prompt(
|
|
file_context={"name": "f.py", "content": "# true & false"}
|
|
)
|
|
assert "true & false" in prompt
|
|
|
|
def test_xml_tags_in_content_are_preserved_literally(self):
|
|
# User code often contains HTML or XML. The prompt builder must embed it
|
|
# verbatim — escaping or stripping tags would corrupt the file content.
|
|
prompt = SystemPrompter.generate_prompt(
|
|
file_context={"name": "template.html", "content": "<div>hello</div>"}
|
|
)
|
|
assert "<div>hello</div>" in prompt
|
|
|
|
def test_prompt_still_contains_file_xml_tag_with_special_content(self):
|
|
# Verifies that special characters in the content don't corrupt the surrounding
|
|
# structural tags (<file ...> and <code>) that the LLM relies on for context.
|
|
prompt = SystemPrompter.generate_prompt(
|
|
file_context={"name": "f.py", "content": "x < y and a > b"}
|
|
)
|
|
assert "<file" in prompt
|
|
assert "<code>" in prompt
|