LangGraph-based release automation agent with: - PR discovery (webhook + polling) - AI code review via Claude Code CLI (subscription-based) - Auto-create Jira tickets for PRs without ticket ID - Jira ticket lifecycle management (code review -> staging -> done) - CI/CD pipeline trigger, polling, and approval gates - Slack interactive messages with approval buttons - Per-repo semantic versioning - PostgreSQL persistence (threads, staging, releases) - FastAPI API (webhooks, approvals, status, manual triggers) - Docker Compose deployment 1069 tests, 95%+ coverage.
150 lines
4.3 KiB
Python
150 lines
4.3 KiB
Python
"""Tests for API FastAPI dependencies. Written FIRST (TDD RED phase)."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from release_agent.api.dependencies import (
|
|
get_db_pool,
|
|
get_graphs,
|
|
get_settings,
|
|
get_staging_store,
|
|
get_tool_clients,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _make_app_with_state(**state_kwargs) -> FastAPI:
|
|
"""Return a minimal FastAPI app with app.state attributes set."""
|
|
app = FastAPI()
|
|
for key, value in state_kwargs.items():
|
|
setattr(app.state, key, value)
|
|
return app
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_settings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetSettings:
|
|
def test_returns_settings_from_state(self) -> None:
|
|
mock_settings = MagicMock()
|
|
app = _make_app_with_state(settings=mock_settings)
|
|
|
|
with TestClient(app) as client:
|
|
# We test the dependency directly by simulating a request
|
|
request = MagicMock()
|
|
request.app = app
|
|
result = get_settings(request)
|
|
assert result is mock_settings
|
|
|
|
def test_raises_when_settings_missing(self) -> None:
|
|
app = FastAPI() # no state.settings
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
|
|
with pytest.raises(AttributeError):
|
|
get_settings(request)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_graphs
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetGraphs:
|
|
def test_returns_graphs_from_state(self) -> None:
|
|
mock_graphs = {"pr_completed": MagicMock(), "release": MagicMock()}
|
|
app = _make_app_with_state(graphs=mock_graphs)
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
result = get_graphs(request)
|
|
assert result is mock_graphs
|
|
|
|
def test_raises_when_graphs_missing(self) -> None:
|
|
app = FastAPI()
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
|
|
with pytest.raises(AttributeError):
|
|
get_graphs(request)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_tool_clients
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetToolClients:
|
|
def test_returns_tool_clients_from_state(self) -> None:
|
|
mock_clients = MagicMock()
|
|
app = _make_app_with_state(tool_clients=mock_clients)
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
result = get_tool_clients(request)
|
|
assert result is mock_clients
|
|
|
|
def test_raises_when_tool_clients_missing(self) -> None:
|
|
app = FastAPI()
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
|
|
with pytest.raises(AttributeError):
|
|
get_tool_clients(request)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_staging_store
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetStagingStore:
|
|
def test_returns_staging_store_from_state(self) -> None:
|
|
mock_store = MagicMock()
|
|
app = _make_app_with_state(staging_store=mock_store)
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
result = get_staging_store(request)
|
|
assert result is mock_store
|
|
|
|
def test_raises_when_staging_store_missing(self) -> None:
|
|
app = FastAPI()
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
|
|
with pytest.raises(AttributeError):
|
|
get_staging_store(request)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_db_pool
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetDbPool:
|
|
def test_returns_db_pool_from_state(self) -> None:
|
|
mock_pool = MagicMock()
|
|
app = _make_app_with_state(db_pool=mock_pool)
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
result = get_db_pool(request)
|
|
assert result is mock_pool
|
|
|
|
def test_raises_when_db_pool_missing(self) -> None:
|
|
app = FastAPI()
|
|
|
|
request = MagicMock()
|
|
request.app = app
|
|
|
|
with pytest.raises(AttributeError):
|
|
get_db_pool(request)
|