Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse files- app/api/routes_governance.py +2 -2
- tests/test_governance.py +37 -0
- tests/test_healing_endpoint.py +28 -0
app/api/routes_governance.py
CHANGED
|
@@ -307,7 +307,7 @@ async def evaluate_intent_endpoint(
|
|
| 307 |
|
| 308 |
record = UsageRecord(
|
| 309 |
api_key=api_key,
|
| 310 |
-
tier=
|
| 311 |
timestamp=start_time,
|
| 312 |
endpoint="/api/v1/intents/evaluate",
|
| 313 |
request_body=intent_req.model_dump(),
|
|
@@ -661,7 +661,7 @@ async def evaluate_healing_decision_endpoint(
|
|
| 661 |
|
| 662 |
record = UsageRecord(
|
| 663 |
api_key=api_key,
|
| 664 |
-
tier=
|
| 665 |
timestamp=start_time,
|
| 666 |
endpoint="/api/v1/healing/evaluate",
|
| 667 |
request_body=decision_req.model_dump(),
|
|
|
|
| 307 |
|
| 308 |
record = UsageRecord(
|
| 309 |
api_key=api_key,
|
| 310 |
+
tier=quota["tier"],
|
| 311 |
timestamp=start_time,
|
| 312 |
endpoint="/api/v1/intents/evaluate",
|
| 313 |
request_body=intent_req.model_dump(),
|
|
|
|
| 661 |
|
| 662 |
record = UsageRecord(
|
| 663 |
api_key=api_key,
|
| 664 |
+
tier=quota["tier"],
|
| 665 |
timestamp=start_time,
|
| 666 |
endpoint="/api/v1/healing/evaluate",
|
| 667 |
request_body=decision_req.model_dump(),
|
tests/test_governance.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
| 1 |
"""
|
| 2 |
Tests for governance endpoints: /api/v1/intents/evaluate
|
| 3 |
"""
|
|
|
|
|
|
|
| 4 |
import pytest
|
|
|
|
|
|
|
| 5 |
from app.database.models_intents import TenantDB
|
| 6 |
|
| 7 |
|
|
@@ -113,3 +117,36 @@ def test_evaluate_with_criticality(client):
|
|
| 113 |
# context_hash is computed by the governance loop (a 64‑char hex string)
|
| 114 |
ctx_hash = healing.get("context_hash")
|
| 115 |
assert isinstance(ctx_hash, str) and len(ctx_hash) == 64
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
Tests for governance endpoints: /api/v1/intents/evaluate
|
| 3 |
"""
|
| 4 |
+
import tempfile
|
| 5 |
+
|
| 6 |
import pytest
|
| 7 |
+
import app.core.usage_tracker as usage_tracker_module
|
| 8 |
+
from app.core.usage_tracker import UsageTracker
|
| 9 |
from app.database.models_intents import TenantDB
|
| 10 |
|
| 11 |
|
|
|
|
| 117 |
# context_hash is computed by the governance loop (a 64‑char hex string)
|
| 118 |
ctx_hash = healing.get("context_hash")
|
| 119 |
assert isinstance(ctx_hash, str) and len(ctx_hash) == 64
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def test_evaluate_intent_against_real_tracker_does_not_crash(client, monkeypatch):
|
| 123 |
+
"""Every other test in this file runs against tests/conftest.py's
|
| 124 |
+
MockTracker, whose consume_quota_and_log ignores record.tier entirely
|
| 125 |
+
and always returns (True, None) -- so none of them would have noticed
|
| 126 |
+
that this endpoint hardcoded tier=None instead of using quota["tier"]
|
| 127 |
+
(already resolved by the enforce_quota dependency). Against the real
|
| 128 |
+
UsageTracker, consume_quota_and_log evaluates tier.monthly_evaluation_limit
|
| 129 |
+
unconditionally, so a None tier raised AttributeError on every real
|
| 130 |
+
call, outside any try/except, surfacing as a raw 500. This test swaps
|
| 131 |
+
in a real UsageTracker (a throwaway SQLite file; the same real
|
| 132 |
+
Postgres the CI service provides backs api_keys/monthly_counts) to
|
| 133 |
+
prove the endpoint no longer crashes."""
|
| 134 |
+
with tempfile.NamedTemporaryFile(suffix=".db") as tmp:
|
| 135 |
+
monkeypatch.setattr(usage_tracker_module, "tracker", UsageTracker(db_path=tmp.name))
|
| 136 |
+
|
| 137 |
+
payload = {
|
| 138 |
+
"intent_type": "provision_resource",
|
| 139 |
+
"environment": "prod",
|
| 140 |
+
"resource_type": "database",
|
| 141 |
+
"region": "eastus",
|
| 142 |
+
"size": "Standard",
|
| 143 |
+
"estimated_cost": 1200,
|
| 144 |
+
"policy_violations": [],
|
| 145 |
+
"requester": "alice",
|
| 146 |
+
"provenance": {},
|
| 147 |
+
"configuration": {}
|
| 148 |
+
}
|
| 149 |
+
response = client.post("/api/v1/intents/evaluate", json=payload,
|
| 150 |
+
headers={"X-Tenant-ID": "test-tenant"})
|
| 151 |
+
assert response.status_code == 200, response.text
|
| 152 |
+
assert "risk_score" in response.json()
|
tests/test_healing_endpoint.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
|
|
|
|
|
| 1 |
from fastapi.testclient import TestClient
|
| 2 |
from app.main import app
|
|
|
|
|
|
|
| 3 |
|
| 4 |
client = TestClient(app)
|
| 5 |
|
|
@@ -18,3 +22,27 @@ def test_healing_evaluate_endpoint():
|
|
| 18 |
response = client.post("/api/v1/healing/evaluate", json=payload,
|
| 19 |
headers={"X-Tenant-ID": "test-tenant"})
|
| 20 |
assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tempfile
|
| 2 |
+
|
| 3 |
from fastapi.testclient import TestClient
|
| 4 |
from app.main import app
|
| 5 |
+
import app.core.usage_tracker as usage_tracker_module
|
| 6 |
+
from app.core.usage_tracker import UsageTracker
|
| 7 |
|
| 8 |
client = TestClient(app)
|
| 9 |
|
|
|
|
| 22 |
response = client.post("/api/v1/healing/evaluate", json=payload,
|
| 23 |
headers={"X-Tenant-ID": "test-tenant"})
|
| 24 |
assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def test_healing_evaluate_against_real_tracker_does_not_crash(monkeypatch):
|
| 28 |
+
"""Same regression as test_governance.py's equivalent test, for this
|
| 29 |
+
endpoint's own hardcoded tier=None (routes_governance.py's
|
| 30 |
+
/healing/evaluate handler) -- see that test's docstring for the full
|
| 31 |
+
explanation. Swaps in a real UsageTracker to prove
|
| 32 |
+
consume_quota_and_log no longer crashes on a None tier here either."""
|
| 33 |
+
payload = {
|
| 34 |
+
"event": {
|
| 35 |
+
"component": "my-service",
|
| 36 |
+
"latency_p99": 450.0,
|
| 37 |
+
"error_rate": 0.25,
|
| 38 |
+
"service_mesh": "default",
|
| 39 |
+
"cpu_util": 0.85,
|
| 40 |
+
"memory_util": 0.90
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
with tempfile.NamedTemporaryFile(suffix=".db") as tmp:
|
| 44 |
+
monkeypatch.setattr(usage_tracker_module, "tracker", UsageTracker(db_path=tmp.name))
|
| 45 |
+
|
| 46 |
+
response = client.post("/api/v1/healing/evaluate", json=payload,
|
| 47 |
+
headers={"X-Tenant-ID": "test-tenant"})
|
| 48 |
+
assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
|