remove some testcases

This commit is contained in:
Livio Meuli 2026-05-21 12:06:32 +02:00
parent 1c6e4a2b9d
commit f91707b5b5
5 changed files with 8 additions and 279 deletions

View File

@ -24,25 +24,10 @@ def _mock_ok(content="AI reply"):
return mock
# ── Initialization ────────────────────────────────────────────────────────────
class TestChatManagerInit:
"""Verify that a fresh ChatManager starts in a clean, predictable state."""
def test_chat_history_starts_empty(self):
cm = ChatManager()
assert cm.chat_history == []
def test_api_url_contains_endpoint(self):
cm = ChatManager()
assert isinstance(cm.api_url, str)
assert "/v1/chat/completions" in cm.api_url
# ── History management ────────────────────────────────────────────────────────
class TestHistory:
"""Tests for add_message, get_history, and clear_history."""
"""Tests for add_message and clear_history."""
@pytest.fixture
def cm(self):
@ -58,22 +43,11 @@ class TestHistory:
assert cm.chat_history[0]["role"] == "user"
assert cm.chat_history[1]["role"] == "assistant"
def test_get_history_returns_copy_not_reference(self, cm):
"""Mutating the returned list must not corrupt internal history."""
cm.add_message("user", "Hi")
history = cm.get_history()
assert history == cm.chat_history
assert history is not cm.chat_history
def test_clear_history_empties_list(self, cm):
cm.add_message("user", "Hi")
cm.clear_history()
assert cm.chat_history == []
def test_clear_history_on_empty_is_safe(self, cm):
cm.clear_history()
assert cm.chat_history == []
# ── send_message (mocked HTTP) ────────────────────────────────────────────────
@ -108,12 +82,6 @@ class TestSendMessage:
assert payload["messages"][0]["role"] == "system"
assert payload["messages"][1]["role"] == "user"
def test_history_grows_by_two_per_call(self, cm):
with patch("requests.post", return_value=_mock_ok()):
cm.send_message("First")
cm.send_message("Second")
assert len(cm.chat_history) == 4
def test_connection_error_raises_and_adds_error_to_history(self, cm):
with patch("requests.post", side_effect=requests.exceptions.ConnectionError("refused")):
with pytest.raises(Exception, match="Connection Error"):
@ -141,14 +109,6 @@ class TestSendMessage:
with pytest.raises(Exception, match="Invalid API response format"):
cm.send_message("Hello")
def test_missing_choices_key_raises(self, cm):
mock = MagicMock()
mock.status_code = 200
mock.json.return_value = {}
with patch("requests.post", return_value=mock):
with pytest.raises(Exception):
cm.send_message("Hello")
def test_api_key_included_in_header_when_set(self, cm):
cm.api_key = "test-key-123"
with patch("requests.post", return_value=_mock_ok()) as mock_post:
@ -164,13 +124,6 @@ class TestSendMessage:
headers = mock_post.call_args.kwargs["headers"]
assert "Authorization" not in headers
def test_api_key_excluded_from_header_when_empty_string(self, cm):
cm.api_key = ""
with patch("requests.post", return_value=_mock_ok()) as mock_post:
cm.send_message("Hello")
headers = mock_post.call_args.kwargs["headers"]
assert "Authorization" not in headers
def test_json_decode_error_raises(self, cm):
import json
mock = MagicMock()
@ -184,15 +137,12 @@ class TestSendMessage:
# ── get_chat_display ──────────────────────────────────────────────────────────
class TestGetChatDisplay:
"""Tests for get_chat_display: correct shape, ordering, isolation, and role coverage."""
"""Tests for get_chat_display: correct shape and ordering."""
@pytest.fixture
def cm(self):
return ChatManager()
def test_empty_history_returns_empty_list(self, cm):
assert cm.get_chat_display() == []
def test_display_has_role_and_content_keys(self, cm):
cm.add_message("user", "Hello")
entry = cm.get_chat_display()[0]
@ -205,41 +155,3 @@ class TestGetChatDisplay:
display = cm.get_chat_display()
assert display[0]["role"] == "user"
assert display[1]["role"] == "assistant"
def test_display_returns_copy_not_reference(self, cm):
"""Mutating the returned list must not corrupt internal history."""
cm.add_message("user", "Hi")
display = cm.get_chat_display()
display.clear()
assert len(cm.chat_history) == 1
def test_system_messages_included_in_display(self, cm):
cm.add_message("system", "Be helpful.")
assert cm.get_chat_display()[0]["role"] == "system"
# ── Integration (skipped when API unreachable) ────────────────────────────────
class TestSendMessageIntegration:
"""End-to-end tests against the live API. Skipped automatically when the API is unreachable."""
@pytest.fixture
def cm(self):
return ChatManager()
def test_real_api_returns_non_empty_string(self, cm):
try:
response = cm.send_message("Reply with exactly the word PONG.")
assert isinstance(response, str)
assert len(response) > 0
assert len(cm.chat_history) == 2
except Exception as e:
pytest.skip(f"API not reachable: {e}")
def test_real_api_multi_turn_history_grows(self, cm):
try:
cm.send_message("Remember the number 42.")
cm.send_message("What number did I ask you to remember?")
assert len(cm.chat_history) == 4
except Exception as e:
pytest.skip(f"API not reachable: {e}")

View File

@ -73,10 +73,6 @@ class TestTruncateResult:
assert len(result) < len(long)
assert "TRUNCATED" in result
def test_exact_limit_not_truncated(self):
text = "a" * MAX_RESULT_LENGTH
assert truncate_result(text) == text
def test_truncated_keeps_start_and_end(self):
text = "START" + "x" * MAX_RESULT_LENGTH + "END"
result = truncate_result(text)
@ -144,9 +140,6 @@ class TestStripCodeFences:
text = "```\nhello\n```"
assert _strip_code_fences(text) == "hello"
def test_strips_whitespace(self):
assert _strip_code_fences(" hello ") == "hello"
# ═════════════════════════════════════════════════════════════════════════════
# TestCodingAgentInit
@ -162,11 +155,6 @@ class TestCodingAgentInit:
assert agent.is_done is False
assert agent.iteration == 0
def test_api_url_is_set(self):
agent = CodingAgent()
assert agent.api_url.startswith("http://")
assert "/v1/chat/completions" in agent.api_url
def test_start_task_sets_messages(self):
agent = CodingAgent()
agent.start_task("Write fibonacci.py")
@ -397,55 +385,3 @@ class TestReject:
assert not list(tmp_path.glob("*"))
# ═════════════════════════════════════════════════════════════════════════════
# TestFullLoop (integration – real API, skipped if unreachable)
# ═════════════════════════════════════════════════════════════════════════════
class TestFullLoop:
"""End-to-end test: agent runs a real task against the live API.
Skipped automatically if the API is not reachable.
"""
MAX_STEPS = 15
async def _run_until_done(self, agent) -> list:
steps = []
for _ in range(self.MAX_STEPS):
action = await agent.propose_next_action()
result = await agent.approve()
steps.append(result)
if result["is_done"]:
break
return steps
@pytest.mark.asyncio
async def test_agent_completes_hello_world_task(self, tmp_path):
try:
with patch("backend.agent.coding_agent.WORKSPACE", tmp_path):
agent = CodingAgent()
agent.start_task(
"Write a Python file called hello.py that prints 'Hello World'. "
"Validate it and run it."
)
steps = await self._run_until_done(agent)
assert agent.is_done, "Agent did not reach done state"
tools_used = [s["tool"] for s in steps]
assert "done" in tools_used
except Exception as e:
pytest.skip(f"API not reachable or environment incomplete: {e}")
@pytest.mark.asyncio
async def test_agent_creates_file_on_disk(self, tmp_path):
try:
with patch("backend.agent.coding_agent.WORKSPACE", tmp_path):
agent = CodingAgent()
agent.start_task("Write a file called output.txt containing the text 'test passed'.")
await self._run_until_done(agent)
py_files = list(tmp_path.glob("*.txt")) + list(tmp_path.glob("*.py"))
assert len(py_files) > 0, "Agent did not create any file"
except Exception as e:
pytest.skip(f"API not reachable or environment incomplete: {e}")
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])

View File

@ -17,19 +17,10 @@ def logger():
return DebugLogger()
# ── Initialization ────────────────────────────────────────────────────────────
class TestInit:
"""Tests that a fresh DebugLogger starts with an empty log list."""
def test_logs_start_empty(self, logger):
assert logger.logs == []
# ── log() ─────────────────────────────────────────────────────────────────────
class TestLog:
"""Tests for log(): each call appends an INFO-level entry with message and timestamp."""
"""Tests for log(): each call appends an INFO-level entry with message."""
def test_log_appends_entry(self, logger):
logger.log("started")
@ -43,31 +34,16 @@ class TestLog:
logger.log("executing file.py")
assert logger.logs[0]["message"] == "executing file.py"
def test_log_adds_timestamp(self, logger):
logger.log("started")
ts = logger.logs[0]["timestamp"]
# HH:MM:SS format — exactly 8 chars with two colons
assert len(ts) == 8
assert ts[2] == ":" and ts[5] == ":"
# ── log_error() ───────────────────────────────────────────────────────────────
class TestLogError:
"""Tests for log_error(): same shape as log() but level is ERROR, not INFO."""
def test_log_error_appends_entry(self, logger):
logger.log_error("something broke")
assert len(logger.logs) == 1
"""Tests for log_error(): level is ERROR, not INFO."""
def test_log_error_sets_level_error(self, logger):
logger.log_error("something broke")
assert logger.logs[0]["level"] == "ERROR"
def test_log_error_stores_message(self, logger):
logger.log_error("exit code 1")
assert logger.logs[0]["message"] == "exit code 1"
def test_log_and_log_error_are_distinct_levels(self, logger):
logger.log("info message")
logger.log_error("error message")
@ -78,24 +54,18 @@ class TestLogError:
# ── get_logs() ────────────────────────────────────────────────────────────────
class TestGetLogs:
"""Tests for get_logs(): returns all entries and a copy, not a reference to the internal list."""
"""Tests for get_logs()."""
def test_get_logs_returns_all_entries(self, logger):
logger.log("first")
logger.log_error("second")
assert len(logger.get_logs()) == 2
def test_get_logs_returns_copy_not_reference(self, logger):
logger.log("entry")
logs = logger.get_logs()
assert logs == logger.logs
assert logs is not logger.logs
# ── clear() ───────────────────────────────────────────────────────────────────
class TestClear:
"""Tests for clear(): empties the log list and leaves the logger ready for reuse."""
"""Tests for clear()."""
def test_clear_removes_all_entries(self, logger):
logger.log("first")
@ -103,23 +73,11 @@ class TestClear:
logger.clear()
assert logger.logs == []
def test_clear_on_empty_is_safe(self, logger):
logger.clear()
assert logger.logs == []
def test_log_after_clear_works(self, logger):
logger.log("before")
logger.clear()
logger.log("after")
assert len(logger.logs) == 1
assert logger.logs[0]["message"] == "after"
# ── format_debug_output() ─────────────────────────────────────────────────────
class TestFormatDebugOutput:
"""Tests for format_debug_output(): renders a dict of {rc, stdout, stderr} plus
accumulated log entries into a human-readable string for the UI."""
"""Tests for format_debug_output(): renders rc, stdout, stderr, and log entries."""
def test_success_exit_code_shows_success(self, logger):
result = logger.format_debug_output({"rc": 0, "stdout": "", "stderr": ""})
@ -129,10 +87,6 @@ class TestFormatDebugOutput:
result = logger.format_debug_output({"rc": 1, "stdout": "", "stderr": ""})
assert "[FAILED]" in result
def test_exit_code_included_in_output(self, logger):
result = logger.format_debug_output({"rc": 42, "stdout": "", "stderr": ""})
assert "42" in result
def test_stdout_included_when_present(self, logger):
result = logger.format_debug_output({"rc": 0, "stdout": "Hello", "stderr": ""})
assert "Hello" in result
@ -143,10 +97,6 @@ class TestFormatDebugOutput:
assert "NameError" in result
assert "stderr" in result
def test_no_output_message_when_both_empty(self, logger):
result = logger.format_debug_output({"rc": 0, "stdout": "", "stderr": ""})
assert "No output produced." in result
def test_log_entries_appended_to_output(self, logger):
logger.log("Executing file.py...")
logger.log_error("exit code 1")
@ -154,10 +104,6 @@ class TestFormatDebugOutput:
assert "Executing file.py..." in result
assert "exit code 1" in result
def test_returns_string(self, logger):
result = logger.format_debug_output({"rc": 0, "stdout": "", "stderr": ""})
assert isinstance(result, str)
def test_missing_keys_do_not_raise(self, logger):
# Defensive: format_debug_output uses .get() so missing keys are safe
result = logger.format_debug_output({})

View File

@ -99,11 +99,6 @@ class TestReadFile:
def test_nonexistent_file_returns_empty_string(self, fm, tmp_path):
assert fm.read_file(tmp_path / "ghost.py") == ""
def test_directory_path_returns_empty_string(self, fm, tmp_path):
sub = tmp_path / "subdir"
sub.mkdir()
assert fm.read_file(sub) == ""
def test_file_outside_workspace_returns_empty_string(self, fm, tmp_path):
outside = tmp_path.parent / "outside.py"
outside.write_text("secret")
@ -195,9 +190,6 @@ class TestDeleteFolder:
assert result is True
assert not sub.exists()
def test_nonexistent_folder_returns_false(self, fm):
assert fm.delete_folder("ghost") is False
def test_path_traversal_returns_false(self, fm):
assert fm.delete_folder("../../") is False
@ -226,8 +218,3 @@ class TestGetFileTree:
tree = fm.get_file_tree()
assert tree["src"]["app.py"] is None
def test_entries_are_sorted(self, fm, tmp_path):
(tmp_path / "z_file.py").touch()
(tmp_path / "a_file.py").touch()
keys = list(fm.get_file_tree().keys())
assert keys == sorted(keys)

View File

@ -6,33 +6,17 @@ 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
from backend.managers.system_prompter import SystemPrompter, MAX_FILE_CHARS
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."""
@ -53,10 +37,6 @@ class TestSystemPrompterWithFileContext:
prompt = SystemPrompter.generate_prompt(file_context={"name": "f.py", "content": "pass"})
assert "<code>" in prompt
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
@ -76,11 +56,6 @@ class TestSystemPrompterTruncation:
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})
@ -90,24 +65,6 @@ class TestSystemPrompterTruncation:
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.
@ -115,12 +72,3 @@ class TestSystemPrompterSpecialCharacters:
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