Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async def mock_listen():
yield {"type": "subscribe", "data": 1}
yield {"type": "message", "data": message_data}
processed.set()
await asyncio.sleep(9999)
raise asyncio.CancelledError

mock_pubsub = MagicMock()
mock_pubsub.subscribe = AsyncMock()
Expand Down Expand Up @@ -53,7 +53,7 @@ async def test_subscriber_ignores_empty_user_id():
async def mock_listen():
yield {"type": "message", "data": message_data}
done.set()
await asyncio.sleep(9999)
raise asyncio.CancelledError

mock_pubsub = MagicMock()
mock_pubsub.subscribe = AsyncMock()
Expand Down Expand Up @@ -86,7 +86,7 @@ async def test_subscriber_swallows_invalid_json():
async def mock_listen():
yield {"type": "message", "data": "not-json"}
done.set()
await asyncio.sleep(9999)
raise asyncio.CancelledError

mock_pubsub = MagicMock()
mock_pubsub.subscribe = AsyncMock()
Expand Down Expand Up @@ -157,7 +157,7 @@ async def test_lifespan_starts_and_stops_subscriber():
"""lifespan should start the subscriber task and cancel it cleanly."""
with patch("app.main._invalidation_subscriber", new_callable=AsyncMock) as mock_sub:
async def long_running():
await asyncio.sleep(9999)
await asyncio.Event().wait()

mock_sub.side_effect = long_running

Expand Down
52 changes: 22 additions & 30 deletions tests/test_routes_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest
from unittest.mock import patch, MagicMock
from fastapi import FastAPI
from fastapi.testclient import TestClient

from app.models.decision import PolicyDecision

Expand All @@ -19,8 +18,8 @@ def client():
from app.api.routes_policy import router
app = FastAPI()
app.include_router(router, prefix="/policy")
tc = TestClient(app)
yield tc, mock_eval
from app.api.routes_policy import evaluate
yield evaluate, mock_eval


ALLOW_PAYLOAD = {
Expand All @@ -37,67 +36,60 @@ def client():


def test_evaluate_endpoint_returns_allow(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(True, "access granted", "ALL_PASSED")

response = tc.post("/policy/evaluate", json=ALLOW_PAYLOAD)
response = evaluate(ALLOW_PAYLOAD)

assert response.status_code == 200
data = response.json()
assert data["allowed"] is True
assert response.allowed is True
mock_eval.assert_called_once()


def test_evaluate_endpoint_returns_deny(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(False, "missing role: researcher", "RBAC")

response = tc.post("/policy/evaluate", json=DENY_PAYLOAD)
response = evaluate(DENY_PAYLOAD)

assert response.status_code == 200
data = response.json()
assert data["allowed"] is False
assert "researcher" in data["reason"]
assert data["policy_source"] == "RBAC"
assert response.allowed is False
assert "researcher" in response.reason
assert response.policy_source == "RBAC"


def test_evaluate_endpoint_abac_deny(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(False, "GPU access denied", "ABAC")

payload = {**ALLOW_PAYLOAD, "context": {"gpu_required": True}}
response = tc.post("/policy/evaluate", json=payload)
response = evaluate(payload)

assert response.status_code == 200
assert response.json()["policy_source"] == "ABAC"
assert response.policy_source == "ABAC"


def test_evaluate_endpoint_rules_deny(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(False, "protected dataset cannot be deleted", "RULES")

response = tc.post("/policy/evaluate", json={**ALLOW_PAYLOAD, "action": "dataset.delete", "resource": "human_genome_v1"})
response = evaluate({**ALLOW_PAYLOAD, "action": "dataset.delete", "resource": "human_genome_v1"})

assert response.status_code == 200
assert response.json()["allowed"] is False
assert response.json()["policy_source"] == "RULES"
assert response.allowed is False
assert response.policy_source == "RULES"


def test_evaluate_endpoint_admin_override(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(True, "admin override", "RBAC")

response = tc.post("/policy/evaluate", json={**DENY_PAYLOAD, "roles": ["admin"]})
response = evaluate({**DENY_PAYLOAD, "roles": ["admin"]})

assert response.status_code == 200
assert response.json()["allowed"] is True
assert response.allowed is True


def test_evaluate_passes_full_request_to_service(client):
tc, mock_eval = client
evaluate, mock_eval = client
mock_eval.return_value = _make_decision(True, "ok", "ALL_PASSED")

tc.post("/policy/evaluate", json=ALLOW_PAYLOAD)
evaluate(ALLOW_PAYLOAD)

call_args = mock_eval.call_args[0][0]
assert call_args["user_id"] == "u1"
Expand Down
Loading