import json from datetime import UTC, datetime from pathlib import Path import pytest from whitehat_security.engagement import AuthorizationError, Engagement def record(): return { "engagement_id": "WH-ENG-1", "authorization_id": "auth-1", "authorizer": "operator", "approved_at": "2026-08-21T08:00:00Z", "expires_at": "2026-08-21T12:00:00Z", "target": "https://fixture.invalid", "target_owner": "target-repo", "environment": "build", "source": "runner", "routes": ["GET /objects/{id}"], "fixture_ids": ["object-a", "object-b"], "credential_lane": "openbao", "credential_role": "runtime", "credential_max_ttl_seconds": 900, "techniques": ["e2-differential"], "prohibited_techniques": ["saturation"], "rate_limit_per_minute": 10, "max_concurrency": 1, "window_start": "2026-08-21T08:00:00Z", "window_end": "2026-08-21T10:00:00Z", "operator_contact": "operator", "abort_contact": "target-owner", "posture_claim": "E2", "attacker_model": "E2-authenticated-tenant-a", "finding_destination": "risk-nexus", "target_owner_acknowledged_at": "2026-08-21T08:01:00Z", } def load(tmp_path, data): path = tmp_path / "engagement.json" path.write_text(json.dumps(data), encoding="utf-8") return Engagement.load(path, now=datetime(2026, 8, 21, 9, tzinfo=UTC)) def test_complete_current_record_is_accepted(tmp_path): engagement = load(tmp_path, record()) engagement.permits(technique="e2-differential", route="GET /objects/{id}") @pytest.mark.parametrize("field", ["target_owner_acknowledged_at", "abort_contact", "fixture_ids"]) def test_incomplete_record_fails_closed(tmp_path, field): data = record() del data[field] with pytest.raises(AuthorizationError): load(tmp_path, data) def test_expired_record_fails_closed(tmp_path): with pytest.raises(AuthorizationError, match="outside"): Engagement.load( _write(tmp_path, record()), now=datetime(2026, 8, 21, 13, tzinfo=UTC) ) def test_unauthorized_route_fails_closed(tmp_path): engagement = load(tmp_path, record()) with pytest.raises(AuthorizationError, match="route"): engagement.permits(technique="e2-differential", route="DELETE /objects/{id}") def test_pending_owner_acknowledgement_fails_closed(tmp_path): data = record() data["target_owner_acknowledged_at"] = None with pytest.raises(AuthorizationError, match="pending"): load(tmp_path, data) def test_retry_record_is_authorized_only_inside_its_window(tmp_path): data = json.loads(Path("engagements/2026-08-22-audit-core-e2-02.json").read_text()) data["status"] = "approved" data.pop("aborted_at", None) data.pop("abort_reason", None) path = tmp_path / "retry.json" path.write_text(json.dumps(data), encoding="utf-8") with pytest.raises(AuthorizationError, match="has not started"): Engagement.load(path, now=datetime(2026, 8, 22, 19, 14, tzinfo=UTC)) engagement = Engagement.load(path, now=datetime(2026, 8, 22, 19, 15, tzinfo=UTC)) assert engagement.raw["engagement_id"] == "WH-ENG-20260822-AUDIT-E2-02" with pytest.raises(AuthorizationError, match="outside"): Engagement.load(path, now=datetime(2026, 8, 22, 19, 31, tzinfo=UTC)) def test_aborted_record_fails_closed(): with pytest.raises(AuthorizationError, match="aborted"): Engagement.load( "engagements/2026-08-22-audit-core-e2-02.json", now=datetime(2026, 8, 22, 19, 20, tzinfo=UTC), ) def test_completed_record_fails_closed(): with pytest.raises(AuthorizationError, match="completed"): Engagement.load( "engagements/2026-08-22-audit-core-e2-03.json", now=datetime(2026, 8, 22, 22, 10, tzinfo=UTC), ) def test_elapsed_record_fails_closed_even_inside_old_window(tmp_path): data = record() data["status"] = "expired" with pytest.raises(AuthorizationError, match="elapsed"): load(tmp_path, data) def test_proposed_record_fails_closed_before_window_checks(tmp_path): data = record() data["status"] = "proposed" data["approved_at"] = None data["target_owner_acknowledged_at"] = None with pytest.raises(AuthorizationError, match="proposed"): load(tmp_path, data) def test_cancelled_record_fails_closed_even_with_owner_acknowledgement(tmp_path): data = record() data["status"] = "cancelled" data["cancelled_at"] = "2026-08-21T08:02:00Z" with pytest.raises(AuthorizationError, match="cancelled"): load(tmp_path, data) def _write(tmp_path, data): path = tmp_path / "engagement.json" path.write_text(json.dumps(data), encoding="utf-8") return path