Files
actions/validate-inputs/tests/test_generate_tests.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

330 lines
13 KiB
Python

"""Tests for the test generation system."""
# pylint: disable=protected-access # Testing private methods is intentional
import importlib.util
import sys
import tempfile
from pathlib import Path
import yaml # pylint: disable=import-error
# Import the test generator
scripts_path = Path(__file__).parent.parent / "scripts"
sys.path.insert(0, str(scripts_path))
spec = importlib.util.spec_from_file_location("generate_tests", scripts_path / "generate-tests.py")
if spec is None or spec.loader is None:
sys.exit("Failed to load generate-tests module")
generate_tests = importlib.util.module_from_spec(spec)
spec.loader.exec_module(generate_tests)
# Import as GeneratorClass to avoid pytest collection warning
GeneratorClass = generate_tests.TestGenerator
class TestTestGenerator:
"""Test cases for the test generation system."""
def setup_method(self): # pylint: disable=attribute-defined-outside-init
"""Set up test fixtures."""
self.temp_dir = Path(tempfile.mkdtemp())
self.generator = GeneratorClass(self.temp_dir)
def teardown_method(self):
"""Clean up test fixtures."""
import shutil # pylint: disable=import-outside-toplevel
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
def test_generator_initialization(self):
"""Test that generator initializes correctly."""
assert self.generator.project_root == self.temp_dir
assert self.generator.validate_inputs_dir == self.temp_dir / "validate-inputs"
assert self.generator.tests_dir == self.temp_dir / "_tests"
assert self.generator.generated_count == 0
assert self.generator.skipped_count == 0
def test_skip_existing_shellspec_test(self):
"""Test that existing ShellSpec tests are not overwritten."""
# Create action directory with action.yml
action_dir = self.temp_dir / "test-action"
action_dir.mkdir(parents=True)
action_yml = action_dir / "action.yml"
action_yml.write_text(
yaml.dump(
{
"name": "Test Action",
"description": "Test action for testing",
"inputs": {"test-input": {"required": True}},
},
),
)
# Create existing test file
test_file = self.temp_dir / "_tests" / "unit" / "test-action" / "validation.spec.sh"
test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.write_text("# Existing test")
# Run generator
self.generator.generate_action_tests()
# Verify test was not overwritten
assert test_file.read_text() == "# Existing test"
assert self.generator.skipped_count == 1
assert self.generator.generated_count == 0
def test_generate_new_shellspec_test(self):
"""Test generation of new ShellSpec test."""
# Create action directory with action.yml
action_dir = self.temp_dir / "test-action"
action_dir.mkdir(parents=True)
action_yml = action_dir / "action.yml"
action_yml.write_text(
yaml.dump(
{
"name": "Test Action",
"description": "Test action for testing",
"inputs": {
"token": {"required": True, "description": "GitHub token"},
"version": {"required": False, "default": "1.0.0"},
},
},
),
)
# Run generator
self.generator.generate_action_tests()
# Verify test was created
test_file = self.temp_dir / "_tests" / "unit" / "test-action" / "validation.spec.sh"
assert test_file.exists()
assert test_file.stat().st_mode & 0o111 # Check executable
content = test_file.read_text()
assert "Test Action Input Validation" in content
assert "should fail when required inputs are missing" in content
assert "should fail without token" in content
assert "should pass with all valid inputs" in content
assert self.generator.generated_count == 1
assert self.generator.skipped_count == 0
def test_skip_existing_pytest_test(self):
"""Test that existing pytest tests are not overwritten."""
# Create validators directory
validators_dir = self.temp_dir / "validate-inputs" / "validators"
validators_dir.mkdir(parents=True, exist_ok=True)
# Create validator file
validator_file = validators_dir / "test_validator.py"
validator_file.write_text("class TestValidator: pass")
# Create existing test file
test_file = self.temp_dir / "validate-inputs" / "tests" / "test_test_validator.py"
test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.write_text("# Existing test")
# Run generator
self.generator.generate_validator_tests()
# Verify test was not overwritten
assert test_file.read_text() == "# Existing test"
assert self.generator.skipped_count == 1
def test_generate_new_pytest_test(self):
"""Test generation of new pytest test."""
# Create validators directory
validators_dir = self.temp_dir / "validate-inputs" / "validators"
validators_dir.mkdir(parents=True, exist_ok=True)
# Create validator file
validator_file = validators_dir / "example_validator.py"
validator_file.write_text("class ExampleValidator: pass")
# Ensure tests directory exists
tests_dir = self.temp_dir / "validate-inputs" / "tests"
tests_dir.mkdir(parents=True, exist_ok=True)
# Run generator
self.generator.generate_validator_tests()
# Verify test was created
test_file = tests_dir / "test_example_validator.py"
assert test_file.exists()
content = test_file.read_text()
assert "Tests for example_validator validator" in content
assert "from validators.example_validator import ExampleValidator" in content
assert "class TestExampleValidator:" in content
assert "def test_validate_inputs(self):" in content
def test_generate_custom_validator_test(self):
"""Test generation of custom validator test."""
# Create action with custom validator
action_dir = self.temp_dir / "docker-build"
action_dir.mkdir(parents=True)
custom_validator = action_dir / "CustomValidator.py"
custom_validator.write_text("class CustomValidator: pass")
# Ensure tests directory exists
tests_dir = self.temp_dir / "validate-inputs" / "tests"
tests_dir.mkdir(parents=True, exist_ok=True)
# Run generator
self.generator.generate_custom_validator_tests()
# Verify test was created
test_file = tests_dir / "test_docker-build_custom.py"
assert test_file.exists()
content = test_file.read_text()
assert "Tests for docker-build custom validator" in content
assert "from CustomValidator import CustomValidator" in content
assert "test_docker_specific_validation" in content # Docker-specific test
def test_get_example_value_patterns(self):
"""Test example value generation for different input patterns."""
# Token patterns
assert (
self.generator._get_example_value("github-token", {}) == "${{ secrets.GITHUB_TOKEN }}"
)
assert self.generator._get_example_value("npm-token", {}) == "${{ secrets.GITHUB_TOKEN }}"
# Version patterns
assert self.generator._get_example_value("version", {}) == "1.2.3"
assert self.generator._get_example_value("node-version", {}) == "1.2.3"
# Path patterns
assert self.generator._get_example_value("file-path", {}) == "./path/to/file"
assert self.generator._get_example_value("directory", {}) == "./path/to/file"
# URL patterns
assert self.generator._get_example_value("webhook-url", {}) == "https://example.com"
assert self.generator._get_example_value("endpoint", {}) == "https://example.com"
# Boolean patterns
assert self.generator._get_example_value("dry-run", {}) == "false"
assert self.generator._get_example_value("debug", {}) == "false"
assert self.generator._get_example_value("push", {}) == "true"
# Default from config
assert self.generator._get_example_value("anything", {"default": "custom"}) == "custom"
# Fallback
assert self.generator._get_example_value("unknown-input", {}) == "test-value"
def test_generate_input_test_cases(self):
"""Test generation of input-specific test cases."""
# Boolean input
cases = self.generator._generate_input_test_cases("dry-run", {})
assert len(cases) == 1
assert "should accept boolean values" in cases[0]
assert "should reject invalid boolean" in cases[0]
# Version input
cases = self.generator._generate_input_test_cases("version", {})
assert len(cases) == 1
assert "should accept valid version" in cases[0]
assert "should accept version with v prefix" in cases[0]
# Token input
cases = self.generator._generate_input_test_cases("github-token", {})
assert len(cases) == 1
assert "should accept GitHub token" in cases[0]
assert "should accept classic PAT" in cases[0]
# Path input
cases = self.generator._generate_input_test_cases("config-file", {})
assert len(cases) == 1
assert "should accept valid path" in cases[0]
assert "should reject path traversal" in cases[0]
# No specific pattern
cases = self.generator._generate_input_test_cases("custom-input", {})
assert len(cases) == 0
def test_generate_pytest_content_by_type(self):
"""Test that different validator types get appropriate test methods."""
# Version validator
content = self.generator._generate_pytest_content("version_validator")
assert "test_valid_semantic_version" in content
assert "test_valid_calver" in content
# Token validator
content = self.generator._generate_pytest_content("token_validator")
assert "test_valid_github_token" in content
assert "test_other_token_types" in content
# Boolean validator
content = self.generator._generate_pytest_content("boolean_validator")
assert "test_valid_boolean_values" in content
assert "test_invalid_boolean_values" in content
# Docker validator
content = self.generator._generate_pytest_content("docker_validator")
assert "test_valid_image_names" in content
assert "test_valid_platforms" in content
# Generic validator
content = self.generator._generate_pytest_content("unknown_validator")
assert "test_validate_inputs" in content
assert "TODO: Add specific test cases" in content
def test_skip_special_directories(self):
"""Test that special directories are skipped."""
# Create special directories that should be skipped
dot_dir = self.temp_dir / ".hidden"
dot_dir.mkdir()
(dot_dir / "action.yml").write_text("name: Hidden")
underscore_dir = self.temp_dir / "_internal"
underscore_dir.mkdir()
(underscore_dir / "action.yml").write_text("name: Internal")
validate_dir = self.temp_dir / "validate-inputs"
validate_dir.mkdir()
(validate_dir / "action.yml").write_text("name: Validate")
# Run generator
self.generator.generate_action_tests()
# Verify no tests were created for special directories
assert not (self.temp_dir / "_tests" / "unit" / ".hidden").exists()
assert not (self.temp_dir / "_tests" / "unit" / "_internal").exists()
assert not (self.temp_dir / "_tests" / "unit" / "validate-inputs").exists()
assert self.generator.generated_count == 0
def test_full_generation_workflow(self):
"""Test the complete generation workflow."""
# Setup test environment
self._setup_test_environment()
# Run full generation
self.generator.generate_all_tests()
# Verify counts
assert self.generator.generated_count > 0
assert self.generator.skipped_count >= 0
# Verify some files were created
shellspec_test = self.temp_dir / "_tests" / "unit" / "test-action" / "validation.spec.sh"
assert shellspec_test.exists()
def _setup_test_environment(self):
"""Set up a minimal test environment."""
# Create an action
action_dir = self.temp_dir / "test-action"
action_dir.mkdir(parents=True)
(action_dir / "action.yml").write_text(
yaml.dump({"name": "Test", "inputs": {"test": {"required": True}}}),
)
# Create validate-inputs structure
(self.temp_dir / "validate-inputs" / "validators").mkdir(parents=True, exist_ok=True)
(self.temp_dir / "validate-inputs" / "tests").mkdir(parents=True, exist_ok=True)