Files
ss-tools/backend/tests/api/test_agent_runs.py

139 lines
5.0 KiB
Python

# backend/tests/api/test_agent_runs.py
# #region Test.Api.AgentRuns [C:3] [TYPE Module] [SEMANTICS test,api,agent-run,rbac,ownership]
# @BRIEF API-level tests for agent runs — RBAC, ownership, permission_denied, foreign-run access.
# @RELATION BINDS_TO -> [Api.AgentRuns]
# @TEST_EDGE missing_permission -> 403.
# @TEST_EDGE foreign_run_access -> 404 (not owner).
# @TEST_EDGE terminal_run_rejects -> 409.
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, MagicMock
from src.app import app
from src.dependencies import get_current_user
from src.models.auth import User
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def mock_user():
u = User(id="user-1", username="qa_analyst")
return u
@pytest.fixture
def mock_other_user():
u = User(id="user-2", username="other_user")
return u
class TestCreateRun:
def test_create_run_requires_auth(self, client):
resp = client.post("/api/agent/runs", json={"context": {}})
assert resp.status_code == 401
def test_create_run_requires_valid_context(self, client, mock_user):
app.dependency_overrides[get_current_user] = lambda: mock_user
try:
resp = client.post("/api/agent/runs", json={
"context": {
"objectType": "dashboard", "objectId": "42",
"envId": "dev", "route": "/dashboards/42",
"contextVersion": 2,
"intent": "build_dashboard_test_scenario",
}
})
# 403 because seed_permissions hasn't been run in test DB
# or 201 if permissions were seeded
assert resp.status_code in (201, 403, 422)
finally:
app.dependency_overrides.pop(get_current_user, None)
def test_create_run_rejects_v1_scenario_intent(self, client, mock_user):
app.dependency_overrides[get_current_user] = lambda: mock_user
try:
resp = client.post("/api/agent/runs", json={
"context": {
"objectType": "dashboard", "objectId": "42",
"envId": "dev", "route": "/dashboards/42",
"contextVersion": 1,
"intent": "build_dashboard_test_scenario",
}
})
# Pydantic validation catches v1+intent combo
assert resp.status_code in (422, 403)
finally:
app.dependency_overrides.pop(get_current_user, None)
class TestGetSnapshot:
def test_nonexistent_run_returns_404(self, client, mock_user):
app.dependency_overrides[get_current_user] = lambda: mock_user
try:
resp = client.get("/api/agent/runs/nonexistent-id")
assert resp.status_code == 404
finally:
app.dependency_overrides.pop(get_current_user, None)
def test_requires_auth(self, client):
resp = client.get("/api/agent/runs/some-id")
assert resp.status_code == 401
class TestAppendEvent:
def test_append_event_requires_auth(self, client):
resp = client.post("/api/agent/runs/some-id/events", json={
"event_type": "progress", "sequence": 1,
})
assert resp.status_code == 401
def test_append_event_invalid_sequence(self, client, mock_user):
app.dependency_overrides[get_current_user] = lambda: mock_user
try:
resp = client.post("/api/agent/runs/some-id/events", json={
"event_type": "progress", "sequence": 0,
})
# 403 from permission check OR 422 from Pydantic validation — both valid
assert resp.status_code in (403, 422)
finally:
app.dependency_overrides.pop(get_current_user, None)
class TestApprovalFlow:
def test_create_gate_requires_auth(self, client):
resp = client.post("/api/agent/runs/some-id/gates", json={
"operation": "repository_write",
"request_hash": "a" * 64,
"target_paths": ["test.yaml"],
"required_permission": "dashboard:testing:WRITE",
})
assert resp.status_code == 401
def test_decide_gate_requires_auth(self, client):
resp = client.post("/api/agent/runs/some-id/gates/gate-1/decide", json={
"decision": "confirm",
})
assert resp.status_code == 401
def test_consume_gate_requires_auth(self, client):
resp = client.post("/api/agent/runs/some-id/gates/gate-1/consume")
assert resp.status_code == 401
class TestResponseSchemas:
"""Verify response models are importable and well-formed."""
def test_snapshot_schema(self):
from src.schemas.agent_run import AgentRunSnapshot
snap = AgentRunSnapshot(
id="r1", user_id="u1", intent="dashboard_scenario_build",
trigger="manual", dashboard_id="42", environment_id="dev",
context_snapshot={}, status="CREATED", last_sequence=0,
created_at="2026-01-01T00:00:00Z", updated_at="2026-01-01T00:00:00Z",
)
assert snap.id == "r1"
# #endregion Test.Api.AgentRuns