feat(backend): c2 callbacks + execute endpoints (sprint 8 M2)
- Add C2Error exception to adapter ABC - Add promote_to_in_progress() helper to simulation_workflow (pending→in_progress) - Flesh out MythicAdapter: list_callbacks() (GraphQL query) + create_task() (mutation) - Expand FakeAdapter to 3 deterministic callbacks; switch task store to per-instance - Add GET /api/engagements/<id>/c2/callbacks — lists active callbacks via adapter - Add POST /api/simulations/<id>/c2/execute — issues tasks, stores C2Task rows, auto-transitions pending→in_progress, blocks on done (409) - Both endpoints: SOC=403, 503 no-key, 502 adapter error, sanitized error messages - Add requests-mock==1.12.1 to requirements.txt - 42 new tests (342 total, 300 M1 baseline preserved green) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
62
backend/tests/test_c2_adapter_fake_m2.py
Normal file
62
backend/tests/test_c2_adapter_fake_m2.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""FakeAdapter M2 tests — list_callbacks shape, create_task monotonicity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.app.services.c2.fake import FakeAdapter
|
||||
|
||||
|
||||
class TestFakeAdapterListCallbacks:
|
||||
def test_returns_three_callbacks(self):
|
||||
adapter = FakeAdapter()
|
||||
callbacks = adapter.list_callbacks()
|
||||
assert len(callbacks) == 3
|
||||
|
||||
def test_all_active(self):
|
||||
adapter = FakeAdapter()
|
||||
for cb in adapter.list_callbacks():
|
||||
assert cb.active is True
|
||||
|
||||
def test_display_ids_are_1_2_3(self):
|
||||
adapter = FakeAdapter()
|
||||
ids = [cb.display_id for cb in adapter.list_callbacks()]
|
||||
assert ids == [1, 2, 3]
|
||||
|
||||
def test_pinned_last_checkin_format(self):
|
||||
adapter = FakeAdapter()
|
||||
for cb in adapter.list_callbacks():
|
||||
assert cb.last_checkin.startswith("2026-06-10")
|
||||
|
||||
def test_callbacks_have_host_user_domain(self):
|
||||
adapter = FakeAdapter()
|
||||
for cb in adapter.list_callbacks():
|
||||
assert cb.host
|
||||
assert cb.user
|
||||
assert cb.domain
|
||||
|
||||
|
||||
class TestFakeAdapterCreateTask:
|
||||
def test_returns_monotonic_ids_from_1000(self):
|
||||
adapter = FakeAdapter()
|
||||
id1 = adapter.create_task(1, "whoami")
|
||||
id2 = adapter.create_task(1, "ipconfig")
|
||||
assert id1 == 1000
|
||||
assert id2 == 1001
|
||||
|
||||
def test_separate_instances_start_at_1000_independently(self):
|
||||
a1 = FakeAdapter()
|
||||
a2 = FakeAdapter()
|
||||
assert a1.create_task(1, "cmd") == 1000
|
||||
assert a2.create_task(1, "cmd") == 1000
|
||||
|
||||
def test_stores_command_and_callback(self):
|
||||
adapter = FakeAdapter()
|
||||
tid = adapter.create_task(callback_display_id=2, command="ls", params="-la")
|
||||
task = adapter._tasks[tid]
|
||||
assert task["command"] == "ls"
|
||||
assert task["params"] == "-la"
|
||||
assert task["callback_display_id"] == 2
|
||||
|
||||
def test_initial_status_submitted(self):
|
||||
adapter = FakeAdapter()
|
||||
tid = adapter.create_task(1, "hostname")
|
||||
assert adapter._tasks[tid]["status"] == "submitted"
|
||||
assert adapter._tasks[tid]["completed"] is False
|
||||
Reference in New Issue
Block a user