feat: initial commit — Billo Release Agent (LangGraph)
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.
This commit is contained in:
149
tests/api/test_dependencies.py
Normal file
149
tests/api/test_dependencies.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user