import asyncio from pathlib import Path import httpx import pytest from _pytest.monkeypatch import MonkeyPatch from fastapi.routing import APIRoute from gewu.bootstrap.app import create_app, create_local_app def test_health_is_available_without_exposing_mutation_routes() -> None: async def request_health() -> httpx.Response: async with httpx.AsyncClient( transport=httpx.ASGITransport(app=create_app()), base_url="http://gewu.test", ) as client: return await client.get("/health") response = asyncio.run(request_health()) assert response.status_code == 200 assert response.json() == {"status": "ok", "service": "gewu"} routes = { f"{method} {route.path}" for route in create_app().routes if isinstance(route, APIRoute) for method in route.methods or () } assert routes == {"GET /health"} def test_local_factory_composes_private_persistent_read_service( monkeypatch: MonkeyPatch, tmp_path: Path ) -> None: data_root = tmp_path / "private-gewu" monkeypatch.setenv("GEWU_DATA_ROOT", str(data_root)) app = create_local_app() route_paths = {route.path for route in app.routes if isinstance(route, APIRoute)} assert route_paths == { "/health", "/v1/diagnostics", "/v1/admin/projects", "/v1/admin/credentials", "/v1/admin/credentials/{credential_id}", "/v1/admin/events", "/v1/admin/users", "/v1/admin/users/{username}", "/v1/users/session", "/v1/me", "/v1/me/password", "/v1/me/agent-credentials", "/v1/me/agent-credentials/{credential_id}", "/v1/admin/projects/{project_id}/archive", "/v1/projects/{project_id}/nodes/{node_id}", "/v1/projects/{project_id}/nodes/{node_id}/history", "/v1/projects/{project_id}/sources/{revision_id}", "/v1/projects/{project_id}/sources/{revision_id}/artifact", "/v1/projects/{project_id}/sources/{revision_id}/extractions/{extraction_id}/blocks/{block_id}", "/v1/neighbors", "/v1/projects", "/v1/search", "/v1/sources", "/v1/session", "/auth/owner-session", } assert (data_root / "knowledge.git" / "HEAD").is_file() assert (data_root / "identity.sqlite3").is_file() assert (data_root / "search.sqlite3").is_file() assert (data_root / "search.sqlite3").stat().st_mode & 0o077 == 0 assert (data_root.stat().st_mode & 0o777) == 0o700 def test_local_factory_rejects_symlinked_data_root( monkeypatch: MonkeyPatch, tmp_path: Path ) -> None: target = tmp_path / "real-root" target.mkdir() link = tmp_path / "linked-root" link.symlink_to(target, target_is_directory=True) monkeypatch.setenv("GEWU_DATA_ROOT", str(link)) with pytest.raises(ValueError, match="symbolic links"): create_local_app()