Spaces:
Build error
Build error
| """ | |
| 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" | |
| 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}") | |
| 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 | |
| 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 | |