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.
149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
"""Tests for models/build.py — BuildStatus and ApprovalRecord.
|
|
|
|
Written FIRST (TDD RED phase).
|
|
"""
|
|
|
|
import pytest
|
|
from dataclasses import FrozenInstanceError
|
|
|
|
from release_agent.models.build import ApprovalRecord, BuildStatus
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# BuildStatus tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBuildStatus:
|
|
"""Tests for BuildStatus frozen dataclass."""
|
|
|
|
def test_can_be_created_with_all_fields(self) -> None:
|
|
bs = BuildStatus(
|
|
status="completed",
|
|
result="succeeded",
|
|
build_url="https://dev.azure.com/org/proj/_build/results?buildId=42",
|
|
)
|
|
assert bs.status == "completed"
|
|
assert bs.result == "succeeded"
|
|
assert bs.build_url == "https://dev.azure.com/org/proj/_build/results?buildId=42"
|
|
|
|
def test_result_can_be_none(self) -> None:
|
|
bs = BuildStatus(
|
|
status="inProgress",
|
|
result=None,
|
|
build_url="https://dev.azure.com/org/proj/_build/results?buildId=99",
|
|
)
|
|
assert bs.result is None
|
|
|
|
def test_build_url_can_be_none(self) -> None:
|
|
bs = BuildStatus(status="notStarted", result=None, build_url=None)
|
|
assert bs.build_url is None
|
|
|
|
def test_is_frozen_status(self) -> None:
|
|
bs = BuildStatus(status="completed", result="succeeded", build_url=None)
|
|
with pytest.raises((FrozenInstanceError, AttributeError)):
|
|
bs.status = "inProgress" # type: ignore[misc]
|
|
|
|
def test_is_frozen_result(self) -> None:
|
|
bs = BuildStatus(status="completed", result="succeeded", build_url=None)
|
|
with pytest.raises((FrozenInstanceError, AttributeError)):
|
|
bs.result = "failed" # type: ignore[misc]
|
|
|
|
def test_equality(self) -> None:
|
|
a = BuildStatus(status="completed", result="succeeded", build_url="http://x")
|
|
b = BuildStatus(status="completed", result="succeeded", build_url="http://x")
|
|
assert a == b
|
|
|
|
def test_inequality_on_status(self) -> None:
|
|
a = BuildStatus(status="completed", result="succeeded", build_url=None)
|
|
b = BuildStatus(status="inProgress", result="succeeded", build_url=None)
|
|
assert a != b
|
|
|
|
def test_inequality_on_result(self) -> None:
|
|
a = BuildStatus(status="completed", result="succeeded", build_url=None)
|
|
b = BuildStatus(status="completed", result="failed", build_url=None)
|
|
assert a != b
|
|
|
|
def test_repr_contains_status(self) -> None:
|
|
bs = BuildStatus(status="completed", result="succeeded", build_url=None)
|
|
assert "completed" in repr(bs)
|
|
|
|
def test_status_values_typical(self) -> None:
|
|
for s in ("notStarted", "inProgress", "completed", "cancelling"):
|
|
bs = BuildStatus(status=s, result=None, build_url=None)
|
|
assert bs.status == s
|
|
|
|
def test_result_values_typical(self) -> None:
|
|
for r in ("succeeded", "failed", "canceled", "partiallySucceeded"):
|
|
bs = BuildStatus(status="completed", result=r, build_url=None)
|
|
assert bs.result == r
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ApprovalRecord tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestApprovalRecord:
|
|
"""Tests for ApprovalRecord frozen dataclass."""
|
|
|
|
def test_can_be_created_with_all_fields(self) -> None:
|
|
ar = ApprovalRecord(
|
|
approval_id="approval-abc-123",
|
|
stage_name="Sandbox",
|
|
status="pending",
|
|
release_id=42,
|
|
)
|
|
assert ar.approval_id == "approval-abc-123"
|
|
assert ar.stage_name == "Sandbox"
|
|
assert ar.status == "pending"
|
|
assert ar.release_id == 42
|
|
|
|
def test_is_frozen_approval_id(self) -> None:
|
|
ar = ApprovalRecord(
|
|
approval_id="abc",
|
|
stage_name="Sandbox",
|
|
status="pending",
|
|
release_id=1,
|
|
)
|
|
with pytest.raises((FrozenInstanceError, AttributeError)):
|
|
ar.approval_id = "xyz" # type: ignore[misc]
|
|
|
|
def test_is_frozen_stage_name(self) -> None:
|
|
ar = ApprovalRecord(
|
|
approval_id="abc",
|
|
stage_name="Sandbox",
|
|
status="pending",
|
|
release_id=1,
|
|
)
|
|
with pytest.raises((FrozenInstanceError, AttributeError)):
|
|
ar.stage_name = "Production" # type: ignore[misc]
|
|
|
|
def test_equality(self) -> None:
|
|
a = ApprovalRecord(approval_id="x", stage_name="S", status="pending", release_id=1)
|
|
b = ApprovalRecord(approval_id="x", stage_name="S", status="pending", release_id=1)
|
|
assert a == b
|
|
|
|
def test_inequality_on_approval_id(self) -> None:
|
|
a = ApprovalRecord(approval_id="x", stage_name="S", status="pending", release_id=1)
|
|
b = ApprovalRecord(approval_id="y", stage_name="S", status="pending", release_id=1)
|
|
assert a != b
|
|
|
|
def test_status_pending(self) -> None:
|
|
ar = ApprovalRecord(approval_id="a", stage_name="Stage", status="pending", release_id=10)
|
|
assert ar.status == "pending"
|
|
|
|
def test_status_approved(self) -> None:
|
|
ar = ApprovalRecord(approval_id="a", stage_name="Stage", status="approved", release_id=10)
|
|
assert ar.status == "approved"
|
|
|
|
def test_status_rejected(self) -> None:
|
|
ar = ApprovalRecord(approval_id="a", stage_name="Stage", status="rejected", release_id=10)
|
|
assert ar.status == "rejected"
|
|
|
|
def test_repr_contains_stage_name(self) -> None:
|
|
ar = ApprovalRecord(approval_id="a", stage_name="Production", status="pending", release_id=5)
|
|
assert "Production" in repr(ar)
|
|
|
|
def test_release_id_is_int(self) -> None:
|
|
ar = ApprovalRecord(approval_id="a", stage_name="S", status="pending", release_id=999)
|
|
assert isinstance(ar.release_id, int)
|