"""Tests for POST /tests/{id}/hold and /resume — must pause/resume the phase timer.""" from datetime import datetime, timedelta from app.models.test import Test from app.models.technique import Technique from app.models.enums import TestState def _seed_technique(db) -> Technique: technique = Technique( mitre_id="T9999", name="Test Technique", tactic="execution", platforms=["linux"], ) db.add(technique) db.commit() db.refresh(technique) return technique def _seed_executing_test(db, technique, created_by) -> Test: test = Test( technique_id=technique.id, name="Holdable test", created_by=created_by, state=TestState.red_executing, red_started_at=datetime.utcnow() - timedelta(minutes=10), ) db.add(test) db.commit() db.refresh(test) return test def _seed_blue_evaluating_test(db, technique, created_by) -> Test: test = Test( technique_id=technique.id, name="Holdable test (blue)", created_by=created_by, state=TestState.blue_evaluating, blue_started_at=datetime.utcnow() - timedelta(minutes=10), ) db.add(test) db.commit() db.refresh(test) return test def test_hold_pauses_the_running_timer(client, db, red_tech_headers, red_tech_user): technique = _seed_technique(db) test = _seed_executing_test(db, technique, red_tech_user.id) assert test.paused_at is None resp = client.post( f"/api/v1/tests/{test.id}/hold", json={"reason": "waiting on lab access"}, headers=red_tech_headers, ) assert resp.status_code == 200, resp.text assert resp.json()["paused_at"] is not None db.refresh(test) assert test.paused_at is not None def test_resume_clears_pause_and_accumulates_seconds(client, db, red_tech_headers, red_tech_user): technique = _seed_technique(db) test = _seed_executing_test(db, technique, red_tech_user.id) resp = client.post( f"/api/v1/tests/{test.id}/hold", json={"reason": "waiting on lab access"}, headers=red_tech_headers, ) assert resp.status_code == 200 resp = client.post(f"/api/v1/tests/{test.id}/resume", headers=red_tech_headers) assert resp.status_code == 200, resp.text assert resp.json()["paused_at"] is None db.refresh(test) assert test.paused_at is None assert test.red_paused_seconds >= 0 def test_hold_does_not_double_pause_an_already_paused_timer(client, db, red_tech_headers, red_tech_user): """If the operator already paused the timer manually, holding must not overwrite paused_at (which would reset the elapsed-pause calculation).""" technique = _seed_technique(db) test = _seed_executing_test(db, technique, red_tech_user.id) resp = client.post(f"/api/v1/tests/{test.id}/pause-timer", headers=red_tech_headers) assert resp.status_code == 200 db.refresh(test) manual_pause_time = test.paused_at resp = client.post( f"/api/v1/tests/{test.id}/hold", json={"reason": "waiting on lab access"}, headers=red_tech_headers, ) assert resp.status_code == 200 db.refresh(test) assert test.paused_at == manual_pause_time def test_red_tech_cannot_hold_a_test_in_blue_evaluating( client, db, red_tech_headers, red_tech_user, ): """Once a test has moved into Blue's queue, only blue_tech may hold it — a red_tech is no longer involved at this stage.""" technique = _seed_technique(db) test = _seed_blue_evaluating_test(db, technique, red_tech_user.id) resp = client.post( f"/api/v1/tests/{test.id}/hold", json={"reason": "waiting on lab access"}, headers=red_tech_headers, ) assert resp.status_code == 403 def test_blue_tech_can_hold_a_test_in_blue_evaluating( client, db, blue_tech_headers, red_tech_user, ): technique = _seed_technique(db) test = _seed_blue_evaluating_test(db, technique, red_tech_user.id) resp = client.post( f"/api/v1/tests/{test.id}/hold", json={"reason": "waiting on EDR access"}, headers=blue_tech_headers, ) assert resp.status_code == 200, resp.text def test_blue_tech_cannot_resume_a_test_that_was_held_during_red_executing( client, db, api, red_tech_headers, blue_tech_headers, red_tech_user, ): technique = _seed_technique(db) test = _seed_executing_test(db, technique, red_tech_user.id) resp = api( "post", f"/api/v1/tests/{test.id}/hold", red_tech_headers, json={"reason": "waiting on lab access"}, ) assert resp.status_code == 200, resp.text resp = api("post", f"/api/v1/tests/{test.id}/resume", blue_tech_headers) assert resp.status_code == 403