# pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=missing-function-docstring import pytest from selfprivacy_api.jobs import JobStatus, Jobs from tests.test_graphql.common import assert_empty, assert_ok, get_data class ProcessMock: """Mock subprocess.Popen""" def __init__(self, args, **kwargs): self.args = args self.kwargs = kwargs def communicate(): # pylint: disable=no-method-argument return (b"", None) returncode = 0 @pytest.fixture def mock_subprocess_popen(mocker): mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) return mock @pytest.fixture def mock_os_chdir(mocker): mock = mocker.patch("os.chdir", autospec=True) return mock @pytest.fixture def mock_subprocess_check_output(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, return_value=b"Testing Linux" ) return mock @pytest.fixture def mock_sleep_intervals(mocker): mock_start = mocker.patch("selfprivacy_api.jobs.upgrade_system.START_INTERVAL", 0) mock_run = mocker.patch("selfprivacy_api.jobs.upgrade_system.RUN_INTERVAL", 0) return (mock_start, mock_run) API_REBUILD_SYSTEM_MUTATION = """ mutation rebuildSystem { system { runSystemRebuild { success message code job { uid } } } } """ API_UPGRADE_SYSTEM_MUTATION = """ mutation upgradeSystem { system { runSystemUpgrade { success message code job { uid } } } } """ @pytest.mark.parametrize("action", ["rebuild", "upgrade"]) def test_graphql_system_rebuild_unauthorized(client, fp, action): """Test system rebuild without authorization""" query = ( API_REBUILD_SYSTEM_MUTATION if action == "rebuild" else API_UPGRADE_SYSTEM_MUTATION ) response = client.post( "/graphql", json={ "query": query, }, ) assert_empty(response) assert fp.call_count([fp.any()]) == 0 def prepare_nixos_rebuild_calls(fp, unit_name): # Start the unit fp.register(["systemctl", "start", unit_name]) # Wait for it to start fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive") fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive") fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") # Check its exectution fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") fp.register( ["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Starting rebuild...", ) fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") fp.register( ["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Rebuilding..." ) fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive") @pytest.mark.parametrize("action", ["rebuild", "upgrade"]) def test_graphql_system_rebuild(authorized_client, fp, action, mock_sleep_intervals): """Test system rebuild""" unit_name = f"sp-nixos-{action}.service" query = ( API_REBUILD_SYSTEM_MUTATION if action == "rebuild" else API_UPGRADE_SYSTEM_MUTATION ) prepare_nixos_rebuild_calls(fp, unit_name) response = authorized_client.post( "/graphql", json={ "query": query, }, ) data = get_data(response)["system"][f"runSystem{action.capitalize()}"] assert_ok(data) assert fp.call_count(["systemctl", "start", unit_name]) == 1 assert fp.call_count(["systemctl", "show", unit_name]) == 6 job_id = response.json()["data"]["system"][f"runSystem{action.capitalize()}"][ "job" ]["uid"] assert Jobs.get_job(job_id).status == JobStatus.FINISHED assert Jobs.get_job(job_id).type_id == f"system.nixos.{action}" @pytest.mark.parametrize("action", ["rebuild", "upgrade"]) def test_graphql_system_rebuild_failed( authorized_client, fp, action, mock_sleep_intervals ): """Test system rebuild""" unit_name = f"sp-nixos-{action}.service" query = ( API_REBUILD_SYSTEM_MUTATION if action == "rebuild" else API_UPGRADE_SYSTEM_MUTATION ) # Start the unit fp.register(["systemctl", "start", unit_name]) # Wait for it to start fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive") fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive") fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") # Check its exectution fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") fp.register( ["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Starting rebuild...", ) fp.register(["systemctl", "show", unit_name], stdout="ActiveState=active") fp.register( ["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Rebuilding..." ) fp.register(["systemctl", "show", unit_name], stdout="ActiveState=failed") fp.register( ["journalctl", "-u", unit_name, "-n", "10", "-o", "cat"], stdout="Some error" ) response = authorized_client.post( "/graphql", json={ "query": query, }, ) data = get_data(response)["system"][f"runSystem{action.capitalize()}"] assert_ok(data) assert fp.call_count(["systemctl", "start", unit_name]) == 1 assert fp.call_count(["systemctl", "show", unit_name]) == 6 job_id = response.json()["data"]["system"][f"runSystem{action.capitalize()}"][ "job" ]["uid"] assert Jobs.get_job(job_id).status == JobStatus.ERROR assert Jobs.get_job(job_id).type_id == f"system.nixos.{action}" API_ROLLBACK_SYSTEM_MUTATION = """ mutation rollbackSystem { system { runSystemRollback { success message code } } } """ def test_graphql_system_rollback_unauthorized(client, mock_subprocess_popen): """Test system rollback without authorization""" response = client.post( "/graphql", json={ "query": API_ROLLBACK_SYSTEM_MUTATION, }, ) assert response.status_code == 200 assert response.json().get("data") is None assert mock_subprocess_popen.call_count == 0 def test_graphql_system_rollback(authorized_client, mock_subprocess_popen): """Test system rollback""" response = authorized_client.post( "/graphql", json={ "query": API_ROLLBACK_SYSTEM_MUTATION, }, ) assert response.status_code == 200 assert response.json().get("data") is not None assert response.json()["data"]["system"]["runSystemRollback"]["success"] is True assert response.json()["data"]["system"]["runSystemRollback"]["message"] is not None assert response.json()["data"]["system"]["runSystemRollback"]["code"] == 200 assert mock_subprocess_popen.call_count == 1 assert mock_subprocess_popen.call_args[0][0] == [ "systemctl", "start", "sp-nixos-rollback.service", ] API_REBOOT_SYSTEM_MUTATION = """ mutation system { system { rebootSystem { success message code } } } """ def test_graphql_reboot_system_unauthorized(client, mock_subprocess_popen): response = client.post( "/graphql", json={ "query": API_REBOOT_SYSTEM_MUTATION, }, ) assert response.status_code == 200 assert response.json().get("data") is None assert mock_subprocess_popen.call_count == 0 def test_graphql_reboot_system(authorized_client, mock_subprocess_popen): response = authorized_client.post( "/graphql", json={ "query": API_REBOOT_SYSTEM_MUTATION, }, ) assert response.status_code == 200 assert response.json().get("data") is not None assert response.json()["data"]["system"]["rebootSystem"]["success"] is True assert response.json()["data"]["system"]["rebootSystem"]["message"] is not None assert response.json()["data"]["system"]["rebootSystem"]["code"] == 200 assert mock_subprocess_popen.call_count == 1 assert mock_subprocess_popen.call_args[0][0] == ["reboot"]