mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 11:34:00 +00:00
* 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
246 lines
8.4 KiB
Python
246 lines
8.4 KiB
Python
"""Tests for the main validator entry point."""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest # pylint: disable=import-error
|
|
|
|
# Add the parent directory to the path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
|
|
class TestValidatorScript:
|
|
"""Test the main validator.py script functionality."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment before each test."""
|
|
# Clear environment variables
|
|
for key in list(os.environ.keys()):
|
|
if key.startswith("INPUT_"):
|
|
del os.environ[key]
|
|
|
|
# Create temporary output file
|
|
# Need persistent file for teardown, can't use context manager
|
|
self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) # noqa: SIM115
|
|
os.environ["GITHUB_OUTPUT"] = self.temp_output.name
|
|
self.temp_output.close()
|
|
|
|
def teardown_method(self):
|
|
"""Clean up after each test."""
|
|
# Clean up temp file
|
|
if hasattr(self, "temp_output") and Path(self.temp_output.name).exists():
|
|
os.unlink(self.temp_output.name)
|
|
|
|
# Clear environment
|
|
for key in list(os.environ.keys()):
|
|
if key.startswith("INPUT_"):
|
|
del os.environ[key]
|
|
|
|
def test_main_no_action_type(self):
|
|
"""Test that validator fails when no action type is provided."""
|
|
# Remove action type
|
|
if "INPUT_ACTION_TYPE" in os.environ:
|
|
del os.environ["INPUT_ACTION_TYPE"]
|
|
|
|
from validator import main
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_main_with_valid_inputs(self):
|
|
"""Test validator with valid inputs."""
|
|
os.environ["INPUT_ACTION_TYPE"] = "docker-build"
|
|
os.environ["INPUT_CONTEXT"] = "." # Required by docker-build custom validator
|
|
os.environ["INPUT_IMAGE"] = "myapp"
|
|
os.environ["INPUT_TAG"] = "v1.0.0"
|
|
|
|
from validator import main
|
|
|
|
# Should not raise SystemExit
|
|
main()
|
|
|
|
# Check output file
|
|
with Path(self.temp_output.name).open() as f:
|
|
output = f.read()
|
|
assert "status=success" in output
|
|
assert "action=docker_build" in output
|
|
|
|
def test_main_with_invalid_inputs(self):
|
|
"""Test validator with invalid inputs."""
|
|
os.environ["INPUT_ACTION_TYPE"] = "docker-build"
|
|
os.environ["INPUT_IMAGE"] = "INVALID-IMAGE" # Uppercase not allowed
|
|
|
|
from validator import main
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
# Check output file
|
|
with Path(self.temp_output.name).open() as f:
|
|
output = f.read()
|
|
assert "status=failure" in output
|
|
|
|
def test_main_collects_all_inputs(self):
|
|
"""Test that validator collects all INPUT_ environment variables."""
|
|
os.environ["INPUT_ACTION_TYPE"] = "test-action"
|
|
os.environ["INPUT_FIRST_INPUT"] = "value1"
|
|
os.environ["INPUT_SECOND_INPUT"] = "value2"
|
|
os.environ["INPUT_THIRD_INPUT"] = "value3"
|
|
|
|
# Mock the validator to capture inputs
|
|
mock_validator = MagicMock()
|
|
mock_validator.validate_inputs.return_value = True
|
|
mock_validator.errors = []
|
|
|
|
# Patch get_validator at module level
|
|
with patch("validator.get_validator") as mock_get_validator:
|
|
mock_get_validator.return_value = mock_validator
|
|
|
|
from validator import main
|
|
|
|
main()
|
|
|
|
# Check that validate_inputs was called with correct inputs
|
|
mock_validator.validate_inputs.assert_called_once()
|
|
inputs = mock_validator.validate_inputs.call_args[0][0]
|
|
# Should have both underscore and dash versions
|
|
assert inputs == {
|
|
"first_input": "value1",
|
|
"first-input": "value1",
|
|
"second_input": "value2",
|
|
"second-input": "value2",
|
|
"third_input": "value3",
|
|
"third-input": "value3",
|
|
}
|
|
|
|
def test_main_output_format(self):
|
|
"""Test that output is formatted correctly for GitHub Actions."""
|
|
os.environ["INPUT_ACTION_TYPE"] = "test-action"
|
|
|
|
from validators.base import BaseValidator
|
|
from validators.registry import ValidatorRegistry
|
|
|
|
class TestValidator(BaseValidator):
|
|
def validate_inputs(self, inputs): # noqa: ARG002
|
|
return True
|
|
|
|
def get_required_inputs(self):
|
|
return []
|
|
|
|
def get_validation_rules(self):
|
|
return {}
|
|
|
|
registry = ValidatorRegistry()
|
|
registry.register_validator("test-action", TestValidator)
|
|
|
|
from validator import main
|
|
|
|
main()
|
|
|
|
# Check GitHub output format
|
|
with Path(self.temp_output.name).open() as f:
|
|
output = f.read()
|
|
|
|
assert "status=success" in output
|
|
assert "action=test_action" in output
|
|
assert "inputs_validated=" in output
|
|
|
|
def test_main_error_reporting(self):
|
|
"""Test that validation errors are properly reported."""
|
|
os.environ["INPUT_ACTION_TYPE"] = "test-action"
|
|
os.environ["INPUT_TEST"] = "invalid"
|
|
|
|
# Create a mock validator that returns errors
|
|
mock_validator = MagicMock()
|
|
mock_validator.validate_inputs.return_value = False
|
|
mock_validator.errors = ["Test error 1", "Test error 2"]
|
|
|
|
# Patch get_validator at module level
|
|
with patch("validator.get_validator") as mock_get_validator:
|
|
mock_get_validator.return_value = mock_validator
|
|
|
|
from validator import main
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
# Check output file contains error count
|
|
with Path(self.temp_output.name).open() as f:
|
|
output = f.read()
|
|
assert "status=failure" in output
|
|
assert "errors=2" in output
|
|
|
|
|
|
class TestValidatorIntegration:
|
|
"""Integration tests for the validator system."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
# Need persistent file for teardown, can't use context manager
|
|
self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) # noqa: SIM115
|
|
os.environ["GITHUB_OUTPUT"] = self.temp_output.name
|
|
self.temp_output.close()
|
|
|
|
def teardown_method(self):
|
|
"""Clean up after tests."""
|
|
if hasattr(self, "temp_output") and Path(self.temp_output.name).exists():
|
|
os.unlink(self.temp_output.name)
|
|
|
|
# Clear environment
|
|
for key in list(os.environ.keys()):
|
|
if key.startswith("INPUT_"):
|
|
del os.environ[key]
|
|
|
|
def test_registry_loads_correct_validator(self):
|
|
"""Test that registry loads the correct validator for each action."""
|
|
from validators.registry import ValidatorRegistry
|
|
|
|
registry = ValidatorRegistry()
|
|
|
|
# Test that we get validators for known actions
|
|
docker_validator = registry.get_validator("docker-build")
|
|
assert docker_validator is not None
|
|
assert hasattr(docker_validator, "validate_inputs")
|
|
|
|
# Test fallback for unknown action
|
|
unknown_validator = registry.get_validator("unknown-action")
|
|
assert unknown_validator is not None
|
|
assert hasattr(unknown_validator, "validate_inputs")
|
|
|
|
def test_custom_validator_loading(self):
|
|
"""Test that custom validators are loaded when available."""
|
|
from validators.registry import ValidatorRegistry
|
|
|
|
registry = ValidatorRegistry()
|
|
|
|
# sync-labels has a custom validator
|
|
validator = registry.get_validator("sync-labels")
|
|
assert validator is not None
|
|
assert validator.__class__.__name__ == "CustomValidator"
|
|
|
|
def test_convention_based_validation(self):
|
|
"""Test that convention-based validation works."""
|
|
from validators.registry import ValidatorRegistry
|
|
|
|
registry = ValidatorRegistry()
|
|
validator = registry.get_validator("test-action")
|
|
|
|
# Test different convention patterns
|
|
test_inputs = {
|
|
"dry-run": "true", # Boolean
|
|
"token": "${{ github.token }}", # Token
|
|
"version": "1.2.3", # Version
|
|
"email": "test@example.com", # Email
|
|
}
|
|
|
|
# Convention validator should handle these
|
|
result = validator.validate_inputs(test_inputs)
|
|
# The result depends on the specific validation logic
|
|
assert isinstance(result, bool)
|