test(coverage): add FuzzyAnchor and HistoryManager unit tests
- test_fuzzy_anchor.py: 6 tests for fuzzy slice resolution - test_history_manager.py: 8 tests for undo/redo and UISnapshot roundtrip
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Feature Coverage (Core Logic)
|
||||
- [~] Task: Write unit tests for C++ AST Tree Masking logic using samples from `gencpp/base/components`.
|
||||
- [x] Task: Write unit tests for C++ AST Tree Masking logic using samples from `gencpp/base/components`.
|
||||
- [ ] Task: Write unit tests for 'Fuzzy Anchor' resolution across simulated file edits.
|
||||
- [ ] Task: Write unit tests for `HistoryManager` context snapshot roundtrips.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
from src.fuzzy_anchor import FuzzyAnchor
|
||||
|
||||
|
||||
class TestFuzzyAnchor:
|
||||
def test_create_slice_basic(self):
|
||||
text = "line0\nline1\nline2\nline3\nline4\n"
|
||||
result = FuzzyAnchor.create_slice(text, 2, 4)
|
||||
assert "start_line" in result
|
||||
assert "end_line" in result
|
||||
assert "content_hash" in result
|
||||
assert "start_context" in result
|
||||
assert "end_context" in result
|
||||
assert result["start_line"] == 2
|
||||
assert result["end_line"] == 4
|
||||
assert result["start_context"] == result["end_context"]
|
||||
|
||||
def test_resolve_slice_exact_match(self):
|
||||
text = "line0\nline1\nline2\nline3\nline4\n"
|
||||
slc = FuzzyAnchor.create_slice(text, 2, 4)
|
||||
result = FuzzyAnchor.resolve_slice(text, slc)
|
||||
assert result is not None
|
||||
start, end = result
|
||||
assert start == 2
|
||||
assert end == 4
|
||||
|
||||
def test_resolve_slice_line_inserted_before(self):
|
||||
original = "line0\nline1\nline2\nline3\nline4\n"
|
||||
modified = "NEW\nline0\nline1\nline2\nline3\nline4\n"
|
||||
slc = FuzzyAnchor.create_slice(original, 2, 4)
|
||||
result = FuzzyAnchor.resolve_slice(modified, slc)
|
||||
assert result is not None
|
||||
start, end = result
|
||||
assert start == 3
|
||||
assert end == 5
|
||||
|
||||
def test_resolve_slice_line_deleted_before_returns_none(self):
|
||||
original = "line0\nline1\nline2\nline3\nline4\n"
|
||||
modified = "line0\nline2\nline3\nline4\n"
|
||||
slc = FuzzyAnchor.create_slice(original, 2, 4)
|
||||
result = FuzzyAnchor.resolve_slice(modified, slc)
|
||||
assert result is None
|
||||
|
||||
def test_resolve_slice_multiple_lines_changed(self):
|
||||
original = "line0\nline1\nline2\nline3\nline4\n"
|
||||
modified = "a\nb\nc\nd\ne\nline0\nline1\nline2\nline3\nline4\n"
|
||||
slc = FuzzyAnchor.create_slice(original, 1, 2)
|
||||
result = FuzzyAnchor.resolve_slice(modified, slc)
|
||||
assert result is not None
|
||||
start, end = result
|
||||
assert start == 6
|
||||
assert end == 7
|
||||
|
||||
def test_resolve_slice_anchor_mismatch_returns_none(self):
|
||||
original = "alpha\nbeta\ngamma\ndelta\nepsilon\n"
|
||||
modified = "foo\nbar\nbaz\ndelta\nepsilon\n"
|
||||
slc = FuzzyAnchor.create_slice(original, 2, 3)
|
||||
result = FuzzyAnchor.resolve_slice(modified, slc)
|
||||
assert result is None
|
||||
@@ -0,0 +1,97 @@
|
||||
import pytest
|
||||
from src.history import HistoryManager, UISnapshot
|
||||
|
||||
|
||||
class TestHistoryManager:
|
||||
def test_push_and_undo(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
hm.push({"value": 1}, "initial")
|
||||
hm.push({"value": 2}, "change to 2")
|
||||
assert hm.can_undo is True
|
||||
result = hm.undo(current_state={"value": 2})
|
||||
assert result is not None
|
||||
assert result.state["value"] == 2
|
||||
assert hm.can_undo is True
|
||||
|
||||
def test_undo_and_redo(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
hm.push({"value": 1}, "initial")
|
||||
hm.push({"value": 2}, "change to 2")
|
||||
assert hm.can_redo is False
|
||||
hm.undo(current_state={"value": 2})
|
||||
assert hm.can_redo is True
|
||||
redone = hm.redo(current_state={"value": 1})
|
||||
assert redone is not None
|
||||
assert redone.state["value"] == 2
|
||||
|
||||
def test_undo_no_history_returns_none(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
assert hm.can_undo is False
|
||||
result = hm.undo(current_state={"value": 1})
|
||||
assert result is None
|
||||
|
||||
def test_redo_no_history_returns_none(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
assert hm.can_redo is False
|
||||
result = hm.redo(current_state={"value": 1})
|
||||
assert result is None
|
||||
|
||||
def test_jump_to_undo(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
hm.push({"value": 1}, "initial")
|
||||
hm.push({"value": 2}, "change to 2")
|
||||
hm.push({"value": 3}, "change to 3")
|
||||
result = hm.jump_to_undo(0, current_state={"value": 3})
|
||||
assert result is not None
|
||||
assert result.state["value"] == 1
|
||||
assert hm.can_redo is True
|
||||
|
||||
def test_get_history_returns_descriptions(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
hm.push({"value": 1}, "first")
|
||||
hm.push({"value": 2}, "second")
|
||||
hm.push({"value": 3}, "third")
|
||||
history = hm.get_history()
|
||||
assert len(history) == 3
|
||||
assert history[0]["description"] == "first"
|
||||
assert history[1]["description"] == "second"
|
||||
assert "timestamp" in history[0]
|
||||
|
||||
def test_snapshot_roundtrip(self):
|
||||
snap = UISnapshot(
|
||||
ai_input="test input",
|
||||
project_system_prompt="project prompt",
|
||||
global_system_prompt="global prompt",
|
||||
base_system_prompt="base prompt",
|
||||
use_default_base_prompt=True,
|
||||
temperature=0.5,
|
||||
top_p=0.9,
|
||||
max_tokens=8192,
|
||||
auto_add_history=True,
|
||||
disc_entries=[{"role": "user", "content": "hello"}],
|
||||
files=[{"path": "a.txt"}],
|
||||
screenshots=["screenshot.png"],
|
||||
)
|
||||
d = snap.to_dict()
|
||||
restored = UISnapshot.from_dict(d)
|
||||
assert restored.ai_input == snap.ai_input
|
||||
assert restored.project_system_prompt == snap.project_system_prompt
|
||||
assert restored.global_system_prompt == snap.global_system_prompt
|
||||
assert restored.base_system_prompt == snap.base_system_prompt
|
||||
assert restored.use_default_base_prompt == snap.use_default_base_prompt
|
||||
assert restored.temperature == snap.temperature
|
||||
assert restored.top_p == snap.top_p
|
||||
assert restored.max_tokens == snap.max_tokens
|
||||
assert restored.auto_add_history == snap.auto_add_history
|
||||
assert restored.disc_entries == snap.disc_entries
|
||||
assert restored.files == snap.files
|
||||
assert restored.screenshots == snap.screenshots
|
||||
|
||||
def test_push_clears_redo_stack(self):
|
||||
hm = HistoryManager(max_capacity=10)
|
||||
hm.push({"value": 1}, "initial")
|
||||
hm.push({"value": 2}, "change to 2")
|
||||
hm.undo(current_state={"value": 2})
|
||||
assert hm.can_redo is True
|
||||
hm.push({"value": 3}, "change to 3")
|
||||
assert hm.can_redo is False
|
||||
Reference in New Issue
Block a user