wrolpi/controller/test/test_scripts_api.py
Roland 603a2c010a
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run
Enhancing Controller to run scripts, starting with the repair and upgrade scripts for onboarding use.
2026-02-25 17:08:32 -07:00

165 lines
7.2 KiB
Python

"""
Integration tests for Controller scripts API endpoints.
"""
from unittest import mock
class TestScriptsListEndpoint:
"""Tests for /api/scripts endpoint."""
def test_scripts_list_returns_200(self, test_client):
"""Scripts list endpoint should return 200 OK."""
response = test_client.get("/api/scripts")
assert response.status_code == 200
def test_scripts_list_returns_list(self, test_client):
"""Scripts list should return a list."""
response = test_client.get("/api/scripts")
data = response.json()
assert isinstance(data, list)
def test_scripts_list_includes_repair(self, test_client):
"""Scripts list should include repair script."""
response = test_client.get("/api/scripts")
data = response.json()
names = [s["name"] for s in data]
assert "repair" in names
def test_scripts_list_has_required_fields(self, test_client):
"""Each script should have required fields."""
response = test_client.get("/api/scripts")
data = response.json()
for script in data:
assert "name" in script
assert "display_name" in script
assert "description" in script
assert "warnings" in script
assert "available" in script
def test_scripts_unavailable_in_docker_mode(self, test_client_docker_mode):
"""Scripts should be marked unavailable in Docker mode."""
response = test_client_docker_mode.get("/api/scripts")
data = response.json()
for script in data:
assert script["available"] is False
class TestScriptsStatusEndpoint:
"""Tests for /api/scripts/status endpoint."""
def test_scripts_status_returns_200(self, test_client):
"""Scripts status endpoint should return 200 OK."""
response = test_client.get("/api/scripts/status")
assert response.status_code == 200
def test_scripts_status_has_running_field(self, test_client):
"""Scripts status should have running field."""
response = test_client.get("/api/scripts/status")
data = response.json()
assert "running" in data
def test_scripts_status_not_running_in_docker(self, test_client_docker_mode):
"""Scripts should not be running in Docker mode."""
response = test_client_docker_mode.get("/api/scripts/status")
data = response.json()
assert data["running"] is False
class TestScriptsStartEndpoint:
"""Tests for /api/scripts/{name}/start endpoint."""
def test_scripts_start_returns_501_in_docker(self, test_client_docker_mode):
"""Scripts start should return 501 in Docker mode."""
response = test_client_docker_mode.post("/api/scripts/repair/start")
assert response.status_code == 501
assert "Docker" in response.json()["detail"]
def test_scripts_start_returns_400_for_unknown_script(self, test_client):
"""Scripts start should return 400 for unknown script."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
response = test_client.post("/api/scripts/nonexistent/start")
assert response.status_code == 400
def test_scripts_start_returns_400_if_already_running(self, test_client):
"""Scripts start should return 400 if script already running."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
with mock.patch("controller.lib.scripts.get_script_status", return_value={
"running": True,
"script_name": "repair",
}):
response = test_client.post("/api/scripts/repair/start")
assert response.status_code == 400
assert "already running" in response.json()["detail"].lower()
class TestScriptsOutputEndpoint:
"""Tests for /api/scripts/{name}/output endpoint."""
def test_scripts_output_returns_501_in_docker(self, test_client_docker_mode):
"""Scripts output should return 501 in Docker mode."""
response = test_client_docker_mode.get("/api/scripts/repair/output")
assert response.status_code == 501
assert "Docker" in response.json()["detail"]
def test_scripts_output_returns_200(self, test_client):
"""Scripts output should return 200 when not in Docker mode."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
mock_result = mock.Mock()
mock_result.stdout = "Log output"
mock_result.returncode = 0
with mock.patch("subprocess.run", return_value=mock_result):
response = test_client.get("/api/scripts/repair/output")
assert response.status_code == 200
def test_scripts_output_has_expected_fields(self, test_client):
"""Scripts output should have expected fields."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
mock_result = mock.Mock()
mock_result.stdout = "Log output"
mock_result.returncode = 0
with mock.patch("subprocess.run", return_value=mock_result):
response = test_client.get("/api/scripts/repair/output")
data = response.json()
assert "output" in data
assert "lines" in data
assert "script_name" in data
def test_scripts_output_respects_lines_param(self, test_client):
"""Scripts output should respect lines parameter."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
mock_result = mock.Mock()
mock_result.stdout = "Log output"
mock_result.returncode = 0
with mock.patch("subprocess.run", return_value=mock_result) as mock_run:
response = test_client.get("/api/scripts/repair/output?lines=500")
assert response.status_code == 200
# Verify journalctl was called with -n 500
call_args = mock_run.call_args[0][0]
assert "500" in call_args
def test_scripts_output_limits_lines(self, test_client):
"""Scripts output should limit lines to 5000."""
with mock.patch("controller.lib.scripts.is_docker_mode", return_value=False):
mock_result = mock.Mock()
mock_result.stdout = "Log output"
mock_result.returncode = 0
with mock.patch("subprocess.run", return_value=mock_result) as mock_run:
response = test_client.get("/api/scripts/repair/output?lines=10000")
assert response.status_code == 200
# Verify journalctl was called with -n 5000 (max)
call_args = mock_run.call_args[0][0]
assert "5000" in call_args
class TestOpenAPIIncludesScriptsEndpoints:
"""Tests that OpenAPI documentation includes scripts endpoints."""
def test_openapi_has_scripts_paths(self, test_client):
"""OpenAPI schema should include scripts paths."""
response = test_client.get("/openapi.json")
data = response.json()
assert "/api/scripts" in data["paths"]
assert "/api/scripts/status" in data["paths"]
assert "/api/scripts/{name}/start" in data["paths"]
assert "/api/scripts/{name}/output" in data["paths"]