feat: replace Aider with secure mini-SWE-agent worker
This commit is contained in:
@@ -1,85 +1,83 @@
|
||||
from dataclasses import replace
|
||||
from __future__ import annotations
|
||||
|
||||
from test_config import settings
|
||||
from unittest.mock import Mock
|
||||
|
||||
from lai_automation.git import ChangeSummary
|
||||
from lai_automation.models import FailureReport
|
||||
from lai_automation.policy import evaluate_changes, evaluate_report_scope
|
||||
import pytest
|
||||
|
||||
from lai_automation.models import ChangeSummary
|
||||
from lai_automation.policy import validate_changes
|
||||
|
||||
|
||||
def test_policy_accepts_small_application_change() -> None:
|
||||
result = evaluate_changes(
|
||||
ChangeSummary(("app/Services/Example.php", "tests/Feature/ExampleTest.php"), 40, 5),
|
||||
settings(),
|
||||
)
|
||||
assert result.allowed
|
||||
def workspace_with_no_symlinks() -> Mock:
|
||||
workspace = Mock()
|
||||
workspace.has_symlink.return_value = False
|
||||
workspace.has_path.return_value = False
|
||||
return workspace
|
||||
|
||||
|
||||
def test_policy_rejects_secrets_and_vendor_paths() -> None:
|
||||
result = evaluate_changes(
|
||||
ChangeSummary((".env", "vendor/foo.php", ".aider.tags.cache.v4/cache.db"), 3, 0),
|
||||
settings(),
|
||||
)
|
||||
assert not result.allowed
|
||||
assert len(result.errors) == 3
|
||||
|
||||
|
||||
def test_policy_rejects_test_only_repair() -> None:
|
||||
result = evaluate_changes(
|
||||
ChangeSummary(("tests/Unit/ExampleTest.php",), 20, 2),
|
||||
settings(),
|
||||
def test_accepts_small_application_change(settings) -> None:
|
||||
summary = ChangeSummary(
|
||||
("app/Services/AIService.php", "tests/Feature/AITest.php"),
|
||||
25,
|
||||
4,
|
||||
)
|
||||
|
||||
assert not result.allowed
|
||||
assert "production behavior was not fixed" in result.errors[0]
|
||||
validate_changes(workspace_with_no_symlinks(), summary, settings)
|
||||
|
||||
|
||||
def test_direct_main_blocks_validator_and_migrations() -> None:
|
||||
config = replace(
|
||||
settings(),
|
||||
mode="direct-main",
|
||||
dry_run=False,
|
||||
allow_direct_main=True,
|
||||
gitea_token="token",
|
||||
)
|
||||
result = evaluate_changes(
|
||||
ChangeSummary(("app/Services/SQLValidatorService.php", "database/migrations/new.php"), 10, 2),
|
||||
config,
|
||||
)
|
||||
assert not result.allowed
|
||||
@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)
|
||||
|
||||
|
||||
def test_policy_rejects_symlinks(tmp_path) -> None:
|
||||
(tmp_path / "link.php").symlink_to("/etc/passwd")
|
||||
result = evaluate_changes(ChangeSummary(("link.php",), 1, 0), settings(), tmp_path)
|
||||
assert not result.allowed
|
||||
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_cross_database_report_requires_application_orchestration() -> None:
|
||||
report = FailureReport(
|
||||
id=56,
|
||||
chat_message_id=1,
|
||||
source="user_feedback",
|
||||
category="incorrect_answer",
|
||||
user_feedback="Use cockpit_config.time_registration",
|
||||
feedback_type="wrong_result",
|
||||
expected_behavior="Join cockpit_config manager_id and FK_AuftragPos",
|
||||
prompt="Hours in April",
|
||||
response="0 hours",
|
||||
sql_query="SELECT 0",
|
||||
error_message=None,
|
||||
context={},
|
||||
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,
|
||||
)
|
||||
|
||||
rejected = evaluate_report_scope(
|
||||
report,
|
||||
ChangeSummary(("config/erp_semantics.php", "tests/Unit/FixTest.php"), 20, 2),
|
||||
)
|
||||
accepted = evaluate_report_scope(
|
||||
report,
|
||||
ChangeSummary(("app/Http/Controllers/ChatController.php", "tests/Unit/FixTest.php"), 20, 2),
|
||||
)
|
||||
|
||||
assert not rejected.allowed
|
||||
assert "staged orchestration" in rejected.errors[0]
|
||||
assert accepted.allowed
|
||||
with pytest.raises(ValueError, match="line limit"):
|
||||
validate_changes(workspace_with_no_symlinks(), summary, settings)
|
||||
|
||||
Reference in New Issue
Block a user