"""The server half of the end-of-turn shape check (milestone 439): what the agent is asked, what the hook's ledger may say, and the outcome record.""" import json from unittest.mock import patch import pytest from tests.helpers import make_mock_session def test_the_ledger_lines_parse_and_garbage_is_dropped(): from scribe.services.shape_check import parse_written raw = ("a.py\tsym\thelper\n" "a.py\tsym\thelper\n" # duplicate "web/A.svelte\tfile\tA\n" "x.py\tbogus\tthing\n" # unknown kind "only-two\tsym\n" # malformed "\tsym\tnameless-path\n") assert parse_written(raw) == [("a.py", "sym", "helper"), ("web/A.svelte", "file", "A")] assert len(parse_written("".join(f"p\tsym\tn{i}\n" for i in range(500)), cap=7)) == 7 def test_a_first_stop_blocks_or_passes_and_the_next_never_blocks(): from scribe.services.shape_check import outcome_for one = [{"path": "a", "kind": "sym", "symbol": "b"}] assert outcome_for("check", one) == "blocked" assert outcome_for("check", []) == "passed" assert outcome_for("after", one) == "left_after_block" assert outcome_for("after", []) == "judged_after_block" def test_the_reason_names_each_shape_its_evidence_and_the_one_call(): from scribe.services.shape_check import block_reason unjudged = [ {"path": "web/A.svelte", "kind": "file", "symbol": "A", "status": "new", "evidence": {}}, {"path": "src/x.py", "kind": "sym", "symbol": "load", "status": "unclassified", "evidence": {"looks_like": {"snippet_id": 12, "basis": "semantic", "score": 0.84}, "diverges_from": 9}}, ] reason = block_reason(unjudged, project_id=3, repo="git.example.com/a/w") assert "2 definitions" in reason assert "web/A.svelte · A (file) — new" in reason assert "looks like #12 0.84" in reason and "#9 is canon in this directory" in reason assert 'classify_shapes(project_id=3, repo="git.example.com/a/w"' in reason for verdict in ("create_snippet", "`instance`", "`variant`", "`exempt`"): assert verdict in reason, verdict def test_a_long_turn_is_listed_with_a_tail_not_dropped(): from scribe.services.shape_check import _LISTED, block_reason many = [{"path": "p.py", "kind": "sym", "symbol": f"f{i}", "status": "new", "evidence": {}} for i in range(_LISTED + 3)] reason = block_reason(many, project_id=1, repo="") assert "… and 3 more" in reason assert "repo=" not in reason async def test_the_outcome_is_recorded_as_a_plugin_event(): from scribe.services.shape_check import record_shape_check session = make_mock_session() with patch("scribe.services.shape_check.async_session", return_value=session): await record_shape_check(7, "blocked", written=5, unjudged=2, project_id=2) row = session.add.call_args.args[0] assert (row.category, row.action, row.user_id) == ("plugin", "shape_check", 7) assert json.loads(row.details) == {"outcome": "blocked", "written": 5, "unjudged": 2, "project_id": 2} async def test_an_unknown_outcome_is_refused_before_anything_is_written(): from scribe.services.shape_check import record_shape_check session = make_mock_session() with patch("scribe.services.shape_check.async_session", return_value=session), \ pytest.raises(ValueError): await record_shape_check(7, "skipped", written=1, unjudged=1) session.add.assert_not_called()