Files
ss-tools/backend/tests/api/test_ready.py
busya a32ca0631b feat(037): capture, verification lifecycle, inheritance + close 036 stabilization
- Authoritative candidate capture with server-issued artifacts and raw-byte
  immutability hashing (source_response_hash server-owned)
- Closed-period lifecycle: request-hash bound approvals, persisted closure
  immutability violations, byte-for-byte catalog stability on reclosure
- Verification runs: persisted VerificationRun model + FK migration,
  publish gate (block_publish), scheduled observability runs (02:00 UTC)
- FR-013 baseline inheritance: prior_release_id migration, plan_inheritance/
  execute_inheritance classification and re-extraction, API endpoints
- Visual executor bound to release-deployment environment; caller mismatch
  rejected; visual SSIM/reconciliation modules
- Query execution decomposed: envelope/model/executor split, no direct SQL
- AgentRun approvals extracted to submodule; evidence adapter; _utils
- Dashboard testing service decomposed into 30+ modules (all <400 LOC)
- Five Feature-037 agent tools with permission guards (tools_037.py)
- API readiness endpoint; Alembic env/migrations; test fixture repos
- Specs 036/037 contracts, openapi.yaml, schema.json, tasks/traceability
  updated; semantic index rebuilt with 0 parse warnings
- Fix ADR-0003 parser ambiguity: remove [DEF🆔ADR] prose example
- Add axiom-mcp-agent-feedback.md: agent findings for MCP rework plan
- Tests: 298 service + 1464 API + 45 agent passing; ruff clean
2026-07-31 11:28:50 +03:00

99 lines
3.4 KiB
Python

# #region Test.Api.Ready [C:2] [TYPE Module] [SEMANTICS test,readiness,api,healthcheck]
# @BRIEF Unit tests for the unauthenticated /api/ready readiness probe.
# @RELATION BINDS_TO -> [Api.Ready.ReadyRouter]
# @TEST_EDGE: db_success -> 200 + {"status": "ready"}
# @TEST_EDGE: db_failure_sqlalchemy -> 503 + {"status": "not_ready"}
# @TEST_EDGE: db_failure_generic -> 503 + {"status": "not_ready"}
import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")
from pathlib import Path
import pytest
import sys
from unittest.mock import MagicMock, patch
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy.exc import SQLAlchemyError
_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
sys.path.insert(0, _src)
@pytest.fixture(name="client")
def fixture_client():
"""Build a TestClient with the ready router mounted (no auth dependencies)."""
from src.api.routes.ready import router
app = FastAPI()
app.include_router(router)
return TestClient(app)
class TestGetReady:
"""GET /api/ready"""
def test_ready_success(self, client):
"""Happy path: DB responds to SELECT 1 -> 200."""
mock_session = MagicMock()
mock_session.execute.return_value = True
with patch("src.api.routes.ready.SessionLocal", return_value=mock_session):
resp = client.get("/api/ready")
assert resp.status_code == 200
assert resp.json() == {"status": "ready"}
mock_session.execute.assert_called_once()
def test_ready_sqlalchemy_error(self, client):
"""SQLAlchemyError during execute -> 503 + not_ready."""
mock_session = MagicMock()
mock_session.execute.side_effect = SQLAlchemyError("connection refused")
with patch("src.api.routes.ready.SessionLocal", return_value=mock_session):
resp = client.get("/api/ready")
assert resp.status_code == 503
body = resp.json()
assert body["status"] == "not_ready"
assert body["detail"] == "Database unavailable"
def test_ready_generic_error(self, client):
"""Non-SQLAlchemy exception during execute -> 503 + not_ready."""
mock_session = MagicMock()
mock_session.execute.side_effect = RuntimeError("something exploded")
with patch("src.api.routes.ready.SessionLocal", return_value=mock_session):
resp = client.get("/api/ready")
assert resp.status_code == 503
body = resp.json()
assert body["status"] == "not_ready"
assert body["detail"] == "Backend not ready"
def test_ready_no_secrets_in_response(self, client):
"""Exception detail must not leak stack traces or connection strings."""
mock_session = MagicMock()
mock_session.execute.side_effect = SQLAlchemyError(
"FATAL: password authentication failed for user 'admin'"
)
with patch("src.api.routes.ready.SessionLocal", return_value=mock_session):
resp = client.get("/api/ready")
assert resp.status_code == 503
body = resp.json()
assert body["status"] == "not_ready"
detail_str = str(body.get("detail", ""))
assert "password" not in detail_str.lower()
assert "admin" not in detail_str
assert "FATAL" not in detail_str
# #endregion Test.Api.Ready