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:
Yaojia Wang
2026-03-24 17:38:23 +01:00
commit f5c2733cfb
104 changed files with 19721 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
"""Tests for graph/full_cycle.py.
Tests that the full cycle graph composes pr_completed and release subgraphs
correctly, and that the routing conditional edge works as expected.
"""
from release_agent.graph.full_cycle import build_full_cycle_graph
from release_agent.graph.routing import should_continue_to_release
class TestBuildFullCycleGraph:
def test_returns_compiled_graph(self) -> None:
graph = build_full_cycle_graph()
assert graph is not None
def test_graph_can_be_built_multiple_times(self) -> None:
graph1 = build_full_cycle_graph()
graph2 = build_full_cycle_graph()
assert graph1 is not None
assert graph2 is not None
def test_graph_has_get_graph_method(self) -> None:
graph = build_full_cycle_graph()
assert hasattr(graph, "get_graph") or hasattr(graph, "nodes")
class TestFullCycleRouting:
"""Test that the routing function used by full_cycle correctly
determines whether to continue to the release subgraph."""
def test_continue_when_flag_true_and_no_errors(self) -> None:
state = {"continue_to_release": True, "errors": []}
assert should_continue_to_release(state) == "yes"
def test_stop_when_flag_false(self) -> None:
state = {"continue_to_release": False}
assert should_continue_to_release(state) == "no"
def test_stop_when_flag_missing(self) -> None:
state = {}
assert should_continue_to_release(state) == "no"
def test_stop_when_errors_present(self) -> None:
state = {"continue_to_release": True, "errors": ["some error"]}
assert should_continue_to_release(state) == "no"
def test_stop_when_flag_true_but_errors_present(self) -> None:
state = {"continue_to_release": True, "errors": ["critical failure"]}
assert should_continue_to_release(state) == "no"
def test_continue_when_errors_empty_list(self) -> None:
state = {"continue_to_release": True, "errors": []}
assert should_continue_to_release(state) == "yes"