"""Only the operator actually assigned to a test may edit its fields — not just any red_tech/blue_tech, and not the lead either, unless the lead is themselves the assignee. Reproduces the bug report: a red_lead could edit a test assigned to a different operator (jesus-huertas).""" from app.models.enums import TestState from app.models.test import Test from app.models.technique import Technique def _seed_technique(db) -> Technique: technique = Technique( mitre_id="T9998", name="Assignee Lock Test Technique", tactic="execution", platforms=["linux"], ) db.add(technique) db.commit() db.refresh(technique) return technique def _seed_test(db, technique, created_by, **overrides) -> Test: test = Test(technique_id=technique.id, name="Locked test", created_by=created_by, **overrides) db.add(test) db.commit() db.refresh(test) return test def test_red_lead_cannot_edit_test_assigned_to_other_operator( client, db, red_lead_headers, red_lead_user, red_tech_user ): technique = _seed_technique(db) test = _seed_test( db, technique, red_lead_user.id, state=TestState.red_executing, red_tech_assignee=red_tech_user.id, ) resp = client.patch( f"/api/v1/tests/{test.id}/red", json={"procedure_text": "changed by the wrong lead"}, headers=red_lead_headers, ) assert resp.status_code == 403 assert "assigned to another operator" in resp.json()["detail"] def test_different_red_tech_cannot_edit_test_assigned_to_other_operator( client, db, red_lead_headers, red_lead_user, red_tech_user, red_tech_headers ): technique = _seed_technique(db) # Assigned to red_lead_user (leads can be operators too via /assign) so # a *different* red_tech is the one locked out here. test = _seed_test( db, technique, red_lead_user.id, state=TestState.red_executing, red_tech_assignee=red_lead_user.id, ) resp = client.patch( f"/api/v1/tests/{test.id}/red", json={"procedure_text": "changed by a different tech"}, headers=red_tech_headers, ) assert resp.status_code == 403 def test_assigned_red_tech_can_edit_own_test( client, db, red_tech_headers, red_tech_user, red_lead_user ): technique = _seed_technique(db) test = _seed_test( db, technique, red_lead_user.id, state=TestState.red_executing, red_tech_assignee=red_tech_user.id, ) resp = client.patch( f"/api/v1/tests/{test.id}/red", json={"procedure_text": "changed by the assigned operator"}, headers=red_tech_headers, ) assert resp.status_code == 200, resp.text def test_blue_lead_cannot_edit_test_assigned_to_other_operator( client, db, blue_lead_headers, blue_lead_user, blue_tech_user ): technique = _seed_technique(db) test = _seed_test( db, technique, blue_lead_user.id, state=TestState.blue_evaluating, blue_tech_assignee=blue_tech_user.id, ) resp = client.patch( f"/api/v1/tests/{test.id}/blue", json={"detection_result": "detected"}, headers=blue_lead_headers, ) assert resp.status_code == 403 assert "assigned to another operator" in resp.json()["detail"] def test_unassigned_test_is_editable_by_any_eligible_role( client, db, red_lead_headers, red_lead_user ): """No assignee yet (e.g. still in draft/newly executing) — the lock only kicks in once an operator has actually been assigned.""" technique = _seed_technique(db) test = _seed_test(db, technique, red_lead_user.id, state=TestState.red_executing) resp = client.patch( f"/api/v1/tests/{test.id}/red", json={"procedure_text": "no assignee set yet"}, headers=red_lead_headers, ) assert resp.status_code == 200, resp.text