Files
actions/validate-inputs/tests/test_version.py
Ismo Vuorinen 7061aafd35 chore: add tests, update docs and actions (#299)
* docs: update documentation

* feat: validate-inputs has it's own pyproject

* security: mask DOCKERHUB_PASSWORD

* chore: add tokens, checkout, recrete docs, integration tests

* fix: add `statuses: write` permission to pr-lint
2025-10-18 13:09:19 +03:00

539 lines
20 KiB
Python

"""Tests for the VersionValidator module."""
import sys
from pathlib import Path
import pytest # pylint: disable=import-error
# Add the parent directory to the path
sys.path.insert(0, str(Path(__file__).parent.parent))
# pylint: disable=wrong-import-position
from tests.fixtures.version_test_data import (
CALVER_INVALID,
CALVER_VALID,
SEMVER_INVALID,
SEMVER_VALID,
)
from validators.version import VersionValidator
class TestVersionValidator: # pylint: disable=too-many-public-methods
"""Test cases for VersionValidator."""
def setup_method(self): # pylint: disable=attribute-defined-outside-init
"""Set up test environment."""
self.validator = VersionValidator()
def test_initialization(self):
"""Test validator initialization."""
assert self.validator.errors == []
rules = self.validator.get_validation_rules()
assert "semantic" in rules
assert "calver" in rules
@pytest.mark.parametrize("version,description", SEMVER_VALID)
def test_validate_semver_valid(self, version, description):
"""Test SemVer validation with valid versions."""
self.validator.errors = []
result = self.validator.validate_semver(version)
assert result is True, f"Failed for {description}: {version}"
assert len(self.validator.errors) == 0
@pytest.mark.parametrize("version,description", SEMVER_INVALID)
def test_validate_semver_invalid(self, version, description):
"""Test SemVer validation with invalid versions."""
self.validator.errors = []
result = self.validator.validate_semver(version)
if version == "": # Empty version might be allowed
assert result is True or result is False # Depends on implementation
else:
assert result is False, f"Should fail for {description}: {version}"
@pytest.mark.parametrize("version,description", CALVER_VALID)
def test_validate_calver_valid(self, version, description):
"""Test CalVer validation with valid versions."""
self.validator.errors = []
result = self.validator.validate_calver(version)
assert result is True, f"Failed for {description}: {version}"
assert len(self.validator.errors) == 0
@pytest.mark.parametrize("version,description", CALVER_INVALID)
def test_validate_calver_invalid(self, version, description):
"""Test CalVer validation with invalid versions."""
self.validator.errors = []
result = self.validator.validate_calver(version)
assert result is False, f"Should fail for {description}: {version}"
assert len(self.validator.errors) > 0
def test_validate_flexible_version(self):
"""Test flexible version validation (CalVer or SemVer)."""
# Test versions that could be either
flexible_versions = [
"2024.3.1", # CalVer
"1.2.3", # SemVer
"v1.0.0", # SemVer with prefix
"2024.03.15", # CalVer
]
for version in flexible_versions:
self.validator.errors = []
result = self.validator.validate_flexible_version(version)
assert result is True, f"Should accept flexible version: {version}"
def test_validate_dotnet_version(self):
"""Test .NET version validation."""
valid_versions = [
"6.0",
"6.0.100",
"7.0.0",
"8.0",
"3.1.426",
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_dotnet_version(version)
assert result is True, f"Should accept .NET version: {version}"
def test_validate_terraform_version(self):
"""Test Terraform version validation."""
valid_versions = [
"1.0.0",
"1.5.7",
"0.14.0",
"1.6.0-alpha",
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_terraform_version(version)
assert result is True, f"Should accept Terraform version: {version}"
def test_validate_node_version(self):
"""Test Node.js version validation."""
valid_versions = [
"18",
"18.0.0",
"20.9.0",
"lts",
"latest",
"lts/hydrogen",
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_node_version(version)
assert result is True, f"Should accept Node version: {version}"
def test_validate_inputs(self):
"""Test the main validate_inputs method."""
inputs = {
"version": "1.2.3",
"release-version": "2024.3.1",
"node-version": "18",
}
result = self.validator.validate_inputs(inputs)
# Should handle version inputs based on conventions
assert isinstance(result, bool)
def test_version_with_prefix(self):
"""Test that version prefixes are handled correctly."""
versions_with_prefix = [
("v1.2.3", True), # Common v prefix
("V1.2.3", True), # Uppercase V
("release-1.2.3", False), # Other prefix
("ver1.2.3", False), # Invalid prefix
]
for version, should_pass in versions_with_prefix:
self.validator.errors = []
result = self.validator.validate_semver(version)
if should_pass:
assert result is True, f"Should accept: {version}"
else:
assert result is False, f"Should reject: {version}"
def test_get_validation_rules(self):
"""Test that validation rules are properly defined."""
rules = self.validator.get_validation_rules()
assert "semantic" in rules
assert "calver" in rules
assert "dotnet" in rules
assert "terraform" in rules
assert "node" in rules
assert "python" in rules
def test_validate_strict_semantic_version_valid(self):
"""Test strict semantic version validation with valid versions."""
valid_versions = [
"1.0.0",
"1.2.3",
"10.20.30",
"1.0.0-alpha",
"1.0.0-beta.1",
"1.0.0-rc.1+build.1",
"1.0.0+build",
"v1.2.3", # v prefix allowed
"latest", # Special case
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_strict_semantic_version(version)
assert result is True, f"Should accept strict semver: {version}"
assert len(self.validator.errors) == 0
def test_validate_strict_semantic_version_invalid(self):
"""Test strict semantic version validation with invalid versions."""
invalid_versions = [
"", # Empty not allowed in strict mode
"1.0", # Must be X.Y.Z
"1", # Must be X.Y.Z
"1.2.a", # Non-numeric
"1.2.3.4", # Too many parts
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_strict_semantic_version(version)
assert result is False, f"Should reject strict semver: {version}"
assert len(self.validator.errors) > 0
def test_validate_version_by_type(self):
"""Test generic validate_version with different types."""
test_cases = [
("1.2.3", "semantic", True),
("2024.3.1", "calver", True),
("2024.3.1", "flexible", True),
("1.2.3", "flexible", True),
("6.0.100", "dotnet", True),
("1.5.7", "terraform", True),
("18.0.0", "node", True),
("3.10", "python", True),
("8.2", "php", True),
("1.21", "go", True),
("latest", "flexible", True), # Special case - only flexible handles latest properly
]
for version, version_type, expected in test_cases:
self.validator.errors = []
result = self.validator.validate_version(version, version_type)
assert result == expected, f"Failed for {version_type}: {version}"
def test_validate_python_version_valid(self):
"""Test Python version validation with valid versions."""
valid_versions = [
"3.8",
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
"3.14",
"3.15",
"3.10.5", # With patch
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_python_version(version)
assert result is True, f"Should accept Python version: {version}"
assert len(self.validator.errors) == 0
def test_validate_python_version_invalid(self):
"""Test Python version validation with invalid versions."""
invalid_versions = [
"2.7", # Python 2 not allowed (major must be 3)
"3.7", # Too old (minor < 8)
"3.16", # Too new (minor > 15)
"4.0", # Wrong major
"v3.10", # v prefix not allowed
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_python_version(version)
assert result is False, f"Should reject Python version: {version}"
assert len(self.validator.errors) > 0
def test_validate_python_version_empty(self):
"""Test Python version allows empty (optional)."""
self.validator.errors = []
result = self.validator.validate_python_version("")
assert result is True
assert len(self.validator.errors) == 0
def test_validate_php_version_valid(self):
"""Test PHP version validation with valid versions."""
valid_versions = [
"7.4",
"8.0",
"8.1",
"8.2",
"8.3",
"9.0",
"7.4.33", # With patch
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_php_version(version)
assert result is True, f"Should accept PHP version: {version}"
assert len(self.validator.errors) == 0
def test_validate_php_version_invalid(self):
"""Test PHP version validation with invalid versions."""
invalid_versions = [
"", # Empty NOT allowed for PHP
"6.0", # Too old (major < 7)
"10.0", # Too new (major > 9)
"v8.2", # v prefix NOT allowed for PHP
"8", # Must have minor version
"8.100", # Minor too high
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_php_version(version)
assert result is False, f"Should reject PHP version: {version}"
assert len(self.validator.errors) > 0
def test_validate_go_version_valid(self):
"""Test Go version validation with valid versions."""
valid_versions = [
"1.18",
"1.19",
"1.20",
"1.21",
"1.22",
"1.23",
"1.30",
"1.20.5", # With patch
]
for version in valid_versions:
self.validator.errors = []
result = self.validator.validate_go_version(version)
assert result is True, f"Should accept Go version: {version}"
assert len(self.validator.errors) == 0
def test_validate_go_version_invalid(self):
"""Test Go version validation with invalid versions."""
invalid_versions = [
"2.0", # Wrong major (must be 1)
"1.17", # Too old (minor < 18)
"1.31", # Too new (minor > 30)
"v1.21", # v prefix not allowed
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_go_version(version)
assert result is False, f"Should reject Go version: {version}"
assert len(self.validator.errors) > 0
def test_validate_go_version_empty(self):
"""Test Go version allows empty (optional)."""
self.validator.errors = []
result = self.validator.validate_go_version("")
assert result is True
assert len(self.validator.errors) == 0
def test_validate_dotnet_version_invalid(self):
"""Test .NET version validation with invalid versions."""
invalid_versions = [
"v6.0", # v prefix not allowed
"2.0", # Major < 3
"21.0", # Major > 20
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_dotnet_version(version)
assert result is False, f"Should reject .NET version: {version}"
assert len(self.validator.errors) > 0
def test_validate_dotnet_version_empty(self):
"""Test .NET version allows empty (optional)."""
self.validator.errors = []
result = self.validator.validate_dotnet_version("")
assert result is True
def test_validate_terraform_version_invalid(self):
"""Test Terraform version validation with invalid versions."""
invalid_versions = [
"1.0", # Must be X.Y.Z
"1", # Must be X.Y.Z
"1.0.0.0", # Too many parts
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_terraform_version(version)
assert result is False, f"Should reject Terraform version: {version}"
def test_validate_terraform_version_empty(self):
"""Test Terraform version allows empty (optional)."""
result = self.validator.validate_terraform_version("")
assert result is True
def test_validate_node_version_keywords(self):
"""Test Node.js version validation with keywords."""
keywords = [
"latest",
"lts",
"current",
"node",
"lts/hydrogen",
"lts/gallium",
"LTS", # Case insensitive
"LATEST",
]
for keyword in keywords:
self.validator.errors = []
result = self.validator.validate_node_version(keyword)
assert result is True, f"Should accept Node keyword: {keyword}"
def test_validate_node_version_invalid(self):
"""Test Node.js version validation with invalid versions."""
invalid_versions = [
"18.0.0.0", # Too many parts
"abc", # Non-numeric
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_node_version(version)
assert result is False, f"Should reject Node version: {version}"
def test_validate_node_version_empty(self):
"""Test Node.js version allows empty (optional)."""
result = self.validator.validate_node_version("")
assert result is True
def test_calver_leap_year_validation(self):
"""Test CalVer validation with leap year dates."""
# 2024 is a leap year
self.validator.errors = []
assert self.validator.validate_calver("2024.2.29") is True
# 2023 is not a leap year
self.validator.errors = []
assert self.validator.validate_calver("2023.2.29") is False
assert len(self.validator.errors) > 0
# 2000 was a leap year (divisible by 400)
self.validator.errors = []
assert self.validator.validate_calver("2000.2.29") is True
# 1900 was not a leap year (divisible by 100 but not 400)
self.validator.errors = []
assert self.validator.validate_calver("1900.2.29") is False
def test_calver_month_boundaries(self):
"""Test CalVer validation with month boundaries."""
# 30-day months
thirty_day_months = [4, 6, 9, 11]
for month in thirty_day_months:
self.validator.errors = []
assert self.validator.validate_calver(f"2024.{month}.30") is True
self.validator.errors = []
assert self.validator.validate_calver(f"2024.{month}.31") is False
# 31-day months
thirty_one_day_months = [1, 3, 5, 7, 8, 10, 12]
for month in thirty_one_day_months:
self.validator.errors = []
assert self.validator.validate_calver(f"2024.{month}.31") is True
def test_validate_flexible_version_with_latest(self):
"""Test flexible version accepts 'latest' keyword."""
self.validator.errors = []
result = self.validator.validate_flexible_version("latest")
assert result is True
assert len(self.validator.errors) == 0
def test_validate_flexible_version_calver_detection(self):
"""Test flexible version correctly detects CalVer vs SemVer."""
# Should detect as CalVer
calver_versions = [
"2024.3.1",
"2024-03-15",
"24.3.1",
]
for version in calver_versions:
self.validator.errors = []
result = self.validator.validate_flexible_version(version)
assert result is True, f"Should accept CalVer: {version}"
# Invalid CalVer should fail (not try SemVer)
self.validator.errors = []
result = self.validator.validate_flexible_version("2024.13.1")
assert result is False
assert "CalVer" in " ".join(self.validator.errors)
def test_validate_inputs_with_different_types(self):
"""Test validate_inputs with different version input types."""
inputs = {
"python-version": "3.10",
"php-version": "8.2",
"go-version": "1.21",
"node-version": "18",
"dotnet-version": "6.0",
"terraform-version": "1.5.7",
"version": "1.2.3",
}
result = self.validator.validate_inputs(inputs)
assert result is True
assert len(self.validator.errors) == 0
def test_validate_inputs_with_invalid_versions(self):
"""Test validate_inputs with invalid versions."""
inputs = {
"python-version": "2.7", # Too old
"php-version": "v8.2", # v prefix not allowed
}
result = self.validator.validate_inputs(inputs)
assert result is False
assert len(self.validator.errors) >= 2
def test_semver_simple_formats(self):
"""Test semantic version with simple formats (X.Y and X)."""
simple_versions = [
"1.0", # X.Y
"2.5", # X.Y
"1", # X
"10", # X
]
for version in simple_versions:
self.validator.errors = []
result = self.validator.validate_semver(version)
assert result is True, f"Should accept simple format: {version}"
def test_semver_with_uppercase_v(self):
"""Test semantic version with uppercase V prefix."""
self.validator.errors = []
result = self.validator.validate_semver("V1.2.3")
assert result is True
assert len(self.validator.errors) == 0
def test_dotnet_leading_zeros_rejection(self):
"""Test .NET version rejects leading zeros."""
invalid_versions = [
"06.0", # Leading zero in major
"6.01", # Leading zero in minor
"6.0.001", # Leading zero in patch
]
for version in invalid_versions:
self.validator.errors = []
result = self.validator.validate_dotnet_version(version)
assert result is False, f"Should reject .NET version with leading zeros: {version}"
def test_get_required_inputs(self):
"""Test get_required_inputs returns empty list."""
required = self.validator.get_required_inputs()
assert isinstance(required, list)
assert len(required) == 0
def test_error_handling_accumulation(self):
"""Test that errors accumulate across validations."""
self.validator.errors = []
self.validator.validate_semver("invalid")
first_error_count = len(self.validator.errors)
self.validator.validate_calver("2024.13.1")
second_error_count = len(self.validator.errors)
assert second_error_count > first_error_count
assert second_error_count >= 2