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:
122
tests/test_branch_parser.py
Normal file
122
tests/test_branch_parser.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Tests for branch_parser module. Written FIRST (TDD RED phase)."""
|
||||
|
||||
|
||||
from release_agent.branch_parser import parse_branch, strip_refs_prefix
|
||||
|
||||
|
||||
class TestStripRefsPrefix:
|
||||
"""Tests for strip_refs_prefix function."""
|
||||
|
||||
def test_strips_refs_heads_prefix(self) -> None:
|
||||
assert strip_refs_prefix("refs/heads/fix/BILL-42_something") == "fix/BILL-42_something"
|
||||
|
||||
def test_strips_refs_heads_prefix_feature(self) -> None:
|
||||
assert strip_refs_prefix("refs/heads/feature/ALLPOST-100_add-feature") == "feature/ALLPOST-100_add-feature"
|
||||
|
||||
def test_no_refs_prefix_unchanged(self) -> None:
|
||||
assert strip_refs_prefix("bug/ALLPOST-4229_fix-review") == "bug/ALLPOST-4229_fix-review"
|
||||
|
||||
def test_main_unchanged(self) -> None:
|
||||
assert strip_refs_prefix("main") == "main"
|
||||
|
||||
def test_develop_unchanged(self) -> None:
|
||||
assert strip_refs_prefix("develop") == "develop"
|
||||
|
||||
def test_empty_string(self) -> None:
|
||||
assert strip_refs_prefix("") == ""
|
||||
|
||||
def test_only_refs_heads(self) -> None:
|
||||
assert strip_refs_prefix("refs/heads/") == ""
|
||||
|
||||
def test_refs_tags_not_stripped(self) -> None:
|
||||
assert strip_refs_prefix("refs/tags/v1.0.0") == "refs/tags/v1.0.0"
|
||||
|
||||
|
||||
class TestParseBranch:
|
||||
"""Tests for parse_branch function."""
|
||||
|
||||
def test_bug_branch_with_ticket(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("bug/ALLPOST-4229_fix-review")
|
||||
assert ticket_id == "ALLPOST-4229"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_feature_branch_with_ticket(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("feature/ALLPOST-100_add-feature")
|
||||
assert ticket_id == "ALLPOST-100"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_refs_heads_fix_branch(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("refs/heads/fix/BILL-42_something")
|
||||
assert ticket_id == "BILL-42"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_feat_branch_short(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("feat/MY-1_x")
|
||||
assert ticket_id == "MY-1"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_chore_without_ticket(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("chore/update-dependencies")
|
||||
assert ticket_id is None
|
||||
assert has_ticket is False
|
||||
|
||||
def test_main_branch(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("main")
|
||||
assert ticket_id is None
|
||||
assert has_ticket is False
|
||||
|
||||
def test_develop_branch(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("develop")
|
||||
assert ticket_id is None
|
||||
assert has_ticket is False
|
||||
|
||||
def test_release_branch(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("release/v1.0.3")
|
||||
assert ticket_id is None
|
||||
assert has_ticket is False
|
||||
|
||||
def test_returns_tuple(self) -> None:
|
||||
result = parse_branch("main")
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_ticket_id_type_when_present(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("bug/ALLPOST-4229_fix-review")
|
||||
assert isinstance(ticket_id, str)
|
||||
assert isinstance(has_ticket, bool)
|
||||
|
||||
def test_ticket_id_type_when_absent(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("main")
|
||||
assert ticket_id is None
|
||||
assert isinstance(has_ticket, bool)
|
||||
|
||||
def test_fix_prefix(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("fix/PROJ-999_some-fix")
|
||||
assert ticket_id == "PROJ-999"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_refs_heads_feature_branch(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("refs/heads/feature/ALLPOST-100_add-feature")
|
||||
assert ticket_id == "ALLPOST-100"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_ticket_with_multiple_digits(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("feature/ABC-12345_some-long-feature")
|
||||
assert ticket_id == "ABC-12345"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_branch_without_underscore_separator(self) -> None:
|
||||
# Branch has ticket pattern but no underscore - still detects ticket
|
||||
ticket_id, has_ticket = parse_branch("feature/PROJ-100")
|
||||
assert ticket_id == "PROJ-100"
|
||||
assert has_ticket is True
|
||||
|
||||
def test_empty_string(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("")
|
||||
assert ticket_id is None
|
||||
assert has_ticket is False
|
||||
|
||||
def test_ticket_with_numeric_project_prefix(self) -> None:
|
||||
ticket_id, has_ticket = parse_branch("feature/AB2-100_feature")
|
||||
assert ticket_id == "AB2-100"
|
||||
assert has_ticket is True
|
||||
Reference in New Issue
Block a user