from __future__ import annotations import asyncio import os import stat import tempfile import threading import unittest from datetime import date from pathlib import Path from unittest.mock import patch from arr_ingestion.contracts import IngestionError from arr_ingestion.direct_contracts import DirectSubmissionReceipt from arr_mcp.auth import BearerAuthASGI, BearerAuthConfig from arr_mcp.database import _read_private_database_config from arr_mcp.gateway import DirectResultGateway from arr_mcp.run import _allowed_hosts from arr_mcp.server import load_tool_schemas TOKEN = "M" * 48 def receipt() -> DirectSubmissionReceipt: return DirectSubmissionReceipt( "committed", "arrjob-mcp-test", 1, date(2026, 7, 27), 71, 2, 5, ) class FakeService: def __init__(self) -> None: self.requests: list[object] = [] def submit(self, raw_request: object) -> DirectSubmissionReceipt: self.requests.append(raw_request) return receipt() class BlockingService(FakeService): def __init__(self) -> None: super().__init__() self.entered = threading.Event() self.release = threading.Event() def submit(self, raw_request: object) -> DirectSubmissionReceipt: self.requests.append(raw_request) self.entered.set() if not self.release.wait(timeout=5): raise AssertionError("blocking test timed out") return receipt() async def invoke_asgi( app: object, headers: list[tuple[bytes, bytes]], ) -> list[dict[str, object]]: messages: list[dict[str, object]] = [] async def receive() -> dict[str, object]: return {"type": "http.disconnect"} async def send(message: dict[str, object]) -> None: messages.append(message) await app( # type: ignore[operator] {"type": "http", "method": "GET", "path": "/mcp", "headers": headers}, receive, send, ) return messages class DirectResultGatewayTests(unittest.TestCase): def test_gateway_returns_only_the_service_receipt(self) -> None: service = FakeService() gateway = DirectResultGateway(service) arguments = {"opaque": "request"} result = gateway.submit_processing_result(arguments) self.assertEqual(service.requests, [arguments]) self.assertEqual(result, receipt().to_dict()) def test_gateway_fails_fast_when_replay_capacity_is_full(self) -> None: service = BlockingService() gateway = DirectResultGateway(service, max_concurrency=1) failures: list[BaseException] = [] def first_call() -> None: try: gateway.submit_processing_result({"call": 1}) except BaseException as error: # pragma: no cover - assertion aid failures.append(error) worker = threading.Thread(target=first_call) worker.start() self.assertTrue(service.entered.wait(timeout=2)) try: with self.assertRaises(IngestionError) as raised: gateway.submit_processing_result({"call": 2}) self.assertEqual(raised.exception.code, "DIRECT_GATEWAY_BUSY") self.assertTrue(raised.exception.retryable) finally: service.release.set() worker.join(timeout=2) self.assertFalse(worker.is_alive()) self.assertEqual(failures, []) class BearerAuthTests(unittest.TestCase): def test_config_is_fail_closed_and_secret_is_not_represented(self) -> None: with patch.dict(os.environ, {"ARR_MCP_BEARER_TOKEN": TOKEN}, clear=False): config = BearerAuthConfig.from_environment() self.assertNotIn(TOKEN, repr(config)) with patch.dict(os.environ, {}, clear=True): with self.assertRaises(ValueError): BearerAuthConfig.from_environment() def test_middleware_requires_one_exact_bearer_header(self) -> None: async def inner(scope, receive, send): del scope, receive await send({"type": "http.response.start", "status": 204, "headers": []}) await send({"type": "http.response.body", "body": b""}) app = BearerAuthASGI(inner, BearerAuthConfig(TOKEN)) accepted = asyncio.run( invoke_asgi(app, [(b"authorization", ("Bearer " + TOKEN).encode())]) ) self.assertEqual(accepted[0]["status"], 204) rejected = asyncio.run(invoke_asgi(app, [])) self.assertEqual(rejected[0]["status"], 401) duplicated = asyncio.run( invoke_asgi( app, [ (b"authorization", ("Bearer " + TOKEN).encode()), (b"authorization", ("Bearer " + TOKEN).encode()), ], ) ) self.assertEqual(duplicated[0]["status"], 401) class McpConfigurationTests(unittest.TestCase): def test_advertised_schema_is_portable_and_server_contract_stays_exact(self) -> None: request, response = load_tool_schemas() self.assertEqual( set(request["properties"]), {"submission_grant", "job_id", "attempt_no", "payload"}, ) payload = request["properties"]["payload"] self.assertEqual( set(payload["required"]), set(payload["properties"]), ) self.assertEqual(payload["properties"]["status"]["const"], "success") self.assertIs( payload["properties"]["activation_eligible"]["const"], True, ) self.assertEqual( payload["properties"]["records"]["items"], {"type": "object"}, ) forbidden_keys: list[str] = [] def collect(value: object) -> None: if isinstance(value, dict): for key, nested in value.items(): if key in {"$ref", "$defs", "$id"}: forbidden_keys.append(key) collect(nested) elif isinstance(value, list): for nested in value: collect(nested) collect(request) self.assertEqual(forbidden_keys, []) self.assertEqual( response["properties"]["contract_version"]["const"], "arr-direct-ingestion-1", ) def test_allowed_hosts_are_fail_closed_beyond_loopback(self) -> None: self.assertIn("127.0.0.1:*", _allowed_hosts("127.0.0.1", [])) with self.assertRaises(ValueError): _allowed_hosts("0.0.0.0", []) self.assertEqual( _allowed_hosts("0.0.0.0", ["mcp.example.test", "mcp.example.test"]), ["mcp.example.test"], ) def test_database_config_requires_owned_mode_0600_file(self) -> None: content = "\n".join( [ "ARR_DB_HOST=127.0.0.1", "ARR_DB_PORT=5432", "ARR_DB_USER=synthetic", "ARR_DB_PASSWORD=synthetic", "ARR_DB_NAME=booking_test", ] ) with tempfile.TemporaryDirectory() as temporary: path = Path(temporary) / "database.env" path.write_text(content, encoding="utf-8") path.chmod(0o600) values = _read_private_database_config(path) self.assertEqual(values["dbname"], "booking_test") path.chmod(0o644) with self.assertRaises(ValueError): _read_private_database_config(path) path.chmod(stat.S_IRUSR | stat.S_IWUSR) if __name__ == "__main__": unittest.main(verbosity=2)