| """Tests for Task 4 — SOAR Playbook Library.""" |
|
|
| import os |
| import sys |
|
|
| import pytest |
|
|
| _PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) |
| if _PROJECT_ROOT not in sys.path: |
| sys.path.insert(0, _PROJECT_ROOT) |
|
|
| from server.soar_playbooks import PLAYBOOKS, check_prerequisites |
| from server.threat_graph import ThreatGraph, HostNode, ProcessNode, IOCNode |
| from models import SOCState |
|
|
|
|
| def _fresh_state(): |
| return SOCState(episode_id="e", step_count=0) |
|
|
|
|
| def _empty_graph(): |
| return ThreatGraph() |
|
|
|
|
| def test_all_five_playbooks_defined(): |
| for k in [ |
| "ransomware_containment", |
| "c2_disruption", |
| "lateral_movement_lockdown", |
| "phishing_response", |
| "data_exfil_stop", |
| ]: |
| assert k in PLAYBOOKS |
|
|
|
|
| def test_playbook_structure(): |
| for name, p in PLAYBOOKS.items(): |
| for key in ["name", "description", "prerequisites", "sub_actions", "target_attack_types"]: |
| assert key in p, f"{name} missing {key}" |
|
|
|
|
| def test_ransomware_containment_sub_actions(): |
| assert PLAYBOOKS["ransomware_containment"]["sub_actions"] == ["kill_process", "block_ioc"] |
|
|
|
|
| def test_check_prerequisites_fails_no_forensics(): |
| ok, reason = check_prerequisites("ransomware_containment", "WS-001", _fresh_state(), _empty_graph()) |
| assert ok is False |
| assert isinstance(reason, str) and len(reason) > 0 |
|
|
|
|
| def test_check_prerequisites_passes_when_met(): |
| state = _fresh_state() |
| state.scanned_hosts = ["WS-001"] |
| g = ThreatGraph() |
| g.add_process(ProcessNode(process_id="WS-001:1234", hostname="WS-001", process_name="evil.exe")) |
| ok, reason = check_prerequisites("ransomware_containment", "WS-001", state, g) |
| assert ok is True |
| assert reason == "" |
|
|
|
|
| def test_unknown_playbook_raises(): |
| with pytest.raises((KeyError, ValueError)): |
| check_prerequisites("nonexistent_playbook", "WS-001", _fresh_state(), _empty_graph()) |
|
|
|
|
| def test_c2_disruption_needs_enriched_ioc(): |
| g = ThreatGraph() |
| |
| g.add_ioc(IOCNode(ioc_value="1.2.3.4", ioc_type="ip", confidence=0.9, enriched=False)) |
| ok, reason = check_prerequisites("c2_disruption", "WS-001", _fresh_state(), g) |
| assert ok is False |
|
|