from __future__ import annotations from unittest.mock import Mock import pytest from lai_automation.models import ChangeSummary from lai_automation.policy import validate_changes def workspace_with_no_symlinks() -> Mock: workspace = Mock() workspace.has_symlink.return_value = False workspace.has_path.return_value = False return workspace def test_accepts_small_application_change(settings) -> None: summary = ChangeSummary( ("app/Services/AIService.php", "tests/Feature/AITest.php"), 25, 4, ) validate_changes(workspace_with_no_symlinks(), summary, settings) @pytest.mark.parametrize( "filename", [ ".env", ".env.example", ".gitea/workflows/ci.yml", "docker-compose.yml", "docker/php/Dockerfile", "storage/logs/output.log", "vendor/package/file.php", ], ) def test_rejects_protected_paths(settings, filename: str) -> None: summary = ChangeSummary((filename, "app/Fix.php"), 2, 0) with pytest.raises(ValueError, match="Protected"): validate_changes(workspace_with_no_symlinks(), summary, settings) @pytest.mark.parametrize( "filename", [ "config/erp_semantics.php.backup", "app/Service.php.orig", "test_debug.php", "scratch.php", ], ) def test_rejects_temporary_artifacts(settings, filename: str) -> None: summary = ChangeSummary((filename, "app/Fix.php"), 2, 0) with pytest.raises(ValueError, match="Temporary artifact"): validate_changes(workspace_with_no_symlinks(), summary, settings) def test_rejects_git_metadata_created_in_worktree(settings) -> None: workspace = workspace_with_no_symlinks() workspace.has_path.return_value = True with pytest.raises(ValueError, match="Git metadata"): validate_changes( workspace, ChangeSummary(("app/Fix.php",), 1, 0), settings, ) def test_rejects_tests_without_application_change(settings) -> None: summary = ChangeSummary(("tests/Feature/FixTest.php",), 20, 0) with pytest.raises(ValueError, match="application source"): validate_changes(workspace_with_no_symlinks(), summary, settings) def test_rejects_symlink(settings) -> None: workspace = workspace_with_no_symlinks() workspace.has_symlink.return_value = True summary = ChangeSummary(("app/Fix.php",), 1, 0) with pytest.raises(ValueError, match="Symbolic links"): validate_changes(workspace, summary, settings) def test_rejects_oversized_diff(settings) -> None: summary = ChangeSummary( ("app/Fix.php",), settings.max_changed_lines + 1, 0, ) with pytest.raises(ValueError, match="line limit"): validate_changes(workspace_with_no_symlinks(), summary, settings)