Spaces:
Build error
Build error
File size: 13,911 Bytes
d0c8f19 f4fbb05 d0c8f19 f8e7d59 d0c8f19 af423c3 d0c8f19 af423c3 d0c8f19 af423c3 d0c8f19 af423c3 d0c8f19 af423c3 d0c8f19 af423c3 d0c8f19 3623a88 d0c8f19 3623a88 d0c8f19 f8e7d59 3623a88 d0c8f19 f8e7d59 3623a88 d0c8f19 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | """
Tests for POST /intents/{id}/execute and POST /admin/executions/{id}/resolve.
arf_enterprise is not installed in this test environment (by design -- it's
an optional, proprietary package; see routes_governance.py's
ENTERPRISE_EXECUTOR_AVAILABLE guard). The 501 "not available"/"not enabled"
paths are real, unconditional behavior and tested as such. For the
success/pending/error paths, the enterprise classes referenced in
routes_governance.py (EnterpriseExecutor, FakeCloudActuator,
PendingApprovalError, EnterpriseExecutionError, EnterpriseSafetyError) are
monkeypatched with lightweight stand-ins -- this tests arf-api's own glue
code (existence/tenant check, exception-to-HTTP-status mapping, response
shaping), not EnterpriseExecutor's internals, which are already covered by
the enterprise repo's own test suite.
"""
import pytest
from app.database.models_intents import TenantDB
import app.api.routes_governance as routes_governance
import app.api.routes_admin as routes_admin
TENANT_ID = "test-tenant"
@pytest.fixture(autouse=True)
def seed_tenant(db_session):
tenant = db_session.query(TenantDB).filter_by(id=TENANT_ID).first()
if not tenant:
db_session.add(TenantDB(id=TENANT_ID, name="Test Tenant"))
db_session.commit()
def _evaluate_intent(client):
payload = {
"intent_type": "provision_resource",
"environment": "prod",
"resource_type": "database",
"region": "eastus",
"size": "Standard",
"estimated_cost": 1200,
"policy_violations": [],
"requester": "alice",
"provenance": {},
"configuration": {},
}
resp = client.post(
"/api/v1/intents/evaluate", json=payload, headers={"X-Tenant-ID": TENANT_ID}
)
assert resp.status_code == 200, resp.text
data = resp.json()
# data["intent_id"] (top level, set by evaluate_intent_endpoint right
# before returning: result["intent_id"] = deterministic_id) is what
# save_evaluated_intent actually persisted to IntentDB.deterministic_id.
# data["healing_intent"]["intent_id"] is a separate, independently
# generated id belonging to the HealingIntent object itself -- using it
# here instead was the exact bug that made every test in this file 404.
return data["intent_id"], data["healing_intent"]
class _FakePendingApprovalError(Exception):
def __init__(self, message, level, approval_required, approval_id=None):
super().__init__(message)
self.level = level
self.approval_required = approval_required
self.approval_id = approval_id
class _FakeExecutionError(Exception):
pass
class _FakeSafetyError(Exception):
pass
class _FakeConfig:
"""Stands in for EnterpriseConfig, which is None in the import fallback
whenever arf_enterprise isn't installed -- as it isn't in CI, since it's
a private-repo package deliberately kept out of requirements.txt.
Records what it was constructed with so a test can assert that
ARF_TRUSTED_SIGNING_KEYS actually reaches the executor. Without that
the trust store could silently go back to empty and every signed intent
would be rejected as "Untrusted signing key" with nothing failing here.
"""
last_trusted_signing_keys = None
def __init__(self, trusted_signing_keys=None, **kwargs):
self.trusted_signing_keys = trusted_signing_keys
type(self).last_trusted_signing_keys = trusted_signing_keys
class _FakeExecutor:
"""Stands in for EnterpriseExecutor. Behavior is selected via a
class-level `mode` set by each test before the request is made."""
mode = "success"
last_config = None
def __init__(self, config=None, actuator=None, approval_store=None,
on_verified_outcome=None):
self._on_verified_outcome = on_verified_outcome
type(self).last_config = config
async def execute(self, intent, human_approved=False, admin_approved=False):
if self.mode == "success":
if self._on_verified_outcome:
self._on_verified_outcome(intent, True, {"observed": {"status": "running"}})
return {"status": "success", "verified": {"status": "running"}, "compensating_action": None}
if self.mode == "pending":
raise _FakePendingApprovalError(
"needs human approval", level="HumanInLoop", approval_required="human",
approval_id="appr_test123",
)
if self.mode == "execution_error":
raise _FakeExecutionError("ladder denied this intent")
if self.mode == "safety_error":
raise _FakeSafetyError("blast radius exceeded")
raise RuntimeError(f"unhandled test mode: {self.mode}")
@pytest.fixture
def enterprise_execution_enabled(monkeypatch):
monkeypatch.setattr(routes_governance, "ENTERPRISE_EXECUTOR_AVAILABLE", True)
monkeypatch.setattr(routes_governance, "ARF_ENABLE_EXECUTION", True)
monkeypatch.setattr(routes_governance, "EnterpriseExecutor", _FakeExecutor)
monkeypatch.setattr(routes_governance, "EnterpriseConfig", _FakeConfig)
monkeypatch.setattr(routes_governance, "FakeCloudActuator", lambda: None)
monkeypatch.setattr(routes_governance, "PendingApprovalError", _FakePendingApprovalError)
monkeypatch.setattr(routes_governance, "EnterpriseExecutionError", _FakeExecutionError)
monkeypatch.setattr(routes_governance, "EnterpriseSafetyError", _FakeSafetyError)
_FakeExecutor.mode = "success"
yield
_FakeExecutor.mode = "success"
def test_execute_returns_501_when_enterprise_package_not_available(client):
resp = client.post(
"/api/v1/intents/does-not-matter/execute",
json={"healing_intent": {}},
)
assert resp.status_code == 501
assert "not installed" in resp.json()["detail"]
def test_execute_returns_501_when_not_enabled(client, monkeypatch):
monkeypatch.setattr(routes_governance, "ENTERPRISE_EXECUTOR_AVAILABLE", True)
monkeypatch.setattr(routes_governance, "ARF_ENABLE_EXECUTION", False)
resp = client.post(
"/api/v1/intents/does-not-matter/execute",
json={"healing_intent": {}},
)
assert resp.status_code == 501
assert "not enabled" in resp.json()["detail"]
def test_execute_returns_404_for_unknown_intent(client, enterprise_execution_enabled):
resp = client.post(
"/api/v1/intents/does-not-exist-at-all/execute",
json={"healing_intent": {}},
)
assert resp.status_code == 404
def test_execute_success_path(client, enterprise_execution_enabled):
deterministic_id, healing_intent = _evaluate_intent(client)
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent, "human_approved": True},
)
assert resp.status_code == 200, resp.text
assert resp.json()["status"] == "success"
def test_trusted_signing_keys_reach_the_executor_split_not_raw(
client, enterprise_execution_enabled, monkeypatch
):
"""ARF_TRUSTED_SIGNING_KEYS holds N comma-separated fingerprints.
Passing the raw string through as a one-element list would register the
literal "abc,def" as a single key -- trusting neither real one -- and
would look identical from the outside, since both spellings produce a
non-empty list and a 200 here."""
monkeypatch.setenv("ARF_TRUSTED_SIGNING_KEYS", " abc123 , def456 ,, ")
deterministic_id, healing_intent = _evaluate_intent(client)
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent, "human_approved": True},
)
assert resp.status_code == 200, resp.text
assert _FakeConfig.last_trusted_signing_keys == ["abc123", "def456"]
assert _FakeExecutor.last_config is not None
def test_unset_trusted_signing_keys_trusts_nothing_rather_than_the_empty_string(
client, enterprise_execution_enabled, monkeypatch
):
"""Fail closed. `"".split(",")` is `[""]`, so the naive parse would
register the empty string as a trusted fingerprint -- trusting a key
nobody holds is harmless, but it makes "trusts nothing" and
"misconfigured" indistinguishable in the logs."""
monkeypatch.delenv("ARF_TRUSTED_SIGNING_KEYS", raising=False)
deterministic_id, healing_intent = _evaluate_intent(client)
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent, "human_approved": True},
)
assert resp.status_code == 200, resp.text
assert _FakeConfig.last_trusted_signing_keys == []
def test_execute_pending_approval_returns_202_with_approval_id(client, enterprise_execution_enabled):
deterministic_id, healing_intent = _evaluate_intent(client)
_FakeExecutor.mode = "pending"
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent},
)
assert resp.status_code == 202
body = resp.json()
assert body["approval_id"] == "appr_test123"
assert body["level"] == "HumanInLoop"
def test_execute_execution_error_returns_422(client, enterprise_execution_enabled):
deterministic_id, healing_intent = _evaluate_intent(client)
_FakeExecutor.mode = "execution_error"
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent, "human_approved": True},
)
assert resp.status_code == 422
assert "ladder denied" in resp.json()["detail"]
def test_execute_safety_error_returns_422(client, enterprise_execution_enabled):
deterministic_id, healing_intent = _evaluate_intent(client)
_FakeExecutor.mode = "safety_error"
resp = client.post(
f"/api/v1/intents/{deterministic_id}/execute",
json={"healing_intent": healing_intent, "human_approved": True},
)
assert resp.status_code == 422
assert "blast radius" in resp.json()["detail"]
def test_execute_belongs_to_different_tenant_returns_404(client, enterprise_execution_enabled, db_session):
"""A deterministic_id that exists but under a different tenant must not
be executable by this caller -- same tenant-scoping guarantee
record_outcome already provides for /intents/outcome."""
other_tenant = "other-tenant"
if not db_session.query(TenantDB).filter_by(id=other_tenant).first():
db_session.add(TenantDB(id=other_tenant, name="Other Tenant"))
db_session.commit()
from app.database.models_intents import IntentDB
import datetime
db_session.add(IntentDB(
deterministic_id="belongs-to-other-tenant",
tenant_id=other_tenant,
intent_type="provision_resource",
payload={},
oss_payload={},
environment="prod",
evaluated_at=datetime.datetime.utcnow(),
risk_score="0.1",
))
db_session.commit()
resp = client.post(
"/api/v1/intents/belongs-to-other-tenant/execute",
json={"healing_intent": {}},
)
assert resp.status_code == 404
# ---------------------------------------------------------------------------
# Admin resolve/list endpoints
# ---------------------------------------------------------------------------
TEST_ADMIN_KEY = "test-admin-key-for-governance-execute-tests"
class _FakeApprovalRecord:
def __init__(self, id, decision_id, intent_id, level, approval_required, requested_at):
self.id = id
self.decision_id = decision_id
self.intent_id = intent_id
self.level = level
self.approval_required = approval_required
self.requested_at = requested_at
class _FakeApprovalStore:
def __init__(self):
import datetime
self._pending = {
"appr_1": _FakeApprovalRecord(
"appr_1", "dec_1", "intent-1", "HumanInLoop", "human", datetime.datetime.utcnow()
)
}
self.resolved = []
def list_pending(self, limit=100, offset=0):
return list(self._pending.values())[:limit]
def resolve(self, approval_id, approved, resolved_by, note=None):
if approval_id not in self._pending:
return False
del self._pending[approval_id]
self.resolved.append((approval_id, approved, resolved_by, note))
return True
@pytest.fixture
def admin_with_approval_store(monkeypatch, client):
monkeypatch.setattr(routes_admin, "ADMIN_API_KEY", TEST_ADMIN_KEY)
fake_store = _FakeApprovalStore()
client.app.state.approval_store = fake_store
yield fake_store
client.app.state.approval_store = None
def test_list_pending_executions_returns_501_without_store(client, monkeypatch):
monkeypatch.setattr(routes_admin, "ADMIN_API_KEY", TEST_ADMIN_KEY)
client.app.state.approval_store = None
resp = client.get("/api/v1/admin/executions/pending", headers={"X-Admin-Key": TEST_ADMIN_KEY})
assert resp.status_code == 501
def test_list_pending_executions(client, admin_with_approval_store):
resp = client.get("/api/v1/admin/executions/pending", headers={"X-Admin-Key": TEST_ADMIN_KEY})
assert resp.status_code == 200
body = resp.json()
assert body["total"] == 1
assert body["pending"][0]["approval_id"] == "appr_1"
def test_resolve_execution_approval(client, admin_with_approval_store):
resp = client.post(
"/api/v1/admin/executions/appr_1/resolve",
headers={"X-Admin-Key": TEST_ADMIN_KEY},
json={"approved": True, "note": "looks fine"},
)
assert resp.status_code == 200
assert admin_with_approval_store.resolved == [("appr_1", True, "admin", "looks fine")]
def test_resolve_unknown_approval_returns_404(client, admin_with_approval_store):
resp = client.post(
"/api/v1/admin/executions/does-not-exist/resolve",
headers={"X-Admin-Key": TEST_ADMIN_KEY},
json={"approved": True},
)
assert resp.status_code == 404
|