Files

81 lines
3.1 KiB
Python

from unittest.mock import Mock
from uuid import uuid4
import pytest
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.testclient import TestClient
from modelforge_api.api.authorization import (
Principal,
PrincipalRole,
authenticate_operator_principal,
)
from modelforge_api.api.routes.lab import get_lab_service, router
from modelforge_api.domain.lab import LabInputSummary, LabLimits, LabPreflightResponse
from modelforge_api.main import validation_error
def request_body() -> dict:
return {
"target": {name: str(uuid4()) for name in (
"deployment_candidate_id", "artifact_set_id", "runtime_profile_id",
"compute_node_id", "execution_approval_id",
)},
"input": {"capability": "rag.embedding", "payload": {"input": "private-input-canary"}},
}
def harness(role: PrincipalRole | None) -> tuple[TestClient, Mock]:
app = FastAPI()
app.include_router(router)
app.add_exception_handler(RequestValidationError, validation_error) # type: ignore[arg-type]
def principal() -> Principal:
if role is None:
raise HTTPException(status_code=401, detail="Authentication required")
return Principal("test-operator", role, "test")
service = Mock()
service.preflight.return_value = LabPreflightResponse(
input_summary=LabInputSummary(sha256="a" * 64, item_count=1, encoded_bytes=40),
limits=LabLimits(), blockers=["CANDIDATE_NOT_FOUND"], checks_passed=False,
)
app.dependency_overrides[authenticate_operator_principal] = principal
app.dependency_overrides[get_lab_service] = lambda: service
return TestClient(app), service
@pytest.mark.parametrize("role,status", [
(None, 401), (PrincipalRole.VIEWER, 403),
(PrincipalRole.OPERATOR, 200), (PrincipalRole.ADMIN, 200),
])
def test_preflight_requires_operator_without_execution(role: PrincipalRole | None, status: int) -> None:
client, service = harness(role)
response = client.post("/api/v1/lab/preflight", json=request_body())
assert response.status_code == status
assert service.preflight.call_count == int(status == 200)
if status == 200:
assert response.json()["execution_started"] is False
assert response.json()["permission_granted"] is False
assert "private-input-canary" not in response.text
@pytest.mark.parametrize("location", ["discriminator", "payload", "extra"])
def test_validation_never_reflects_payload_or_discriminator(location: str) -> None:
client, service = harness(PrincipalRole.OPERATOR)
body = request_body()
canary = "sensitive-lab-validation-canary"
if location == "discriminator":
body["input"]["capability"] = canary
elif location == "payload":
body["input"]["payload"]["input"] = {canary: canary}
else:
body[canary] = canary
response = client.post("/api/v1/lab/preflight", json=body)
assert response.status_code == 422
assert canary not in response.text
assert "private-input-canary" not in response.text
assert response.json()["error"]["code"] == "request_validation_failed"
service.preflight.assert_not_called()