- Pyproject.toml mit FastAPI, Pydantic v2, SQLAlchemy, httpx, asyncio, BeautifulSoup4, selectolax, trafilatura, uvicorn, pytest-asyncio - Multi-stage Dockerfile (Python 3.12-slim, Non-Root-User nsct) - docker-compose.yml (nsct-api + postgres + optional searxng) - .env.example mit allen Config-Parametern - Config-System: AppSettings mit LLMConfig, VisionConfig, AudioConfig, DatabaseConfig — komplett aus Environment, keine Hardcodes - Strukturiertes Logging mit research_id/llm_request_id Tracking - Pydantic v2 Schemas: SearchQuery, Source, Claim, EvidenceRelation, CitationEdge, ResearchReport - SQLAlchemy 2.0 Declarative Models + async Engine Factory - SSRF-Schutz: URL-Validation, IP-Blocklist (RFC1918, Cloud Metadata, file://, ftp://) - Provider-Interfaces: LLMProvider, VisionProvider, AudioProvider, SearchProvider, ContentFetcher als ABCs - Health-Endpoints: /health, /ready (LLM-Connect-Test), /providers - FastAPI App mit CORS, lifespan (LLM Pre-Flight) - CLI-Stub mit Entry-Points: nsct, nsct-core, nsct-api - 6 Test-Cases: /health, /ready, /providers + No-Secrets-Test - Vollständige Dokumentation: README, ARCHITECTURE, SECURITY, METHODOLOGY, API, DEPLOYMENT - .gitignore (Python, Docker, IDE, .env)
78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
"""Tests for the health endpoints (/health, /ready, /providers)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Generator
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture
|
|
def client() -> Generator[TestClient, None, None]:
|
|
"""Synchronous test client."""
|
|
from nsct.api.main import create_app
|
|
|
|
app = create_app()
|
|
with TestClient(app) as c:
|
|
yield c
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /health
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_health_returns_ok(client: TestClient) -> None:
|
|
"""GET /health should return status ok and the version."""
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["status"] == "ok"
|
|
assert "version" in body
|
|
assert body["version"] == "0.1.0"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /ready
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_ready_returns_response(client: TestClient) -> None:
|
|
"""GET /ready should return a dict with status, llm key, etc."""
|
|
resp = client.get("/ready")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert "status" in body
|
|
assert "llm" in body
|
|
|
|
|
|
def test_ready_status_field(client: TestClient) -> None:
|
|
"""GET /ready status should be 'ready' or 'not_ready'."""
|
|
resp = client.get("/ready")
|
|
body = resp.json()
|
|
assert body["status"] in ("ready", "not_ready")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /providers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_providers_returns_provider_info(client: TestClient) -> None:
|
|
"""GET /providers should return keys for llm, vision, audio."""
|
|
resp = client.get("/providers")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert "llm" in body
|
|
assert "vision" in body
|
|
assert "audio" in body
|
|
|
|
|
|
def test_providers_no_secrets(client: TestClient) -> None:
|
|
"""GET /providers must not contain secrets."""
|
|
resp = client.get("/providers")
|
|
body = resp.json()
|
|
flat = str(body)
|
|
assert "API_KEY" not in flat
|
|
assert "secret" not in flat.lower().replace("available", "") |