89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
"""Contract/admission-route tests; Root file/atomicity and drain acceptance are VM-only."""
|
|
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from jsonschema import Draft202012Validator
|
|
|
|
from kf_oj.api.availability import AvailabilityMiddleware, needs_open
|
|
from kf_oj.contracts.availability import AvailabilityRecord
|
|
|
|
|
|
@pytest.mark.parametrize("state", ["open", "draining", "quiescent"])
|
|
def test_availability_is_exactly_bound_to_a_known_version_and_state(state):
|
|
path = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "tests/fixtures/contracts/delivery-availability.valid.json"
|
|
)
|
|
value = json.loads(path.read_text())
|
|
value["state"] = state
|
|
assert AvailabilityRecord.model_validate(value).state == state
|
|
schema = path.parents[3] / "contracts/schemas/delivery-availability.v1.schema.json"
|
|
Draft202012Validator(json.loads(schema.read_text())).validate(value)
|
|
for key, replacement in (
|
|
("schema_version", True),
|
|
("release_id", "../release"),
|
|
("state", "always_allow"),
|
|
):
|
|
with pytest.raises(ValueError):
|
|
AvailabilityRecord.model_validate({**value, key: replacement})
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"method,path,blocked",
|
|
[
|
|
("POST", "/api/v1/submissions", True),
|
|
("POST", "/api/v1/admin/problem-imports", True),
|
|
("POST", "/api/v1/admin/problems/publish", True),
|
|
("POST", "/api/v1/auth/login", True),
|
|
("GET", "/api/v1/internal/evaluations/artifacts", False),
|
|
("POST", "/api/v1/auth/logout", False),
|
|
("POST", "/api/v1/submissions/11111111-1111-4111-8111-111111111111/cancel", False),
|
|
(
|
|
"PUT",
|
|
"/api/v1/admin/problem-imports/11111111-1111-4111-8111-111111111111/archive",
|
|
False,
|
|
),
|
|
],
|
|
)
|
|
def test_drain_blocks_new_mutations_without_cutting_accepted_reads_and_cancel(
|
|
method, path, blocked
|
|
):
|
|
assert needs_open(method, path) is blocked
|
|
|
|
|
|
def test_missing_admission_record_rejects_before_body_or_domain_execution(monkeypatch):
|
|
# Transport policy only: this stub does not verify Root ownership or Linux file safety.
|
|
calls = []
|
|
messages = []
|
|
|
|
def missing(*_):
|
|
raise FileNotFoundError("record missing")
|
|
|
|
monkeypatch.setattr("kf_oj.api.availability.current", missing)
|
|
|
|
async def receive():
|
|
calls.append("body")
|
|
return {"type": "http.request", "body": b""}
|
|
|
|
async def domain(*_):
|
|
calls.append("domain")
|
|
|
|
async def send(message):
|
|
messages.append(message)
|
|
|
|
settings = SimpleNamespace(
|
|
environment="production", delivery_release_id=None, delivery_configuration_id=None
|
|
)
|
|
asyncio.run(
|
|
AvailabilityMiddleware(domain, settings=settings)(
|
|
{"type": "http", "method": "POST", "path": "/api/v1/submissions"}, receive, send
|
|
)
|
|
)
|
|
assert calls == []
|
|
assert messages[0]["status"] == 503
|
|
assert (b"cache-control", b"no-store") in messages[0]["headers"]
|