Files
Unraid-AppOps-Gateway-Public/backend/tests/test_privatemuse_webhook_repair.py
T

178 lines
6.2 KiB
Python

from types import SimpleNamespace
from sqlmodel import Session, SQLModel, create_engine
from backend.app.services import action_execution
from backend.app.services.action_execution import approval_execution_supported
from backend.app.services.action_registry import (
ACTION_RISK,
ACTIONS,
action_default_target,
)
from backend.app.services.privatemuse_webhook_repair import (
PrivateMuseWebhookRepair,
_ENDPOINT,
_REPAIR_SCRIPT,
)
class FakeResponse:
def __init__(self, payload):
self.payload = payload
def json(self):
return self.payload
class FakePortainer:
def __init__(self, exit_code=0):
self.exit_code = exit_code
self.calls = []
def get_container_status(self, name):
assert name == "gitea-deploy-control"
return {
"status": "ok",
"container": {
"id": "controller-id",
"name": "gitea-deploy-control",
"state": "running",
},
}
def _config(self):
return SimpleNamespace(endpoint_id="3")
def _request(self, method, path, **kwargs):
self.calls.append((method, path, kwargs))
if path.endswith("/containers/controller-id/exec"):
return FakeResponse({"Id": "exec-webhook"})
if path.endswith("/exec/exec-webhook/start"):
return FakeResponse({})
if path.endswith("/exec/exec-webhook/json"):
return FakeResponse({"Running": False, "ExitCode": self.exit_code})
raise AssertionError(path)
def test_privatemuse_webhook_repair_is_fixed_high_risk_typed_action():
action = "deployment.webhook.repair_privatemuse"
assert action in ACTIONS
assert ACTION_RISK[action] == "high"
assert action_default_target(action) == "jens/privatemuse"
assert approval_execution_supported(action) is True
def test_privatemuse_webhook_repair_executes_only_fixed_contract():
fake = FakePortainer()
result = PrivateMuseWebhookRepair(fake).run()
assert result == {
"status": "repaired",
"repository": "jens/privatemuse",
"endpoint": _ENDPOINT,
"branch": "main",
"event": "push",
}
command = fake.calls[0][2]["json"]["Cmd"]
assert command[:3] == ["python3", "-c", _REPAIR_SCRIPT]
assert command[3:] == ["jens/privatemuse", _ENDPOINT]
def test_privatemuse_webhook_repair_script_never_returns_credentials():
assert "runner_guard_admin_token" in _REPAIR_SCRIPT
assert 'secret_root = Path("/controller-config/webhook-secrets")' in _REPAIR_SCRIPT
assert 'secret_path = secret_root / "jens__privatemuse"' in _REPAIR_SCRIPT
assert "secretMaterialReturned" not in _REPAIR_SCRIPT
assert 'print(f"privatemuse_runnerless_webhook_ready hook_id={hook_id}"' in _REPAIR_SCRIPT
assert "print(secret" not in _REPAIR_SCRIPT
assert "print(token" not in _REPAIR_SCRIPT
def test_privatemuse_webhook_repair_proves_effective_controller_reachability():
assert "gitea_config_path" not in _REPAIR_SCRIPT
assert '"docker", "exec", "Gitea"' in _REPAIR_SCRIPT
assert '"http://198.19.0.1:1299/health"' in _REPAIR_SCRIPT
assert 'health_payload.get("status") != "ok"' in _REPAIR_SCRIPT
assert 'health_payload.get("protocolVersion") != "itworx-controller-request/v2"' in _REPAIR_SCRIPT
def test_privatemuse_webhook_repair_is_fail_closed_and_non_destructive():
assert 'repository != "jens/privatemuse"' in _REPAIR_SCRIPT
assert 'project.get("branch") != "main"' in _REPAIR_SCRIPT
assert '"Managed validation / full (push)"' in _REPAIR_SCRIPT
assert 'url.endswith("/v1/gitea/push/jens/privatemuse")' in _REPAIR_SCRIPT
assert "len(exact) > 1 or semantic_other" in _REPAIR_SCRIPT
assert 'request("PATCH", f"/repos/Jens/PrivateMuse/hooks/{hook_id}", payload)' in _REPAIR_SCRIPT
assert 'request(\n "POST",\n "/repos/Jens/PrivateMuse/hooks",' in _REPAIR_SCRIPT
for forbidden in (
"docker restart",
"docker stop",
"docker rm",
"docker compose",
'request("DELETE"',
):
assert forbidden not in _REPAIR_SCRIPT
def test_privatemuse_webhook_repair_maps_validation_failures():
result = PrivateMuseWebhookRepair(FakePortainer(exit_code=34)).run()
assert result == {
"status": "validation_failed",
"reason": "PrivateMuse has ambiguous or stale runnerless webhook endpoints",
}
def test_privatemuse_webhook_repair_rejects_any_other_action_target():
result = action_execution.execute_governed_action(
None, # type: ignore[arg-type]
action="deployment.webhook.repair_privatemuse",
target="jens/not-privatemuse",
source="chatgpt-mcp",
risk="high",
reason="Regression test for exact-target enforcement.",
original_policy_decision="allowed",
)
assert result["success"] is False
assert result["result"] == "blocked"
assert result["message"] == "PrivateMuse webhook repair accepts only jens/privatemuse."
def test_privatemuse_webhook_repair_dispatches_fixed_executor(monkeypatch, tmp_path):
engine = create_engine(f"sqlite:///{tmp_path / 'webhook-action.db'}")
SQLModel.metadata.create_all(engine)
class FakeRepair:
def run(self):
return {
"status": "repaired",
"repository": "jens/privatemuse",
"endpoint": _ENDPOINT,
"branch": "main",
"event": "push",
}
import backend.app.services.privatemuse_webhook_repair as repair_module
monkeypatch.setattr(repair_module, "PrivateMuseWebhookRepair", FakeRepair)
with Session(engine) as session:
result = action_execution.execute_governed_action(
session,
action="deployment.webhook.repair_privatemuse",
target="jens/privatemuse",
source="chatgpt-mcp",
risk="high",
reason="Repair the fixed PrivateMuse runnerless hook.",
original_policy_decision="approval_required",
)
assert result["success"] is True
assert result["status"] == "success"
assert result["policyDecision"] == "allowed"
assert result["originalPolicyDecision"] == "approval_required"
assert result["jobId"].startswith("job-")
assert result["auditId"].startswith("audit-")