"""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"