"""test_testing_mode.py — T19 §19L/19M/19N/19O/19P held-outs.""" from __future__ import annotations import sys from pathlib import Path import pytest REPO = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO / "src")) from rune import behaviour_ledger as bl # noqa: E402 from rune import forge # noqa: E402 from rune import testing_mode as tm # noqa: E402 @pytest.fixture() def env(tmp_path): beh = bl.BehaviourLedger(tmp_path) beh.define(behaviour_id="T-1", owning_project="p", intended_behaviour="first behaviour", procedure="run the thing; expect output X", pass_criteria="output == X", fail_criteria="output != X", owner_component="component:shared", dependencies=["component:shared"]) beh.define(behaviour_id="T-2", owning_project="p", intended_behaviour="second behaviour") beh.save() return beh def test_19n_session_is_coordination_not_authority(tmp_path, env): s = tm.TestingSession(tmp_path, project="p") item = s.next_item(env) assert item["behaviour_id"] == "behaviour:T-1" # FAIL items none yet; # UNVALIDATED sorts after — both outstanding, T-1 first alphabetically s.record(env, "T-1", passed=True, detail="checked by hand") assert env.get("T-1")["state"] == bl.USER_CONFIRMED s.pause() assert s.next_item(env) is None # paused s.resume() assert s.next_item(env)["behaviour_id"] == "behaviour:T-2" # session file holds coordination, NOT spec truth import json sess = json.loads((tmp_path / "testing_session.json").read_text()) assert "intended_behaviour" not in str(sess) # durable truth intact in the ledger assert env.get("T-1")["intended_behaviour"] == "first behaviour" def test_19m_fix_reopens_dependent_confirmation(tmp_path, env): """Test-B-fix-invalidates-Test-A (19S-9): A confirmed; B fails; the B fix touches a dependency shared with A → A re-enters the queue.""" env.record_auto_verification("T-1", passed=True, generation="g1") env.record_real_world("T-1", passed=True, generation="g1") # A hardened # B fails in the field env.record_real_world("T-2", passed=False, detail="broke") assert env.get("T-2")["state"] == bl.TEST_FAILED # the fix structurally touches A's dependency invalidated = env.invalidate_for_change( changed_entities={"component:shared"}, generation="g2", reasons={"behaviour:T-1": "fix for T-2 rewrote shared component"}) assert [r["behaviour_id"] for r in invalidated] == ["behaviour:T-1"] assert env.get("T-1")["state"] == bl.RETEST_REQUIRED # dynamic queue now contains BOTH ids = [b["behaviour_id"] for b in env.outstanding()] assert "behaviour:T-1" in ids and "behaviour:T-2" in ids # A's generation-1 confirmation remains queryable hist = env.retest_history("T-1") assert any(h["generation"] == "g1" and h["event"].endswith( "USER_CONFIRMED") for h in hist) def test_19s10_no_blanket_invalidation(tmp_path, env): env.record_real_world("T-1", passed=True) env.record_real_world("T-2", passed=True) # a change to an unrelated entity invalidates NOTHING invalidated = env.invalidate_for_change( changed_entities={"component:unrelated-thing"}, generation="g") assert invalidated == [] assert env.get("T-1")["state"] == bl.USER_CONFIRMED assert env.get("T-2")["state"] == bl.USER_CONFIRMED def test_19o_observation_to_closure(tmp_path): obs = tm.record_observation( tmp_path, observed="button does nothing", expected="button saves the form", behaviour_id="behaviour:T-1", environment="production", reproduction="click save with empty field", candidate_areas=["component:form-handler"]) # hypothesis only assert obs["candidate_areas"] == ["component:form-handler"] # premature closure (no user confirm) is recorded as NOT closed r = tm.close_observation(tmp_path, obs["observation_id"], behaviour_retest_passed=True, user_confirmed=False) assert r["closure"]["closed"] is False # automated green ≠ closure # full closure requires retest AND user confirmation r2 = tm.close_observation(tmp_path, obs["observation_id"], behaviour_retest_passed=True, user_confirmed=True) assert r2["closure"]["closed"] is True def test_19p_forge_projection_never_rewrites_truth(tmp_path, env): """19S-14: forge unavailable → projection fails loudly; ledger truth unchanged.""" obs = tm.record_observation(tmp_path, observed="x", expected="y", behaviour_id="behaviour:T-1") before = env.get("T-1")["state"] bad = forge.GiteaForge(server="https://invalid.invalid") with pytest.raises(forge.ForgeUnavailable): tm.project_failure_to_forge(bad, "conductor/rune", behaviour_id="T-1", observation=obs) assert env.get("T-1")["state"] == before # truth untouched def test_19l_aggregate_across_projects(tmp_path): reg = tm.ProjectRegistry(tmp_path) p1 = tmp_path / "p1" p1.mkdir() beh = bl.BehaviourLedger(p1) beh.define(behaviour_id="X-1", owning_project="p1", intended_behaviour="x") beh.save() reg.register(project_id="p1", scaffold_location=str(p1)) reg.register(project_id="p2", scaffold_location=str(tmp_path / "p2")) rows = tm.aggregate(reg, tmp_path) by_p = {r["project"]: r for r in rows} assert by_p["p1"]["outstanding"] == 1 assert by_p["p2"]["outstanding"] is None # no ledger yet