Files
billo-release-agent/tests/test_versioning.py
Yaojia Wang f5c2733cfb 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.
2026-03-24 17:38:23 +01:00

125 lines
4.5 KiB
Python

"""Tests for versioning module. Written FIRST (TDD RED phase)."""
import pytest
from release_agent.versioning import (
calculate_next_version,
format_version,
parse_version,
)
class TestParseVersion:
"""Tests for parse_version function."""
def test_parse_with_v_prefix(self) -> None:
assert parse_version("v1.2.3") == (1, 2, 3)
def test_parse_without_v_prefix(self) -> None:
assert parse_version("1.2.3") == (1, 2, 3)
def test_parse_zeros(self) -> None:
assert parse_version("v0.0.0") == (0, 0, 0)
def test_parse_large_numbers(self) -> None:
assert parse_version("v10.20.300") == (10, 20, 300)
def test_parse_returns_tuple_of_ints(self) -> None:
result = parse_version("v1.2.3")
assert isinstance(result, tuple)
assert len(result) == 3
assert all(isinstance(x, int) for x in result)
def test_parse_invalid_raises_value_error(self) -> None:
with pytest.raises(ValueError):
parse_version("invalid")
def test_parse_partial_version_raises_value_error(self) -> None:
with pytest.raises(ValueError):
parse_version("v1.2")
def test_parse_non_numeric_raises_value_error(self) -> None:
with pytest.raises(ValueError):
parse_version("va.b.c")
class TestFormatVersion:
"""Tests for format_version function."""
def test_format_basic(self) -> None:
assert format_version(1, 0, 3) == "v1.0.3"
def test_format_zeros(self) -> None:
assert format_version(0, 0, 0) == "v0.0.0"
def test_format_large_numbers(self) -> None:
assert format_version(10, 20, 300) == "v10.20.300"
def test_format_returns_string(self) -> None:
result = format_version(1, 2, 3)
assert isinstance(result, str)
def test_format_starts_with_v(self) -> None:
result = format_version(1, 2, 3)
assert result.startswith("v")
class TestCalculateNextVersion:
"""Tests for calculate_next_version function."""
def test_empty_list_returns_v1_0_0(self) -> None:
assert calculate_next_version("my-repo", []) == "v1.0.0"
def test_single_version_increments_patch(self) -> None:
assert calculate_next_version("my-repo", ["v1.0.0"]) == "v1.0.1"
def test_multiple_versions_uses_highest(self) -> None:
assert calculate_next_version("my-repo", ["v1.0.3", "v1.0.1"]) == "v1.0.4"
def test_different_major_versions(self) -> None:
assert calculate_next_version("my-repo", ["v2.1.0", "v1.9.9"]) == "v2.1.1"
def test_skips_malformed_versions(self) -> None:
assert calculate_next_version("my-repo", ["invalid", "v1.0.0"]) == "v1.0.1"
def test_all_malformed_versions_returns_v1_0_0(self) -> None:
assert calculate_next_version("my-repo", ["invalid", "bad", "nope"]) == "v1.0.0"
def test_repo_name_does_not_affect_result(self) -> None:
result_a = calculate_next_version("repo-a", ["v1.0.0"])
result_b = calculate_next_version("repo-b", ["v1.0.0"])
assert result_a == result_b
def test_versions_out_of_order(self) -> None:
assert calculate_next_version("my-repo", ["v1.0.1", "v1.0.3", "v1.0.2"]) == "v1.0.4"
def test_patch_overflow_does_not_occur(self) -> None:
# Just increments patch - no overflow logic required
result = calculate_next_version("my-repo", ["v1.0.99"])
assert result == "v1.0.100"
def test_versions_without_v_prefix_skipped(self) -> None:
# Versions without 'v' prefix are treated as malformed per spec
result = calculate_next_version("my-repo", ["1.0.0", "v2.0.0"])
assert result == "v2.0.1"
def test_result_format_starts_with_v(self) -> None:
result = calculate_next_version("my-repo", ["v1.0.0"])
assert result.startswith("v")
def test_result_has_three_parts(self) -> None:
result = calculate_next_version("my-repo", ["v1.0.0"])
parts = result[1:].split(".")
assert len(parts) == 3
assert all(p.isdigit() for p in parts)
def test_v_prefix_with_nonnumeric_parts_skipped(self) -> None:
# Starts with 'v' but is malformed - should be skipped gracefully
result = calculate_next_version("my-repo", ["va.b.c", "v1.0.0"])
assert result == "v1.0.1"
def test_v_prefix_partial_version_skipped(self) -> None:
# Starts with 'v' but only has two parts - should be skipped
result = calculate_next_version("my-repo", ["v1.0", "v2.0.0"])
assert result == "v2.0.1"