Files
stack/tests/api/test_routes_coverage.py
kert e07c8e76e9 add P8 quality: 99% coverage, health checks, snapshots, CI pipeline
- 76 new tests covering CLI commands, API routes, auth, runner,
  load modules — coverage 99% (169 remaining lines are infrastructure)
- Enhanced /health endpoint with DuckDB, bib, pipeline service checks
- Added `stack health` CLI command with graceful degradation
- SnapshotManager for DuckDB data versioning and rollback
- .woodpecker.yml CI pipeline: lint, test (99% gate), marimo check
- Error handling audit: no bare except clauses, all exceptions logged
Closes #31, #32, #33, #34, #35.
2026-03-12 18:28:55 -04:00

104 lines
3.1 KiB
Python

"""Coverage tests for API route edge cases and deps."""
from __future__ import annotations
import time
import jwt
import pytest
from fastapi.testclient import TestClient
@pytest.fixture()
def api_secret(monkeypatch):
secret = "test-secret-key-for-jwt-testing"
monkeypatch.setenv("STACK_API_SECRET", secret)
return secret
@pytest.fixture()
def client():
from api.server import app
return TestClient(app)
@pytest.fixture()
def authed_client(client, api_secret):
token = jwt.encode({"sub": "test"}, api_secret, algorithm="HS256")
client.headers["Authorization"] = f"Bearer {token}"
return client
class TestDepsAuth:
def test_expired_token(self, client, api_secret) -> None:
token = jwt.encode(
{"sub": "test", "exp": int(time.time()) - 10},
api_secret,
algorithm="HS256",
)
client.headers["Authorization"] = f"Bearer {token}"
r = client.post("/pipelines/run/readmissions")
assert r.status_code == 401
assert "expired" in r.json()["detail"].lower()
def test_invalid_token(self, client, api_secret) -> None:
client.headers["Authorization"] = "Bearer invalid.token.here"
r = client.post("/pipelines/run/readmissions")
assert r.status_code == 401
def test_missing_bearer(self, client, api_secret) -> None:
r = client.post("/pipelines/run/readmissions")
assert r.status_code in (401, 403)
class TestBibRoutes:
def test_list_items_with_filter(self, client) -> None:
r = client.get("/bib/items?tag=module:aco&limit=5")
assert r.status_code == 200
def test_list_items_with_type(self, client) -> None:
r = client.get("/bib/items?item_type=Source")
assert r.status_code == 200
def test_list_tags(self, client) -> None:
r = client.get("/bib/tags")
assert r.status_code == 200
class TestSchemaRoutes:
def test_get_schema_found(self, client) -> None:
# Try to find a real table
# Pick any table that exists
import aco.table.core as core_mod
for attr in dir(core_mod):
cls = getattr(core_mod, attr)
if isinstance(cls, type) and hasattr(cls, "__tablename__"):
table_name = cls.__tablename__
r = client.get(f"/schema/{table_name}")
if r.status_code == 200:
assert "properties" in r.json()
return
pytest.skip("No table models found")
class TestPipelineRoutes:
def test_run_with_body(self, authed_client) -> None:
r = authed_client.post(
"/pipelines/run/readmissions",
json={"target": "local", "save": False},
)
assert r.status_code == 200
data = r.json()
assert data["status"] == "running"
def test_pipeline_detail_inputs_outputs(self, client) -> None:
r = client.get("/pipelines/readmissions")
assert r.status_code == 200
data = r.json()
assert "inputs" in data
assert "outputs" in data
assert len(data["outputs"]) > 0