From cda8d70bd946541e9b07868d052995052a459e7f Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 14 Aug 2023 14:49:58 +0000 Subject: [PATCH 001/130] test(rest-dismantling): remove auth tests after confirming gql counterparts exist --- tests/test_rest_endpoints/test_auth.py | 457 ------------------------- 1 file changed, 457 deletions(-) delete mode 100644 tests/test_rest_endpoints/test_auth.py diff --git a/tests/test_rest_endpoints/test_auth.py b/tests/test_rest_endpoints/test_auth.py deleted file mode 100644 index 4d0d2ed..0000000 --- a/tests/test_rest_endpoints/test_auth.py +++ /dev/null @@ -1,457 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -# pylint: disable=missing-function-docstring -import datetime -from datetime import timezone -import pytest - -from tests.conftest import TOKENS_FILE_CONTENTS -from tests.common import ( - RECOVERY_KEY_VALIDATION_DATETIME, - DEVICE_KEY_VALIDATION_DATETIME, - NearFuture, - assert_recovery_recent, -) -from tests.common import five_minutes_into_future_naive_utc as five_minutes_into_future -from tests.common import five_minutes_into_past_naive_utc as five_minutes_into_past - -DATE_FORMATS = [ - "%Y-%m-%dT%H:%M:%S.%fZ", - "%Y-%m-%dT%H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S.%fZ", - "%Y-%m-%d %H:%M:%S.%f", -] - - -def assert_original(client): - new_tokens = rest_get_tokens_info(client) - - for token in TOKENS_FILE_CONTENTS["tokens"]: - assert_token_valid(client, token["token"]) - for new_token in new_tokens: - if new_token["name"] == token["name"]: - assert ( - datetime.datetime.fromisoformat(new_token["date"]) == token["date"] - ) - assert_no_recovery(client) - - -def assert_token_valid(client, token): - client.headers.update({"Authorization": "Bearer " + token}) - assert rest_get_tokens_info(client) is not None - - -def rest_get_tokens_info(client): - response = client.get("/auth/tokens") - assert response.status_code == 200 - return response.json() - - -def rest_try_authorize_new_device(client, token, device_name): - response = client.post( - "/auth/new_device/authorize", - json={ - "token": token, - "device": device_name, - }, - ) - return response - - -def rest_make_recovery_token(client, expires_at=None, timeformat=None, uses=None): - json = {} - - if expires_at is not None: - assert timeformat is not None - expires_at_str = expires_at.strftime(timeformat) - json["expiration"] = expires_at_str - - if uses is not None: - json["uses"] = uses - - if json == {}: - response = client.post("/auth/recovery_token") - else: - response = client.post( - "/auth/recovery_token", - json=json, - ) - - if not response.status_code == 200: - raise ValueError(response.reason, response.text, response.json()["detail"]) - assert response.status_code == 200 - assert "token" in response.json() - return response.json()["token"] - - -def rest_get_recovery_status(client): - response = client.get("/auth/recovery_token") - assert response.status_code == 200 - return response.json() - - -def rest_get_recovery_date(client): - status = rest_get_recovery_status(client) - assert "date" in status - return status["date"] - - -def assert_no_recovery(client): - assert not rest_get_recovery_status(client)["exists"] - - -def rest_recover_with_mnemonic(client, mnemonic_token, device_name): - recovery_response = client.post( - "/auth/recovery_token/use", - json={"token": mnemonic_token, "device": device_name}, - ) - assert recovery_response.status_code == 200 - new_token = recovery_response.json()["token"] - assert_token_valid(client, new_token) - return new_token - - -# Tokens - - -def test_get_tokens_info(authorized_client, tokens_file): - assert sorted(rest_get_tokens_info(authorized_client), key=lambda x: x["name"]) == [ - {"name": "test_token", "date": "2022-01-14T08:31:10.789314", "is_caller": True}, - { - "name": "test_token2", - "date": "2022-01-14T08:31:10.789314", - "is_caller": False, - }, - ] - - -def test_get_tokens_unauthorized(client, tokens_file): - response = client.get("/auth/tokens") - assert response.status_code == 401 - - -def test_delete_token_unauthorized(client, authorized_client, tokens_file): - response = client.delete("/auth/tokens") - assert response.status_code == 401 - assert_original(authorized_client) - - -def test_delete_token(authorized_client, tokens_file): - response = authorized_client.delete( - "/auth/tokens", json={"token_name": "test_token2"} - ) - assert response.status_code == 200 - assert rest_get_tokens_info(authorized_client) == [ - {"name": "test_token", "date": "2022-01-14T08:31:10.789314", "is_caller": True} - ] - - -def test_delete_self_token(authorized_client, tokens_file): - response = authorized_client.delete( - "/auth/tokens", json={"token_name": "test_token"} - ) - assert response.status_code == 400 - assert_original(authorized_client) - - -def test_delete_nonexistent_token(authorized_client, tokens_file): - response = authorized_client.delete( - "/auth/tokens", json={"token_name": "test_token3"} - ) - assert response.status_code == 404 - assert_original(authorized_client) - - -def test_refresh_token_unauthorized(client, authorized_client, tokens_file): - response = client.post("/auth/tokens") - assert response.status_code == 401 - assert_original(authorized_client) - - -def test_refresh_token(authorized_client, tokens_file): - response = authorized_client.post("/auth/tokens") - assert response.status_code == 200 - new_token = response.json()["token"] - assert_token_valid(authorized_client, new_token) - - -# New device - - -def test_get_new_device_auth_token_unauthorized(client, authorized_client, tokens_file): - response = client.post("/auth/new_device") - assert response.status_code == 401 - assert "token" not in response.json() - assert "detail" in response.json() - # We only can check existence of a token we know. - - -def test_get_and_delete_new_device_token(client, authorized_client, tokens_file): - token = rest_get_new_device_token(authorized_client) - response = authorized_client.delete("/auth/new_device", json={"token": token}) - assert response.status_code == 200 - assert rest_try_authorize_new_device(client, token, "new_device").status_code == 404 - - -def test_delete_token_unauthenticated(client, authorized_client, tokens_file): - token = rest_get_new_device_token(authorized_client) - response = client.delete("/auth/new_device", json={"token": token}) - assert response.status_code == 401 - assert rest_try_authorize_new_device(client, token, "new_device").status_code == 200 - - -def rest_get_new_device_token(client): - response = client.post("/auth/new_device") - assert response.status_code == 200 - assert "token" in response.json() - return response.json()["token"] - - -def test_get_and_authorize_new_device(client, authorized_client, tokens_file): - token = rest_get_new_device_token(authorized_client) - response = rest_try_authorize_new_device(client, token, "new_device") - assert response.status_code == 200 - assert_token_valid(authorized_client, response.json()["token"]) - - -def test_authorize_new_device_with_invalid_token( - client, authorized_client, tokens_file -): - response = rest_try_authorize_new_device(client, "invalid_token", "new_device") - assert response.status_code == 404 - assert_original(authorized_client) - - -def test_get_and_authorize_used_token(client, authorized_client, tokens_file): - token_to_be_used_2_times = rest_get_new_device_token(authorized_client) - response = rest_try_authorize_new_device( - client, token_to_be_used_2_times, "new_device" - ) - assert response.status_code == 200 - assert_token_valid(authorized_client, response.json()["token"]) - - response = rest_try_authorize_new_device( - client, token_to_be_used_2_times, "new_device" - ) - assert response.status_code == 404 - - -def test_get_and_authorize_token_after_12_minutes( - client, authorized_client, tokens_file, mocker -): - token = rest_get_new_device_token(authorized_client) - - # TARDIS sounds - mock = mocker.patch(DEVICE_KEY_VALIDATION_DATETIME, NearFuture) - - response = rest_try_authorize_new_device(client, token, "new_device") - assert response.status_code == 404 - assert_original(authorized_client) - - -def test_authorize_without_token(client, authorized_client, tokens_file): - response = client.post( - "/auth/new_device/authorize", - json={"device": "new_device"}, - ) - assert response.status_code == 422 - assert_original(authorized_client) - - -# Recovery tokens -# GET /auth/recovery_token returns token status -# - if token is valid, returns 200 and token status -# - token status: -# - exists (boolean) -# - valid (boolean) -# - date (string) -# - expiration (string) -# - uses_left (int) -# - if token is invalid, returns 400 and empty body -# POST /auth/recovery_token generates a new token -# has two optional parameters: -# - expiration (string in datetime format) -# - uses_left (int) -# POST /auth/recovery_token/use uses the token -# required arguments: -# - token (string) -# - device (string) -# - if token is valid, returns 200 and token -# - if token is invalid, returns 404 -# - if request is invalid, returns 400 - - -def test_get_recovery_token_status_unauthorized(client, authorized_client, tokens_file): - response = client.get("/auth/recovery_token") - assert response.status_code == 401 - assert_original(authorized_client) - - -def test_get_recovery_token_when_none_exists(authorized_client, tokens_file): - response = authorized_client.get("/auth/recovery_token") - assert response.status_code == 200 - assert response.json() == { - "exists": False, - "valid": False, - "date": None, - "expiration": None, - "uses_left": None, - } - assert_original(authorized_client) - - -def test_generate_recovery_token(authorized_client, client, tokens_file): - # Generate token without expiration and uses_left - mnemonic_token = rest_make_recovery_token(authorized_client) - - time_generated = rest_get_recovery_date(authorized_client) - assert_recovery_recent(time_generated) - - assert rest_get_recovery_status(authorized_client) == { - "exists": True, - "valid": True, - "date": time_generated, - "expiration": None, - "uses_left": None, - } - - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device") - # And again - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2") - - -@pytest.mark.parametrize("timeformat", DATE_FORMATS) -def test_generate_recovery_token_with_expiration_date( - authorized_client, client, tokens_file, timeformat, mocker -): - # Generate token with expiration date - # Generate expiration date in the future - expiration_date = five_minutes_into_future() - mnemonic_token = rest_make_recovery_token( - authorized_client, expires_at=expiration_date, timeformat=timeformat - ) - - time_generated = rest_get_recovery_date(authorized_client) - assert_recovery_recent(time_generated) - - assert rest_get_recovery_status(authorized_client) == { - "exists": True, - "valid": True, - "date": time_generated, - "expiration": expiration_date.replace(tzinfo=timezone.utc).isoformat(), - "uses_left": None, - } - - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device") - # And again - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2") - - # Try to use token after expiration date - mock = mocker.patch(RECOVERY_KEY_VALIDATION_DATETIME, NearFuture) - device_name = "recovery_device3" - recovery_response = client.post( - "/auth/recovery_token/use", - json={"token": mnemonic_token, "device": device_name}, - ) - assert recovery_response.status_code == 404 - # Assert that the token was not created - assert device_name not in [ - token["name"] for token in rest_get_tokens_info(authorized_client) - ] - - -@pytest.mark.parametrize("timeformat", DATE_FORMATS) -def test_generate_recovery_token_with_expiration_in_the_past( - authorized_client, tokens_file, timeformat -): - # Server must return 400 if expiration date is in the past - expiration_date = five_minutes_into_past() - expiration_date_str = expiration_date.strftime(timeformat) - response = authorized_client.post( - "/auth/recovery_token", - json={"expiration": expiration_date_str}, - ) - assert response.status_code == 400 - assert_no_recovery(authorized_client) - - -def test_generate_recovery_token_with_invalid_time_format( - authorized_client, tokens_file -): - # Server must return 400 if expiration date is in the past - expiration_date = "invalid_time_format" - response = authorized_client.post( - "/auth/recovery_token", - json={"expiration": expiration_date}, - ) - assert response.status_code == 422 - assert_no_recovery(authorized_client) - - -def test_generate_recovery_token_with_limited_uses( - authorized_client, client, tokens_file -): - # Generate token with limited uses - mnemonic_token = rest_make_recovery_token(authorized_client, uses=2) - - time_generated = rest_get_recovery_date(authorized_client) - assert_recovery_recent(time_generated) - - assert rest_get_recovery_status(authorized_client) == { - "exists": True, - "valid": True, - "date": time_generated, - "expiration": None, - "uses_left": 2, - } - - # Try to use the token - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device") - - assert rest_get_recovery_status(authorized_client) == { - "exists": True, - "valid": True, - "date": time_generated, - "expiration": None, - "uses_left": 1, - } - - # Try to use token again - rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2") - - assert rest_get_recovery_status(authorized_client) == { - "exists": True, - "valid": False, - "date": time_generated, - "expiration": None, - "uses_left": 0, - } - - # Try to use token after limited uses - recovery_response = client.post( - "/auth/recovery_token/use", - json={"token": mnemonic_token, "device": "recovery_device3"}, - ) - assert recovery_response.status_code == 404 - - -def test_generate_recovery_token_with_negative_uses( - authorized_client, client, tokens_file -): - # Generate token with limited uses - response = authorized_client.post( - "/auth/recovery_token", - json={"uses": -2}, - ) - assert response.status_code == 400 - assert_no_recovery(authorized_client) - - -def test_generate_recovery_token_with_zero_uses(authorized_client, client, tokens_file): - # Generate token with limited uses - response = authorized_client.post( - "/auth/recovery_token", - json={"uses": 0}, - ) - assert response.status_code == 400 - assert_no_recovery(authorized_client) From 7c8ea19608a7d9d15eb6a0c387c7ce7bb40a1e9a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 14 Aug 2023 15:45:41 +0000 Subject: [PATCH 002/130] test(rest-dismantling): remove system tests with gql counterparts --- tests/test_rest_endpoints/test_system.py | 249 ----------------------- 1 file changed, 249 deletions(-) diff --git a/tests/test_rest_endpoints/test_system.py b/tests/test_rest_endpoints/test_system.py index 90c1499..f2b20db 100644 --- a/tests/test_rest_endpoints/test_system.py +++ b/tests/test_rest_endpoints/test_system.py @@ -103,200 +103,6 @@ def mock_subprocess_check_output(mocker): return mock -def test_wrong_auth(wrong_auth_client): - response = wrong_auth_client.get("/system/pythonVersion") - assert response.status_code == 401 - - -def test_get_domain(authorized_client, domain_file): - assert get_domain() == "test-domain.tld" - - -## Timezones - - -def test_get_timezone_unauthorized(client, turned_on): - response = client.get("/system/configuration/timezone") - assert response.status_code == 401 - - -def test_get_timezone(authorized_client, turned_on): - response = authorized_client.get("/system/configuration/timezone") - assert response.status_code == 200 - assert response.json() == "Europe/Moscow" - - -def test_get_timezone_on_undefined(authorized_client, undefined_config): - response = authorized_client.get("/system/configuration/timezone") - assert response.status_code == 200 - assert response.json() == "Europe/Uzhgorod" - - -def test_put_timezone_unauthorized(client, turned_on): - response = client.put( - "/system/configuration/timezone", json={"timezone": "Europe/Moscow"} - ) - assert response.status_code == 401 - - -def test_put_timezone(authorized_client, turned_on): - response = authorized_client.put( - "/system/configuration/timezone", json={"timezone": "Europe/Helsinki"} - ) - assert response.status_code == 200 - assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Helsinki" - - -def test_put_timezone_on_undefined(authorized_client, undefined_config): - response = authorized_client.put( - "/system/configuration/timezone", json={"timezone": "Europe/Helsinki"} - ) - assert response.status_code == 200 - assert ( - read_json(undefined_config / "undefined.json")["timezone"] == "Europe/Helsinki" - ) - - -def test_put_timezone_without_timezone(authorized_client, turned_on): - response = authorized_client.put("/system/configuration/timezone", json={}) - assert response.status_code == 422 - assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Moscow" - - -def test_put_invalid_timezone(authorized_client, turned_on): - response = authorized_client.put( - "/system/configuration/timezone", json={"timezone": "Invalid/Timezone"} - ) - assert response.status_code == 400 - assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Moscow" - - -## AutoUpgrade - - -def test_get_auto_upgrade_unauthorized(client, turned_on): - response = client.get("/system/configuration/autoUpgrade") - assert response.status_code == 401 - - -def test_get_auto_upgrade(authorized_client, turned_on): - response = authorized_client.get("/system/configuration/autoUpgrade") - assert response.status_code == 200 - assert response.json() == { - "enable": True, - "allowReboot": True, - } - - -def test_get_auto_upgrade_on_undefined(authorized_client, undefined_config): - response = authorized_client.get("/system/configuration/autoUpgrade") - assert response.status_code == 200 - assert response.json() == { - "enable": True, - "allowReboot": False, - } - - -def test_get_auto_upgrade_without_values(authorized_client, no_values): - response = authorized_client.get("/system/configuration/autoUpgrade") - assert response.status_code == 200 - assert response.json() == { - "enable": True, - "allowReboot": False, - } - - -def test_get_auto_upgrade_turned_off(authorized_client, turned_off): - response = authorized_client.get("/system/configuration/autoUpgrade") - assert response.status_code == 200 - assert response.json() == { - "enable": False, - "allowReboot": False, - } - - -def test_put_auto_upgrade_unauthorized(client, turned_on): - response = client.put( - "/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True} - ) - assert response.status_code == 401 - - -def test_put_auto_upgrade(authorized_client, turned_on): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"enable": False, "allowReboot": True} - ) - assert response.status_code == 200 - assert read_json(turned_on / "turned_on.json")["autoUpgrade"] == { - "enable": False, - "allowReboot": True, - } - - -def test_put_auto_upgrade_on_undefined(authorized_client, undefined_config): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"enable": False, "allowReboot": True} - ) - assert response.status_code == 200 - assert read_json(undefined_config / "undefined.json")["autoUpgrade"] == { - "enable": False, - "allowReboot": True, - } - - -def test_put_auto_upgrade_without_values(authorized_client, no_values): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True} - ) - assert response.status_code == 200 - assert read_json(no_values / "no_values.json")["autoUpgrade"] == { - "enable": True, - "allowReboot": True, - } - - -def test_put_auto_upgrade_turned_off(authorized_client, turned_off): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True} - ) - assert response.status_code == 200 - assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == { - "enable": True, - "allowReboot": True, - } - - -def test_put_auto_upgrade_without_enable(authorized_client, turned_off): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"allowReboot": True} - ) - assert response.status_code == 200 - assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == { - "enable": False, - "allowReboot": True, - } - - -def test_put_auto_upgrade_without_allow_reboot(authorized_client, turned_off): - response = authorized_client.put( - "/system/configuration/autoUpgrade", json={"enable": True} - ) - assert response.status_code == 200 - assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == { - "enable": True, - "allowReboot": False, - } - - -def test_put_auto_upgrade_with_empty_json(authorized_client, turned_off): - response = authorized_client.put("/system/configuration/autoUpgrade", json={}) - assert response.status_code == 200 - assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == { - "enable": False, - "allowReboot": False, - } - - def test_system_rebuild_unauthorized(client, mock_subprocess_popen): response = client.get("/system/configuration/apply") assert response.status_code == 401 @@ -348,20 +154,6 @@ def test_system_rollback(authorized_client, mock_subprocess_popen): ] -def test_get_system_version_unauthorized(client, mock_subprocess_check_output): - response = client.get("/system/version") - assert response.status_code == 401 - assert mock_subprocess_check_output.call_count == 0 - - -def test_get_system_version(authorized_client, mock_subprocess_check_output): - response = authorized_client.get("/system/version") - assert response.status_code == 200 - assert response.json() == {"system_version": "Testing Linux"} - assert mock_subprocess_check_output.call_count == 1 - assert mock_subprocess_check_output.call_args[0][0] == ["uname", "-a"] - - def test_reboot_system_unauthorized(client, mock_subprocess_popen): response = client.get("/system/reboot") assert response.status_code == 401 @@ -373,44 +165,3 @@ def test_reboot_system(authorized_client, mock_subprocess_popen): assert response.status_code == 200 assert mock_subprocess_popen.call_count == 1 assert mock_subprocess_popen.call_args[0][0] == ["reboot"] - - -def test_get_python_version_unauthorized(client, mock_subprocess_check_output): - response = client.get("/system/pythonVersion") - assert response.status_code == 401 - assert mock_subprocess_check_output.call_count == 0 - - -def test_get_python_version(authorized_client, mock_subprocess_check_output): - response = authorized_client.get("/system/pythonVersion") - assert response.status_code == 200 - assert response.json() == "Testing Linux" - assert mock_subprocess_check_output.call_count == 1 - assert mock_subprocess_check_output.call_args[0][0] == ["python", "-V"] - - -def test_pull_system_unauthorized(client, mock_subprocess_popen): - response = client.get("/system/configuration/pull") - assert response.status_code == 401 - assert mock_subprocess_popen.call_count == 0 - - -def test_pull_system(authorized_client, mock_subprocess_popen, mock_os_chdir): - current_dir = os.getcwd() - response = authorized_client.get("/system/configuration/pull") - assert response.status_code == 200 - assert mock_subprocess_popen.call_count == 1 - assert mock_subprocess_popen.call_args[0][0] == ["git", "pull"] - assert mock_os_chdir.call_count == 2 - assert mock_os_chdir.call_args_list[0][0][0] == "/etc/nixos" - assert mock_os_chdir.call_args_list[1][0][0] == current_dir - - -def test_pull_system_broken_repo(authorized_client, mock_broken_service, mock_os_chdir): - current_dir = os.getcwd() - response = authorized_client.get("/system/configuration/pull") - assert response.status_code == 500 - assert mock_broken_service.call_count == 1 - assert mock_os_chdir.call_count == 2 - assert mock_os_chdir.call_args_list[0][0][0] == "/etc/nixos" - assert mock_os_chdir.call_args_list[1][0][0] == current_dir From d34b98e27bbdab1dc59902f1c11eadfea5717d6b Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 14 Aug 2023 16:42:26 +0000 Subject: [PATCH 003/130] test(rest-dismantling): remove user tests with gql counterparts --- tests/test_rest_endpoints/test_users.py | 120 +----------------------- 1 file changed, 5 insertions(+), 115 deletions(-) diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index ebb3eff..c7c5f5b 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -113,41 +113,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### -def test_get_users_unauthorized(client, some_users, mock_subprocess_popen): - response = client.get("/users") - assert response.status_code == 401 - - -def test_get_some_users(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.get("/users") - assert response.status_code == 200 - assert response.json() == ["user1", "user2", "user3"] - - -def test_get_one_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.get("/users") - assert response.status_code == 200 - assert response.json() == ["user1"] - - -def test_get_one_user_with_main(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.get("/users?withMainUser=true") - assert response.status_code == 200 - assert response.json().sort() == ["tester", "user1"].sort() - - -def test_get_no_users(authorized_client, no_users, mock_subprocess_popen): - response = authorized_client.get("/users") - assert response.status_code == 200 - assert response.json() == [] - - -def test_get_no_users_with_main(authorized_client, no_users, mock_subprocess_popen): - response = authorized_client.get("/users?withMainUser=true") - assert response.status_code == 200 - assert response.json() == ["tester"] - - def test_get_undefined_users( authorized_client, undefined_settings, mock_subprocess_popen ): @@ -156,28 +121,7 @@ def test_get_undefined_users( assert response.json() == [] -def test_post_users_unauthorized(client, some_users, mock_subprocess_popen): - response = client.post("/users") - assert response.status_code == 401 - - -def test_post_one_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/users", json={"username": "user4", "password": "password"} - ) - assert response.status_code == 201 - assert read_json(one_user / "one_user.json")["users"] == [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": ["ssh-rsa KEY user1@pc"], - }, - { - "username": "user4", - "sshKeys": [], - "hashedPassword": "NEW_HASHED", - }, - ] +# graphql tests still provide these fields even if with empty values def test_post_without_username(authorized_client, one_user, mock_subprocess_popen): @@ -197,47 +141,10 @@ def test_post_without_username_and_password( assert response.status_code == 422 -@pytest.mark.parametrize("username", invalid_usernames) -def test_post_system_user(authorized_client, one_user, mock_subprocess_popen, username): - response = authorized_client.post( - "/users", json={"username": username, "password": "password"} - ) - assert response.status_code == 409 - - -def test_post_existing_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/users", json={"username": "user1", "password": "password"} - ) - assert response.status_code == 409 - - -def test_post_existing_main_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/users", json={"username": "tester", "password": "password"} - ) - assert response.status_code == 409 - - -def test_post_user_to_undefined_users( - authorized_client, undefined_settings, mock_subprocess_popen -): - response = authorized_client.post( - "/users", json={"username": "user4", "password": "password"} - ) - assert response.status_code == 201 - assert read_json(undefined_settings / "undefined.json")["users"] == [ - {"username": "user4", "sshKeys": [], "hashedPassword": "NEW_HASHED"} - ] - - -def test_post_very_long_username(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/users", json={"username": "a" * 32, "password": "password"} - ) - assert response.status_code == 400 - +# end of BUT THERE ARE FIELDS! rant +# the final user is not in gql checks +# I think maybe generate a bunch? @pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "№:%##$^&@$&^()_"]) def test_post_invalid_username( authorized_client, one_user, mock_subprocess_popen, username @@ -248,16 +155,7 @@ def test_post_invalid_username( assert response.status_code == 400 -def test_delete_user_unauthorized(client, some_users, mock_subprocess_popen): - response = client.delete("/users/user1") - assert response.status_code == 401 - - -def test_delete_user_not_found(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.delete("/users/user4") - assert response.status_code == 404 - - +# gql counterpart is too weak def test_delete_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.delete("/users/user1") assert response.status_code == 200 @@ -267,14 +165,6 @@ def test_delete_user(authorized_client, some_users, mock_subprocess_popen): ] -@pytest.mark.parametrize("username", invalid_usernames) -def test_delete_system_user( - authorized_client, some_users, mock_subprocess_popen, username -): - response = authorized_client.delete("/users/" + username) - assert response.status_code == 400 or response.status_code == 404 - - def test_delete_main_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.delete("/users/tester") assert response.status_code == 400 From 011e052962f194f6a6dc09834f492f2c79d994a5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 14 Aug 2023 11:50:59 +0000 Subject: [PATCH 004/130] test(backups): more checks regarding tmpdirs and mounting --- tests/test_graphql/test_backup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index b66a90d..882e086 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -13,6 +13,8 @@ import tempfile from selfprivacy_api.utils.huey import huey +import tempfile + import selfprivacy_api.services as services from selfprivacy_api.services import Service, get_all_services from selfprivacy_api.services import get_service_by_id From d4b2ca14bbd6ec69a13b7962ddb0da3e9635be70 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 1 Sep 2023 10:41:27 +0000 Subject: [PATCH 005/130] feature(backups): a task to autorefresh cache. Redis expiry abolished --- selfprivacy_api/backup/tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfprivacy_api/backup/tasks.py b/selfprivacy_api/backup/tasks.py index f0422ca..5b36252 100644 --- a/selfprivacy_api/backup/tasks.py +++ b/selfprivacy_api/backup/tasks.py @@ -16,6 +16,8 @@ from selfprivacy_api.backup import Backups from selfprivacy_api.jobs import Jobs, JobStatus, Job +SNAPSHOT_CACHE_TTL_HOURS = 6 + SNAPSHOT_CACHE_TTL_HOURS = 6 From 85c90105ea901a6f90207b90fa589ed29309200d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 13:17:57 +0000 Subject: [PATCH 006/130] test(backup): ensure we use correct repo folder --- selfprivacy_api/backup/backuppers/restic_backupper.py | 1 + tests/test_graphql/test_backup.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/selfprivacy_api/backup/backuppers/restic_backupper.py b/selfprivacy_api/backup/backuppers/restic_backupper.py index fd653e6..b7ceb90 100644 --- a/selfprivacy_api/backup/backuppers/restic_backupper.py +++ b/selfprivacy_api/backup/backuppers/restic_backupper.py @@ -197,6 +197,7 @@ class ResticBackupper(AbstractBackupper): output, "parsed messages:", messages, + backup_command, ) from error @staticmethod diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index 882e086..32b5d40 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -52,9 +52,11 @@ TESTFILE_BODY = "testytest!" TESTFILE_2_BODY = "testissimo!" REPO_NAME = "test_backup" +REPOFILE_NAME = "totallyunrelated" + def prepare_localfile_backups(temp_dir): - test_repo_path = path.join(temp_dir, "totallyunrelated") + test_repo_path = path.join(temp_dir, REPOFILE_NAME) assert not path.exists(test_repo_path) Backups.set_localfile_repo(test_repo_path) @@ -79,6 +81,7 @@ def backups(tmpdir): # assert not repo_path Backups.init_repo() + assert Backups.provider().location == str(tmpdir) + "/" + REPOFILE_NAME yield Backups.erase_repo() From 69a05de3d7d5c9060e6b7ba4ee01593c5e56abb4 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 13:34:44 +0000 Subject: [PATCH 007/130] test(backup): ensure we actually call backup fixture and related resets --- tests/test_graphql/test_api_backup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index b44fd44..9543f22 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -1,5 +1,6 @@ from os import path -from tests.test_graphql.test_backup import dummy_service, backups, raw_dummy_service +from tests.test_graphql.test_backup import backups +from tests.test_graphql.test_backup import raw_dummy_service, dummy_service from tests.common import generate_backup_query @@ -301,7 +302,7 @@ def test_dummy_service_convertible_to_gql(dummy_service): assert gql_service is not None -def test_snapshots_empty(authorized_client, dummy_service): +def test_snapshots_empty(authorized_client, dummy_service, backups): snaps = api_snapshots(authorized_client) assert snaps == [] @@ -370,7 +371,6 @@ def test_remove(authorized_client, generic_userdata, backups): assert len(configuration["encryptionKey"]) > 1 assert configuration["isInitialized"] is False - def test_autobackup_quotas_nonzero(authorized_client, backups): quotas = _AutobackupQuotas( last=3, From 6e9d86e8447f43d6dc1f1d4772b02d2c4092325d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 13:48:30 +0000 Subject: [PATCH 008/130] test(backup): break out dummy service fixture --- .../backup/backuppers/restic_backupper.py | 1 + tests/test_common.py | 53 +++++++++++++++++++ tests/test_graphql/test_backup.py | 50 +---------------- tests/test_services.py | 2 +- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/selfprivacy_api/backup/backuppers/restic_backupper.py b/selfprivacy_api/backup/backuppers/restic_backupper.py index b7ceb90..0d74d9c 100644 --- a/selfprivacy_api/backup/backuppers/restic_backupper.py +++ b/selfprivacy_api/backup/backuppers/restic_backupper.py @@ -197,6 +197,7 @@ class ResticBackupper(AbstractBackupper): output, "parsed messages:", messages, + "command: ", backup_command, ) from error diff --git a/tests/test_common.py b/tests/test_common.py index e5d3f62..0bcd4bc 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -6,6 +6,59 @@ import pytest from selfprivacy_api.utils import WriteUserData, ReadUserData +from os import path +from os import makedirs +from typing import Generator + +# import pickle +import selfprivacy_api.services as services +from selfprivacy_api.services import get_service_by_id, Service +from selfprivacy_api.services.test_service import DummyService + + +TESTFILE_BODY = "testytest!" +TESTFILE_2_BODY = "testissimo!" + + +@pytest.fixture() +def raw_dummy_service(tmpdir): + dirnames = ["test_service", "also_test_service"] + service_dirs = [] + for d in dirnames: + service_dir = path.join(tmpdir, d) + makedirs(service_dir) + service_dirs.append(service_dir) + + testfile_path_1 = path.join(service_dirs[0], "testfile.txt") + with open(testfile_path_1, "w") as file: + file.write(TESTFILE_BODY) + + testfile_path_2 = path.join(service_dirs[1], "testfile2.txt") + with open(testfile_path_2, "w") as file: + file.write(TESTFILE_2_BODY) + + # we need this to not change get_folders() much + class TestDummyService(DummyService, folders=service_dirs): + pass + + service = TestDummyService() + # assert pickle.dumps(service) is not None + return service + + +@pytest.fixture() +def dummy_service(tmpdir, raw_dummy_service) -> Generator[Service, None, None]: + service = raw_dummy_service + + # register our service + services.services.append(service) + + assert get_service_by_id(service.get_id()) is not None + yield service + + # cleanup because apparently it matters wrt tasks + services.services.remove(service) + def test_get_api_version(authorized_client): response = authorized_client.get("/api/version") diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index 32b5d40..187ce11 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -2,7 +2,6 @@ import pytest import os import os.path as path -from os import makedirs from os import remove from os import listdir from os import urandom @@ -15,7 +14,8 @@ from selfprivacy_api.utils.huey import huey import tempfile -import selfprivacy_api.services as services +from tests.test_common import dummy_service, raw_dummy_service + from selfprivacy_api.services import Service, get_all_services from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import ServiceStatus @@ -48,8 +48,6 @@ from selfprivacy_api.backup.tasks import ( from selfprivacy_api.backup.storage import Storage -TESTFILE_BODY = "testytest!" -TESTFILE_2_BODY = "testissimo!" REPO_NAME = "test_backup" REPOFILE_NAME = "totallyunrelated" @@ -78,7 +76,6 @@ def backups(tmpdir): else: prepare_localfile_backups(tmpdir) Jobs.reset() - # assert not repo_path Backups.init_repo() assert Backups.provider().location == str(tmpdir) + "/" + REPOFILE_NAME @@ -91,49 +88,6 @@ def backups_backblaze(generic_userdata): Backups.reset(reset_json=False) -@pytest.fixture() -def raw_dummy_service(tmpdir): - dirnames = ["test_service", "also_test_service"] - service_dirs = [] - for d in dirnames: - service_dir = path.join(tmpdir, d) - makedirs(service_dir) - service_dirs.append(service_dir) - - testfile_path_1 = path.join(service_dirs[0], "testfile.txt") - with open(testfile_path_1, "w") as file: - file.write(TESTFILE_BODY) - - testfile_path_2 = path.join(service_dirs[1], "testfile2.txt") - with open(testfile_path_2, "w") as file: - file.write(TESTFILE_2_BODY) - - # we need this to not change get_folders() much - class TestDummyService(DummyService, folders=service_dirs): - pass - - service = TestDummyService() - return service - - -@pytest.fixture() -def dummy_service(tmpdir, backups, raw_dummy_service) -> Service: - service = raw_dummy_service - - # register our service - services.services.append(service) - - # make sure we are in immediate mode because this thing is non pickleable to store on queue. - huey.immediate = True - assert huey.immediate is True - - assert get_service_by_id(service.get_id()) is not None - yield service - - # cleanup because apparently it matters wrt tasks - services.services.remove(service) - - @pytest.fixture() def memory_backup() -> AbstractBackupProvider: ProviderClass = providers.get_provider(BackupProvider.MEMORY) diff --git a/tests/test_services.py b/tests/test_services.py index b83a7f2..3eef0cd 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -12,7 +12,7 @@ from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService from selfprivacy_api.utils.waitloop import wait_until_true -from tests.test_graphql.test_backup import raw_dummy_service +from tests.test_common import raw_dummy_service def test_unimplemented_folders_raises(): From 7ef751db9874225c6f18ec81ce8b15811d33e64e Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 14:28:32 +0000 Subject: [PATCH 009/130] test(services): break out graphql basics --- tests/test_graphql/test_api_backup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index 9543f22..bc4b7f1 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -371,6 +371,7 @@ def test_remove(authorized_client, generic_userdata, backups): assert len(configuration["encryptionKey"]) > 1 assert configuration["isInitialized"] is False + def test_autobackup_quotas_nonzero(authorized_client, backups): quotas = _AutobackupQuotas( last=3, From 87248c3f8c0c5a5f636c975cf7f326082e47d0fc Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 14:29:06 +0000 Subject: [PATCH 010/130] test(services): add services query generator --- tests/common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/common.py b/tests/common.py index 5199899..8061721 100644 --- a/tests/common.py +++ b/tests/common.py @@ -67,6 +67,10 @@ def generate_backup_query(query_array): return "query TestBackup {\n backup {" + "\n".join(query_array) + "}\n}" +def generate_service_query(query_array): + return "query TestService {\n services {" + "\n".join(query_array) + "}\n}" + + def mnemonic_to_hex(mnemonic): return Mnemonic(language="english").to_entropy(mnemonic).hex() From 9bf239c3a8cd494c717af9fe39a8af51b309b841 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Sep 2023 14:30:18 +0000 Subject: [PATCH 011/130] test(services): disable usual services for testing --- tests/test_graphql/test_services.py | 106 ++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/test_graphql/test_services.py diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py new file mode 100644 index 0000000..00b4633 --- /dev/null +++ b/tests/test_graphql/test_services.py @@ -0,0 +1,106 @@ +import pytest + +from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations +import selfprivacy_api.services as service_module +from selfprivacy_api.services.service import Service + +import tests.test_graphql.test_api_backup +from tests.test_common import raw_dummy_service, dummy_service +from tests.common import generate_service_query +from tests.test_graphql.common import get_data + + +@pytest.fixture() +def only_dummy_service(dummy_service): + # because queries to services that are not really there error out + back_copy = service_module.services.copy() + service_module.services.clear() + service_module.services.append(dummy_service) + yield dummy_service + service_module.services.clear() + service_module.services.extend(back_copy) + + + +API_START_MUTATION = """ +mutation TestStartService($service_id: String!) { + services { + startService(serviceId: $service_id) { + success + message + code + service { + id + status + } + } + } +} +""" + +API_STOP_MUTATION = """ +mutation TestStopService($service_id: String!) { + services { + stopService(serviceId: $service_id) { + success + message + code + service { + id + status + } + } + } +} + +""" +API_SERVICES_QUERY = """ +allServices { + id + status +} +""" + + +def api_start(client, service): + response = client.post( + "/graphql", + json={ + "query": API_START_MUTATION, + "variables": {"service_id": service.get_id()}, + }, + ) + return response + + +def api_stop(client, service): + response = client.post( + "/graphql", + json={ + "query": API_STOP_MUTATION, + "variables": {"service_id": service.get_id()}, + }, + ) + return response + + +def api_all_services(authorized_client): + response = authorized_client.post( + "/graphql", + json={"query": generate_service_query([API_SERVICES_QUERY])}, + ) + data = get_data(response) + result = data["services"]["allServices"] + assert result is not None + return result + + +def api_service(authorized_client, service: Service): + id = service.get_id() + for _service in api_all_services(authorized_client): + if _service["id"] == id: + return _service + + +def test_get_services(authorized_client, only_dummy_service): + assert len(api_all_services(authorized_client)) == 1 From 7808033bef47d782f610eb2f805607c11cb66ab5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 11:52:26 +0000 Subject: [PATCH 012/130] test(services): check id and status --- tests/test_graphql/test_services.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 00b4633..93fe682 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -2,7 +2,7 @@ import pytest from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations import selfprivacy_api.services as service_module -from selfprivacy_api.services.service import Service +from selfprivacy_api.services.service import Service, ServiceStatus import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service @@ -21,7 +21,6 @@ def only_dummy_service(dummy_service): service_module.services.extend(back_copy) - API_START_MUTATION = """ mutation TestStartService($service_id: String!) { services { @@ -103,4 +102,9 @@ def api_service(authorized_client, service: Service): def test_get_services(authorized_client, only_dummy_service): - assert len(api_all_services(authorized_client)) == 1 + services = api_all_services(authorized_client) + assert len(services) == 1 + + api_dummy_service = services[0] + assert api_dummy_service["id"] == "testservice" + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value From 728ea44823c42ee2d46cf344f8e56ab6323cb0fa Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 12:30:44 +0000 Subject: [PATCH 013/130] test(service): startstop --- tests/test_graphql/test_services.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 93fe682..ea1f272 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -61,7 +61,7 @@ allServices { """ -def api_start(client, service): +def api_start(client, service: Service): response = client.post( "/graphql", json={ @@ -72,7 +72,7 @@ def api_start(client, service): return response -def api_stop(client, service): +def api_stop(client, service: Service): response = client.post( "/graphql", json={ @@ -108,3 +108,28 @@ def test_get_services(authorized_client, only_dummy_service): api_dummy_service = services[0] assert api_dummy_service["id"] == "testservice" assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + +def test_stop_start(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + # attempting to start an already started service + api_start(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + api_stop(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["status"] == ServiceStatus.INACTIVE.value + + # attempting to stop an already stopped service + api_stop(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["status"] == ServiceStatus.INACTIVE.value + + api_start(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value From 92612906efc05913417a84937b246311a4c23153 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 12:52:13 +0000 Subject: [PATCH 014/130] test(service): enabled status get --- .../services/test_service/__init__.py | 25 +++++++++++++++++-- tests/test_graphql/test_services.py | 2 ++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index 187a1c6..3baf193 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -30,9 +30,10 @@ class DummyService(Service): def __init__(self): super().__init__() - status_file = self.status_file() - with open(status_file, "w") as file: + with open(self.status_file(), "w") as file: file.write(ServiceStatus.ACTIVE.value) + with open(self.enabled_file(), "w") as file: + file.write("True") @staticmethod def get_id() -> str: @@ -83,6 +84,26 @@ class DummyService(Service): # we do not REALLY want to store our state in our declared folders return path.join(dir, "..", "service_status") + @classmethod + def enabled_file(cls) -> str: + dir = cls.folders[0] + return path.join(dir, "..", "service_enabled") + + @classmethod + def get_enabled(cls) -> bool: + with open(cls.enabled_file(), "r") as file: + string = file.read().strip() + if "True" in string: + return True + if "False" in string: + return False + raise ValueError("test service enabled/disabled status file got corrupted") + + @classmethod + def set_enabled(cls, enabled: bool): + with open(cls.enabled_file(), "w") as file: + status_string = file.write(str(enabled)) + @classmethod def set_status(cls, status: ServiceStatus): with open(cls.status_file(), "w") as file: diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index ea1f272..efd86d8 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -57,6 +57,7 @@ API_SERVICES_QUERY = """ allServices { id status + isEnabled } """ @@ -108,6 +109,7 @@ def test_get_services(authorized_client, only_dummy_service): api_dummy_service = services[0] assert api_dummy_service["id"] == "testservice" assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + assert api_dummy_service["isEnabled"] is True def test_stop_start(authorized_client, only_dummy_service): From 47cfaad160b817ed2c3d92467e3c85938b2c6687 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 13:46:22 +0000 Subject: [PATCH 015/130] test(service): startstop return values --- tests/test_graphql/test_services.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index efd86d8..85090cf 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -7,7 +7,7 @@ from selfprivacy_api.services.service import Service, ServiceStatus import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_service_query -from tests.test_graphql.common import get_data +from tests.test_graphql.common import assert_ok, get_data @pytest.fixture() @@ -62,7 +62,7 @@ allServices { """ -def api_start(client, service: Service): +def api_start(client, service: Service) -> dict: response = client.post( "/graphql", json={ @@ -73,7 +73,7 @@ def api_start(client, service: Service): return response -def api_stop(client, service: Service): +def api_stop(client, service: Service) -> dict: response = client.post( "/graphql", json={ @@ -112,6 +112,26 @@ def test_get_services(authorized_client, only_dummy_service): assert api_dummy_service["isEnabled"] is True +def test_start_return_value(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_start(authorized_client, dummy_service) + data = get_data(mutation_response)["services"]["startService"] + assert_ok(data) + service = data["service"] + assert service["id"] == dummy_service.get_id() + assert service["status"] == ServiceStatus.ACTIVE.value + + +def test_stop_return_value(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_stop(authorized_client, dummy_service) + data = get_data(mutation_response)["services"]["stopService"] + assert_ok(data) + service = data["service"] + assert service["id"] == dummy_service.get_id() + assert service["status"] == ServiceStatus.INACTIVE.value + + def test_stop_start(authorized_client, only_dummy_service): dummy_service = only_dummy_service From bd43bdb33579581770c3f1b44b89606b7be385ca Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 14:03:01 +0000 Subject: [PATCH 016/130] test(service): breakout raw api calls with ids --- tests/test_graphql/test_services.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 85090cf..c9b909b 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -63,22 +63,30 @@ allServices { def api_start(client, service: Service) -> dict: + return api_start_by_name(client, service.get_id()) + + +def api_start_by_name(client, service_id: str) -> dict: response = client.post( "/graphql", json={ "query": API_START_MUTATION, - "variables": {"service_id": service.get_id()}, + "variables": {"service_id": service_id}, }, ) return response def api_stop(client, service: Service) -> dict: + return api_stop_by_name(client, service.get_id()) + + +def api_stop_by_name(client, service_id: str) -> dict: response = client.post( "/graphql", json={ "query": API_STOP_MUTATION, - "variables": {"service_id": service.get_id()}, + "variables": {"service_id": service_id}, }, ) return response From b9f3aa49bd8a68406b6db8bc26f31fed6748e488 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 15:27:34 +0000 Subject: [PATCH 017/130] test(service): enable-disable return values --- .../services/test_service/__init__.py | 12 +-- tests/test_graphql/test_services.py | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index 3baf193..60aea3b 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -74,9 +74,9 @@ class DummyService(Service): def get_backup_description() -> str: return "How did we get here?" - @staticmethod - def is_enabled() -> bool: - return True + @classmethod + def is_enabled(cls) -> bool: + return cls.get_enabled() @classmethod def status_file(cls) -> str: @@ -144,11 +144,11 @@ class DummyService(Service): @classmethod def enable(cls): - pass + cls.set_enabled(True) @classmethod - def disable(cls, delay): - pass + def disable(cls): + cls.set_enabled(False) @classmethod def set_delay(cls, new_delay): diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index c9b909b..bc9eab0 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -37,6 +37,37 @@ mutation TestStartService($service_id: String!) { } """ +API_ENABLE_MUTATION = """ +mutation TestStartService($service_id: String!) { + services { + enableService(serviceId: $service_id) { + success + message + code + service { + id + isEnabled + } + } + } +} +""" +API_DISABLE_MUTATION = """ +mutation TestStartService($service_id: String!) { + services { + disableService(serviceId: $service_id) { + success + message + code + service { + id + isEnabled + } + } + } +} +""" + API_STOP_MUTATION = """ mutation TestStopService($service_id: String!) { services { @@ -62,6 +93,36 @@ allServices { """ +def api_enable(client, service: Service) -> dict: + return api_enable_by_name(client, service.get_id()) + + +def api_enable_by_name(client, service_id: str) -> dict: + response = client.post( + "/graphql", + json={ + "query": API_ENABLE_MUTATION, + "variables": {"service_id": service_id}, + }, + ) + return response + + +def api_disable(client, service: Service) -> dict: + return api_disable_by_name(client, service.get_id()) + + +def api_disable_by_name(client, service_id: str) -> dict: + response = client.post( + "/graphql", + json={ + "query": API_DISABLE_MUTATION, + "variables": {"service_id": service_id}, + }, + ) + return response + + def api_start(client, service: Service) -> dict: return api_start_by_name(client, service.get_id()) @@ -120,6 +181,26 @@ def test_get_services(authorized_client, only_dummy_service): assert api_dummy_service["isEnabled"] is True +def test_enable_return_value(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_enable(authorized_client, dummy_service) + data = get_data(mutation_response)["services"]["enableService"] + assert_ok(data) + service = data["service"] + assert service["id"] == dummy_service.get_id() + assert service["isEnabled"] == True + + +def test_disable_return_value(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_disable(authorized_client, dummy_service) + data = get_data(mutation_response)["services"]["disableService"] + assert_ok(data) + service = data["service"] + assert service["id"] == dummy_service.get_id() + assert service["isEnabled"] == False + + def test_start_return_value(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_start(authorized_client, dummy_service) From a1637181202104976f7418b09302ea45ad670566 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 15:50:40 +0000 Subject: [PATCH 018/130] test(service): start nonexistent service --- tests/test_graphql/test_services.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index bc9eab0..712b11b 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -93,6 +93,16 @@ allServices { """ +def assert_notfound(data): + assert_errorcode(data, 404) + + +def assert_errorcode(data, errorcode): + assert data["code"] == errorcode + assert data["success"] is False + assert data["message"] is not None + + def api_enable(client, service: Service) -> dict: return api_enable_by_name(client, service.get_id()) @@ -221,6 +231,15 @@ def test_stop_return_value(authorized_client, only_dummy_service): assert service["status"] == ServiceStatus.INACTIVE.value +def test_start_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_start_by_name(authorized_client, "bogus_service") + data = get_data(mutation_response)["services"]["startService"] + assert_notfound(data) + + assert data["service"] is None + + def test_stop_start(authorized_client, only_dummy_service): dummy_service = only_dummy_service From bfdd98cb60bb9431fdaf77785c8c78f4e647a6a7 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 15:55:30 +0000 Subject: [PATCH 019/130] test(service): stop nonexistent service --- tests/test_graphql/test_services.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 712b11b..eb9f591 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -240,6 +240,15 @@ def test_start_nonexistent(authorized_client, only_dummy_service): assert data["service"] is None +def test_stop_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_stop_by_name(authorized_client, "bogus_service") + data = get_data(mutation_response)["services"]["stopService"] + assert_notfound(data) + + assert data["service"] is None + + def test_stop_start(authorized_client, only_dummy_service): dummy_service = only_dummy_service From 34782a3ca8cb680e519342f1fccbd6be8c9f21cf Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 16:02:09 +0000 Subject: [PATCH 020/130] test(service): enable nonexistent service --- tests/test_graphql/test_services.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index eb9f591..95d1693 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -249,6 +249,15 @@ def test_stop_nonexistent(authorized_client, only_dummy_service): assert data["service"] is None +def test_enable_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_enable_by_name(authorized_client, "bogus_service") + data = get_data(mutation_response)["services"]["enableService"] + assert_notfound(data) + + assert data["service"] is None + + def test_stop_start(authorized_client, only_dummy_service): dummy_service = only_dummy_service From 6d244fb603e591da54a9beabc1c2c6deefaa6820 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 16:08:28 +0000 Subject: [PATCH 021/130] test(service): disable nonexistent service --- tests/test_graphql/test_services.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 95d1693..94e8c69 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -258,6 +258,15 @@ def test_enable_nonexistent(authorized_client, only_dummy_service): assert data["service"] is None +def test_disable_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_disable_by_name(authorized_client, "bogus_service") + data = get_data(mutation_response)["services"]["disableService"] + assert_notfound(data) + + assert data["service"] is None + + def test_stop_start(authorized_client, only_dummy_service): dummy_service = only_dummy_service From 83c639596c0089ded4ca0783120a803b132d9f3f Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 16:20:39 +0000 Subject: [PATCH 022/130] test(service): start service unauthorized --- tests/test_graphql/test_services.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 94e8c69..d6f5c4e 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -231,6 +231,14 @@ def test_stop_return_value(authorized_client, only_dummy_service): assert service["status"] == ServiceStatus.INACTIVE.value +def test_start_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_start(client, dummy_service) + + assert mutation_response.status_code == 200 + assert mutation_response.json().get("data") is None + + def test_start_nonexistent(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_start_by_name(authorized_client, "bogus_service") From b06f1a4153fb4e9795bb604e07b49f9fa15e7279 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 16:30:41 +0000 Subject: [PATCH 023/130] test(service): other unauthorized mutations --- tests/test_graphql/test_services.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index d6f5c4e..4a170cf 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -239,6 +239,30 @@ def test_start_unauthorized(client, only_dummy_service): assert mutation_response.json().get("data") is None +def test_stop_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_stop(client, dummy_service) + + assert mutation_response.status_code == 200 + assert mutation_response.json().get("data") is None + + +def test_enable_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_enable(client, dummy_service) + + assert mutation_response.status_code == 200 + assert mutation_response.json().get("data") is None + + +def test_disable_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_disable(client, dummy_service) + + assert mutation_response.status_code == 200 + assert mutation_response.json().get("data") is None + + def test_start_nonexistent(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_start_by_name(authorized_client, "bogus_service") From 018a8ce24886256996d7eccd606db747dd3d6910 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Sep 2023 16:50:16 +0000 Subject: [PATCH 024/130] test(service): an unauthorized query --- tests/test_graphql/test_services.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 4a170cf..fcd2b85 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -164,16 +164,20 @@ def api_stop_by_name(client, service_id: str) -> dict: def api_all_services(authorized_client): - response = authorized_client.post( - "/graphql", - json={"query": generate_service_query([API_SERVICES_QUERY])}, - ) + response = api_all_services_raw(authorized_client) data = get_data(response) result = data["services"]["allServices"] assert result is not None return result +def api_all_services_raw(client): + return client.post( + "/graphql", + json={"query": generate_service_query([API_SERVICES_QUERY])}, + ) + + def api_service(authorized_client, service: Service): id = service.get_id() for _service in api_all_services(authorized_client): @@ -231,6 +235,14 @@ def test_stop_return_value(authorized_client, only_dummy_service): assert service["status"] == ServiceStatus.INACTIVE.value +def test_allservices_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + response = api_all_services_raw(client) + + assert response.status_code == 200 + assert response.json().get("data") is None + + def test_start_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_start(client, dummy_service) From 1e77129f4fa0b0ceaea76ea3944a4a2d92a225d0 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 6 Oct 2023 10:45:46 +0000 Subject: [PATCH 025/130] test(service): restart --- .../services/test_service/__init__.py | 6 +- tests/test_graphql/test_services.py | 63 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index 60aea3b..e4ed4cc 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -22,7 +22,7 @@ class DummyService(Service): """A test service""" folders: List[str] = [] - startstop_delay = 0 + startstop_delay = 0.0 backuppable = True def __init_subclass__(cls, folders: List[str]): @@ -151,8 +151,8 @@ class DummyService(Service): cls.set_enabled(False) @classmethod - def set_delay(cls, new_delay): - cls.startstop_delay = new_delay + def set_delay(cls, new_delay_sec: float) -> None: + cls.startstop_delay = new_delay_sec @classmethod def stop(cls): diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index fcd2b85..0b652c5 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -1,8 +1,10 @@ import pytest +from typing import Generator from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations import selfprivacy_api.services as service_module from selfprivacy_api.services.service import Service, ServiceStatus +from selfprivacy_api.services.test_service import DummyService import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service @@ -11,7 +13,7 @@ from tests.test_graphql.common import assert_ok, get_data @pytest.fixture() -def only_dummy_service(dummy_service): +def only_dummy_service(dummy_service) -> Generator[DummyService, None, None]: # because queries to services that are not really there error out back_copy = service_module.services.copy() service_module.services.clear() @@ -37,6 +39,22 @@ mutation TestStartService($service_id: String!) { } """ +API_RESTART_MUTATION = """ +mutation TestRestartService($service_id: String!) { + services { + restartService(serviceId: $service_id) { + success + message + code + service { + id + status + } + } + } +} +""" + API_ENABLE_MUTATION = """ mutation TestStartService($service_id: String!) { services { @@ -148,6 +166,21 @@ def api_start_by_name(client, service_id: str) -> dict: return response +def api_restart(client, service: Service) -> dict: + return api_restart_by_name(client, service.get_id()) + + +def api_restart_by_name(client, service_id: str) -> dict: + response = client.post( + "/graphql", + json={ + "query": API_RESTART_MUTATION, + "variables": {"service_id": service_id}, + }, + ) + return response + + def api_stop(client, service: Service) -> dict: return api_stop_by_name(client, service.get_id()) @@ -225,6 +258,17 @@ def test_start_return_value(authorized_client, only_dummy_service): assert service["status"] == ServiceStatus.ACTIVE.value +def test_restart(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + dummy_service.set_delay(0.3) + mutation_response = api_restart(authorized_client, dummy_service) + data = get_data(mutation_response)["services"]["restartService"] + assert_ok(data) + service = data["service"] + assert service["id"] == dummy_service.get_id() + assert service["status"] == ServiceStatus.RELOADING.value + + def test_stop_return_value(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_stop(authorized_client, dummy_service) @@ -251,6 +295,14 @@ def test_start_unauthorized(client, only_dummy_service): assert mutation_response.json().get("data") is None +def test_restart_unauthorized(client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_restart(client, dummy_service) + + assert mutation_response.status_code == 200 + assert mutation_response.json().get("data") is None + + def test_stop_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_stop(client, dummy_service) @@ -284,6 +336,15 @@ def test_start_nonexistent(authorized_client, only_dummy_service): assert data["service"] is None +def test_restart_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_restart_by_name(authorized_client, "bogus_service") + data = get_data(mutation_response)["services"]["restartService"] + assert_notfound(data) + + assert data["service"] is None + + def test_stop_nonexistent(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_stop_by_name(authorized_client, "bogus_service") From 9a3800ac7bc4aa0765cffcade144cefa3684fbe3 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 6 Oct 2023 13:17:48 +0000 Subject: [PATCH 026/130] test(service): moving errors --- selfprivacy_api/services/service.py | 2 + .../services/test_service/__init__.py | 13 +++- tests/test_graphql/test_services.py | 73 +++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index 8446133..fbe0007 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -247,6 +247,8 @@ class Service(ABC): @abstractmethod def move_to_volume(self, volume: BlockDevice) -> Job: + """Cannot raise errors. + Returns errors as an errored out Job instead.""" pass @classmethod diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index e4ed4cc..1cb5d9f 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -24,6 +24,7 @@ class DummyService(Service): folders: List[str] = [] startstop_delay = 0.0 backuppable = True + movable = True def __init_subclass__(cls, folders: List[str]): cls.folders = folders @@ -62,9 +63,9 @@ class DummyService(Service): domain = "test.com" return f"https://password.{domain}" - @staticmethod - def is_movable() -> bool: - return True + @classmethod + def is_movable(cls) -> bool: + return cls.movable @staticmethod def is_required() -> bool: @@ -137,6 +138,12 @@ class DummyService(Service): we can only set it up dynamically for tests via a classmethod""" cls.backuppable = new_value + @classmethod + def set_movable(cls, new_value: bool) -> None: + """For tests: because is_movale is static, + we can only set it up dynamically for tests via a classmethod""" + cls.movable = new_value + @classmethod def can_be_backed_up(cls) -> bool: """`True` if the service can be backed up.""" diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 0b652c5..df409b9 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -110,6 +110,26 @@ allServices { } """ +API_MOVE_MUTATION = """ +mutation TestMoveService($input: MoveServiceInput!) { + services { + moveService(input: $input) { + success + message + code + job { + uid + status + } + service { + id + status + } + } + } +} +""" + def assert_notfound(data): assert_errorcode(data, 404) @@ -166,6 +186,26 @@ def api_start_by_name(client, service_id: str) -> dict: return response +def api_move(client, service: Service, location: str) -> dict: + return api_move_by_name(client, service.get_id(), location) + + +def api_move_by_name(client, service_id: str, location: str) -> dict: + response = client.post( + "/graphql", + json={ + "query": API_MOVE_MUTATION, + "variables": { + "input": { + "serviceId": service_id, + "location": location, + } + }, + }, + ) + return response + + def api_restart(client, service: Service) -> dict: return api_restart_by_name(client, service.get_id()) @@ -327,6 +367,16 @@ def test_disable_unauthorized(client, only_dummy_service): assert mutation_response.json().get("data") is None +def test_move_nonexistent(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_move_by_name(authorized_client, "bogus_service", "sda1") + data = get_data(mutation_response)["services"]["moveService"] + assert_notfound(data) + + assert data["service"] is None + assert data["job"] is None + + def test_start_nonexistent(authorized_client, only_dummy_service): dummy_service = only_dummy_service mutation_response = api_start_by_name(authorized_client, "bogus_service") @@ -395,3 +445,26 @@ def test_stop_start(authorized_client, only_dummy_service): api_start(authorized_client, dummy_service) api_dummy_service = api_all_services(authorized_client)[0] assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + +def test_move_immovable(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + dummy_service.set_movable(False) + mutation_response = api_move(authorized_client, dummy_service, "sda1") + data = get_data(mutation_response)["services"]["moveService"] + assert_errorcode(data, 400) + + # is there a meaning in returning the service in this? + assert data["service"] is not None + assert data["job"] is None + + +def test_move_no_such_volume(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + mutation_response = api_move(authorized_client, dummy_service, "bogus_volume") + data = get_data(mutation_response)["services"]["moveService"] + assert_notfound(data) + + # is there a meaning in returning the service in this? + assert data["service"] is not None + assert data["job"] is None From 9d7857cb3fc6550d482b952eff764586ea8fb3c0 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 9 Oct 2023 19:22:43 +0000 Subject: [PATCH 027/130] fix(blockdevices): handle nested lsblk --- selfprivacy_api/utils/block_devices.py | 132 ++++++++++++++----------- tests/test_block_device_utils.py | 86 +++++++++++----- 2 files changed, 132 insertions(+), 86 deletions(-) diff --git a/selfprivacy_api/utils/block_devices.py b/selfprivacy_api/utils/block_devices.py index 83fc28f..ab3794d 100644 --- a/selfprivacy_api/utils/block_devices.py +++ b/selfprivacy_api/utils/block_devices.py @@ -1,4 +1,5 @@ -"""Wrapper for block device functions.""" +"""A block device API wrapping lsblk""" +from __future__ import annotations import subprocess import json import typing @@ -11,6 +12,7 @@ def get_block_device(device_name): """ Return a block device by name. """ + # TODO: remove the function and related tests: dublicated by singleton lsblk_output = subprocess.check_output( [ "lsblk", @@ -43,22 +45,37 @@ class BlockDevice: A block device. """ - def __init__(self, block_device): - self.name = block_device["name"] - self.path = block_device["path"] - self.fsavail = str(block_device["fsavail"]) - self.fssize = str(block_device["fssize"]) - self.fstype = block_device["fstype"] - self.fsused = str(block_device["fsused"]) - self.mountpoints = block_device["mountpoints"] - self.label = block_device["label"] - self.uuid = block_device["uuid"] - self.size = str(block_device["size"]) - self.model = block_device["model"] - self.serial = block_device["serial"] - self.type = block_device["type"] + def __init__(self, device_dict: dict): + self.update_from_dict(device_dict) + + def update_from_dict(self, device_dict: dict): + self.name = device_dict["name"] + self.path = device_dict["path"] + self.fsavail = str(device_dict["fsavail"]) + self.fssize = str(device_dict["fssize"]) + self.fstype = device_dict["fstype"] + self.fsused = str(device_dict["fsused"]) + self.mountpoints = device_dict["mountpoints"] + self.label = device_dict["label"] + self.uuid = device_dict["uuid"] + self.size = str(device_dict["size"]) + self.model = device_dict["model"] + self.serial = device_dict["serial"] + self.type = device_dict["type"] self.locked = False + self.children: typing.List[BlockDevice] = [] + if "children" in device_dict.keys(): + for child in device_dict["children"]: + self.children.append(BlockDevice(child)) + + def all_children(self) -> typing.List[BlockDevice]: + result = [] + for child in self.children: + result.extend(child.all_children()) + result.append(child) + return result + def __str__(self): return self.name @@ -82,17 +99,7 @@ class BlockDevice: Update current data and return a dictionary of stats. """ device = get_block_device(self.name) - self.fsavail = str(device["fsavail"]) - self.fssize = str(device["fssize"]) - self.fstype = device["fstype"] - self.fsused = str(device["fsused"]) - self.mountpoints = device["mountpoints"] - self.label = device["label"] - self.uuid = device["uuid"] - self.size = str(device["size"]) - self.model = device["model"] - self.serial = device["serial"] - self.type = device["type"] + self.update_from_dict(device) return { "name": self.name, @@ -110,6 +117,14 @@ class BlockDevice: "type": self.type, } + def is_usable_partition(self): + # Ignore devices with type "rom" + if self.type == "rom": + return False + if self.fstype == "ext4": + return True + return False + def resize(self): """ Resize the block device. @@ -165,41 +180,16 @@ class BlockDevices(metaclass=SingletonMetaclass): """ Update the list of block devices. """ - devices = [] - lsblk_output = subprocess.check_output( - [ - "lsblk", - "-J", - "-b", - "-o", - "NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE", - ] - ) - lsblk_output = lsblk_output.decode("utf-8") - lsblk_output = json.loads(lsblk_output) - for device in lsblk_output["blockdevices"]: - # Ignore devices with type "rom" - if device["type"] == "rom": - continue - # Ignore iso9660 devices - if device["fstype"] == "iso9660": - continue - if device["fstype"] is None: - if "children" in device: - for child in device["children"]: - if child["fstype"] == "ext4": - device = child - break - devices.append(device) - # Add new devices and delete non-existent devices + devices = BlockDevices.lsblk_devices() + + children = [] for device in devices: - if device["name"] not in [ - block_device.name for block_device in self.block_devices - ]: - self.block_devices.append(BlockDevice(device)) - for block_device in self.block_devices: - if block_device.name not in [device["name"] for device in devices]: - self.block_devices.remove(block_device) + children.extend(device.all_children()) + devices.extend(children) + + valid_devices = [device for device in devices if device.is_usable_partition()] + + self.block_devices = valid_devices def get_block_device(self, name: str) -> typing.Optional[BlockDevice]: """ @@ -236,3 +226,25 @@ class BlockDevices(metaclass=SingletonMetaclass): if "/" in block_device.mountpoints: return block_device raise RuntimeError("No root block device found") + + @staticmethod + def lsblk_device_dicts() -> typing.List[dict]: + lsblk_output_bytes = subprocess.check_output( + [ + "lsblk", + "-J", + "-b", + "-o", + "NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE", + ] + ) + lsblk_output = lsblk_output_bytes.decode("utf-8") + return json.loads(lsblk_output)["blockdevices"] + + @staticmethod + def lsblk_devices() -> typing.List[BlockDevice]: + devices = [] + for device in BlockDevices.lsblk_device_dicts(): + devices.append(device) + + return [BlockDevice(device) for device in devices] diff --git a/tests/test_block_device_utils.py b/tests/test_block_device_utils.py index f821e96..b41c89e 100644 --- a/tests/test_block_device_utils.py +++ b/tests/test_block_device_utils.py @@ -13,6 +13,7 @@ from selfprivacy_api.utils.block_devices import ( resize_block_device, ) from tests.common import read_json +from tests.test_common import dummy_service, raw_dummy_service SINGLE_LSBLK_OUTPUT = b""" { @@ -416,32 +417,37 @@ def lsblk_full_mock(mocker): def test_get_block_devices(lsblk_full_mock, authorized_client): block_devices = BlockDevices().get_block_devices() assert len(block_devices) == 2 - assert block_devices[0].name == "sda1" - assert block_devices[0].path == "/dev/sda1" - assert block_devices[0].fsavail == "4605702144" - assert block_devices[0].fssize == "19814920192" - assert block_devices[0].fstype == "ext4" - assert block_devices[0].fsused == "14353719296" - assert block_devices[0].mountpoints == ["/nix/store", "/"] - assert block_devices[0].label is None - assert block_devices[0].uuid == "ec80c004-baec-4a2c-851d-0e1807135511" - assert block_devices[0].size == "20210236928" - assert block_devices[0].model is None - assert block_devices[0].serial is None - assert block_devices[0].type == "part" - assert block_devices[1].name == "sdb" - assert block_devices[1].path == "/dev/sdb" - assert block_devices[1].fsavail == "11888545792" - assert block_devices[1].fssize == "12573614080" - assert block_devices[1].fstype == "ext4" - assert block_devices[1].fsused == "24047616" - assert block_devices[1].mountpoints == ["/volumes/sdb"] - assert block_devices[1].label is None - assert block_devices[1].uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751" - assert block_devices[1].size == "12884901888" - assert block_devices[1].model == "Volume" - assert block_devices[1].serial == "21378102" - assert block_devices[1].type == "disk" + devices_by_name = {device.name: device for device in block_devices} + sda1 = devices_by_name["sda1"] + sdb = devices_by_name["sdb"] + + assert sda1.name == "sda1" + assert sda1.path == "/dev/sda1" + assert sda1.fsavail == "4605702144" + assert sda1.fssize == "19814920192" + assert sda1.fstype == "ext4" + assert sda1.fsused == "14353719296" + assert sda1.mountpoints == ["/nix/store", "/"] + assert sda1.label is None + assert sda1.uuid == "ec80c004-baec-4a2c-851d-0e1807135511" + assert sda1.size == "20210236928" + assert sda1.model is None + assert sda1.serial is None + assert sda1.type == "part" + + assert sdb.name == "sdb" + assert sdb.path == "/dev/sdb" + assert sdb.fsavail == "11888545792" + assert sdb.fssize == "12573614080" + assert sdb.fstype == "ext4" + assert sdb.fsused == "24047616" + assert sdb.mountpoints == ["/volumes/sdb"] + assert sdb.label is None + assert sdb.uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751" + assert sdb.size == "12884901888" + assert sdb.model == "Volume" + assert sdb.serial == "21378102" + assert sdb.type == "disk" def test_get_block_device(lsblk_full_mock, authorized_client): @@ -506,3 +512,31 @@ def test_get_root_block_device(lsblk_full_mock, authorized_client): assert block_device.model is None assert block_device.serial is None assert block_device.type == "part" + + +# Unassuming sanity check, yes this did fail +def test_get_real_devices(): + block_devices = BlockDevices().get_block_devices() + + assert block_devices is not None + assert len(block_devices) > 0 + + +# Unassuming sanity check +def test_get_real_root_device(): + BlockDevices().update() + devices = BlockDevices().get_block_devices() + try: + block_device = BlockDevices().get_root_block_device() + except Exception as e: + raise Exception("cannot get root device:", e, "devices found:", devices) + assert block_device is not None + assert block_device.name is not None + assert block_device.name != "" + + +def test_get_real_root_device_raw(authorized_client): + block_device = BlockDevices().get_root_block_device() + assert block_device is not None + assert block_device.name is not None + assert block_device.name != "" From a12126f6850e8e31ed4528edce2a794e27241370 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 11 Oct 2023 17:04:30 +0000 Subject: [PATCH 028/130] feature(service): error handling on moves --- .../graphql/mutations/services_mutations.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/selfprivacy_api/graphql/mutations/services_mutations.py b/selfprivacy_api/graphql/mutations/services_mutations.py index 86cab10..bac4d88 100644 --- a/selfprivacy_api/graphql/mutations/services_mutations.py +++ b/selfprivacy_api/graphql/mutations/services_mutations.py @@ -4,6 +4,7 @@ import typing import strawberry from selfprivacy_api.graphql import IsAuthenticated from selfprivacy_api.graphql.common_types.jobs import job_to_api_job +from selfprivacy_api.jobs import JobStatus from selfprivacy_api.graphql.common_types.service import ( Service, @@ -160,10 +161,19 @@ class ServicesMutations: service=service_to_graphql_service(service), ) job = service.move_to_volume(volume) - return ServiceJobMutationReturn( - success=True, - message="Service moved.", - code=200, - service=service_to_graphql_service(service), - job=job_to_api_job(job), - ) + if job.status == JobStatus.FINISHED: + return ServiceJobMutationReturn( + success=True, + message="Service moved.", + code=200, + service=service_to_graphql_service(service), + job=job_to_api_job(job), + ) + else: + return ServiceJobMutationReturn( + success=False, + message=f"Service move failure: {job.status_text}", + code=400, + service=service_to_graphql_service(service), + job=job_to_api_job(job), + ) From 9a1d82ec128046d0302a351ef322ecbca4c7763a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 11 Oct 2023 17:19:45 +0000 Subject: [PATCH 029/130] test(service): somewhat support moves for dummy service --- .../services/test_service/__init__.py | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index 1cb5d9f..c75fc07 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -8,9 +8,10 @@ from os import path # from enum import Enum -from selfprivacy_api.jobs import Job +from selfprivacy_api.jobs import Job, Jobs, JobStatus from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus from selfprivacy_api.utils.block_devices import BlockDevice +from selfprivacy_api.services.generic_service_mover import move_service, FolderMoveNames import selfprivacy_api.utils.network as network_utils from selfprivacy_api.services.test_service.icon import BITWARDEN_ICON @@ -25,6 +26,9 @@ class DummyService(Service): startstop_delay = 0.0 backuppable = True movable = True + # if False, we try to actually move + simulate_moving = True + drive = "sda1" def __init_subclass__(cls, folders: List[str]): cls.folders = folders @@ -161,6 +165,16 @@ class DummyService(Service): def set_delay(cls, new_delay_sec: float) -> None: cls.startstop_delay = new_delay_sec + @classmethod + def set_drive(cls, new_drive: str) -> None: + cls.drive = new_drive + + @classmethod + def set_simulated_moves(cls, enabled: bool) -> None: + """If True, this service will not actually call moving code + when moved""" + cls.simulate_moving = enabled + @classmethod def stop(cls): # simulate a failing service unable to stop @@ -197,9 +211,9 @@ class DummyService(Service): storage_usage = 0 return storage_usage - @staticmethod - def get_drive() -> str: - return "sda1" + @classmethod + def get_drive(cls) -> str: + return cls.drive @classmethod def get_folders(cls) -> List[str]: @@ -226,4 +240,22 @@ class DummyService(Service): ] def move_to_volume(self, volume: BlockDevice) -> Job: - pass + job = Jobs.add( + type_id=f"services.{self.get_id()}.move", + name=f"Move {self.get_display_name()}", + description=f"Moving {self.get_display_name()} data to {volume.name}", + ) + if self.simulate_moving is False: + # completely generic code, TODO: make it the default impl. + move_service( + self, + volume, + job, + FolderMoveNames.default_foldermoves(self), + self.get_id(), + ) + else: + Jobs.update(job, status=JobStatus.FINISHED) + + self.set_drive(volume.name) + return job From c83b1a3442d98c101c5a58dfaf21786a8dc1ddee Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 11 Oct 2023 17:26:16 +0000 Subject: [PATCH 030/130] test(block devices): delete an extra update --- tests/test_block_device_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_block_device_utils.py b/tests/test_block_device_utils.py index b41c89e..0fa99f1 100644 --- a/tests/test_block_device_utils.py +++ b/tests/test_block_device_utils.py @@ -524,7 +524,6 @@ def test_get_real_devices(): # Unassuming sanity check def test_get_real_root_device(): - BlockDevices().update() devices = BlockDevices().get_block_devices() try: block_device = BlockDevices().get_root_block_device() From aa287d9cf32e0a8dabb7d736c43ff571f0a8dfc9 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 11 Oct 2023 17:33:03 +0000 Subject: [PATCH 031/130] test(services): try moving to the same device --- tests/data/turned_on.json | 48 ++++++++--------------------- tests/test_graphql/test_services.py | 18 +++++++++++ 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/tests/data/turned_on.json b/tests/data/turned_on.json index c6b758b..5b41501 100644 --- a/tests/data/turned_on.json +++ b/tests/data/turned_on.json @@ -1,11 +1,6 @@ { - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, + "api": {"token": "TEST_TOKEN", "enableSwagger": false}, + "bitwarden": {"enable": true}, "databasePassword": "PASSWORD", "domain": "test.tld", "hashedMasterPassword": "HASHED_PASSWORD", @@ -19,38 +14,19 @@ "ssh": { "enable": true, "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] + "rootKeys": ["ssh-ed25519 KEY test@pc"] }, "username": "tester", - "gitea": { - "enable": true - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "jitsi": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, + "gitea": {"enable": true}, + "ocserv": {"enable": true}, + "pleroma": {"enable": true}, + "jitsi": {"enable": true}, + "autoUpgrade": {"enable": true, "allowReboot": true}, + "useBinds": true, "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, + "sshKeys": ["ssh-rsa KEY test@pc"], + "dns": {"provider": "CLOUDFLARE", "apiKey": "TOKEN"}, + "server": {"provider": "HETZNER"}, "backup": { "provider": "BACKBLAZE", "accountId": "ID", diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index df409b9..2ab6a41 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -5,6 +5,7 @@ from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutatio import selfprivacy_api.services as service_module from selfprivacy_api.services.service import Service, ServiceStatus from selfprivacy_api.services.test_service import DummyService +from selfprivacy_api.utils.block_devices import BlockDevices import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service @@ -468,3 +469,20 @@ def test_move_no_such_volume(authorized_client, only_dummy_service): # is there a meaning in returning the service in this? assert data["service"] is not None assert data["job"] is None + + +def test_move_same_volume(authorized_client, dummy_service): + # dummy_service = only_dummy_service + + # we need a drive that actually exists + root_volume = BlockDevices().get_root_block_device() + dummy_service.set_simulated_moves(False) + dummy_service.set_drive(root_volume.name) + + mutation_response = api_move(authorized_client, dummy_service, root_volume.name) + data = get_data(mutation_response)["services"]["moveService"] + assert_errorcode(data, 400) + + # is there a meaning in returning the service in this? + assert data["service"] is not None + assert data["job"] is not None From 267cdd391b8728326b1c683b61777813188da95c Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 11 Oct 2023 17:34:53 +0000 Subject: [PATCH 032/130] fix(backup): do not store maybe unpicklable service on the queue --- selfprivacy_api/backup/tasks.py | 7 ++++++- .../graphql/mutations/backup_mutations.py | 2 +- tests/test_graphql/test_backup.py | 16 +++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/selfprivacy_api/backup/tasks.py b/selfprivacy_api/backup/tasks.py index 5b36252..a948bff 100644 --- a/selfprivacy_api/backup/tasks.py +++ b/selfprivacy_api/backup/tasks.py @@ -11,7 +11,9 @@ from selfprivacy_api.graphql.common_types.backup import ( from selfprivacy_api.models.backup.snapshot import Snapshot from selfprivacy_api.utils.huey import huey from huey import crontab + from selfprivacy_api.services.service import Service +from selfprivacy_api.services import get_service_by_id from selfprivacy_api.backup import Backups from selfprivacy_api.jobs import Jobs, JobStatus, Job @@ -34,11 +36,14 @@ def validate_datetime(dt: datetime) -> bool: # huey tasks need to return something @huey.task() def start_backup( - service: Service, reason: BackupReason = BackupReason.EXPLICIT + service_id: str, reason: BackupReason = BackupReason.EXPLICIT ) -> bool: """ The worker task that starts the backup process. """ + service = get_service_by_id(service_id) + if service is None: + raise ValueError(f"No such service: {service_id}") Backups.back_up(service, reason) return True diff --git a/selfprivacy_api/graphql/mutations/backup_mutations.py b/selfprivacy_api/graphql/mutations/backup_mutations.py index cc1538e..820564c 100644 --- a/selfprivacy_api/graphql/mutations/backup_mutations.py +++ b/selfprivacy_api/graphql/mutations/backup_mutations.py @@ -148,7 +148,7 @@ class BackupMutations: ) job = add_backup_job(service) - start_backup(service) + start_backup(service_id) return GenericJobMutationReturn( success=True, diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index 187ce11..bb9e217 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -14,6 +14,8 @@ from selfprivacy_api.utils.huey import huey import tempfile +from selfprivacy_api.utils.huey import huey + from tests.test_common import dummy_service, raw_dummy_service from selfprivacy_api.services import Service, get_all_services @@ -69,7 +71,15 @@ def backups_local(tmpdir): @pytest.fixture(scope="function") def backups(tmpdir): - # for those tests that are supposed to pass with any repo + """ + For those tests that are supposed to pass with + both local and cloud repos + """ + + # Sometimes this is false. Idk why. + huey.immediate = True + assert huey.immediate is True + Backups.reset() if BACKUP_PROVIDER_ENVS["kind"] in os.environ.keys(): Backups.set_provider_from_envs() @@ -736,7 +746,7 @@ def simulated_service_stopping_delay(request) -> float: def test_backup_service_task(backups, dummy_service, simulated_service_stopping_delay): dummy_service.set_delay(simulated_service_stopping_delay) - handle = start_backup(dummy_service) + handle = start_backup(dummy_service.get_id()) handle(blocking=True) snaps = Backups.get_snapshots(dummy_service) @@ -781,7 +791,7 @@ def test_backup_larger_file(backups, dummy_service): mega = 2**20 make_large_file(dir, 100 * mega) - handle = start_backup(dummy_service) + handle = start_backup(dummy_service.get_id()) handle(blocking=True) # results will be slightly different on different machines. if someone has troubles with it on their machine, consider dropping this test. From 0b10c083af014858961d9800122d1d041b795b99 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 15:34:14 +0000 Subject: [PATCH 033/130] test(services): test double enables and disables --- tests/test_graphql/test_services.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 2ab6a41..e46ea33 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -448,6 +448,35 @@ def test_stop_start(authorized_client, only_dummy_service): assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value +def test_disable_enable(authorized_client, only_dummy_service): + dummy_service = only_dummy_service + + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["isEnabled"] is True + + # attempting to enable an already enableed service + api_enable(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["isEnabled"] is True + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + api_disable(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["isEnabled"] is False + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + # attempting to disable an already disableped service + api_disable(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["isEnabled"] is False + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + api_enable(authorized_client, dummy_service) + api_dummy_service = api_all_services(authorized_client)[0] + assert api_dummy_service["isEnabled"] is True + assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value + + def test_move_immovable(authorized_client, only_dummy_service): dummy_service = only_dummy_service dummy_service.set_movable(False) From 23cc33b9d9604f45adf044c8bd6492b663645461 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 15:38:19 +0000 Subject: [PATCH 034/130] test(services): delete redundant rest tests from bitwarden --- .../services/test_bitwarden.py | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_bitwarden.py b/tests/test_rest_endpoints/services/test_bitwarden.py index 3977253..f3e3674 100644 --- a/tests/test_rest_endpoints/services/test_bitwarden.py +++ b/tests/test_rest_endpoints/services/test_bitwarden.py @@ -43,60 +43,6 @@ def bitwarden_undefined(mocker, datadir): ############################################################################### -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_unauthorized(client, bitwarden_off, endpoint): - response = client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_illegal_methods(authorized_client, bitwarden_off, endpoint): - response = authorized_client.get(f"/services/bitwarden/{endpoint}") - assert response.status_code == 405 - response = authorized_client.put(f"/services/bitwarden/{endpoint}") - assert response.status_code == 405 - response = authorized_client.delete(f"/services/bitwarden/{endpoint}") - assert response.status_code == 405 - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_off(authorized_client, bitwarden_off, endpoint, target_file): - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - assert read_json(bitwarden_off / "turned_off.json") == read_json( - bitwarden_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_on(authorized_client, bitwarden_on, endpoint, target_file): - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - assert read_json(bitwarden_on / "turned_on.json") == read_json( - bitwarden_on / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_twice(authorized_client, bitwarden_off, endpoint, target_file): - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - assert read_json(bitwarden_off / "turned_off.json") == read_json( - bitwarden_off / target_file - ) - - @pytest.mark.parametrize( "endpoint,target_file", [("enable", "turned_on.json"), ("disable", "turned_off.json")], From e1083f32212801a9b48720048767e8ed864cc76d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 16:05:12 +0000 Subject: [PATCH 035/130] refactor(services): make a default implementation of enable/disable --- selfprivacy_api/services/service.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index fbe0007..636b7f8 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -12,6 +12,7 @@ from selfprivacy_api.services.generic_size_counter import get_storage_usage from selfprivacy_api.services.owned_path import OwnedPath from selfprivacy_api import utils from selfprivacy_api.utils.waitloop import wait_until_true +from selfprivacy_api.utils import ReadUserData, WriteUserData, get_domain DEFAULT_START_STOP_TIMEOUT = 5 * 60 @@ -137,17 +138,24 @@ class Service(ABC): """The status of the service, reported by systemd.""" pass - @staticmethod - @abstractmethod - def enable(): + # But they do not really enable? + @classmethod + def enable(cls): """Enable the service. Usually this means enabling systemd unit.""" - pass + name = cls.get_id() + with WriteUserData() as user_data: + if "gitea" not in user_data: + user_data[name] = {} + user_data[name]["enable"] = True - @staticmethod - @abstractmethod - def disable(): + @classmethod + def disable(cls): """Disable the service. Usually this means disabling systemd unit.""" - pass + name = cls.get_id() + with WriteUserData() as user_data: + if "gitea" not in user_data: + user_data[name] = {} + user_data[name]["enable"] = False @staticmethod @abstractmethod From 708c5cbc98e0d5310e032b364c694407e3084f64 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 16:06:30 +0000 Subject: [PATCH 036/130] refactor(services): delete enable/disable from gitea --- selfprivacy_api/services/gitea/__init__.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/selfprivacy_api/services/gitea/__init__.py b/selfprivacy_api/services/gitea/__init__.py index 08f223e..f2aa6d0 100644 --- a/selfprivacy_api/services/gitea/__init__.py +++ b/selfprivacy_api/services/gitea/__init__.py @@ -71,22 +71,6 @@ class Gitea(Service): """ return get_service_status("gitea.service") - @staticmethod - def enable(): - """Enable Gitea service.""" - with WriteUserData() as user_data: - if "gitea" not in user_data: - user_data["gitea"] = {} - user_data["gitea"]["enable"] = True - - @staticmethod - def disable(): - """Disable Gitea service.""" - with WriteUserData() as user_data: - if "gitea" not in user_data: - user_data["gitea"] = {} - user_data["gitea"]["enable"] = False - @staticmethod def stop(): subprocess.run(["systemctl", "stop", "gitea.service"]) From 6f035dc0db8ff83226547f419957110e1d0276bc Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 16:14:43 +0000 Subject: [PATCH 037/130] refactor(services): add default implementation to get_enabled --- selfprivacy_api/services/service.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index 636b7f8..eca366f 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -126,11 +126,12 @@ class Service(ABC): """ pass - @staticmethod - @abstractmethod - def is_enabled() -> bool: + @classmethod + def is_enabled(cls) -> bool: """`True` if the service is enabled.""" - pass + name = cls.get_id() + with ReadUserData() as user_data: + return user_data.get(name, {}).get("enable", False) @staticmethod @abstractmethod From c7be9c7427fe1b51b5fe40d90c0a46edea5012c4 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 16:21:20 +0000 Subject: [PATCH 038/130] refactor(services): delete is_enabled() from gitea --- selfprivacy_api/services/gitea/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/selfprivacy_api/services/gitea/__init__.py b/selfprivacy_api/services/gitea/__init__.py index f2aa6d0..9b6f80f 100644 --- a/selfprivacy_api/services/gitea/__init__.py +++ b/selfprivacy_api/services/gitea/__init__.py @@ -54,11 +54,6 @@ class Gitea(Service): def get_backup_description() -> str: return "Git repositories, database and user data." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("gitea", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: """ From 0078ed0c3a911a0666fce2cde8c11fd662c88fa5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 16:31:52 +0000 Subject: [PATCH 039/130] refactor(services): delete xxenablexx functions from jitsi --- selfprivacy_api/services/jitsi/__init__.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/selfprivacy_api/services/jitsi/__init__.py b/selfprivacy_api/services/jitsi/__init__.py index fed6f33..d5677cc 100644 --- a/selfprivacy_api/services/jitsi/__init__.py +++ b/selfprivacy_api/services/jitsi/__init__.py @@ -55,33 +55,12 @@ class Jitsi(Service): def get_backup_description() -> str: return "Secrets that are used to encrypt the communication." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("jitsi", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: return get_service_status_from_several_units( ["jitsi-videobridge.service", "jicofo.service"] ) - @staticmethod - def enable(): - """Enable Jitsi service.""" - with WriteUserData() as user_data: - if "jitsi" not in user_data: - user_data["jitsi"] = {} - user_data["jitsi"]["enable"] = True - - @staticmethod - def disable(): - """Disable Gitea service.""" - with WriteUserData() as user_data: - if "jitsi" not in user_data: - user_data["jitsi"] = {} - user_data["jitsi"]["enable"] = False - @staticmethod def stop(): subprocess.run( From d7c75e0aa8a5a3de0d8ce8c6d199538e93e0f56c Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 20 Oct 2023 17:02:43 +0000 Subject: [PATCH 040/130] fix(services): do not randomly exit the huey immediate mode --- tests/test_common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_common.py b/tests/test_common.py index 0bcd4bc..5da43e9 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -5,6 +5,7 @@ import os import pytest from selfprivacy_api.utils import WriteUserData, ReadUserData +from selfprivacy_api.utils.huey import huey from os import path from os import makedirs @@ -53,6 +54,9 @@ def dummy_service(tmpdir, raw_dummy_service) -> Generator[Service, None, None]: # register our service services.services.append(service) + huey.immediate = True + assert huey.immediate is True + assert get_service_by_id(service.get_id()) is not None yield service From 1a65545c290ab50c8106a8316e6997ca1ff33c4f Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 4 Dec 2023 15:35:50 +0000 Subject: [PATCH 041/130] test(backups, token_repo): move non-graphql tests out of graphql --- tests/{test_graphql => }/test_backup.py | 0 tests/test_graphql/test_api_backup.py | 4 ++-- tests/test_graphql/test_services.py | 2 +- tests/{test_graphql => }/test_localsecret.py | 0 .../test_repository/test_json_tokens_repository.py | 0 .../test_json_tokens_repository/empty_keys.json | 9 +++++++++ .../test_json_tokens_repository/null_keys.json | 0 .../test_json_tokens_repository/tokens.json | 0 .../test_repository/test_tokens_repository.py | 0 .../test_tokens_repository/empty_keys.json | 9 +++++++++ .../test_tokens_repository/null_keys.json | 0 .../test_repository/test_tokens_repository/tokens.json | 0 12 files changed, 21 insertions(+), 3 deletions(-) rename tests/{test_graphql => }/test_backup.py (100%) rename tests/{test_graphql => }/test_localsecret.py (100%) rename tests/{test_graphql => }/test_repository/test_json_tokens_repository.py (100%) create mode 100644 tests/test_repository/test_json_tokens_repository/empty_keys.json rename tests/{test_graphql => }/test_repository/test_json_tokens_repository/null_keys.json (100%) rename tests/{test_graphql => }/test_repository/test_json_tokens_repository/tokens.json (100%) rename tests/{test_graphql => }/test_repository/test_tokens_repository.py (100%) create mode 100644 tests/test_repository/test_tokens_repository/empty_keys.json rename tests/{test_graphql => }/test_repository/test_tokens_repository/null_keys.json (100%) rename tests/{test_graphql => }/test_repository/test_tokens_repository/tokens.json (100%) diff --git a/tests/test_graphql/test_backup.py b/tests/test_backup.py similarity index 100% rename from tests/test_graphql/test_backup.py rename to tests/test_backup.py diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index bc4b7f1..50d65d8 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -1,6 +1,6 @@ from os import path -from tests.test_graphql.test_backup import backups -from tests.test_graphql.test_backup import raw_dummy_service, dummy_service +from tests.test_backup import backups +from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_backup_query diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index e46ea33..aa8d2f3 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -10,7 +10,7 @@ from selfprivacy_api.utils.block_devices import BlockDevices import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_service_query -from tests.test_graphql.common import assert_ok, get_data +from tests.test_graphql.test_api_backup import assert_ok, get_data @pytest.fixture() diff --git a/tests/test_graphql/test_localsecret.py b/tests/test_localsecret.py similarity index 100% rename from tests/test_graphql/test_localsecret.py rename to tests/test_localsecret.py diff --git a/tests/test_graphql/test_repository/test_json_tokens_repository.py b/tests/test_repository/test_json_tokens_repository.py similarity index 100% rename from tests/test_graphql/test_repository/test_json_tokens_repository.py rename to tests/test_repository/test_json_tokens_repository.py diff --git a/tests/test_repository/test_json_tokens_repository/empty_keys.json b/tests/test_repository/test_json_tokens_repository/empty_keys.json new file mode 100644 index 0000000..2131ddf --- /dev/null +++ b/tests/test_repository/test_json_tokens_repository/empty_keys.json @@ -0,0 +1,9 @@ +{ + "tokens": [ + { + "token": "KG9ni-B-CMPk327Zv1qC7YBQaUGaBUcgdkvMvQ2atFI", + "name": "primary_token", + "date": "2022-07-15 17:41:31.675698" + } + ] +} diff --git a/tests/test_graphql/test_repository/test_json_tokens_repository/null_keys.json b/tests/test_repository/test_json_tokens_repository/null_keys.json similarity index 100% rename from tests/test_graphql/test_repository/test_json_tokens_repository/null_keys.json rename to tests/test_repository/test_json_tokens_repository/null_keys.json diff --git a/tests/test_graphql/test_repository/test_json_tokens_repository/tokens.json b/tests/test_repository/test_json_tokens_repository/tokens.json similarity index 100% rename from tests/test_graphql/test_repository/test_json_tokens_repository/tokens.json rename to tests/test_repository/test_json_tokens_repository/tokens.json diff --git a/tests/test_graphql/test_repository/test_tokens_repository.py b/tests/test_repository/test_tokens_repository.py similarity index 100% rename from tests/test_graphql/test_repository/test_tokens_repository.py rename to tests/test_repository/test_tokens_repository.py diff --git a/tests/test_repository/test_tokens_repository/empty_keys.json b/tests/test_repository/test_tokens_repository/empty_keys.json new file mode 100644 index 0000000..2131ddf --- /dev/null +++ b/tests/test_repository/test_tokens_repository/empty_keys.json @@ -0,0 +1,9 @@ +{ + "tokens": [ + { + "token": "KG9ni-B-CMPk327Zv1qC7YBQaUGaBUcgdkvMvQ2atFI", + "name": "primary_token", + "date": "2022-07-15 17:41:31.675698" + } + ] +} diff --git a/tests/test_graphql/test_repository/test_tokens_repository/null_keys.json b/tests/test_repository/test_tokens_repository/null_keys.json similarity index 100% rename from tests/test_graphql/test_repository/test_tokens_repository/null_keys.json rename to tests/test_repository/test_tokens_repository/null_keys.json diff --git a/tests/test_graphql/test_repository/test_tokens_repository/tokens.json b/tests/test_repository/test_tokens_repository/tokens.json similarity index 100% rename from tests/test_graphql/test_repository/test_tokens_repository/tokens.json rename to tests/test_repository/test_tokens_repository/tokens.json From 2e59e7e880f85345a367eee437fdf15e3f8db207 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 6 Dec 2023 13:57:39 +0000 Subject: [PATCH 042/130] better error reporting in graphql tests --- tests/test_graphql/common.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_graphql/common.py b/tests/test_graphql/common.py index d473433..1a415bc 100644 --- a/tests/test_graphql/common.py +++ b/tests/test_graphql/common.py @@ -25,7 +25,13 @@ def assert_empty(response): def assert_data(response): assert response.status_code == 200 - data = response.json().get("data") + response = response.json() + + if ( + "errors" in response.keys() + ): # convenience for debugging, this will display error + raise ValueError(response["errors"]) + data = response.get("data") assert data is not None return data From f5999516fa3722922ac507940deca75469ce9664 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 14:47:57 +0000 Subject: [PATCH 043/130] feature(services): better error reporting in disable and enable service --- .../graphql/mutations/services_mutations.py | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/selfprivacy_api/graphql/mutations/services_mutations.py b/selfprivacy_api/graphql/mutations/services_mutations.py index bac4d88..ad3b1b9 100644 --- a/selfprivacy_api/graphql/mutations/services_mutations.py +++ b/selfprivacy_api/graphql/mutations/services_mutations.py @@ -48,14 +48,22 @@ class ServicesMutations: @strawberry.mutation(permission_classes=[IsAuthenticated]) def enable_service(self, service_id: str) -> ServiceMutationReturn: """Enable service.""" - service = get_service_by_id(service_id) - if service is None: + try: + service = get_service_by_id(service_id) + if service is None: + return ServiceMutationReturn( + success=False, + message="Service not found.", + code=404, + ) + service.enable() + except Exception as e: return ServiceMutationReturn( success=False, - message="Service not found.", - code=404, + message=format_error(e), + code=400, ) - service.enable() + return ServiceMutationReturn( success=True, message="Service enabled.", @@ -66,14 +74,21 @@ class ServicesMutations: @strawberry.mutation(permission_classes=[IsAuthenticated]) def disable_service(self, service_id: str) -> ServiceMutationReturn: """Disable service.""" - service = get_service_by_id(service_id) - if service is None: + try: + service = get_service_by_id(service_id) + if service is None: + return ServiceMutationReturn( + success=False, + message="Service not found.", + code=404, + ) + service.disable() + except Exception as e: return ServiceMutationReturn( success=False, - message="Service not found.", - code=404, + message=format_error(e), + code=400, ) - service.disable() return ServiceMutationReturn( success=True, message="Service disabled.", @@ -177,3 +192,7 @@ class ServicesMutations: service=service_to_graphql_service(service), job=job_to_api_job(job), ) + + +def format_error(e: Exception) -> str: + return type(e).__name__ + ": " + str(e) From 368ab22fbb67dd0d69d9cc5ce716fac7317bb6fb Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 14:50:53 +0000 Subject: [PATCH 044/130] fix(services): replace stray gitea reference with a generic identifier in deiable/enable --- selfprivacy_api/services/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index eca366f..dc7579d 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -145,7 +145,7 @@ class Service(ABC): """Enable the service. Usually this means enabling systemd unit.""" name = cls.get_id() with WriteUserData() as user_data: - if "gitea" not in user_data: + if name not in user_data: user_data[name] = {} user_data[name]["enable"] = True @@ -154,7 +154,7 @@ class Service(ABC): """Disable the service. Usually this means disabling systemd unit.""" name = cls.get_id() with WriteUserData() as user_data: - if "gitea" not in user_data: + if name not in user_data: user_data[name] = {} user_data[name]["enable"] = False From 5c1dd93931648960dcfe5285472607202b85d214 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 14:52:33 +0000 Subject: [PATCH 045/130] test(services): test that undisableable services are handled correctly --- tests/test_graphql/test_services.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index aa8d2f3..a266f63 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -3,6 +3,7 @@ from typing import Generator from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations import selfprivacy_api.services as service_module +from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import Service, ServiceStatus from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.utils.block_devices import BlockDevices @@ -515,3 +516,18 @@ def test_move_same_volume(authorized_client, dummy_service): # is there a meaning in returning the service in this? assert data["service"] is not None assert data["job"] is not None + + +def test_mailservice_cannot_enable_disable(authorized_client): + mailservice = get_service_by_id("email") + + mutation_response = api_enable(authorized_client, mailservice) + data = get_data(mutation_response)["services"]["enableService"] + assert_errorcode(data, 400) + # TODO?: we cannot convert mailservice to graphql Service without /var/domain yet + # assert data["service"] is not None + + mutation_response = api_disable(authorized_client, mailservice) + data = get_data(mutation_response)["services"]["disableService"] + assert_errorcode(data, 400) + # assert data["service"] is not None From ffc60fc8b4863bf9704e7965311dd683fceb018e Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 15:26:21 +0000 Subject: [PATCH 046/130] test(services): use actual json enabling and disabling --- .../services/test_service/__init__.py | 34 ------------------- tests/test_common.py | 5 ++- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index c75fc07..1e315f5 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -37,8 +37,6 @@ class DummyService(Service): super().__init__() with open(self.status_file(), "w") as file: file.write(ServiceStatus.ACTIVE.value) - with open(self.enabled_file(), "w") as file: - file.write("True") @staticmethod def get_id() -> str: @@ -79,36 +77,12 @@ class DummyService(Service): def get_backup_description() -> str: return "How did we get here?" - @classmethod - def is_enabled(cls) -> bool: - return cls.get_enabled() - @classmethod def status_file(cls) -> str: dir = cls.folders[0] # we do not REALLY want to store our state in our declared folders return path.join(dir, "..", "service_status") - @classmethod - def enabled_file(cls) -> str: - dir = cls.folders[0] - return path.join(dir, "..", "service_enabled") - - @classmethod - def get_enabled(cls) -> bool: - with open(cls.enabled_file(), "r") as file: - string = file.read().strip() - if "True" in string: - return True - if "False" in string: - return False - raise ValueError("test service enabled/disabled status file got corrupted") - - @classmethod - def set_enabled(cls, enabled: bool): - with open(cls.enabled_file(), "w") as file: - status_string = file.write(str(enabled)) - @classmethod def set_status(cls, status: ServiceStatus): with open(cls.status_file(), "w") as file: @@ -153,14 +127,6 @@ class DummyService(Service): """`True` if the service can be backed up.""" return cls.backuppable - @classmethod - def enable(cls): - cls.set_enabled(True) - - @classmethod - def disable(cls): - cls.set_enabled(False) - @classmethod def set_delay(cls, new_delay_sec: float) -> None: cls.startstop_delay = new_delay_sec diff --git a/tests/test_common.py b/tests/test_common.py index 5da43e9..5c433a0 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -48,7 +48,9 @@ def raw_dummy_service(tmpdir): @pytest.fixture() -def dummy_service(tmpdir, raw_dummy_service) -> Generator[Service, None, None]: +def dummy_service( + tmpdir, raw_dummy_service, generic_userdata +) -> Generator[Service, None, None]: service = raw_dummy_service # register our service @@ -58,6 +60,7 @@ def dummy_service(tmpdir, raw_dummy_service) -> Generator[Service, None, None]: assert huey.immediate is True assert get_service_by_id(service.get_id()) is not None + service.enable() yield service # cleanup because apparently it matters wrt tasks From 22f157b6ff38bae971f4f0f52c32bd78d8373ade Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 16:02:57 +0000 Subject: [PATCH 047/130] test(services): add a test that we actually read json --- tests/test_graphql/test_services.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index a266f63..0a84122 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -1,12 +1,14 @@ import pytest from typing import Generator +from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.utils.block_devices import BlockDevices + from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations import selfprivacy_api.services as service_module from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import Service, ServiceStatus from selfprivacy_api.services.test_service import DummyService -from selfprivacy_api.utils.block_devices import BlockDevices import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service @@ -531,3 +533,12 @@ def test_mailservice_cannot_enable_disable(authorized_client): data = get_data(mutation_response)["services"]["disableService"] assert_errorcode(data, 400) # assert data["service"] is not None + + +def enabling_disabling_reads_json(dummy_service: DummyService): + with WriteUserData() as data: + data[dummy_service.get_id()]["enabled"] = False + assert dummy_service.is_enabled() is False + with WriteUserData() as data: + data[dummy_service.get_id()]["enabled"] = True + assert dummy_service.is_enabled() is True From 834e8c060331854755a40bb9acbc1c0629e98a63 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 16:08:47 +0000 Subject: [PATCH 048/130] test(services): add a test that we actually write json --- tests/test_graphql/test_services.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 0a84122..f55a488 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -542,3 +542,15 @@ def enabling_disabling_reads_json(dummy_service: DummyService): with WriteUserData() as data: data[dummy_service.get_id()]["enabled"] = True assert dummy_service.is_enabled() is True + + +def enabling_disabling_writes_json(dummy_service: DummyService): + dummy_service.disable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enabled"] is False + dummy_service.enable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enabled"] is True + dummy_service.disable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enabled"] is False From bf0b774295e294bbba54b308c57e8017d6645fb6 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 16:33:21 +0000 Subject: [PATCH 049/130] test(services): fix last tests being not enabled, and a field typo0 --- tests/test_graphql/test_services.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index f55a488..d67c053 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -535,22 +535,23 @@ def test_mailservice_cannot_enable_disable(authorized_client): # assert data["service"] is not None -def enabling_disabling_reads_json(dummy_service: DummyService): +def test_enabling_disabling_reads_json(dummy_service: DummyService): with WriteUserData() as data: - data[dummy_service.get_id()]["enabled"] = False + data[dummy_service.get_id()]["enable"] = False assert dummy_service.is_enabled() is False with WriteUserData() as data: - data[dummy_service.get_id()]["enabled"] = True + data[dummy_service.get_id()]["enable"] = True assert dummy_service.is_enabled() is True -def enabling_disabling_writes_json(dummy_service: DummyService): +def test_enabling_disabling_writes_json(dummy_service: DummyService): + dummy_service.disable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enabled"] is False + assert data[dummy_service.get_id()]["enable"] is False dummy_service.enable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enabled"] is True + assert data[dummy_service.get_id()]["enable"] is True dummy_service.disable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enabled"] is False + assert data[dummy_service.get_id()]["enable"] is False From bcf57ea738ea9a715d1051262a019cc08502be58 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 16:36:26 +0000 Subject: [PATCH 050/130] test(services): test possibly undefined json fields. On writing --- tests/test_graphql/test_services.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index d67c053..8a88ef7 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -544,7 +544,23 @@ def test_enabling_disabling_reads_json(dummy_service: DummyService): assert dummy_service.is_enabled() is True -def test_enabling_disabling_writes_json(dummy_service: DummyService): +@pytest.fixture(params=["normally_enabled", "deleted_attribute", "service_not_in_json"]) +def possibly_dubiously_enabled_service( + dummy_service: DummyService, request +) -> DummyService: + if request.param == "deleted_attribute": + with WriteUserData() as data: + del data[dummy_service.get_id()]["enable"] + if request.param == "service_not_in_json": + with WriteUserData() as data: + del data[dummy_service.get_id()] + return dummy_service + + +def test_enabling_disabling_writes_json( + possibly_dubiously_enabled_service: DummyService, +): + dummy_service = possibly_dubiously_enabled_service dummy_service.disable() with ReadUserData() as data: From 9d3fd45c2c98c035554f141ae54e81f5ebb9274a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 16:51:07 +0000 Subject: [PATCH 051/130] =?UTF-8?q?test=EE=81=91(services):=20missing=20in?= =?UTF-8?q?fo=20on=20service=20enabled=20status=20returns=20False?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- selfprivacy_api/services/service.py | 7 ++++++- tests/test_graphql/test_services.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index dc7579d..a53c028 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -128,7 +128,12 @@ class Service(ABC): @classmethod def is_enabled(cls) -> bool: - """`True` if the service is enabled.""" + """ + `True` if the service is enabled. + `False` if it is not enabled or not defined in file + If there is nothing in the file, this is equivalent to False + because NixOS won't enable it then. + """ name = cls.get_id() with ReadUserData() as user_data: return user_data.get(name, {}).get("enable", False) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index 8a88ef7..f28f204 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -557,6 +557,25 @@ def possibly_dubiously_enabled_service( return dummy_service +# Yeah, idk yet how to dry it. +@pytest.fixture(params=["deleted_attribute", "service_not_in_json"]) +def undefined_enabledness_service(dummy_service: DummyService, request) -> DummyService: + if request.param == "deleted_attribute": + with WriteUserData() as data: + del data[dummy_service.get_id()]["enable"] + if request.param == "service_not_in_json": + with WriteUserData() as data: + del data[dummy_service.get_id()] + return dummy_service + + +def test_undefined_enabledness_in_json_means_False( + undefined_enabledness_service: DummyService, +): + dummy_service = undefined_enabledness_service + assert dummy_service.is_enabled() is False + + def test_enabling_disabling_writes_json( possibly_dubiously_enabled_service: DummyService, ): From c1cc1e00ed0dfbee325ee1e9db87b0278638ccb3 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 17:26:00 +0000 Subject: [PATCH 052/130] test(services): move non-gql enable+json tests out of gql tests towards backend tests --- tests/test_graphql/test_services.py | 60 --------------------------- tests/test_services.py | 64 ++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index f28f204..e86d070 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -1,16 +1,13 @@ import pytest from typing import Generator -from selfprivacy_api.utils import ReadUserData, WriteUserData from selfprivacy_api.utils.block_devices import BlockDevices -from selfprivacy_api.graphql.mutations.services_mutations import ServicesMutations import selfprivacy_api.services as service_module from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import Service, ServiceStatus from selfprivacy_api.services.test_service import DummyService -import tests.test_graphql.test_api_backup from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_service_query from tests.test_graphql.test_api_backup import assert_ok, get_data @@ -533,60 +530,3 @@ def test_mailservice_cannot_enable_disable(authorized_client): data = get_data(mutation_response)["services"]["disableService"] assert_errorcode(data, 400) # assert data["service"] is not None - - -def test_enabling_disabling_reads_json(dummy_service: DummyService): - with WriteUserData() as data: - data[dummy_service.get_id()]["enable"] = False - assert dummy_service.is_enabled() is False - with WriteUserData() as data: - data[dummy_service.get_id()]["enable"] = True - assert dummy_service.is_enabled() is True - - -@pytest.fixture(params=["normally_enabled", "deleted_attribute", "service_not_in_json"]) -def possibly_dubiously_enabled_service( - dummy_service: DummyService, request -) -> DummyService: - if request.param == "deleted_attribute": - with WriteUserData() as data: - del data[dummy_service.get_id()]["enable"] - if request.param == "service_not_in_json": - with WriteUserData() as data: - del data[dummy_service.get_id()] - return dummy_service - - -# Yeah, idk yet how to dry it. -@pytest.fixture(params=["deleted_attribute", "service_not_in_json"]) -def undefined_enabledness_service(dummy_service: DummyService, request) -> DummyService: - if request.param == "deleted_attribute": - with WriteUserData() as data: - del data[dummy_service.get_id()]["enable"] - if request.param == "service_not_in_json": - with WriteUserData() as data: - del data[dummy_service.get_id()] - return dummy_service - - -def test_undefined_enabledness_in_json_means_False( - undefined_enabledness_service: DummyService, -): - dummy_service = undefined_enabledness_service - assert dummy_service.is_enabled() is False - - -def test_enabling_disabling_writes_json( - possibly_dubiously_enabled_service: DummyService, -): - dummy_service = possibly_dubiously_enabled_service - - dummy_service.disable() - with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is False - dummy_service.enable() - with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is True - dummy_service.disable() - with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is False diff --git a/tests/test_services.py b/tests/test_services.py index 3eef0cd..3addf05 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -1,8 +1,12 @@ """ Tests for generic service methods """ +import pytest from pytest import raises +from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.utils.waitloop import wait_until_true + from selfprivacy_api.services.bitwarden import Bitwarden from selfprivacy_api.services.pleroma import Pleroma from selfprivacy_api.services.owned_path import OwnedPath @@ -10,9 +14,8 @@ from selfprivacy_api.services.generic_service_mover import FolderMoveNames from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService -from selfprivacy_api.utils.waitloop import wait_until_true -from tests.test_common import raw_dummy_service +from tests.test_common import raw_dummy_service, dummy_service def test_unimplemented_folders_raises(): @@ -87,3 +90,60 @@ def test_foldermoves_from_ownedpaths(): group="vaultwarden", owner="vaultwarden", ) + + +def test_enabling_disabling_reads_json(dummy_service: DummyService): + with WriteUserData() as data: + data[dummy_service.get_id()]["enable"] = False + assert dummy_service.is_enabled() is False + with WriteUserData() as data: + data[dummy_service.get_id()]["enable"] = True + assert dummy_service.is_enabled() is True + + +@pytest.fixture(params=["normally_enabled", "deleted_attribute", "service_not_in_json"]) +def possibly_dubiously_enabled_service( + dummy_service: DummyService, request +) -> DummyService: + if request.param == "deleted_attribute": + with WriteUserData() as data: + del data[dummy_service.get_id()]["enable"] + if request.param == "service_not_in_json": + with WriteUserData() as data: + del data[dummy_service.get_id()] + return dummy_service + + +# Yeah, idk yet how to dry it. +@pytest.fixture(params=["deleted_attribute", "service_not_in_json"]) +def undefined_enabledness_service(dummy_service: DummyService, request) -> DummyService: + if request.param == "deleted_attribute": + with WriteUserData() as data: + del data[dummy_service.get_id()]["enable"] + if request.param == "service_not_in_json": + with WriteUserData() as data: + del data[dummy_service.get_id()] + return dummy_service + + +def test_undefined_enabledness_in_json_means_False( + undefined_enabledness_service: DummyService, +): + dummy_service = undefined_enabledness_service + assert dummy_service.is_enabled() is False + + +def test_enabling_disabling_writes_json( + possibly_dubiously_enabled_service: DummyService, +): + dummy_service = possibly_dubiously_enabled_service + + dummy_service.disable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enable"] is False + dummy_service.enable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enable"] is True + dummy_service.disable() + with ReadUserData() as data: + assert data[dummy_service.get_id()]["enable"] is False From 29870652314c6c2af10a3fef6742e3051ec55645 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 17:31:13 +0000 Subject: [PATCH 053/130] test(services): remove bitwarden tests because redundant --- .../services/test_bitwarden.py | 71 ------------------- .../test_bitwarden/enable_undefined.json | 56 --------------- .../services/test_bitwarden/turned_off.json | 57 --------------- .../services/test_bitwarden/turned_on.json | 57 --------------- .../services/test_bitwarden/undefined.json | 54 -------------- 5 files changed, 295 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_bitwarden.py delete mode 100644 tests/test_rest_endpoints/services/test_bitwarden/enable_undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_bitwarden/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_bitwarden/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_bitwarden/undefined.json diff --git a/tests/test_rest_endpoints/services/test_bitwarden.py b/tests/test_rest_endpoints/services/test_bitwarden.py deleted file mode 100644 index f3e3674..0000000 --- a/tests/test_rest_endpoints/services/test_bitwarden.py +++ /dev/null @@ -1,71 +0,0 @@ -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r") as f: - return json.load(f) - - -############################################################################### - - -@pytest.fixture -def bitwarden_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["bitwarden"]["enable"] == False - return datadir - - -@pytest.fixture -def bitwarden_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["bitwarden"]["enable"] == True - return datadir - - -@pytest.fixture -def bitwarden_enable_undefined(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json" - ) - assert "enable" not in read_json(datadir / "enable_undefined.json")["bitwarden"] - return datadir - - -@pytest.fixture -def bitwarden_undefined(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "bitwarden" not in read_json(datadir / "undefined.json") - return datadir - - -############################################################################### - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_attribute_deleted( - authorized_client, bitwarden_enable_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - assert read_json(bitwarden_enable_undefined / "enable_undefined.json") == read_json( - bitwarden_enable_undefined / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_bitwarden_undefined( - authorized_client, bitwarden_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/bitwarden/{endpoint}") - assert response.status_code == 200 - assert read_json(bitwarden_undefined / "undefined.json") == read_json( - bitwarden_undefined / target_file - ) diff --git a/tests/test_rest_endpoints/services/test_bitwarden/enable_undefined.json b/tests/test_rest_endpoints/services/test_bitwarden/enable_undefined.json deleted file mode 100644 index 1a95e85..0000000 --- a/tests/test_rest_endpoints/services/test_bitwarden/enable_undefined.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_bitwarden/turned_off.json b/tests/test_rest_endpoints/services/test_bitwarden/turned_off.json deleted file mode 100644 index c1691ea..0000000 --- a/tests/test_rest_endpoints/services/test_bitwarden/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_bitwarden/turned_on.json b/tests/test_rest_endpoints/services/test_bitwarden/turned_on.json deleted file mode 100644 index 42999d8..0000000 --- a/tests/test_rest_endpoints/services/test_bitwarden/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_bitwarden/undefined.json b/tests/test_rest_endpoints/services/test_bitwarden/undefined.json deleted file mode 100644 index ee288c2..0000000 --- a/tests/test_rest_endpoints/services/test_bitwarden/undefined.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 5214d5e462c19a1905bd6e0c15a665844330c130 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 18:13:07 +0000 Subject: [PATCH 054/130] test(services): add unauthorized move test --- tests/test_graphql/api_common.py | 89 ------------------------- tests/test_graphql/common.py | 26 ++++---- tests/test_graphql/test_api_backup.py | 2 +- tests/test_graphql/test_api_devices.py | 27 ++++---- tests/test_graphql/test_api_recovery.py | 46 ++++++++----- tests/test_graphql/test_services.py | 42 ++++-------- 6 files changed, 72 insertions(+), 160 deletions(-) delete mode 100644 tests/test_graphql/api_common.py diff --git a/tests/test_graphql/api_common.py b/tests/test_graphql/api_common.py deleted file mode 100644 index 4e4aec2..0000000 --- a/tests/test_graphql/api_common.py +++ /dev/null @@ -1,89 +0,0 @@ -from tests.common import generate_api_query -from tests.conftest import TOKENS_FILE_CONTENTS, DEVICE_WE_AUTH_TESTS_WITH - -ORIGINAL_DEVICES = TOKENS_FILE_CONTENTS["tokens"] - - -def assert_ok(response, request): - data = assert_data(response) - assert data[request]["success"] is True - assert data[request]["message"] is not None - assert data[request]["code"] == 200 - - -def assert_errorcode(response, request, code): - data = assert_data(response) - assert data[request]["success"] is False - assert data[request]["message"] is not None - assert data[request]["code"] == code - - -def assert_empty(response): - assert response.status_code == 200 - assert response.json().get("data") is None - - -def assert_data(response): - assert response.status_code == 200 - data = response.json().get("data") - assert data is not None - assert "api" in data.keys() - return data["api"] - - -API_DEVICES_QUERY = """ -devices { - creationDate - isCaller - name -} -""" - - -def request_devices(client): - return client.post( - "/graphql", - json={"query": generate_api_query([API_DEVICES_QUERY])}, - ) - - -def graphql_get_devices(client): - response = request_devices(client) - data = assert_data(response) - devices = data["devices"] - assert devices is not None - return devices - - -def set_client_token(client, token): - client.headers.update({"Authorization": "Bearer " + token}) - - -def assert_token_valid(client, token): - set_client_token(client, token) - assert graphql_get_devices(client) is not None - - -def assert_same(graphql_devices, abstract_devices): - """Orderless comparison""" - assert len(graphql_devices) == len(abstract_devices) - for original_device in abstract_devices: - assert original_device["name"] in [device["name"] for device in graphql_devices] - for device in graphql_devices: - if device["name"] == original_device["name"]: - assert device["creationDate"] == original_device["date"].isoformat() - - -def assert_original(client): - devices = graphql_get_devices(client) - assert_original_devices(devices) - - -def assert_original_devices(devices): - assert_same(devices, ORIGINAL_DEVICES) - - for device in devices: - if device["name"] == DEVICE_WE_AUTH_TESTS_WITH["name"]: - assert device["isCaller"] is True - else: - assert device["isCaller"] is False diff --git a/tests/test_graphql/common.py b/tests/test_graphql/common.py index 1a415bc..286df67 100644 --- a/tests/test_graphql/common.py +++ b/tests/test_graphql/common.py @@ -4,18 +4,20 @@ from tests.conftest import TOKENS_FILE_CONTENTS, DEVICE_WE_AUTH_TESTS_WITH ORIGINAL_DEVICES = TOKENS_FILE_CONTENTS["tokens"] -def assert_ok(response, request): - data = assert_data(response) - data[request]["success"] is True - data[request]["message"] is not None - data[request]["code"] == 200 +def assert_ok(output: dict) -> None: + if output["success"] is False: + # convenience for debugging, this should display error + # if message is empty, consider adding helpful messages + raise ValueError(output["code"], output["message"]) + assert output["success"] is True + assert output["message"] is not None + assert output["code"] == 200 -def assert_errorcode(response, request, code): - data = assert_data(response) - data[request]["success"] is False - data[request]["message"] is not None - data[request]["code"] == code +def assert_errorcode(output: dict, code) -> None: + assert output["success"] is False + assert output["message"] is not None + assert output["code"] == code def assert_empty(response): @@ -23,7 +25,7 @@ def assert_empty(response): assert response.json().get("data") is None -def assert_data(response): +def get_data(response): assert response.status_code == 200 response = response.json() @@ -54,7 +56,7 @@ def request_devices(client): def graphql_get_devices(client): response = request_devices(client) - data = assert_data(response) + data = get_data(response) devices = data["api"]["devices"] assert devices is not None return devices diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index 50d65d8..675c1b8 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -280,7 +280,7 @@ def get_data(response): if ( "errors" in response.keys() ): # convenience for debugging, this will display error - assert response["errors"] == [] + raise ValueError(response["errors"]) assert response["data"] is not None data = response["data"] return data diff --git a/tests/test_graphql/test_api_devices.py b/tests/test_graphql/test_api_devices.py index b24bc7f..ef77414 100644 --- a/tests/test_graphql/test_api_devices.py +++ b/tests/test_graphql/test_api_devices.py @@ -8,8 +8,8 @@ from tests.common import ( generate_api_query, ) from tests.conftest import DEVICE_WE_AUTH_TESTS_WITH, TOKENS_FILE_CONTENTS -from tests.test_graphql.api_common import ( - assert_data, +from tests.test_graphql.common import ( + get_data, assert_empty, assert_ok, assert_errorcode, @@ -36,7 +36,7 @@ def graphql_get_new_device_key(authorized_client) -> str: "/graphql", json={"query": NEW_DEVICE_KEY_MUTATION}, ) - assert_ok(response, "getNewDeviceApiKey") + assert_ok(get_data(response)["api"]["getNewDeviceApiKey"]) key = response.json()["data"]["api"]["getNewDeviceApiKey"]["key"] assert key.split(" ").__len__() == 12 @@ -60,9 +60,10 @@ def graphql_try_auth_new_device(client, mnemonic_key, device_name): def graphql_authorize_new_device(client, mnemonic_key, device_name) -> str: response = graphql_try_auth_new_device(client, mnemonic_key, "new_device") - assert_ok(response, "authorizeWithNewDeviceApiKey") + assert_ok(get_data(response)["api"]["authorizeWithNewDeviceApiKey"]) token = response.json()["data"]["api"]["authorizeWithNewDeviceApiKey"]["token"] assert_token_valid(client, token) + return token def test_graphql_tokens_info(authorized_client, tokens_file): @@ -114,7 +115,7 @@ def test_graphql_delete_token(authorized_client, tokens_file): }, }, ) - assert_ok(response, "deleteDeviceApiToken") + assert_ok(get_data(response)["api"]["deleteDeviceApiToken"]) devices = graphql_get_devices(authorized_client) assert_same(devices, test_devices) @@ -130,7 +131,7 @@ def test_graphql_delete_self_token(authorized_client, tokens_file): }, }, ) - assert_errorcode(response, "deleteDeviceApiToken", 400) + assert_errorcode(get_data(response)["api"]["deleteDeviceApiToken"], 400) assert_original(authorized_client) @@ -147,7 +148,7 @@ def test_graphql_delete_nonexistent_token( }, }, ) - assert_errorcode(response, "deleteDeviceApiToken", 404) + assert_errorcode(get_data(response)["api"]["deleteDeviceApiToken"], 404) assert_original(authorized_client) @@ -180,7 +181,7 @@ def test_graphql_refresh_token(authorized_client, client, tokens_file): "/graphql", json={"query": REFRESH_TOKEN_MUTATION}, ) - assert_ok(response, "refreshDeviceApiToken") + assert_ok(get_data(response)["api"]["refreshDeviceApiToken"]) new_token = response.json()["data"]["api"]["refreshDeviceApiToken"]["token"] assert_token_valid(client, new_token) @@ -250,10 +251,10 @@ def test_graphql_get_and_delete_new_device_key(client, authorized_client, tokens "/graphql", json={"query": INVALIDATE_NEW_DEVICE_KEY_MUTATION}, ) - assert_ok(response, "invalidateNewDeviceApiKey") + assert_ok(get_data(response)["api"]["invalidateNewDeviceApiKey"]) response = graphql_try_auth_new_device(client, mnemonic_key, "new_device") - assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404) + assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404) AUTHORIZE_WITH_NEW_DEVICE_KEY_MUTATION = """ @@ -285,7 +286,7 @@ def test_graphql_authorize_new_device_with_invalid_key( client, authorized_client, tokens_file ): response = graphql_try_auth_new_device(client, "invalid_token", "new_device") - assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404) + assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404) assert_original(authorized_client) @@ -297,7 +298,7 @@ def test_graphql_get_and_authorize_used_key(client, authorized_client, tokens_fi devices = graphql_get_devices(authorized_client) response = graphql_try_auth_new_device(client, mnemonic_key, "new_device2") - assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404) + assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404) assert graphql_get_devices(authorized_client) == devices @@ -309,7 +310,7 @@ def test_graphql_get_and_authorize_key_after_12_minutes( mock = mocker.patch(DEVICE_KEY_VALIDATION_DATETIME, NearFuture) response = graphql_try_auth_new_device(client, mnemonic_key, "new_device") - assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404) + assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404) def test_graphql_authorize_without_token( diff --git a/tests/test_graphql/test_api_recovery.py b/tests/test_graphql/test_api_recovery.py index 629bac0..f53394f 100644 --- a/tests/test_graphql/test_api_recovery.py +++ b/tests/test_graphql/test_api_recovery.py @@ -18,9 +18,9 @@ from tests.common import five_minutes_into_future_naive_utc as five_minutes_into from tests.common import five_minutes_into_future as five_minutes_into_future_tz from tests.common import five_minutes_into_past_naive_utc as five_minutes_into_past -from tests.test_graphql.api_common import ( +from tests.test_graphql.common import ( assert_empty, - assert_data, + get_data, assert_ok, assert_errorcode, assert_token_valid, @@ -49,9 +49,9 @@ def request_recovery_status(client): def graphql_recovery_status(client): response = request_recovery_status(client) - data = assert_data(response) + data = get_data(response) - status = data["recoveryKey"] + status = data["api"]["recoveryKey"] assert status is not None return status @@ -74,8 +74,10 @@ def request_make_new_recovery_key(client, expires_at=None, uses=None): def graphql_make_new_recovery_key(client, expires_at=None, uses=None): response = request_make_new_recovery_key(client, expires_at, uses) - assert_ok(response, "getNewRecoveryApiKey") - key = response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] + output = get_data(response)["api"]["getNewRecoveryApiKey"] + assert_ok(output) + + key = output["key"] assert key is not None assert key.split(" ").__len__() == 18 return key @@ -98,8 +100,10 @@ def request_recovery_auth(client, key, device_name): def graphql_use_recovery_key(client, key, device_name): response = request_recovery_auth(client, key, device_name) - assert_ok(response, "useRecoveryApiKey") - token = response.json()["data"]["api"]["useRecoveryApiKey"]["token"] + output = get_data(response)["api"]["useRecoveryApiKey"] + assert_ok(output) + + token = output["token"] assert token is not None assert_token_valid(client, token) set_client_token(client, token) @@ -198,8 +202,10 @@ def test_graphql_use_recovery_key_after_expiration( mock = mocker.patch(RECOVERY_KEY_VALIDATION_DATETIME, NearFuture) response = request_recovery_auth(client, key, "new_test_token3") - assert_errorcode(response, "useRecoveryApiKey", 404) - assert response.json()["data"]["api"]["useRecoveryApiKey"]["token"] is None + output = get_data(response)["api"]["useRecoveryApiKey"] + assert_errorcode(output, 404) + + assert output["token"] is None assert_original(authorized_client) status = graphql_recovery_status(authorized_client) @@ -222,8 +228,10 @@ def test_graphql_generate_recovery_key_with_expiration_in_the_past( authorized_client, expires_at=expiration_date ) - assert_errorcode(response, "getNewRecoveryApiKey", 400) - assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None + output = get_data(response)["api"]["getNewRecoveryApiKey"] + assert_errorcode(output, 400) + + assert output["key"] is None assert graphql_recovery_status(authorized_client)["exists"] is False @@ -280,7 +288,8 @@ def test_graphql_generate_recovery_key_with_limited_uses( assert status["usesLeft"] == 0 response = request_recovery_auth(client, mnemonic_key, "new_test_token3") - assert_errorcode(response, "useRecoveryApiKey", 404) + output = get_data(response)["api"]["useRecoveryApiKey"] + assert_errorcode(output, 404) def test_graphql_generate_recovery_key_with_negative_uses( @@ -288,13 +297,16 @@ def test_graphql_generate_recovery_key_with_negative_uses( ): response = request_make_new_recovery_key(authorized_client, uses=-1) - assert_errorcode(response, "getNewRecoveryApiKey", 400) - assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None + output = get_data(response)["api"]["getNewRecoveryApiKey"] + assert_errorcode(output, 400) + assert output["key"] is None + assert graphql_recovery_status(authorized_client)["exists"] is False def test_graphql_generate_recovery_key_with_zero_uses(authorized_client, tokens_file): response = request_make_new_recovery_key(authorized_client, uses=0) - assert_errorcode(response, "getNewRecoveryApiKey", 400) - assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None + output = get_data(response)["api"]["getNewRecoveryApiKey"] + assert_errorcode(output, 400) + assert output["key"] is None assert graphql_recovery_status(authorized_client)["exists"] is False diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index e86d070..bd3e373 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -10,7 +10,7 @@ from selfprivacy_api.services.test_service import DummyService from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_service_query -from tests.test_graphql.test_api_backup import assert_ok, get_data +from tests.test_graphql.common import assert_empty, assert_ok, get_data @pytest.fixture() @@ -330,52 +330,38 @@ def test_allservices_unauthorized(client, only_dummy_service): def test_start_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_start(client, dummy_service) - - assert mutation_response.status_code == 200 - assert mutation_response.json().get("data") is None + response = api_start(client, dummy_service) + assert_empty(response) def test_restart_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_restart(client, dummy_service) - - assert mutation_response.status_code == 200 - assert mutation_response.json().get("data") is None + response = api_restart(client, dummy_service) + assert_empty(response) def test_stop_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_stop(client, dummy_service) - - assert mutation_response.status_code == 200 - assert mutation_response.json().get("data") is None + response = api_stop(client, dummy_service) + assert_empty(response) def test_enable_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_enable(client, dummy_service) - - assert mutation_response.status_code == 200 - assert mutation_response.json().get("data") is None + response = api_enable(client, dummy_service) + assert_empty(response) def test_disable_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_disable(client, dummy_service) - - assert mutation_response.status_code == 200 - assert mutation_response.json().get("data") is None + response = api_disable(client, dummy_service) + assert_empty(response) -def test_move_nonexistent(authorized_client, only_dummy_service): +def test_move_unauthorized(client, only_dummy_service): dummy_service = only_dummy_service - mutation_response = api_move_by_name(authorized_client, "bogus_service", "sda1") - data = get_data(mutation_response)["services"]["moveService"] - assert_notfound(data) - - assert data["service"] is None - assert data["job"] is None + response = api_move(client, dummy_service, "sda1") + assert_empty(response) def test_start_nonexistent(authorized_client, only_dummy_service): From 7038d69069917f5db6ae7541b5d93ac53bc1cc58 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 22 Nov 2023 18:19:03 +0000 Subject: [PATCH 055/130] test(services): remove redundant gitea tests --- .../services/test_gitea.py | 121 ------------------ .../services/test_gitea/enable_undefined.json | 56 -------- .../services/test_gitea/turned_off.json | 57 --------- .../services/test_gitea/turned_on.json | 57 --------- .../services/test_gitea/undefined.json | 54 -------- 5 files changed, 345 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_gitea.py delete mode 100644 tests/test_rest_endpoints/services/test_gitea/enable_undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_gitea/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_gitea/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_gitea/undefined.json diff --git a/tests/test_rest_endpoints/services/test_gitea.py b/tests/test_rest_endpoints/services/test_gitea.py deleted file mode 100644 index 0a50c19..0000000 --- a/tests/test_rest_endpoints/services/test_gitea.py +++ /dev/null @@ -1,121 +0,0 @@ -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r") as f: - return json.load(f) - - -############################################################################### - - -@pytest.fixture -def gitea_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["gitea"]["enable"] == False - return datadir - - -@pytest.fixture -def gitea_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["gitea"]["enable"] == True - return datadir - - -@pytest.fixture -def gitea_enable_undefined(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json" - ) - assert "enable" not in read_json(datadir / "enable_undefined.json")["gitea"] - return datadir - - -@pytest.fixture -def gitea_undefined(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "gitea" not in read_json(datadir / "undefined.json") - return datadir - - -############################################################################### - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_unauthorized(client, gitea_off, endpoint): - response = client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_illegal_methods(authorized_client, gitea_off, endpoint): - response = authorized_client.get(f"/services/gitea/{endpoint}") - assert response.status_code == 405 - response = authorized_client.put(f"/services/gitea/{endpoint}") - assert response.status_code == 405 - response = authorized_client.delete(f"/services/gitea/{endpoint}") - assert response.status_code == 405 - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_off(authorized_client, gitea_off, endpoint, target_file): - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - assert read_json(gitea_off / "turned_off.json") == read_json( - gitea_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_on(authorized_client, gitea_on, endpoint, target_file): - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - assert read_json(gitea_on / "turned_on.json") == read_json(gitea_on / target_file) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_twice(authorized_client, gitea_off, endpoint, target_file): - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - assert read_json(gitea_off / "turned_off.json") == read_json( - gitea_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_attribute_deleted( - authorized_client, gitea_enable_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - assert read_json(gitea_enable_undefined / "enable_undefined.json") == read_json( - gitea_enable_undefined / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_gitea_undefined(authorized_client, gitea_undefined, endpoint, target_file): - response = authorized_client.post(f"/services/gitea/{endpoint}") - assert response.status_code == 200 - assert read_json(gitea_undefined / "undefined.json") == read_json( - gitea_undefined / target_file - ) diff --git a/tests/test_rest_endpoints/services/test_gitea/enable_undefined.json b/tests/test_rest_endpoints/services/test_gitea/enable_undefined.json deleted file mode 100644 index f9fb878..0000000 --- a/tests/test_rest_endpoints/services/test_gitea/enable_undefined.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_gitea/turned_off.json b/tests/test_rest_endpoints/services/test_gitea/turned_off.json deleted file mode 100644 index c1691ea..0000000 --- a/tests/test_rest_endpoints/services/test_gitea/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_gitea/turned_on.json b/tests/test_rest_endpoints/services/test_gitea/turned_on.json deleted file mode 100644 index f9a1eaf..0000000 --- a/tests/test_rest_endpoints/services/test_gitea/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": true - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_gitea/undefined.json b/tests/test_rest_endpoints/services/test_gitea/undefined.json deleted file mode 100644 index a50a070..0000000 --- a/tests/test_rest_endpoints/services/test_gitea/undefined.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 9f04729296e6e95913c5035c4393f42e5fb46354 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 11:26:13 +0000 Subject: [PATCH 056/130] test(services, system): untie dkim tests from rest --- selfprivacy_api/utils/__init__.py | 4 +- tests/data/domain | 1 + tests/test_dkim.py | 119 ++++++++++++++++++ tests/test_graphql/test_system.py | 24 ++++ .../services/test_mailserver.py | 102 --------------- 5 files changed, 147 insertions(+), 103 deletions(-) create mode 100644 tests/data/domain create mode 100644 tests/test_dkim.py delete mode 100644 tests/test_rest_endpoints/services/test_mailserver.py diff --git a/selfprivacy_api/utils/__init__.py b/selfprivacy_api/utils/__init__.py index 40ed5b6..5263b89 100644 --- a/selfprivacy_api/utils/__init__.py +++ b/selfprivacy_api/utils/__init__.py @@ -6,6 +6,7 @@ import json import os import subprocess import portalocker +import typing USERDATA_FILE = "/etc/nixos/userdata/userdata.json" @@ -166,9 +167,10 @@ def parse_date(date_str: str) -> datetime.datetime: raise ValueError("Invalid date string") -def get_dkim_key(domain, parse=True): +def get_dkim_key(domain: str, parse: bool = True) -> typing.Optional[str]: """Get DKIM key from /var/dkim/.selector.txt""" if os.path.exists("/var/dkim/" + domain + ".selector.txt"): + # Is this really neccessary to use Popen here? cat_process = subprocess.Popen( ["cat", "/var/dkim/" + domain + ".selector.txt"], stdout=subprocess.PIPE ) diff --git a/tests/data/domain b/tests/data/domain new file mode 100644 index 0000000..3679d0d --- /dev/null +++ b/tests/data/domain @@ -0,0 +1 @@ +test-domain.tld \ No newline at end of file diff --git a/tests/test_dkim.py b/tests/test_dkim.py new file mode 100644 index 0000000..c9662d0 --- /dev/null +++ b/tests/test_dkim.py @@ -0,0 +1,119 @@ +import pytest +import typing + +from os import path +from unittest.mock import DEFAULT +from tests.conftest import global_data_dir + +from selfprivacy_api.utils import get_dkim_key, get_domain +import selfprivacy_api.utils as utils + +############################################################################### + + +class ProcessMock: + """Mock subprocess.Popen""" + + def __init__(self, args, **kwargs): + self.args = args + self.kwargs = kwargs + + def communicate(): + return ( + b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for test-domain.tld\n', + None, + ) + + +class NoFileMock(ProcessMock): + def communicate(): + return (b"", None) + + +def _path_exists_with_masked_paths(filepath, masked_paths: typing.List[str]): + if filepath in masked_paths: + return False + else: + # this will cause the mocker to return the standard path.exists output + # see https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect + return DEFAULT + + +def path_exists_func_but_with_masked_paths(masked_paths: typing.List[str]): + """ + Sometimes we do not want to pretend that no files exist at all, but that only specific files do not exist + This provides the needed path.exists function for some arbitrary list of masked paths + """ + return lambda x: _path_exists_with_masked_paths(x, masked_paths) + + +@pytest.fixture +def mock_all_paths_exist(mocker): + mock = mocker.patch("os.path.exists", autospec=True, return_value=True) + return mock + + +@pytest.fixture +def mock_subproccess_popen_dkimfile(mocker): + mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) + return mock + + +@pytest.fixture +def mock_subproccess_popen(mocker): + mock = mocker.patch("subprocess.Popen", autospec=True, return_value=NoFileMock) + return mock + + +@pytest.fixture +def domain_file(mocker): + # TODO: move to conftest. Challenge: it does not behave with "/" like pytest datadir does + domain_path = path.join(global_data_dir(), "domain") + mocker.patch("selfprivacy_api.utils.DOMAIN_FILE", domain_path) + return domain_path + + +@pytest.fixture +def mock_no_dkim_file(mocker): + """ + Should have domain mocks + """ + domain = utils.get_domain() + # try: + # domain = get_domain() + # except Exception as e: + # domain = "" + + masked_files = ["/var/dkim/" + domain + ".selector.txt"] + mock = mocker.patch( + "os.path.exists", + side_effect=path_exists_func_but_with_masked_paths(masked_files), + ) + return mock + + +############################################################################### + + +def test_get_dkim_key( + mock_subproccess_popen_dkimfile, mock_all_paths_exist, domain_file +): + """Test DKIM key""" + dkim_key = get_dkim_key("test-domain.tld") + assert ( + dkim_key + == "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" + ) + assert mock_subproccess_popen_dkimfile.call_args[0][0] == [ + "cat", + "/var/dkim/test-domain.tld.selector.txt", + ] + + +def test_no_dkim_key( + authorized_client, domain_file, mock_no_dkim_file, mock_subproccess_popen +): + """Test no DKIM key""" + dkim_key = get_dkim_key("test-domain.tld") + assert dkim_key is None + assert mock_subproccess_popen.called == False diff --git a/tests/test_graphql/test_system.py b/tests/test_graphql/test_system.py index ed00268..b6b4362 100644 --- a/tests/test_graphql/test_system.py +++ b/tests/test_graphql/test_system.py @@ -6,6 +6,7 @@ import pytest from tests.common import generate_system_query, read_json from tests.test_graphql.common import assert_empty +from tests.test_dkim import mock_no_dkim_file @pytest.fixture @@ -332,6 +333,29 @@ def test_graphql_get_domain( ) +def test_graphql_get_domain_no_dkim( + authorized_client, + domain_file, + mock_get_ip4, + mock_get_ip6, + mock_no_dkim_file, + turned_on, +): + """Test no DKIM file situation gets properly handled""" + response = authorized_client.post( + "/graphql", + json={ + "query": generate_system_query([API_GET_DOMAIN_INFO]), + }, + ) + assert response.status_code == 200 + assert response.json().get("data") is not None + dns_records = response.json()["data"]["system"]["domainInfo"]["requiredDnsRecords"] + for record in dns_records: + if record["name"] == "selector._domainkey": + raise ValueError("unexpected record found:", record) + + API_GET_TIMEZONE = """ settings { timezone diff --git a/tests/test_rest_endpoints/services/test_mailserver.py b/tests/test_rest_endpoints/services/test_mailserver.py deleted file mode 100644 index 2803683..0000000 --- a/tests/test_rest_endpoints/services/test_mailserver.py +++ /dev/null @@ -1,102 +0,0 @@ -import base64 -import json -import pytest - -from selfprivacy_api.utils import get_dkim_key - -############################################################################### - - -class ProcessMock: - """Mock subprocess.Popen""" - - def __init__(self, args, **kwargs): - self.args = args - self.kwargs = kwargs - - def communicate(): - return ( - b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for example.com\n', - None, - ) - - -class NoFileMock(ProcessMock): - def communicate(): - return (b"", None) - - -@pytest.fixture -def mock_subproccess_popen(mocker): - mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) - mocker.patch( - "selfprivacy_api.rest.services.get_domain", - autospec=True, - return_value="example.com", - ) - mocker.patch("os.path.exists", autospec=True, return_value=True) - return mock - - -@pytest.fixture -def mock_no_file(mocker): - mock = mocker.patch("subprocess.Popen", autospec=True, return_value=NoFileMock) - mocker.patch( - "selfprivacy_api.rest.services.get_domain", - autospec=True, - return_value="example.com", - ) - mocker.patch("os.path.exists", autospec=True, return_value=False) - return mock - - -############################################################################### - - -def test_unauthorized(client, mock_subproccess_popen): - """Test unauthorized""" - response = client.get("/services/mailserver/dkim") - assert response.status_code == 401 - - -def test_illegal_methods(authorized_client, mock_subproccess_popen): - response = authorized_client.post("/services/mailserver/dkim") - assert response.status_code == 405 - response = authorized_client.put("/services/mailserver/dkim") - assert response.status_code == 405 - response = authorized_client.delete("/services/mailserver/dkim") - assert response.status_code == 405 - - -def test_get_dkim_key(mock_subproccess_popen): - """Test DKIM key""" - dkim_key = get_dkim_key("example.com") - assert ( - dkim_key - == "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" - ) - assert mock_subproccess_popen.call_args[0][0] == [ - "cat", - "/var/dkim/example.com.selector.txt", - ] - - -def test_dkim_key(authorized_client, mock_subproccess_popen): - """Test old REST DKIM key endpoint""" - response = authorized_client.get("/services/mailserver/dkim") - assert response.status_code == 200 - assert ( - base64.b64decode(response.text) - == b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for example.com\n' - ) - assert mock_subproccess_popen.call_args[0][0] == [ - "cat", - "/var/dkim/example.com.selector.txt", - ] - - -def test_no_dkim_key(authorized_client, mock_no_file): - """Test no DKIM key""" - response = authorized_client.get("/services/mailserver/dkim") - assert response.status_code == 404 - assert mock_no_file.called == False From e63acc6d5629571d2970bc580d259fc2e348a656 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 11:31:49 +0000 Subject: [PATCH 057/130] test(services): remove redundant nextcloud tests --- .../services/test_nextcloud.py | 123 ------------------ .../test_nextcloud/enable_undefined.json | 56 -------- .../services/test_nextcloud/turned_off.json | 57 -------- .../services/test_nextcloud/turned_on.json | 57 -------- .../services/test_nextcloud/undefined.json | 49 ------- 5 files changed, 342 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_nextcloud.py delete mode 100644 tests/test_rest_endpoints/services/test_nextcloud/enable_undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_nextcloud/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_nextcloud/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_nextcloud/undefined.json diff --git a/tests/test_rest_endpoints/services/test_nextcloud.py b/tests/test_rest_endpoints/services/test_nextcloud.py deleted file mode 100644 index b05c363..0000000 --- a/tests/test_rest_endpoints/services/test_nextcloud.py +++ /dev/null @@ -1,123 +0,0 @@ -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r") as f: - return json.load(f) - - -############################################################################### - - -@pytest.fixture -def nextcloud_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["nextcloud"]["enable"] == False - return datadir - - -@pytest.fixture -def nextcloud_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["nextcloud"]["enable"] == True - return datadir - - -@pytest.fixture -def nextcloud_enable_undefined(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json" - ) - assert "enable" not in read_json(datadir / "enable_undefined.json")["nextcloud"] - return datadir - - -@pytest.fixture -def nextcloud_undefined(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "nextcloud" not in read_json(datadir / "undefined.json") - return datadir - - -############################################################################### - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_unauthorized(client, nextcloud_off, endpoint): - response = client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_illegal_methods(authorized_client, nextcloud_off, endpoint): - response = authorized_client.get(f"/services/nextcloud/{endpoint}") - assert response.status_code == 405 - response = authorized_client.put(f"/services/nextcloud/{endpoint}") - assert response.status_code == 405 - response = authorized_client.delete(f"/services/nextcloud/{endpoint}") - assert response.status_code == 405 - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_off(authorized_client, nextcloud_off, endpoint, target_file): - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - assert read_json(nextcloud_off / "turned_off.json") == read_json( - nextcloud_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_on(authorized_client, nextcloud_on, endpoint, target_file): - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - assert read_json(nextcloud_on / "turned_on.json") == read_json( - nextcloud_on / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_twice(authorized_client, nextcloud_off, endpoint, target_file): - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - assert read_json(nextcloud_off / "turned_off.json") == read_json( - nextcloud_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_attribute_deleted( - authorized_client, nextcloud_enable_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - assert read_json(nextcloud_enable_undefined / "enable_undefined.json") == read_json( - nextcloud_enable_undefined / target_file - ) - - -@pytest.mark.parametrize("endpoint,target", [("enable", True), ("disable", False)]) -def test_on_nextcloud_undefined( - authorized_client, nextcloud_undefined, endpoint, target -): - response = authorized_client.post(f"/services/nextcloud/{endpoint}") - assert response.status_code == 200 - assert ( - read_json(nextcloud_undefined / "undefined.json")["nextcloud"]["enable"] - == target - ) diff --git a/tests/test_rest_endpoints/services/test_nextcloud/enable_undefined.json b/tests/test_rest_endpoints/services/test_nextcloud/enable_undefined.json deleted file mode 100644 index 19f1f2d..0000000 --- a/tests/test_rest_endpoints/services/test_nextcloud/enable_undefined.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN" - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_nextcloud/turned_off.json b/tests/test_rest_endpoints/services/test_nextcloud/turned_off.json deleted file mode 100644 index b80ad9e..0000000 --- a/tests/test_rest_endpoints/services/test_nextcloud/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_nextcloud/turned_on.json b/tests/test_rest_endpoints/services/test_nextcloud/turned_on.json deleted file mode 100644 index c1691ea..0000000 --- a/tests/test_rest_endpoints/services/test_nextcloud/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_nextcloud/undefined.json b/tests/test_rest_endpoints/services/test_nextcloud/undefined.json deleted file mode 100644 index 46c09f3..0000000 --- a/tests/test_rest_endpoints/services/test_nextcloud/undefined.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 15eafbb524a770fe723f7cab95b1a80fe61fba77 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 11:34:06 +0000 Subject: [PATCH 058/130] test(services): remove redundant ocserv tests --- .../services/test_ocserv.py | 123 ------------------ .../test_ocserv/enable_undefined.json | 56 -------- .../services/test_ocserv/turned_off.json | 57 -------- .../services/test_ocserv/turned_on.json | 57 -------- .../services/test_ocserv/undefined.json | 54 -------- 5 files changed, 347 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_ocserv.py delete mode 100644 tests/test_rest_endpoints/services/test_ocserv/enable_undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_ocserv/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_ocserv/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_ocserv/undefined.json diff --git a/tests/test_rest_endpoints/services/test_ocserv.py b/tests/test_rest_endpoints/services/test_ocserv.py deleted file mode 100644 index 8f43e70..0000000 --- a/tests/test_rest_endpoints/services/test_ocserv.py +++ /dev/null @@ -1,123 +0,0 @@ -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r") as f: - return json.load(f) - - -############################################################################### - - -@pytest.fixture -def ocserv_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["ocserv"]["enable"] == False - return datadir - - -@pytest.fixture -def ocserv_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["ocserv"]["enable"] == True - return datadir - - -@pytest.fixture -def ocserv_enable_undefined(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json" - ) - assert "enable" not in read_json(datadir / "enable_undefined.json")["ocserv"] - return datadir - - -@pytest.fixture -def ocserv_undefined(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "ocserv" not in read_json(datadir / "undefined.json") - return datadir - - -############################################################################### - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_unauthorized(client, ocserv_off, endpoint): - response = client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_illegal_methods(authorized_client, ocserv_off, endpoint): - response = authorized_client.get(f"/services/ocserv/{endpoint}") - assert response.status_code == 405 - response = authorized_client.put(f"/services/ocserv/{endpoint}") - assert response.status_code == 405 - response = authorized_client.delete(f"/services/ocserv/{endpoint}") - assert response.status_code == 405 - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_off(authorized_client, ocserv_off, endpoint, target_file): - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - assert read_json(ocserv_off / "turned_off.json") == read_json( - ocserv_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_on(authorized_client, ocserv_on, endpoint, target_file): - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - assert read_json(ocserv_on / "turned_on.json") == read_json(ocserv_on / target_file) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_twice(authorized_client, ocserv_off, endpoint, target_file): - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - assert read_json(ocserv_off / "turned_off.json") == read_json( - ocserv_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_attribute_deleted( - authorized_client, ocserv_enable_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - assert read_json(ocserv_enable_undefined / "enable_undefined.json") == read_json( - ocserv_enable_undefined / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_ocserv_undefined( - authorized_client, ocserv_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/ocserv/{endpoint}") - assert response.status_code == 200 - assert read_json(ocserv_undefined / "undefined.json") == read_json( - ocserv_undefined / target_file - ) diff --git a/tests/test_rest_endpoints/services/test_ocserv/enable_undefined.json b/tests/test_rest_endpoints/services/test_ocserv/enable_undefined.json deleted file mode 100644 index e080110..0000000 --- a/tests/test_rest_endpoints/services/test_ocserv/enable_undefined.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ocserv/turned_off.json b/tests/test_rest_endpoints/services/test_ocserv/turned_off.json deleted file mode 100644 index 1c08123..0000000 --- a/tests/test_rest_endpoints/services/test_ocserv/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": false - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ocserv/turned_on.json b/tests/test_rest_endpoints/services/test_ocserv/turned_on.json deleted file mode 100644 index b80ad9e..0000000 --- a/tests/test_rest_endpoints/services/test_ocserv/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ocserv/undefined.json b/tests/test_rest_endpoints/services/test_ocserv/undefined.json deleted file mode 100644 index 12eb73a..0000000 --- a/tests/test_rest_endpoints/services/test_ocserv/undefined.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 113f512565fc0e0efee73c747e0216ba4367ffba Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 11:37:29 +0000 Subject: [PATCH 059/130] test(services): remove redundant pleroma tests --- .../services/test_pleroma.py | 125 ------------------ .../test_pleroma/enable_undefined.json | 56 -------- .../services/test_pleroma/turned_off.json | 57 -------- .../services/test_pleroma/turned_on.json | 57 -------- .../services/test_pleroma/undefined.json | 54 -------- 5 files changed, 349 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_pleroma.py delete mode 100644 tests/test_rest_endpoints/services/test_pleroma/enable_undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_pleroma/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_pleroma/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_pleroma/undefined.json diff --git a/tests/test_rest_endpoints/services/test_pleroma.py b/tests/test_rest_endpoints/services/test_pleroma.py deleted file mode 100644 index 0d7f149..0000000 --- a/tests/test_rest_endpoints/services/test_pleroma.py +++ /dev/null @@ -1,125 +0,0 @@ -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r") as f: - return json.load(f) - - -############################################################################### - - -@pytest.fixture -def pleroma_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["pleroma"]["enable"] == False - return datadir - - -@pytest.fixture -def pleroma_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["pleroma"]["enable"] == True - return datadir - - -@pytest.fixture -def pleroma_enable_undefined(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json" - ) - assert "enable" not in read_json(datadir / "enable_undefined.json")["pleroma"] - return datadir - - -@pytest.fixture -def pleroma_undefined(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "pleroma" not in read_json(datadir / "undefined.json") - return datadir - - -############################################################################### - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_unauthorized(client, pleroma_off, endpoint): - response = client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["enable", "disable"]) -def test_illegal_methods(authorized_client, pleroma_off, endpoint): - response = authorized_client.get(f"/services/pleroma/{endpoint}") - assert response.status_code == 405 - response = authorized_client.put(f"/services/pleroma/{endpoint}") - assert response.status_code == 405 - response = authorized_client.delete(f"/services/pleroma/{endpoint}") - assert response.status_code == 405 - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_off(authorized_client, pleroma_off, endpoint, target_file): - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - assert read_json(pleroma_off / "turned_off.json") == read_json( - pleroma_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_from_on(authorized_client, pleroma_on, endpoint, target_file): - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - assert read_json(pleroma_on / "turned_on.json") == read_json( - pleroma_on / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_switch_twice(authorized_client, pleroma_off, endpoint, target_file): - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - assert read_json(pleroma_off / "turned_off.json") == read_json( - pleroma_off / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_attribute_deleted( - authorized_client, pleroma_enable_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - assert read_json(pleroma_enable_undefined / "enable_undefined.json") == read_json( - pleroma_enable_undefined / target_file - ) - - -@pytest.mark.parametrize( - "endpoint,target_file", - [("enable", "turned_on.json"), ("disable", "turned_off.json")], -) -def test_on_pleroma_undefined( - authorized_client, pleroma_undefined, endpoint, target_file -): - response = authorized_client.post(f"/services/pleroma/{endpoint}") - assert response.status_code == 200 - assert read_json(pleroma_undefined / "undefined.json") == read_json( - pleroma_undefined / target_file - ) diff --git a/tests/test_rest_endpoints/services/test_pleroma/enable_undefined.json b/tests/test_rest_endpoints/services/test_pleroma/enable_undefined.json deleted file mode 100644 index 0903875..0000000 --- a/tests/test_rest_endpoints/services/test_pleroma/enable_undefined.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": false - }, - "pleroma": { - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_pleroma/turned_off.json b/tests/test_rest_endpoints/services/test_pleroma/turned_off.json deleted file mode 100644 index 813c01f..0000000 --- a/tests/test_rest_endpoints/services/test_pleroma/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": false - }, - "pleroma": { - "enable": false - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_pleroma/turned_on.json b/tests/test_rest_endpoints/services/test_pleroma/turned_on.json deleted file mode 100644 index 1c08123..0000000 --- a/tests/test_rest_endpoints/services/test_pleroma/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": false - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_pleroma/undefined.json b/tests/test_rest_endpoints/services/test_pleroma/undefined.json deleted file mode 100644 index 77d8ad2..0000000 --- a/tests/test_rest_endpoints/services/test_pleroma/undefined.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": false - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": false - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 80e00740fb889e3b1ea7d24f6e79b6930eafcd0a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 11:54:18 +0000 Subject: [PATCH 060/130] test(services): remove legacy restic test data --- .../services/test_restic/no_values.json | 72 ------------------ .../services/test_restic/some_values.json | 76 ------------------- .../services/test_restic/undefined.json | 70 ----------------- 3 files changed, 218 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/test_restic/no_values.json delete mode 100644 tests/test_rest_endpoints/services/test_restic/some_values.json delete mode 100644 tests/test_rest_endpoints/services/test_restic/undefined.json diff --git a/tests/test_rest_endpoints/services/test_restic/no_values.json b/tests/test_rest_endpoints/services/test_restic/no_values.json deleted file mode 100644 index 3b4a2f5..0000000 --- a/tests/test_rest_endpoints/services/test_restic/no_values.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - }, - { - "username": "user2", - "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] - }, - { - "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_restic/some_values.json b/tests/test_rest_endpoints/services/test_restic/some_values.json deleted file mode 100644 index c003d10..0000000 --- a/tests/test_rest_endpoints/services/test_restic/some_values.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - }, - { - "username": "user2", - "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] - }, - { - "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "BUCKET" - } -} diff --git a/tests/test_rest_endpoints/services/test_restic/undefined.json b/tests/test_rest_endpoints/services/test_restic/undefined.json deleted file mode 100644 index 5bd1220..0000000 --- a/tests/test_rest_endpoints/services/test_restic/undefined.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - }, - { - "username": "user2", - "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] - }, - { - "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - } -} \ No newline at end of file From 125d221442dc8bf13d962cd0c3b67046da323c46 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 24 Nov 2023 14:16:42 +0000 Subject: [PATCH 061/130] test(services): untie dkim-related service tests from rest --- ...test_services.py => test_services_dkim.py} | 65 ++++++------------- 1 file changed, 19 insertions(+), 46 deletions(-) rename tests/{test_rest_endpoints/services/test_services.py => test_services_dkim.py} (60%) diff --git a/tests/test_rest_endpoints/services/test_services.py b/tests/test_services_dkim.py similarity index 60% rename from tests/test_rest_endpoints/services/test_services.py rename to tests/test_services_dkim.py index 1108e8c..02998c2 100644 --- a/tests/test_rest_endpoints/services/test_services.py +++ b/tests/test_services_dkim.py @@ -1,11 +1,12 @@ -import base64 -import json import pytest - -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as file: - return json.load(file) +from selfprivacy_api.services.service import ServiceStatus +from selfprivacy_api.services.bitwarden import Bitwarden +from selfprivacy_api.services.gitea import Gitea +from selfprivacy_api.services.mailserver import MailServer +from selfprivacy_api.services.nextcloud import Nextcloud +from selfprivacy_api.services.ocserv import Ocserv +from selfprivacy_api.services.pleroma import Pleroma def call_args_asserts(mocked_object): @@ -90,49 +91,21 @@ def mock_broken_service(mocker): ############################################################################### - -def test_unauthorized(client, mock_subproccess_popen): - """Test unauthorized""" - response = client.get("/services/status") - assert response.status_code == 401 - - -def test_illegal_methods(authorized_client, mock_subproccess_popen): - response = authorized_client.post("/services/status") - assert response.status_code == 405 - response = authorized_client.put("/services/status") - assert response.status_code == 405 - response = authorized_client.delete("/services/status") - assert response.status_code == 405 - - def test_dkim_key(authorized_client, mock_subproccess_popen): - response = authorized_client.get("/services/status") - assert response.status_code == 200 - assert response.json() == { - "imap": 0, - "smtp": 0, - "http": 0, - "bitwarden": 0, - "gitea": 0, - "nextcloud": 0, - "ocserv": 0, - "pleroma": 0, - } + assert MailServer.get_status() == ServiceStatus.ACTIVE + assert Bitwarden.get_status() == ServiceStatus.ACTIVE + assert Gitea.get_status() == ServiceStatus.ACTIVE + assert Nextcloud.get_status() == ServiceStatus.ACTIVE + assert Ocserv.get_status() == ServiceStatus.ACTIVE + assert Pleroma.get_status() == ServiceStatus.ACTIVE call_args_asserts(mock_subproccess_popen) def test_no_dkim_key(authorized_client, mock_broken_service): - response = authorized_client.get("/services/status") - assert response.status_code == 200 - assert response.json() == { - "imap": 1, - "smtp": 1, - "http": 0, - "bitwarden": 1, - "gitea": 1, - "nextcloud": 1, - "ocserv": 1, - "pleroma": 1, - } + assert MailServer.get_status() == ServiceStatus.FAILED + assert Bitwarden.get_status() == ServiceStatus.FAILED + assert Gitea.get_status() == ServiceStatus.FAILED + assert Nextcloud.get_status() == ServiceStatus.FAILED + assert Ocserv.get_status() == ServiceStatus.FAILED + assert Pleroma.get_status() == ServiceStatus.FAILED call_args_asserts(mock_broken_service) From 980d3622e8acce3e5c7c9a90eeda06f76a1f13d0 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 27 Nov 2023 18:12:45 +0000 Subject: [PATCH 062/130] test(services): remove redundant legacy bad-ssh-key test from rest-enfpo --- tests/test_rest_endpoints/services/test_ssh.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index a17bdab..a1a33f8 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -269,13 +269,6 @@ def test_add_existing_root_key(authorized_client, root_and_admin_have_keys): ] -def test_add_invalid_root_key(authorized_client, ssh_on): - response = authorized_client.put( - "/services/ssh/key/send", json={"public_key": "INVALID KEY test@pc"} - ) - assert response.status_code == 400 - - ## /ssh/keys/{user} ###################################################### From 1b520a80935b786201de7382b242d68b9acf159a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 09:46:59 +0000 Subject: [PATCH 063/130] feature(ssh): change ssh settings from graphql --- .../graphql/mutations/system_mutations.py | 37 +++++++++++++++++++ tests/test_graphql/test_ssh.py | 33 +++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/selfprivacy_api/graphql/mutations/system_mutations.py b/selfprivacy_api/graphql/mutations/system_mutations.py index daada17..b0cdae8 100644 --- a/selfprivacy_api/graphql/mutations/system_mutations.py +++ b/selfprivacy_api/graphql/mutations/system_mutations.py @@ -9,6 +9,7 @@ from selfprivacy_api.graphql.mutations.mutation_interface import ( ) import selfprivacy_api.actions.system as system_actions +import selfprivacy_api.actions.ssh as ssh_actions @strawberry.type @@ -26,6 +27,22 @@ class AutoUpgradeSettingsMutationReturn(MutationReturnInterface): allowReboot: bool +@strawberry.type +class SSHSettingsMutationReturn(MutationReturnInterface): + """A return type for after changing SSH settings""" + + enable: bool + password_authentication: bool + + +@strawberry.input +class SSHSettingsInput: + """Input type for SSH settings""" + + enable: bool + password_authentication: bool + + @strawberry.input class AutoUpgradeSettingsInput: """Input type for auto upgrade settings""" @@ -76,6 +93,26 @@ class SystemMutations: allowReboot=new_settings.allowReboot, ) + @strawberry.mutation(permission_classes=[IsAuthenticated]) + def change_ssh_settings( + self, settings: SSHSettingsInput + ) -> SSHSettingsMutationReturn: + """Change ssh settings of the server.""" + ssh_actions.set_ssh_settings( + enable=settings.enable, + password_authentication=settings.password_authentication, + ) + + new_settings = ssh_actions.get_ssh_settings() + + return SSHSettingsMutationReturn( + success=True, + message="SSH settings changed", + code=200, + enable=new_settings.enable, + password_authentication=new_settings.passwordAuthentication, + ) + @strawberry.mutation(permission_classes=[IsAuthenticated]) def run_system_rebuild(self) -> GenericMutationReturn: system_actions.rebuild_system() diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index eabf049..5f16c53 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -4,6 +4,7 @@ import pytest from tests.common import read_json from tests.test_graphql.common import assert_empty +from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations class ProcessMock: @@ -59,6 +60,38 @@ mutation addSshKey($sshInput: SshMutationInput!) { } """ +API_SET_SSH_SETTINGS = """ +mutation enableSsh($sshInput: SSHSettingsInput!) { + system { + changeSshSettings(sshInput: $sshInput) { + success + message + code + enable + password_authentication + } + } +} +""" + + +def test_graphql_change_ssh_settings_unauthorized( + client, some_users, mock_subprocess_popen +): + response = client.post( + "/graphql", + json={ + "query": API_SET_SSH_SETTINGS, + "variables": { + "sshInput": { + "enable": True, + "passwordAuthentication": True, + }, + }, + }, + ) + assert_empty(response) + def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): response = client.post( From 66561308bf94a68c1f746c89a40c88c973569a2c Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 10:09:35 +0000 Subject: [PATCH 064/130] test(ssh): add graphql ssh status query test --- tests/test_graphql/test_ssh.py | 35 +++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 5f16c53..409ecad 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -2,9 +2,11 @@ # pylint: disable=unused-argument import pytest -from tests.common import read_json -from tests.test_graphql.common import assert_empty from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations +from selfprivacy_api.graphql.queries.system import System + +from tests.common import read_json, generate_system_query +from tests.test_graphql.common import assert_empty, get_data class ProcessMock: @@ -68,12 +70,39 @@ mutation enableSsh($sshInput: SSHSettingsInput!) { message code enable - password_authentication + passwordAuthentication } } } """ +API_SSH_SETTINGS_QUERY = """ +settings { + ssh { + enable + passwordAuthentication + } +} +""" + + +def api_ssh_settings(authorized_client): + response = authorized_client.post( + "/graphql", + json={"query": generate_system_query([API_SSH_SETTINGS_QUERY])}, + ) + data = get_data(response) + result = data["system"]["settings"]["ssh"] + assert result is not None + return result + + +def test_graphql_ssh_enabled_by_default(authorized_client, some_users): + # TODO: Should it be enabled by default though if there are no keys anyway? + settings = api_ssh_settings(authorized_client) + assert settings["enable"] is True + assert settings["passwordAuthentication"] is True + def test_graphql_change_ssh_settings_unauthorized( client, some_users, mock_subprocess_popen From f179cff0b4ebd101f8ae61f3c01ac8f791a1e996 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 10:43:07 +0000 Subject: [PATCH 065/130] test(ssh): try disabling ssh --- tests/test_graphql/test_ssh.py | 35 +++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 409ecad..9b007c0 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -6,7 +6,7 @@ from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations from selfprivacy_api.graphql.queries.system import System from tests.common import read_json, generate_system_query -from tests.test_graphql.common import assert_empty, get_data +from tests.test_graphql.common import assert_empty, assert_ok, get_data class ProcessMock: @@ -63,9 +63,9 @@ mutation addSshKey($sshInput: SshMutationInput!) { """ API_SET_SSH_SETTINGS = """ -mutation enableSsh($sshInput: SSHSettingsInput!) { +mutation enableSsh($settings: SSHSettingsInput!) { system { - changeSshSettings(sshInput: $sshInput) { + changeSshSettings(settings: $settings) { success message code @@ -97,8 +97,26 @@ def api_ssh_settings(authorized_client): return result -def test_graphql_ssh_enabled_by_default(authorized_client, some_users): - # TODO: Should it be enabled by default though if there are no keys anyway? +def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool): + response = authorized_client.post( + "/graphql", + json={ + "query": API_SET_SSH_SETTINGS, + "variables": { + "settings": { + "enable": enable, + "passwordAuthentication": password_auth, + }, + }, + }, + ) + data = get_data(response) + result = data["system"]["changeSshSettings"] + assert result is not None + return result + + +def test_graphql_ssh_query(authorized_client, some_users): settings = api_ssh_settings(authorized_client) assert settings["enable"] is True assert settings["passwordAuthentication"] is True @@ -122,6 +140,13 @@ def test_graphql_change_ssh_settings_unauthorized( assert_empty(response) +def test_graphql_disable_ssh(authorized_client, some_users, mock_subprocess_popen): + output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) + assert_ok(output) + assert output["enable"] == False + assert output["passwordAuthentication"] == False + + def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): response = client.post( "/graphql", From 7c382c4779ad57854f068169f69402b9519c8ec2 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 10:51:47 +0000 Subject: [PATCH 066/130] test(ssh): flip flop ssh --- tests/test_graphql/test_ssh.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 9b007c0..eefa6d8 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -140,12 +140,29 @@ def test_graphql_change_ssh_settings_unauthorized( assert_empty(response) -def test_graphql_disable_ssh(authorized_client, some_users, mock_subprocess_popen): +def test_graphql_disable_enable_ssh( + authorized_client, some_users, mock_subprocess_popen +): output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) assert_ok(output) assert output["enable"] == False assert output["passwordAuthentication"] == False + output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) + assert_ok(output) + assert output["enable"] == True + assert output["passwordAuthentication"] == True + + output = api_set_ssh_settings(authorized_client, enable=True, password_auth=False) + assert_ok(output) + assert output["enable"] == True + assert output["passwordAuthentication"] == False + + output = api_set_ssh_settings(authorized_client, enable=False, password_auth=True) + assert_ok(output) + assert output["enable"] == False + assert output["passwordAuthentication"] == True + def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): response = client.post( From 4e730f015a2b453d75db208a22729dfb4150a8c1 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 11:02:31 +0000 Subject: [PATCH 067/130] test(ssh): test that query is in sync --- tests/test_graphql/test_ssh.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index eefa6d8..a0d82ba 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -140,6 +140,11 @@ def test_graphql_change_ssh_settings_unauthorized( assert_empty(response) +def assert_includes(smaller_dict: dict, bigger_dict: dict): + for item in smaller_dict.items(): + assert item in bigger_dict.items() + + def test_graphql_disable_enable_ssh( authorized_client, some_users, mock_subprocess_popen ): @@ -147,21 +152,25 @@ def test_graphql_disable_enable_ssh( assert_ok(output) assert output["enable"] == False assert output["passwordAuthentication"] == False + assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) assert_ok(output) assert output["enable"] == True assert output["passwordAuthentication"] == True + assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=True, password_auth=False) assert_ok(output) assert output["enable"] == True assert output["passwordAuthentication"] == False + assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=False, password_auth=True) assert_ok(output) assert output["enable"] == False assert output["passwordAuthentication"] == True + assert_includes(api_ssh_settings(authorized_client), output) def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): From 1bb24b5f932edbd388d89baf49e773e7590e2b23 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 11:09:20 +0000 Subject: [PATCH 068/130] test(ssh): test idempotency of enablement --- tests/test_graphql/test_ssh.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index a0d82ba..7ec51d4 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -173,6 +173,32 @@ def test_graphql_disable_enable_ssh( assert_includes(api_ssh_settings(authorized_client), output) +def test_graphql_disable_twice(authorized_client, some_users, mock_subprocess_popen): + output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) + assert_ok(output) + assert output["enable"] == False + assert output["passwordAuthentication"] == False + + output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) + assert_ok(output) + assert output["enable"] == False + assert output["passwordAuthentication"] == False + + +def test_graphql_enable_twice(authorized_client, some_users, mock_subprocess_popen): + output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) + assert_ok(output) + assert output["enable"] == True + assert output["passwordAuthentication"] == True + assert_includes(api_ssh_settings(authorized_client), output) + + output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) + assert_ok(output) + assert output["enable"] == True + assert output["passwordAuthentication"] == True + assert_includes(api_ssh_settings(authorized_client), output) + + def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): response = client.post( "/graphql", From b644208c29e9fa06b607ce747dda43af5d4de20a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 11:22:20 +0000 Subject: [PATCH 069/130] test(ssh): cleanup --- tests/test_graphql/test_ssh.py | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 7ec51d4..c601b28 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -16,7 +16,7 @@ class ProcessMock: self.args = args self.kwargs = kwargs - def communicate(): # pylint: disable=no-method-argument + def communicate(self): # pylint: disable=no-method-argument return (b"NEW_HASHED", None) returncode = 0 @@ -150,52 +150,52 @@ def test_graphql_disable_enable_ssh( ): output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) assert_ok(output) - assert output["enable"] == False - assert output["passwordAuthentication"] == False + assert output["enable"] is False + assert output["passwordAuthentication"] is False assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) assert_ok(output) - assert output["enable"] == True - assert output["passwordAuthentication"] == True + assert output["enable"] is True + assert output["passwordAuthentication"] is True assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=True, password_auth=False) assert_ok(output) - assert output["enable"] == True - assert output["passwordAuthentication"] == False + assert output["enable"] is True + assert output["passwordAuthentication"] is False assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=False, password_auth=True) assert_ok(output) - assert output["enable"] == False - assert output["passwordAuthentication"] == True + assert output["enable"] is False + assert output["passwordAuthentication"] is True assert_includes(api_ssh_settings(authorized_client), output) -def test_graphql_disable_twice(authorized_client, some_users, mock_subprocess_popen): +def test_graphql_disable_twice(authorized_client, some_users): output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) assert_ok(output) - assert output["enable"] == False - assert output["passwordAuthentication"] == False + assert output["enable"] is False + assert output["passwordAuthentication"] is False output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) assert_ok(output) - assert output["enable"] == False - assert output["passwordAuthentication"] == False + assert output["enable"] is False + assert output["passwordAuthentication"] is False -def test_graphql_enable_twice(authorized_client, some_users, mock_subprocess_popen): +def test_graphql_enable_twice(authorized_client, some_users): output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) assert_ok(output) - assert output["enable"] == True - assert output["passwordAuthentication"] == True + assert output["enable"] is True + assert output["passwordAuthentication"] is True assert_includes(api_ssh_settings(authorized_client), output) output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) assert_ok(output) - assert output["enable"] == True - assert output["passwordAuthentication"] == True + assert output["enable"] is True + assert output["passwordAuthentication"] is True assert_includes(api_ssh_settings(authorized_client), output) From e11e73f8725032426a219ca99aa92dae1bbe29e8 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 15 Dec 2023 12:48:18 +0000 Subject: [PATCH 070/130] test(ssh): add json storage writing tests --- tests/test_ssh.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/test_ssh.py diff --git a/tests/test_ssh.py b/tests/test_ssh.py new file mode 100644 index 0000000..6cb255d --- /dev/null +++ b/tests/test_ssh.py @@ -0,0 +1,84 @@ +import pytest + +from selfprivacy_api.actions.ssh import set_ssh_settings, get_ssh_settings +from selfprivacy_api.utils import WriteUserData, ReadUserData + + +@pytest.fixture(params=[True, False]) +def bool_value(request): + return request.param + + +@pytest.fixture( + params=[ + "normal_populated_json", + "deleted_enabled", + "deleted_auth", + "empty", + "ssh_not_in_json", + ] +) +def possibly_undefined_ssh_settings(generic_userdata, request, bool_value): + with WriteUserData() as data: + data["ssh"] = {"enable": bool_value, "passswordAuthentication": bool_value} + assert get_raw_json_ssh_setting("enable") == bool_value + assert get_raw_json_ssh_setting("passswordAuthentication") == bool_value + + if request.param == "deleted_enabled": + with WriteUserData() as data: + del data["ssh"]["enable"] + + if request.param == "deleted_auth": + with WriteUserData() as data: + del data["ssh"]["passswordAuthentication"] + + if request.param == "empty": + with WriteUserData() as data: + del data["ssh"]["passswordAuthentication"] + del data["ssh"]["enable"] + + if request.param == "ssh_not_in_json": + with WriteUserData() as data: + del data["ssh"] + + +@pytest.fixture(params=[True, False, None]) +def ssh_enable_spectrum(request): + return request.param + + +@pytest.fixture(params=[True, False, None]) +def password_auth_spectrum(request): + return request.param + + +def get_raw_json_ssh_setting(setting: str): + with ReadUserData() as data: + return (data.get("ssh") or {}).get(setting) + + +def test_enabling_disabling_writes_json( + possibly_undefined_ssh_settings, ssh_enable_spectrum, password_auth_spectrum +): + + original_enable = get_raw_json_ssh_setting("enable") + original_password_auth = get_raw_json_ssh_setting("passwordAuthentication") + + set_ssh_settings(ssh_enable_spectrum, password_auth_spectrum) + + with ReadUserData() as data: + if ssh_enable_spectrum is None: + assert get_raw_json_ssh_setting("enable") == original_enable + else: + assert get_raw_json_ssh_setting("enable") == ssh_enable_spectrum + + if password_auth_spectrum is None: + assert ( + get_raw_json_ssh_setting("passwordAuthentication") + == original_password_auth + ) + else: + assert ( + get_raw_json_ssh_setting("passwordAuthentication") + == password_auth_spectrum + ) From f35280b76478343bd05e3f33dc63cb69cdc2defd Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 11:21:21 +0000 Subject: [PATCH 071/130] test(ssh): add json storage reading tests --- tests/test_ssh.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 6cb255d..5c3414b 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -57,6 +57,31 @@ def get_raw_json_ssh_setting(setting: str): return (data.get("ssh") or {}).get(setting) +def test_read_json(possibly_undefined_ssh_settings): + with ReadUserData() as data: + if "ssh" not in data.keys(): + assert get_ssh_settings().enable is not None + assert get_ssh_settings().passwordAuthentication is not None + + # TODO: Is it really a good idea to have password ssh enabled by default? + assert get_ssh_settings().enable is True + assert get_ssh_settings().passwordAuthentication is True + return + + if "enable" not in data["ssh"].keys(): + assert get_ssh_settings().enable is True + else: + assert get_ssh_settings().enable == data["ssh"]["enable"] + + if "passwordAuthentication" not in data["ssh"].keys(): + assert get_ssh_settings().passwordAuthentication is True + else: + assert ( + get_ssh_settings().passwordAuthentication + == data["ssh"]["passwordAuthentication"] + ) + + def test_enabling_disabling_writes_json( possibly_undefined_ssh_settings, ssh_enable_spectrum, password_auth_spectrum ): From 5651dcd94ec73962aaa603cdcbb44a72692fbb7b Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 11:49:58 +0000 Subject: [PATCH 072/130] test(ssh): remove rest tests for undefined ssh settings --- .../test_rest_endpoints/services/test_ssh.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index a1a33f8..961b277 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -152,18 +152,6 @@ def test_get_current_settings_all_off(authorized_client, all_off): assert response.json() == {"enable": False, "passwordAuthentication": False} -def test_get_current_settings_undefined(authorized_client, undefined_settings): - response = authorized_client.get("/services/ssh") - assert response.status_code == 200 - assert response.json() == {"enable": True, "passwordAuthentication": True} - - -def test_get_current_settings_mostly_undefined(authorized_client, undefined_values): - response = authorized_client.get("/services/ssh") - assert response.status_code == 200 - assert response.json() == {"enable": True, "passwordAuthentication": True} - - ## PUT ON /ssh ###################################################### available_settings = [ @@ -211,17 +199,6 @@ def test_set_settings_all_off(authorized_client, all_off, settings): assert data["passwordAuthentication"] == settings["passwordAuthentication"] -@pytest.mark.parametrize("settings", available_settings) -def test_set_settings_undefined(authorized_client, undefined_settings, settings): - response = authorized_client.put("/services/ssh", json=settings) - assert response.status_code == 200 - data = read_json(undefined_settings / "undefined.json")["ssh"] - if "enable" in settings: - assert data["enable"] == settings["enable"] - if "passwordAuthentication" in settings: - assert data["passwordAuthentication"] == settings["passwordAuthentication"] - - ## PUT ON /ssh/key/send ###################################################### From ed4f6bfe327f531a3705456c6435a3b87db47919 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 11:50:24 +0000 Subject: [PATCH 073/130] test(ssh): add test for unauthorized settings getting --- tests/test_graphql/test_ssh.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index c601b28..9f6b6eb 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -86,11 +86,15 @@ settings { """ -def api_ssh_settings(authorized_client): - response = authorized_client.post( +def api_ssh_settings_raw(client): + return client.post( "/graphql", json={"query": generate_system_query([API_SSH_SETTINGS_QUERY])}, ) + + +def api_ssh_settings(authorized_client): + response = api_ssh_settings_raw(authorized_client) data = get_data(response) result = data["system"]["settings"]["ssh"] assert result is not None @@ -122,9 +126,12 @@ def test_graphql_ssh_query(authorized_client, some_users): assert settings["passwordAuthentication"] is True -def test_graphql_change_ssh_settings_unauthorized( - client, some_users, mock_subprocess_popen -): +def test_graphql_get_ssh_settings_unauthorized(client, some_users): + response = api_ssh_settings_raw(client) + assert_empty(response) + + +def test_graphql_change_ssh_settings_unauthorized(client, some_users): response = client.post( "/graphql", json={ @@ -148,18 +155,21 @@ def assert_includes(smaller_dict: dict, bigger_dict: dict): def test_graphql_disable_enable_ssh( authorized_client, some_users, mock_subprocess_popen ): + # Off output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) assert_ok(output) assert output["enable"] is False assert output["passwordAuthentication"] is False assert_includes(api_ssh_settings(authorized_client), output) + # On output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) assert_ok(output) assert output["enable"] is True assert output["passwordAuthentication"] is True assert_includes(api_ssh_settings(authorized_client), output) + # Criss-Cross output = api_set_ssh_settings(authorized_client, enable=True, password_auth=False) assert_ok(output) assert output["enable"] is True From 6c0d4ab42ab4a37514b7de5f98d050f05843e4ba Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 11:57:21 +0000 Subject: [PATCH 074/130] test(ssh): remove basic unauthorized tests from rest ssh tests --- tests/test_rest_endpoints/services/test_ssh.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 961b277..1d8343a 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -92,21 +92,6 @@ def some_users(mocker, datadir): return datadir -## TEST 401 ###################################################### - - -@pytest.mark.parametrize("endpoint", ["ssh/enable", "ssh/keys/user"]) -def test_unauthorized(client, ssh_off, endpoint): - response = client.post(f"/services/{endpoint}") - assert response.status_code == 401 - - -@pytest.mark.parametrize("endpoint", ["ssh", "ssh/key/send"]) -def test_unauthorized_put(client, ssh_off, endpoint): - response = client.put(f"/services/{endpoint}") - assert response.status_code == 401 - - ## TEST ENABLE ###################################################### From 9822d42dac7b32ae093534029802f9a6c09ba958 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 13:26:47 +0000 Subject: [PATCH 075/130] test(ssh): remove rest enablement tests --- .../test_rest_endpoints/services/test_ssh.py | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 1d8343a..d09f089 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -92,30 +92,6 @@ def some_users(mocker, datadir): return datadir -## TEST ENABLE ###################################################### - - -def test_legacy_enable(authorized_client, ssh_off): - response = authorized_client.post("/services/ssh/enable") - assert response.status_code == 200 - assert read_json(ssh_off / "turned_off.json") == read_json( - ssh_off / "turned_on.json" - ) - - -def test_legacy_on_undefined(authorized_client, undefined_settings): - response = authorized_client.post("/services/ssh/enable") - assert response.status_code == 200 - data = read_json(undefined_settings / "undefined.json") - assert data["ssh"]["enable"] == True - - -def test_legacy_enable_when_enabled(authorized_client, ssh_on): - response = authorized_client.post("/services/ssh/enable") - assert response.status_code == 200 - assert read_json(ssh_on / "turned_on.json") == read_json(ssh_on / "turned_on.json") - - ## GET ON /ssh ###################################################### From 60c7e9a7e2b846fd8e861ad9d099f336147e039c Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 14:16:30 +0000 Subject: [PATCH 076/130] test(ssh): full ssh enablement-via-gql readwrite testing --- tests/test_graphql/test_ssh.py | 83 +++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 9f6b6eb..a911fb1 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -101,16 +101,13 @@ def api_ssh_settings(authorized_client): return result -def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool): +def api_set_ssh_settings_dict(authorized_client, dict): response = authorized_client.post( "/graphql", json={ "query": API_SET_SSH_SETTINGS, "variables": { - "settings": { - "enable": enable, - "passwordAuthentication": password_auth, - }, + "settings": dict, }, }, ) @@ -120,6 +117,16 @@ def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool): return result +def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool): + return api_set_ssh_settings_dict( + authorized_client, + { + "enable": enable, + "passwordAuthentication": password_auth, + }, + ) + + def test_graphql_ssh_query(authorized_client, some_users): settings = api_ssh_settings(authorized_client) assert settings["enable"] is True @@ -152,35 +159,57 @@ def assert_includes(smaller_dict: dict, bigger_dict: dict): assert item in bigger_dict.items() -def test_graphql_disable_enable_ssh( - authorized_client, some_users, mock_subprocess_popen +available_settings = [ + {"enable": True, "passwordAuthentication": True}, + {"enable": True, "passwordAuthentication": False}, + {"enable": False, "passwordAuthentication": True}, + {"enable": False, "passwordAuthentication": False}, +] + + +original_settings = [ + {"enable": True, "passwordAuthentication": True}, + {"enable": True, "passwordAuthentication": False}, + {"enable": False, "passwordAuthentication": True}, + {"enable": False, "passwordAuthentication": False}, +] + + +@pytest.mark.parametrize("original_settings", original_settings) +@pytest.mark.parametrize("settings", available_settings) +def test_graphql_readwrite_ssh_settings( + authorized_client, some_users, settings, original_settings ): - # Off - output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False) - assert_ok(output) - assert output["enable"] is False - assert output["passwordAuthentication"] is False + + # Userdata-related tests like undefined fields are in actions-level tests. + output = api_set_ssh_settings_dict(authorized_client, original_settings) assert_includes(api_ssh_settings(authorized_client), output) - # On - output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True) + output = api_set_ssh_settings_dict(authorized_client, settings) assert_ok(output) - assert output["enable"] is True - assert output["passwordAuthentication"] is True + assert_includes(settings, output) + if "enable" not in settings.keys(): + assert output["enable"] == original_settings["enable"] assert_includes(api_ssh_settings(authorized_client), output) - # Criss-Cross - output = api_set_ssh_settings(authorized_client, enable=True, password_auth=False) - assert_ok(output) - assert output["enable"] is True - assert output["passwordAuthentication"] is False - assert_includes(api_ssh_settings(authorized_client), output) - output = api_set_ssh_settings(authorized_client, enable=False, password_auth=True) - assert_ok(output) - assert output["enable"] is False - assert output["passwordAuthentication"] is True - assert_includes(api_ssh_settings(authorized_client), output) +forbidden_settings = [ + # we include this here so that if the next version makes the fields + # optional, the tests will remind the person that tests are to be extended accordingly + {"enable": True}, + {"passwordAuthentication": True}, +] + + +@pytest.mark.parametrize("original_settings", original_settings) +@pytest.mark.parametrize("settings", forbidden_settings) +def test_graphql_readwrite_ssh_settings_partial( + authorized_client, some_users, settings, original_settings +): + + output = api_set_ssh_settings_dict(authorized_client, original_settings) + with pytest.raises(Exception): + output = api_set_ssh_settings_dict(authorized_client, settings) def test_graphql_disable_twice(authorized_client, some_users): From 0b90e3d20f5071c2ec4a073664e1096adf7b2f27 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 18 Dec 2023 14:53:47 +0000 Subject: [PATCH 077/130] test(ssh): remove rest ssh enablement tests --- .../test_rest_endpoints/services/test_ssh.py | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index d09f089..759e5ed 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -92,74 +92,6 @@ def some_users(mocker, datadir): return datadir -## GET ON /ssh ###################################################### - - -def test_get_current_settings_ssh_off(authorized_client, ssh_off): - response = authorized_client.get("/services/ssh") - assert response.status_code == 200 - assert response.json() == {"enable": False, "passwordAuthentication": True} - - -def test_get_current_settings_ssh_on(authorized_client, ssh_on): - response = authorized_client.get("/services/ssh") - assert response.status_code == 200 - assert response.json() == {"enable": True, "passwordAuthentication": True} - - -def test_get_current_settings_all_off(authorized_client, all_off): - response = authorized_client.get("/services/ssh") - assert response.status_code == 200 - assert response.json() == {"enable": False, "passwordAuthentication": False} - - -## PUT ON /ssh ###################################################### - -available_settings = [ - {"enable": True, "passwordAuthentication": True}, - {"enable": True, "passwordAuthentication": False}, - {"enable": False, "passwordAuthentication": True}, - {"enable": False, "passwordAuthentication": False}, - {"enable": True}, - {"enable": False}, - {"passwordAuthentication": True}, - {"passwordAuthentication": False}, -] - - -@pytest.mark.parametrize("settings", available_settings) -def test_set_settings_ssh_off(authorized_client, ssh_off, settings): - response = authorized_client.put("/services/ssh", json=settings) - assert response.status_code == 200 - data = read_json(ssh_off / "turned_off.json")["ssh"] - if "enable" in settings: - assert data["enable"] == settings["enable"] - if "passwordAuthentication" in settings: - assert data["passwordAuthentication"] == settings["passwordAuthentication"] - - -@pytest.mark.parametrize("settings", available_settings) -def test_set_settings_ssh_on(authorized_client, ssh_on, settings): - response = authorized_client.put("/services/ssh", json=settings) - assert response.status_code == 200 - data = read_json(ssh_on / "turned_on.json")["ssh"] - if "enable" in settings: - assert data["enable"] == settings["enable"] - if "passwordAuthentication" in settings: - assert data["passwordAuthentication"] == settings["passwordAuthentication"] - - -@pytest.mark.parametrize("settings", available_settings) -def test_set_settings_all_off(authorized_client, all_off, settings): - response = authorized_client.put("/services/ssh", json=settings) - assert response.status_code == 200 - data = read_json(all_off / "all_off.json")["ssh"] - if "enable" in settings: - assert data["enable"] == settings["enable"] - if "passwordAuthentication" in settings: - assert data["passwordAuthentication"] == settings["passwordAuthentication"] - - ## PUT ON /ssh/key/send ###################################################### From a5ab0df1614a64e32615454bcab53411d6448307 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 10:26:54 +0000 Subject: [PATCH 078/130] test(ssh): add rootkey json tests --- tests/test_ssh.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 5c3414b..f291dd4 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -1,6 +1,12 @@ import pytest -from selfprivacy_api.actions.ssh import set_ssh_settings, get_ssh_settings +from selfprivacy_api.actions.ssh import ( + set_ssh_settings, + get_ssh_settings, + create_ssh_key, + remove_ssh_key, +) +from selfprivacy_api.actions.users import get_users from selfprivacy_api.utils import WriteUserData, ReadUserData @@ -107,3 +113,77 @@ def test_enabling_disabling_writes_json( get_raw_json_ssh_setting("passwordAuthentication") == password_auth_spectrum ) + + +def test_read_root_keys_from_json(generic_userdata): + assert get_ssh_settings().rootKeys == ["ssh-ed25519 KEY test@pc"] + new_keys = ["ssh-ed25519 KEY test@pc", "ssh-ed25519 KEY2 test@pc"] + + with WriteUserData() as data: + data["ssh"]["rootKeys"] = new_keys + + assert get_ssh_settings().rootKeys == new_keys + + with WriteUserData() as data: + del data["ssh"]["rootKeys"] + + assert get_ssh_settings().rootKeys == [] + + with WriteUserData() as data: + del data["ssh"] + + assert get_ssh_settings().rootKeys == [] + + +def test_removing_root_key_writes_json(generic_userdata): + # generic userdata has a a single root key + rootkeys = get_ssh_settings().rootKeys + assert len(rootkeys) == 1 + key1 = rootkeys[0] + key2 = "ssh-rsa MYSUPERKEY root@pc" + + create_ssh_key("root", key2) + rootkeys = get_ssh_settings().rootKeys + assert len(rootkeys) == 2 + + remove_ssh_key("root", key2) + with ReadUserData() as data: + assert "ssh" in data + assert "rootKeys" in data["ssh"] + assert data["ssh"]["rootKeys"] == [key1] + + remove_ssh_key("root", key1) + with ReadUserData() as data: + assert "ssh" in data + assert "rootKeys" in data["ssh"] + assert data["ssh"]["rootKeys"] == [] + + +def test_adding_root_key_writes_json(generic_userdata): + with WriteUserData() as data: + del data["ssh"] + key1 = "ssh-ed25519 KEY test@pc" + key2 = "ssh-ed25519 KEY2 test@pc" + create_ssh_key("root", key1) + + with ReadUserData() as data: + assert "ssh" in data + assert "rootKeys" in data["ssh"] + assert data["ssh"]["rootKeys"] == [key1] + + with WriteUserData() as data: + del data["ssh"]["rootKeys"] + create_ssh_key("root", key1) + + with ReadUserData() as data: + assert "ssh" in data + assert "rootKeys" in data["ssh"] + assert data["ssh"]["rootKeys"] == [key1] + + create_ssh_key("root", key2) + + with ReadUserData() as data: + assert "ssh" in data + assert "rootKeys" in data["ssh"] + # order is irrelevant + assert set(data["ssh"]["rootKeys"]) == set([key1, key2]) From 25d2537208cefb1aeed133f5d007baeb39e48687 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 10:32:02 +0000 Subject: [PATCH 079/130] test(ssh): add docstring with scope to tests/test_ssh --- tests/test_ssh.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index f291dd4..739d321 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -1,3 +1,8 @@ +""" +Action-level tests of ssh +(For API-independent logic incl. connection to persistent storage) +""" + import pytest from selfprivacy_api.actions.ssh import ( From a2065b87b76f378ff83fdabd505ec2a6fe7691b5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 11:36:58 +0000 Subject: [PATCH 080/130] test(ssh): delete undefined root keys --- .../test_rest_endpoints/services/test_ssh.py | 15 ------------ tests/test_ssh.py | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 759e5ed..cd7ed86 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -105,15 +105,6 @@ def test_add_root_key(authorized_client, ssh_on): ] -def test_add_root_key_on_undefined(authorized_client, undefined_settings): - response = authorized_client.put( - "/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 201 - data = read_json(undefined_settings / "undefined.json") - assert data["ssh"]["rootKeys"] == ["ssh-rsa KEY test@pc"] - - def test_add_root_key_one_more(authorized_client, root_and_admin_have_keys): response = authorized_client.put( "/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"} @@ -154,12 +145,6 @@ def test_get_root_key_when_none(authorized_client, ssh_on): assert response.json() == [] -def test_get_root_key_on_undefined(authorized_client, undefined_settings): - response = authorized_client.get("/services/ssh/keys/root") - assert response.status_code == 200 - assert response.json() == [] - - def test_delete_root_key(authorized_client, root_and_admin_have_keys): response = authorized_client.delete( "/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"} diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 739d321..ec8b4b2 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -10,6 +10,7 @@ from selfprivacy_api.actions.ssh import ( get_ssh_settings, create_ssh_key, remove_ssh_key, + KeyNotFound, ) from selfprivacy_api.actions.users import get_users from selfprivacy_api.utils import WriteUserData, ReadUserData @@ -164,6 +165,29 @@ def test_removing_root_key_writes_json(generic_userdata): assert data["ssh"]["rootKeys"] == [] +def test_remove_root_key_on_undefined(generic_userdata): + # generic userdata has a a single root key + rootkeys = get_ssh_settings().rootKeys + assert len(rootkeys) == 1 + key1 = rootkeys[0] + + with WriteUserData() as data: + del data["ssh"]["rootKeys"] + + with pytest.raises(KeyNotFound): + remove_ssh_key("root", key1) + rootkeys = get_ssh_settings().rootKeys + assert len(rootkeys) == 0 + + with WriteUserData() as data: + del data["ssh"] + + with pytest.raises(KeyNotFound): + remove_ssh_key("root", key1) + rootkeys = get_ssh_settings().rootKeys + assert len(rootkeys) == 0 + + def test_adding_root_key_writes_json(generic_userdata): with WriteUserData() as data: del data["ssh"] From ee854aad1a975ba3b8e99451c564ab9f91b7b0e6 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 11:42:29 +0000 Subject: [PATCH 081/130] test(ssh): delete rest test of undefined root key deletion --- tests/test_rest_endpoints/services/test_ssh.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index cd7ed86..e9e668a 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -174,14 +174,6 @@ def test_delete_root_nonexistent_key(authorized_client, root_and_admin_have_keys ] -def test_delete_root_key_on_undefined(authorized_client, undefined_settings): - response = authorized_client.delete( - "/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"} - ) - assert response.status_code == 404 - assert "ssh" not in read_json(undefined_settings / "undefined.json") - - def test_get_admin_key(authorized_client, root_and_admin_have_keys): response = authorized_client.get("/services/ssh/keys/tester") assert response.status_code == 200 From 90c0c34a8d42302588a590f861462c883dcf3a83 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 12:08:15 +0000 Subject: [PATCH 082/130] test(ssh): add root key when none --- tests/test_graphql/test_ssh.py | 13 +++++++++++-- tests/test_rest_endpoints/services/test_ssh.py | 10 ---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index a911fb1..effc3a7 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -5,6 +5,9 @@ import pytest from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations from selfprivacy_api.graphql.queries.system import System +# only allowed in fixtures +from selfprivacy_api.actions.ssh import remove_ssh_key, get_ssh_settings + from tests.common import read_json, generate_system_query from tests.test_graphql.common import assert_empty, assert_ok, get_data @@ -43,6 +46,13 @@ def some_users(mocker, datadir): return datadir +@pytest.fixture +def no_rootkeys(generic_userdata): + for rootkey in get_ssh_settings().rootKeys: + remove_ssh_key("root", rootkey) + assert get_ssh_settings().rootKeys == [] + + # TESTS ######################################################## @@ -281,7 +291,7 @@ def test_graphql_add_ssh_key(authorized_client, some_users, mock_subprocess_pope ] -def test_graphql_add_root_ssh_key(authorized_client, some_users, mock_subprocess_popen): +def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): response = authorized_client.post( "/graphql", json={ @@ -303,7 +313,6 @@ def test_graphql_add_root_ssh_key(authorized_client, some_users, mock_subprocess assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "root" assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [ - "ssh-ed25519 KEY test@pc", "ssh-rsa KEY test_key@pc", ] diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index e9e668a..dfcce57 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,16 +95,6 @@ def some_users(mocker, datadir): ## PUT ON /ssh/key/send ###################################################### -def test_add_root_key(authorized_client, ssh_on): - response = authorized_client.put( - "/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 201 - assert read_json(ssh_on / "turned_on.json")["ssh"]["rootKeys"] == [ - "ssh-rsa KEY test@pc", - ] - - def test_add_root_key_one_more(authorized_client, root_and_admin_have_keys): response = authorized_client.put( "/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"} From e1db00e509f41f06df50d85f2f494c1e08b486d9 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 12:41:29 +0000 Subject: [PATCH 083/130] test(ssh): add one more root key --- tests/test_graphql/test_ssh.py | 91 +++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index effc3a7..766b059 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -84,6 +84,7 @@ mutation enableSsh($settings: SSHSettingsInput!) { } } } + """ API_SSH_SETTINGS_QUERY = """ @@ -96,6 +97,15 @@ settings { """ +API_ROOTKEYS_QUERY = """ +settings { + ssh { + rootSshKeys + } +} +""" + + def api_ssh_settings_raw(client): return client.post( "/graphql", @@ -103,6 +113,40 @@ def api_ssh_settings_raw(client): ) +def api_rootkeys_raw(client): + return client.post( + "/graphql", + json={"query": generate_system_query([API_ROOTKEYS_QUERY])}, + ) + + +def api_add_ssh_key(authorized_client, user: str, key: str): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CREATE_SSH_KEY_MUTATION, + "variables": { + "sshInput": { + "username": user, + "sshKey": key, + }, + }, + }, + ) + data = get_data(response) + result = data["users"]["addSshKey"] + assert result is not None + return result + + +def api_rootkeys(authorized_client): + response = api_rootkeys_raw(authorized_client) + data = get_data(response) + result = data["system"]["settings"]["ssh"]["rootSshKeys"] + assert result is not None + return result + + def api_ssh_settings(authorized_client): response = api_ssh_settings_raw(authorized_client) data = get_data(response) @@ -248,6 +292,9 @@ def test_graphql_enable_twice(authorized_client, some_users): assert_includes(api_ssh_settings(authorized_client), output) +############## KEYS + + def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen): response = client.post( "/graphql", @@ -292,30 +339,36 @@ def test_graphql_add_ssh_key(authorized_client, some_users, mock_subprocess_pope def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "root", - "sshKey": "ssh-rsa KEY test_key@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") - assert response.json()["data"]["users"]["addSshKey"]["code"] == 201 - assert response.json()["data"]["users"]["addSshKey"]["message"] is not None - assert response.json()["data"]["users"]["addSshKey"]["success"] is True + assert output["code"] == 201 + assert output["message"] is not None + assert output["success"] is True - assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "root" - assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [ + assert output["user"]["username"] == "root" + assert output["user"]["sshKeys"] == ["ssh-rsa KEY test_key@pc"] + assert api_rootkeys(authorized_client) == ["ssh-rsa KEY test_key@pc"] + + +def test_graphql_add_root_ssh_key_one_more(authorized_client, no_rootkeys): + output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") + assert output["user"]["sshKeys"] == ["ssh-rsa KEY test_key@pc"] + + output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY2 test_key@pc") + assert output["code"] == 201 + assert output["message"] is not None + assert output["success"] is True + + assert output["user"]["username"] == "root" + + expected_keys = [ "ssh-rsa KEY test_key@pc", + "ssh-rsa KEY2 test_key@pc", ] + assert output["user"]["sshKeys"] == expected_keys + assert api_rootkeys(authorized_client) == expected_keys + def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( From 4b51f42e1b3a3035d7f762e5253b88ee1ce35dbb Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 12:50:01 +0000 Subject: [PATCH 084/130] test(ssh): remove corresponding rest test --- tests/test_rest_endpoints/services/test_ssh.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index dfcce57..5045149 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -94,20 +94,6 @@ def some_users(mocker, datadir): ## PUT ON /ssh/key/send ###################################################### - -def test_add_root_key_one_more(authorized_client, root_and_admin_have_keys): - response = authorized_client.put( - "/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 201 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][ - "rootKeys" - ] == [ - "ssh-ed25519 KEY test@pc", - "ssh-rsa KEY test@pc", - ] - - def test_add_existing_root_key(authorized_client, root_and_admin_have_keys): response = authorized_client.put( "/services/ssh/key/send", json={"public_key": "ssh-ed25519 KEY test@pc"} From 641959a083e981870d792dee63a07f7427e83a0d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 20 Dec 2023 13:09:43 +0000 Subject: [PATCH 085/130] test(ssh): adding same key --- tests/test_graphql/test_ssh.py | 9 ++++++++- tests/test_rest_endpoints/services/test_ssh.py | 14 -------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 766b059..dc436cb 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -9,7 +9,7 @@ from selfprivacy_api.graphql.queries.system import System from selfprivacy_api.actions.ssh import remove_ssh_key, get_ssh_settings from tests.common import read_json, generate_system_query -from tests.test_graphql.common import assert_empty, assert_ok, get_data +from tests.test_graphql.common import assert_empty, assert_ok, get_data, assert_errorcode class ProcessMock: @@ -369,6 +369,13 @@ def test_graphql_add_root_ssh_key_one_more(authorized_client, no_rootkeys): assert output["user"]["sshKeys"] == expected_keys assert api_rootkeys(authorized_client) == expected_keys +def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): + key = "ssh-rsa KEY test_key@pc" + output = api_add_ssh_key(authorized_client, "root", key) + assert output["user"]["sshKeys"] == [key] + + output = api_add_ssh_key(authorized_client, "root", key) + assert_errorcode(output, 409) def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 5045149..56a4020 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -92,20 +92,6 @@ def some_users(mocker, datadir): return datadir -## PUT ON /ssh/key/send ###################################################### - -def test_add_existing_root_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.put( - "/services/ssh/key/send", json={"public_key": "ssh-ed25519 KEY test@pc"} - ) - assert response.status_code == 409 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][ - "rootKeys" - ] == [ - "ssh-ed25519 KEY test@pc", - ] - - ## /ssh/keys/{user} ###################################################### From 7f1fcd66e35d9f572feabec707ede5a64bbc73f2 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 08:13:08 +0000 Subject: [PATCH 086/130] test(ssh): get root key --- tests/test_graphql/test_ssh.py | 13 ++++++++++++- tests/test_rest_endpoints/services/test_ssh.py | 6 ------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index dc436cb..e1fe80f 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -9,7 +9,12 @@ from selfprivacy_api.graphql.queries.system import System from selfprivacy_api.actions.ssh import remove_ssh_key, get_ssh_settings from tests.common import read_json, generate_system_query -from tests.test_graphql.common import assert_empty, assert_ok, get_data, assert_errorcode +from tests.test_graphql.common import ( + assert_empty, + assert_ok, + get_data, + assert_errorcode, +) class ProcessMock: @@ -338,6 +343,10 @@ def test_graphql_add_ssh_key(authorized_client, some_users, mock_subprocess_pope ] +def test_graphql_get_root_key(authorized_client, some_users): + assert api_rootkeys(authorized_client) == ["ssh-ed25519 KEY test@pc"] + + def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") @@ -369,6 +378,7 @@ def test_graphql_add_root_ssh_key_one_more(authorized_client, no_rootkeys): assert output["user"]["sshKeys"] == expected_keys assert api_rootkeys(authorized_client) == expected_keys + def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): key = "ssh-rsa KEY test_key@pc" output = api_add_ssh_key(authorized_client, "root", key) @@ -377,6 +387,7 @@ def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): output = api_add_ssh_key(authorized_client, "root", key) assert_errorcode(output, 409) + def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( "/graphql", diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 56a4020..f77e71f 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,12 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_get_root_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.get("/services/ssh/keys/root") - assert response.status_code == 200 - assert response.json() == ["ssh-ed25519 KEY test@pc"] - - def test_get_root_key_when_none(authorized_client, ssh_on): response = authorized_client.get("/services/ssh/keys/root") assert response.status_code == 200 From cf2935938d7163265bc4a11790b1f6805a8523aa Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 08:24:17 +0000 Subject: [PATCH 087/130] test(ssh): get root key when none --- tests/test_graphql/test_ssh.py | 4 ++++ tests/test_rest_endpoints/services/test_ssh.py | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index e1fe80f..752f808 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -347,6 +347,10 @@ def test_graphql_get_root_key(authorized_client, some_users): assert api_rootkeys(authorized_client) == ["ssh-ed25519 KEY test@pc"] +def test_graphql_get_root_key_when_none(authorized_client, no_rootkeys): + assert api_rootkeys(authorized_client) == [] + + def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index f77e71f..973adb6 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,12 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_get_root_key_when_none(authorized_client, ssh_on): - response = authorized_client.get("/services/ssh/keys/root") - assert response.status_code == 200 - assert response.json() == [] - - def test_delete_root_key(authorized_client, root_and_admin_have_keys): response = authorized_client.delete( "/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"} From 8fc7796da01802cf6d8f9a17f92c8413b4d76e30 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 08:48:29 +0000 Subject: [PATCH 088/130] test(ssh): remove root key --- tests/test_graphql/test_ssh.py | 4 +--- tests/test_rest_endpoints/services/test_ssh.py | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 752f808..b16bf4c 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -522,9 +522,7 @@ def test_graphql_remove_ssh_key(authorized_client, some_users, mock_subprocess_p assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] -def test_graphql_remove_root_ssh_key( - authorized_client, some_users, mock_subprocess_popen -): +def test_graphql_remove_root_ssh_key(authorized_client, some_users): response = authorized_client.post( "/graphql", json={ diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 973adb6..9eaaf17 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,23 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_delete_root_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.delete( - "/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"} - ) - assert response.status_code == 200 - assert ( - "rootKeys" - not in read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[ - "ssh" - ] - or read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][ - "rootKeys" - ] - == [] - ) - - def test_delete_root_nonexistent_key(authorized_client, root_and_admin_have_keys): response = authorized_client.delete( "/services/ssh/keys/root", json={"public_key": "ssh-rsa KEY test@pc"} From e7075546c5195386d74426dcf30ebb76150c6b18 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 09:04:27 +0000 Subject: [PATCH 089/130] test(ssh): remove root key nonexistent --- tests/test_graphql/test_ssh.py | 46 ++++++++++++------- .../test_rest_endpoints/services/test_ssh.py | 12 ----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index b16bf4c..cf71c78 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -144,6 +144,25 @@ def api_add_ssh_key(authorized_client, user: str, key: str): return result +def api_remove_ssh_key(authorized_client, user: str, key: str): + response = authorized_client.post( + "/graphql", + json={ + "query": API_REMOVE_SSH_KEY_MUTATION, + "variables": { + "sshInput": { + "username": user, + "sshKey": key, + }, + }, + }, + ) + data = get_data(response) + result = data["users"]["removeSshKey"] + assert result is not None + return result + + def api_rootkeys(authorized_client): response = api_rootkeys_raw(authorized_client) data = get_data(response) @@ -579,24 +598,17 @@ def test_graphql_remove_main_ssh_key( def test_graphql_remove_nonexistent_ssh_key( authorized_client, some_users, mock_subprocess_popen ): - response = authorized_client.post( - "/graphql", - json={ - "query": API_REMOVE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "user1", - "sshKey": "ssh-rsa KEY test_key@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_remove_ssh_key(authorized_client, "user1", "ssh-rsa KEY test_key@pc") + assert_errorcode(output, 404) - assert response.json()["data"]["users"]["removeSshKey"]["code"] == 404 - assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None - assert response.json()["data"]["users"]["removeSshKey"]["success"] is False + +def test_graphql_remove_nonexistent_root_key( + authorized_client, some_users, mock_subprocess_popen +): + output = api_remove_ssh_key( + authorized_client, "root", "ssh-rsa gone in a puff of logic" + ) + assert_errorcode(output, 404) def test_graphql_remove_ssh_key_nonexistent_user( diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 9eaaf17..3ede686 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,18 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_delete_root_nonexistent_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.delete( - "/services/ssh/keys/root", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 404 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][ - "rootKeys" - ] == [ - "ssh-ed25519 KEY test@pc", - ] - - def test_get_admin_key(authorized_client, root_and_admin_have_keys): response = authorized_client.get("/services/ssh/keys/tester") assert response.status_code == 200 From ac41cc00ced4ddf016ee88ffef9607e73dc65963 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 10:08:34 +0000 Subject: [PATCH 090/130] test(ssh): admin keys getting --- tests/test_graphql/test_ssh.py | 49 +++++++++++++++++-- .../test_rest_endpoints/services/test_ssh.py | 12 ----- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index cf71c78..2d83eea 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -1,20 +1,23 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument import pytest +from typing import Optional from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations from selfprivacy_api.graphql.queries.system import System -# only allowed in fixtures +# only allowed in fixtures and utils from selfprivacy_api.actions.ssh import remove_ssh_key, get_ssh_settings +from selfprivacy_api.actions.users import get_users, UserDataUserOrigin -from tests.common import read_json, generate_system_query +from tests.common import read_json, generate_system_query, generate_users_query from tests.test_graphql.common import ( assert_empty, assert_ok, get_data, assert_errorcode, ) +from tests.test_graphql.test_users import API_USERS_INFO class ProcessMock: @@ -58,6 +61,38 @@ def no_rootkeys(generic_userdata): assert get_ssh_settings().rootKeys == [] +@pytest.fixture +def no_admin_key(generic_userdata, authorized_client): + admin_keys = api_get_user_keys(authorized_client, admin_name()) + + for admin_key in admin_keys: + remove_ssh_key(admin_name(), admin_key) + + assert api_get_user_keys(authorized_client, admin_name()) == [] + + +def admin_name() -> Optional[str]: + users = get_users() + for user in users: + if user.origin == UserDataUserOrigin.PRIMARY: + return user.username + return None + + +def api_get_user_keys(authorized_client, user: str): + response = authorized_client.post( + "/graphql", + json={ + "query": generate_users_query([API_USERS_INFO]), + }, + ) + data = get_data(response)["users"]["allUsers"] + for _user in data: + if _user["username"] == user: + return _user["sshKeys"] + return None + + # TESTS ######################################################## @@ -370,6 +405,13 @@ def test_graphql_get_root_key_when_none(authorized_client, no_rootkeys): assert api_rootkeys(authorized_client) == [] +# Getting admin keys when they are present is tested in test_users.py + + +def test_get_admin_key_when_none(authorized_client, no_admin_key): + assert api_get_user_keys(authorized_client, admin_name()) == [] + + def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") @@ -411,7 +453,7 @@ def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): assert_errorcode(output, 409) -def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess_popen): +def test_graphql_add_admin_key(authorized_client, some_users): response = authorized_client.post( "/graphql", json={ @@ -438,6 +480,7 @@ def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess ] +# TODO: multiplex for root and admin def test_graphql_add_bad_ssh_key(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( "/graphql", diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 3ede686..b9817c6 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,18 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_get_admin_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.get("/services/ssh/keys/tester") - assert response.status_code == 200 - assert response.json() == ["ssh-rsa KEY test@pc"] - - -def test_get_admin_key_when_none(authorized_client, ssh_on): - response = authorized_client.get("/services/ssh/keys/tester") - assert response.status_code == 200 - assert response.json() == [] - - def test_delete_admin_key(authorized_client, root_and_admin_have_keys): response = authorized_client.delete( "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} From f24aba8abb3c7bbc97edfa16903eb7ddae59ca00 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 11:40:08 +0000 Subject: [PATCH 091/130] test(ssh): admin keys deleting --- tests/test_graphql/test_ssh.py | 41 ++++--------------- .../test_rest_endpoints/services/test_ssh.py | 21 ---------- 2 files changed, 9 insertions(+), 53 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 2d83eea..ee95cbe 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -93,9 +93,6 @@ def api_get_user_keys(authorized_client, user: str): return None -# TESTS ######################################################## - - API_CREATE_SSH_KEY_MUTATION = """ mutation addSshKey($sshInput: SshMutationInput!) { users { @@ -240,6 +237,9 @@ def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool): ) +# TESTS ######################################################## + + def test_graphql_ssh_query(authorized_client, some_users): settings = api_ssh_settings(authorized_client) assert settings["enable"] is True @@ -638,40 +638,17 @@ def test_graphql_remove_main_ssh_key( assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] -def test_graphql_remove_nonexistent_ssh_key( - authorized_client, some_users, mock_subprocess_popen -): - output = api_remove_ssh_key(authorized_client, "user1", "ssh-rsa KEY test_key@pc") - assert_errorcode(output, 404) +key_users = ["root", "tester", "user1"] -def test_graphql_remove_nonexistent_root_key( - authorized_client, some_users, mock_subprocess_popen -): - output = api_remove_ssh_key( - authorized_client, "root", "ssh-rsa gone in a puff of logic" - ) +@pytest.mark.parametrize("user", key_users) +def test_graphql_remove_nonexistent_ssh_key(authorized_client, some_users, user): + output = api_remove_ssh_key(authorized_client, user, "ssh-rsa nonexistent") assert_errorcode(output, 404) def test_graphql_remove_ssh_key_nonexistent_user( authorized_client, some_users, mock_subprocess_popen ): - response = authorized_client.post( - "/graphql", - json={ - "query": API_REMOVE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "user666", - "sshKey": "ssh-rsa KEY test_key@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["removeSshKey"]["code"] == 404 - assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None - assert response.json()["data"]["users"]["removeSshKey"]["success"] is False + output = api_remove_ssh_key(authorized_client, "user666", "ssh-rsa KEY test_key@pc") + assert_errorcode(output, 404) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index b9817c6..03dadb3 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,27 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_delete_admin_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.delete( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 200 - assert ( - read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["sshKeys"] - == [] - ) - - -def test_delete_nonexistent_admin_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.delete( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa NO KEY test@pc"} - ) - assert response.status_code == 404 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[ - "sshKeys" - ] == ["ssh-rsa KEY test@pc"] - - def test_delete_admin_key_on_undefined(authorized_client, undefined_settings): response = authorized_client.delete( "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} From b1eec1e37bcdaf0e25f22334ba4a7926d8320cb2 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Thu, 21 Dec 2023 13:05:06 +0000 Subject: [PATCH 092/130] test(ssh): admin keys json storage tests --- .../test_rest_endpoints/services/test_ssh.py | 8 -- tests/test_ssh.py | 100 +++++++++++++++++- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 03dadb3..10f7752 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,14 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_delete_admin_key_on_undefined(authorized_client, undefined_settings): - response = authorized_client.delete( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 404 - assert "sshKeys" not in read_json(undefined_settings / "undefined.json") - - def test_add_admin_key(authorized_client, ssh_on): response = authorized_client.post( "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} diff --git a/tests/test_ssh.py b/tests/test_ssh.py index ec8b4b2..d333eef 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -4,6 +4,7 @@ Action-level tests of ssh """ import pytest +from typing import Optional from selfprivacy_api.actions.ssh import ( set_ssh_settings, @@ -12,7 +13,11 @@ from selfprivacy_api.actions.ssh import ( remove_ssh_key, KeyNotFound, ) -from selfprivacy_api.actions.users import get_users +from selfprivacy_api.actions.users import ( + get_users, + get_user_by_username, + UserDataUserOrigin, +) from selfprivacy_api.utils import WriteUserData, ReadUserData @@ -64,6 +69,14 @@ def password_auth_spectrum(request): return request.param +def admin_name() -> Optional[str]: + users = get_users() + for user in users: + if user.origin == UserDataUserOrigin.PRIMARY: + return user.username + return None + + def get_raw_json_ssh_setting(setting: str): with ReadUserData() as data: return (data.get("ssh") or {}).get(setting) @@ -121,6 +134,9 @@ def test_enabling_disabling_writes_json( ) +############### ROOTKEYS + + def test_read_root_keys_from_json(generic_userdata): assert get_ssh_settings().rootKeys == ["ssh-ed25519 KEY test@pc"] new_keys = ["ssh-ed25519 KEY test@pc", "ssh-ed25519 KEY2 test@pc"] @@ -216,3 +232,85 @@ def test_adding_root_key_writes_json(generic_userdata): assert "rootKeys" in data["ssh"] # order is irrelevant assert set(data["ssh"]["rootKeys"]) == set([key1, key2]) + + +############### ADMIN KEYS + + +def test_read_admin_keys_from_json(generic_userdata): + admin_name = "tester" + assert get_user_by_username(admin_name).ssh_keys == ["ssh-rsa KEY test@pc"] + new_keys = ["ssh-rsa KEY test@pc", "ssh-ed25519 KEY2 test@pc"] + + with WriteUserData() as data: + data["sshKeys"] = new_keys + + get_user_by_username(admin_name).ssh_keys == new_keys + + with WriteUserData() as data: + del data["sshKeys"] + + get_user_by_username(admin_name).ssh_keys == [] + + +def test_adding_admin_key_writes_json(generic_userdata): + admin_name = "tester" + + with WriteUserData() as data: + del data["sshKeys"] + key1 = "ssh-ed25519 KEY test@pc" + key2 = "ssh-ed25519 KEY2 test@pc" + create_ssh_key(admin_name, key1) + + with ReadUserData() as data: + assert "sshKeys" in data + assert data["sshKeys"] == [key1] + + create_ssh_key(admin_name, key2) + + with ReadUserData() as data: + assert "sshKeys" in data + # order is irrelevant + assert set(data["sshKeys"]) == set([key1, key2]) + + +def test_removing_admin_key_writes_json(generic_userdata): + # generic userdata has a a single root key + admin_name = "tester" + + admin_keys = get_user_by_username(admin_name).ssh_keys + assert len(admin_keys) == 1 + key1 = admin_keys[0] + key2 = "ssh-rsa MYSUPERKEY admin@pc" + + create_ssh_key(admin_name, key2) + admin_keys = get_user_by_username(admin_name).ssh_keys + assert len(admin_keys) == 2 + + remove_ssh_key(admin_name, key2) + + with ReadUserData() as data: + assert "sshKeys" in data + assert data["sshKeys"] == [key1] + + remove_ssh_key(admin_name, key1) + with ReadUserData() as data: + assert "sshKeys" in data + assert data["sshKeys"] == [] + + +def test_remove_admin_key_on_undefined(generic_userdata): + # generic userdata has a a single root key + admin_name = "tester" + + admin_keys = get_user_by_username(admin_name).ssh_keys + assert len(admin_keys) == 1 + key1 = admin_keys[0] + + with WriteUserData() as data: + del data["sshKeys"] + + with pytest.raises(KeyNotFound): + remove_ssh_key(admin_name, key1) + admin_keys = get_user_by_username(admin_name).ssh_keys + assert len(admin_keys) == 0 From 7c4c5929df5b78599f758346e418642372a849b1 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 06:57:13 +0000 Subject: [PATCH 093/130] test(ssh): parametrized testing of ssh key addition --- tests/data/turned_on.json | 21 ++++- tests/test_graphql/test_ssh.py | 92 +++++++------------ .../test_rest_endpoints/services/test_ssh.py | 10 -- 3 files changed, 52 insertions(+), 71 deletions(-) diff --git a/tests/data/turned_on.json b/tests/data/turned_on.json index 5b41501..06e957a 100644 --- a/tests/data/turned_on.json +++ b/tests/data/turned_on.json @@ -32,5 +32,24 @@ "accountId": "ID", "accountKey": "KEY", "bucket": "selfprivacy" - } + }, + "users": [ + { + "username": "user1", + "hashedPassword": "HASHED_PASSWORD_1", + "sshKeys": [ + "ssh-rsa KEY user1@pc" + ] + }, + { + "username": "user2", + "hashedPassword": "HASHED_PASSWORD_2", + "sshKeys": [ + ] + }, + { + "username": "user3", + "hashedPassword": "HASHED_PASSWORD_3" + } + ] } diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index ee95cbe..2b2e521 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -19,6 +19,8 @@ from tests.test_graphql.common import ( ) from tests.test_graphql.test_users import API_USERS_INFO +key_users = ["root", "tester", "user1"] + class ProcessMock: """Mock subprocess.Popen""" @@ -61,6 +63,19 @@ def no_rootkeys(generic_userdata): assert get_ssh_settings().rootKeys == [] +@pytest.fixture +def no_keys(generic_userdata): + # this removes root and admin keys too + + users = get_users() + for user in users: + for key in user.ssh_keys: + remove_ssh_key(user.username, key) + users = get_users() + for user in users: + assert user.ssh_keys == [] + + @pytest.fixture def no_admin_key(generic_userdata, authorized_client): admin_keys = api_get_user_keys(authorized_client, admin_name()) @@ -370,31 +385,7 @@ def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_po assert_empty(response) -def test_graphql_add_ssh_key(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "user1", - "sshKey": "ssh-rsa KEY test_key@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["addSshKey"]["code"] == 201 - assert response.json()["data"]["users"]["addSshKey"]["message"] is not None - assert response.json()["data"]["users"]["addSshKey"]["success"] is True - - assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "user1" - assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [ - "ssh-rsa KEY user1@pc", - "ssh-rsa KEY test_key@pc", - ] +# Unauth getting of keys is tested in test_users.py because it is a part of users interface def test_graphql_get_root_key(authorized_client, some_users): @@ -412,16 +403,27 @@ def test_get_admin_key_when_none(authorized_client, no_admin_key): assert api_get_user_keys(authorized_client, admin_name()) == [] -def test_graphql_add_root_ssh_key(authorized_client, no_rootkeys): - output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") +@pytest.mark.parametrize("user", key_users) +def test_graphql_add_ssh_key_when_none(authorized_client, no_keys, user): + key1 = "ssh-rsa KEY test_key@pc" + if user == "root": + assert api_rootkeys(authorized_client) == [] + else: + assert api_get_user_keys(authorized_client, user) == [] + + output = api_add_ssh_key(authorized_client, user, key1) assert output["code"] == 201 assert output["message"] is not None assert output["success"] is True - assert output["user"]["username"] == "root" - assert output["user"]["sshKeys"] == ["ssh-rsa KEY test_key@pc"] - assert api_rootkeys(authorized_client) == ["ssh-rsa KEY test_key@pc"] + assert output["user"]["username"] == user + assert output["user"]["sshKeys"] == [key1] + + if user == "root": + assert api_rootkeys(authorized_client) == [key1] + else: + assert api_get_user_keys(authorized_client, user) == [key1] def test_graphql_add_root_ssh_key_one_more(authorized_client, no_rootkeys): @@ -453,33 +455,6 @@ def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): assert_errorcode(output, 409) -def test_graphql_add_admin_key(authorized_client, some_users): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "tester", - "sshKey": "ssh-rsa KEY test_key@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["addSshKey"]["code"] == 201 - assert response.json()["data"]["users"]["addSshKey"]["message"] is not None - assert response.json()["data"]["users"]["addSshKey"]["success"] is True - - assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "tester" - assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [ - "ssh-rsa KEY test@pc", - "ssh-rsa KEY test_key@pc", - ] - - # TODO: multiplex for root and admin def test_graphql_add_bad_ssh_key(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( @@ -638,9 +613,6 @@ def test_graphql_remove_main_ssh_key( assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] -key_users = ["root", "tester", "user1"] - - @pytest.mark.parametrize("user", key_users) def test_graphql_remove_nonexistent_ssh_key(authorized_client, some_users, user): output = api_remove_ssh_key(authorized_client, user, "ssh-rsa nonexistent") diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 10f7752..ebc60fb 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,16 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_add_admin_key(authorized_client, ssh_on): - response = authorized_client.post( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 201 - assert read_json(ssh_on / "turned_on.json")["sshKeys"] == [ - "ssh-rsa KEY test@pc", - ] - - def test_add_admin_key_one_more(authorized_client, root_and_admin_have_keys): response = authorized_client.post( "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY_2 test@pc"} From 65c2023366c350daf4cf6746909482c008a057a8 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 07:37:04 +0000 Subject: [PATCH 094/130] test(ssh): parametrized testing of ssh key addition, more --- tests/test_graphql/common.py | 4 ++-- tests/test_graphql/test_ssh.py | 36 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_graphql/common.py b/tests/test_graphql/common.py index 286df67..5e6dc04 100644 --- a/tests/test_graphql/common.py +++ b/tests/test_graphql/common.py @@ -4,14 +4,14 @@ from tests.conftest import TOKENS_FILE_CONTENTS, DEVICE_WE_AUTH_TESTS_WITH ORIGINAL_DEVICES = TOKENS_FILE_CONTENTS["tokens"] -def assert_ok(output: dict) -> None: +def assert_ok(output: dict, code=200) -> None: if output["success"] is False: # convenience for debugging, this should display error # if message is empty, consider adding helpful messages raise ValueError(output["code"], output["message"]) assert output["success"] is True assert output["message"] is not None - assert output["code"] == 200 + assert output["code"] == code def assert_errorcode(output: dict, code) -> None: diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index 2b2e521..b27c5fe 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -413,9 +413,7 @@ def test_graphql_add_ssh_key_when_none(authorized_client, no_keys, user): output = api_add_ssh_key(authorized_client, user, key1) - assert output["code"] == 201 - assert output["message"] is not None - assert output["success"] is True + assert_ok(output, code=201) assert output["user"]["username"] == user assert output["user"]["sshKeys"] == [key1] @@ -426,24 +424,26 @@ def test_graphql_add_ssh_key_when_none(authorized_client, no_keys, user): assert api_get_user_keys(authorized_client, user) == [key1] -def test_graphql_add_root_ssh_key_one_more(authorized_client, no_rootkeys): - output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY test_key@pc") - assert output["user"]["sshKeys"] == ["ssh-rsa KEY test_key@pc"] - - output = api_add_ssh_key(authorized_client, "root", "ssh-rsa KEY2 test_key@pc") - assert output["code"] == 201 - assert output["message"] is not None - assert output["success"] is True - - assert output["user"]["username"] == "root" - - expected_keys = [ +@pytest.mark.parametrize("user", key_users) +def test_graphql_add_ssh_key_one_more(authorized_client, no_keys, user): + keys = [ "ssh-rsa KEY test_key@pc", "ssh-rsa KEY2 test_key@pc", ] + output = api_add_ssh_key(authorized_client, user, keys[0]) + assert output["user"]["sshKeys"] == [keys[0]] - assert output["user"]["sshKeys"] == expected_keys - assert api_rootkeys(authorized_client) == expected_keys + output = api_add_ssh_key(authorized_client, user, keys[1]) + + assert_ok(output, code=201) + + assert output["user"]["username"] == user + assert output["user"]["sshKeys"] == keys + + if user == "root": + assert api_rootkeys(authorized_client) == keys + else: + assert api_get_user_keys(authorized_client, user) == keys def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): @@ -585,7 +585,7 @@ def test_graphql_remove_root_ssh_key(authorized_client, some_users): assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] -def test_graphql_remove_main_ssh_key( +def test_graphql_remove_admin_ssh_key( authorized_client, some_users, mock_subprocess_popen ): response = authorized_client.post( From 16c2598e9b7a4d8a8b470faaa5e3145c33cec3da Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 08:12:50 +0000 Subject: [PATCH 095/130] test(ssh): parametrized testing of ssh key addition, existing and invalid --- tests/test_graphql/test_ssh.py | 31 +++++-------------- .../test_rest_endpoints/services/test_ssh.py | 29 ----------------- 2 files changed, 8 insertions(+), 52 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index b27c5fe..e6a619c 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -446,35 +446,20 @@ def test_graphql_add_ssh_key_one_more(authorized_client, no_keys, user): assert api_get_user_keys(authorized_client, user) == keys -def test_graphql_add_root_ssh_key_same(authorized_client, no_rootkeys): +@pytest.mark.parametrize("user", key_users) +def test_graphql_add_ssh_key_same(authorized_client, no_keys, user): key = "ssh-rsa KEY test_key@pc" - output = api_add_ssh_key(authorized_client, "root", key) + output = api_add_ssh_key(authorized_client, user, key) assert output["user"]["sshKeys"] == [key] - output = api_add_ssh_key(authorized_client, "root", key) + output = api_add_ssh_key(authorized_client, user, key) assert_errorcode(output, 409) -# TODO: multiplex for root and admin -def test_graphql_add_bad_ssh_key(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "user1", - "sshKey": "trust me, this is the ssh key", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["addSshKey"]["code"] == 400 - assert response.json()["data"]["users"]["addSshKey"]["message"] is not None - assert response.json()["data"]["users"]["addSshKey"]["success"] is False +@pytest.mark.parametrize("user", key_users) +def test_graphql_add_bad_ssh_key(authorized_client, some_users, user): + output = api_add_ssh_key(authorized_client, user, "trust me, this is the ssh key") + assert_errorcode(output, 400) def test_graphql_add_ssh_key_nonexistent_user( diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index ebc60fb..ce9a39c 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,35 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_add_admin_key_one_more(authorized_client, root_and_admin_have_keys): - response = authorized_client.post( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY_2 test@pc"} - ) - assert response.status_code == 201 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[ - "sshKeys" - ] == ["ssh-rsa KEY test@pc", "ssh-rsa KEY_2 test@pc"] - - -def test_add_existing_admin_key(authorized_client, root_and_admin_have_keys): - response = authorized_client.post( - "/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"} - ) - assert response.status_code == 409 - assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[ - "sshKeys" - ] == [ - "ssh-rsa KEY test@pc", - ] - - -def test_add_invalid_admin_key(authorized_client, ssh_on): - response = authorized_client.post( - "/services/ssh/keys/tester", json={"public_key": "INVALID KEY test@pc"} - ) - assert response.status_code == 400 - - @pytest.mark.parametrize("user", [1, 2, 3]) def test_get_user_key(authorized_client, some_users, user): response = authorized_client.get(f"/services/ssh/keys/user{user}") From 42d96bcd6d0689519ac2c1fc36cfd160aae2b8d1 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 08:25:20 +0000 Subject: [PATCH 096/130] test(ssh): remove rest user getting tests (they are covered by users tests --- tests/test_rest_endpoints/services/test_ssh.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index ce9a39c..604b7cd 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,21 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -@pytest.mark.parametrize("user", [1, 2, 3]) -def test_get_user_key(authorized_client, some_users, user): - response = authorized_client.get(f"/services/ssh/keys/user{user}") - assert response.status_code == 200 - if user == 1: - assert response.json() == ["ssh-rsa KEY user1@pc"] - else: - assert response.json() == [] - - -def test_get_keys_of_nonexistent_user(authorized_client, some_users): - response = authorized_client.get("/services/ssh/keys/user4") - assert response.status_code == 404 - - def test_get_keys_of_undefined_users(authorized_client, undefined_settings): response = authorized_client.get("/services/ssh/keys/user1") assert response.status_code == 404 From 946413615b344a8b4aba7414f2b1c1549e19f6c8 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 08:34:45 +0000 Subject: [PATCH 097/130] test(ssh): dealing with undefined users --- tests/test_graphql/test_users.py | 36 ++++++++++++++++++- .../test_rest_endpoints/services/test_ssh.py | 5 --- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index af40981..e397600 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -6,7 +6,7 @@ from tests.common import ( generate_users_query, read_json, ) -from tests.test_graphql.common import assert_empty +from tests.test_graphql.common import assert_empty, assert_errorcode invalid_usernames = [ "messagebus", @@ -170,6 +170,23 @@ def test_graphql_get_no_users(authorized_client, no_users, mock_subprocess_popen ] +def test_graphql_get_users_undefined(authorized_client, undefined_settings): + response = authorized_client.post( + "/graphql", + json={ + "query": generate_users_query([API_USERS_INFO]), + }, + ) + assert response.status_code == 200 + assert response.json().get("data") is not None + + assert len(response.json()["data"]["users"]["allUsers"]) == 1 + assert response.json()["data"]["users"]["allUsers"][0]["username"] == "tester" + assert response.json()["data"]["users"]["allUsers"][0]["sshKeys"] == [ + "ssh-rsa KEY test@pc" + ] + + API_GET_USERS = """ query TestUsers($username: String!) { users { @@ -216,6 +233,23 @@ def test_graphql_get_one_user(authorized_client, one_user, mock_subprocess_popen ] +def test_graphql_get_some_user_undefined(authorized_client, undefined_settings): + + response = authorized_client.post( + "/graphql", + json={ + "query": API_GET_USERS, + "variables": { + "username": "user1", + }, + }, + ) + assert response.status_code == 200 + assert response.json().get("data") is not None + + assert response.json()["data"]["users"]["getUser"] is None + + def test_graphql_get_some_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.post( "/graphql", diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 604b7cd..beadbd2 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,11 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_get_keys_of_undefined_users(authorized_client, undefined_settings): - response = authorized_client.get("/services/ssh/keys/user1") - assert response.status_code == 404 - - @pytest.mark.parametrize("user", [1, 2, 3]) def test_add_user_key(authorized_client, some_users, user): response = authorized_client.post( From ca4b3c972d2cac2a2f919ec943c4ede65134692d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 08:49:54 +0000 Subject: [PATCH 098/130] test(ssh): regular users --- tests/test_graphql/test_ssh.py | 2 +- .../test_rest_endpoints/services/test_ssh.py | 36 ------------------- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index e6a619c..f810598 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -19,7 +19,7 @@ from tests.test_graphql.common import ( ) from tests.test_graphql.test_users import API_USERS_INFO -key_users = ["root", "tester", "user1"] +key_users = ["root", "tester", "user1", "user2", "user3"] class ProcessMock: diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index beadbd2..3851230 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,42 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -@pytest.mark.parametrize("user", [1, 2, 3]) -def test_add_user_key(authorized_client, some_users, user): - response = authorized_client.post( - f"/services/ssh/keys/user{user}", json={"public_key": "ssh-ed25519 KEY test@pc"} - ) - assert response.status_code == 201 - if user == 1: - assert read_json(some_users / "some_users.json")["users"][user - 1][ - "sshKeys" - ] == [ - "ssh-rsa KEY user1@pc", - "ssh-ed25519 KEY test@pc", - ] - else: - assert read_json(some_users / "some_users.json")["users"][user - 1][ - "sshKeys" - ] == ["ssh-ed25519 KEY test@pc"] - - -def test_add_existing_user_key(authorized_client, some_users): - response = authorized_client.post( - "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"} - ) - assert response.status_code == 409 - assert read_json(some_users / "some_users.json")["users"][0]["sshKeys"] == [ - "ssh-rsa KEY user1@pc", - ] - - -def test_add_invalid_user_key(authorized_client, some_users): - response = authorized_client.post( - "/services/ssh/keys/user1", json={"public_key": "INVALID KEY user1@pc"} - ) - assert response.status_code == 400 - - def test_delete_user_key(authorized_client, some_users): response = authorized_client.delete( "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"} From b120858fa17aff01365769619abc8c38c37339aa Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 09:04:07 +0000 Subject: [PATCH 099/130] test(ssh): parametrized removing keys --- tests/test_graphql/test_ssh.py | 93 ++++--------------- .../test_rest_endpoints/services/test_ssh.py | 8 -- 2 files changed, 17 insertions(+), 84 deletions(-) diff --git a/tests/test_graphql/test_ssh.py b/tests/test_graphql/test_ssh.py index f810598..2a2c259 100644 --- a/tests/test_graphql/test_ssh.py +++ b/tests/test_graphql/test_ssh.py @@ -518,84 +518,25 @@ def test_graphql_remove_ssh_key_unauthorized(client, some_users, mock_subprocess assert_empty(response) -def test_graphql_remove_ssh_key(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_REMOVE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "user1", - "sshKey": "ssh-rsa KEY user1@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None +@pytest.mark.parametrize("user", key_users) +def test_graphql_remove_ssh_key(authorized_client, no_keys, user): + keys = [ + "ssh-rsa KEY test_key@pc", + "ssh-rsa KEY2 test_key@pc", + ] + output = api_add_ssh_key(authorized_client, user, keys[0]) + output = api_add_ssh_key(authorized_client, user, keys[1]) + assert output["user"]["sshKeys"] == keys - assert response.json()["data"]["users"]["removeSshKey"]["code"] == 200 - assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None - assert response.json()["data"]["users"]["removeSshKey"]["success"] is True + output = api_remove_ssh_key(authorized_client, user, keys[1]) + assert_ok(output) + assert output["user"]["username"] == user + assert output["user"]["sshKeys"] == [keys[0]] - assert ( - response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "user1" - ) - assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] - - -def test_graphql_remove_root_ssh_key(authorized_client, some_users): - response = authorized_client.post( - "/graphql", - json={ - "query": API_REMOVE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "root", - "sshKey": "ssh-ed25519 KEY test@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["removeSshKey"]["code"] == 200 - assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None - assert response.json()["data"]["users"]["removeSshKey"]["success"] is True - - assert ( - response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "root" - ) - assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] - - -def test_graphql_remove_admin_ssh_key( - authorized_client, some_users, mock_subprocess_popen -): - response = authorized_client.post( - "/graphql", - json={ - "query": API_REMOVE_SSH_KEY_MUTATION, - "variables": { - "sshInput": { - "username": "tester", - "sshKey": "ssh-rsa KEY test@pc", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["removeSshKey"]["code"] == 200 - assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None - assert response.json()["data"]["users"]["removeSshKey"]["success"] is True - - assert ( - response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "tester" - ) - assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == [] + if user == "root": + assert api_rootkeys(authorized_client) == [keys[0]] + else: + assert api_get_user_keys(authorized_client, user) == [keys[0]] @pytest.mark.parametrize("user", key_users) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 3851230..452e5d5 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,14 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -def test_delete_user_key(authorized_client, some_users): - response = authorized_client.delete( - "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"} - ) - assert response.status_code == 200 - assert read_json(some_users / "some_users.json")["users"][0]["sshKeys"] == [] - - @pytest.mark.parametrize("user", [2, 3]) def test_delete_nonexistent_user_key(authorized_client, some_users, user): response = authorized_client.delete( From c5bb18215bd3d2bab7a7196607907b262afe9c8d Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 09:07:20 +0000 Subject: [PATCH 100/130] test(ssh): delete redundant ssh tests --- .../test_rest_endpoints/services/test_ssh.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py index 452e5d5..cb91f96 100644 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ b/tests/test_rest_endpoints/services/test_ssh.py @@ -95,28 +95,6 @@ def some_users(mocker, datadir): ## /ssh/keys/{user} ###################################################### -@pytest.mark.parametrize("user", [2, 3]) -def test_delete_nonexistent_user_key(authorized_client, some_users, user): - response = authorized_client.delete( - f"/services/ssh/keys/user{user}", json={"public_key": "ssh-rsa KEY user1@pc"} - ) - assert response.status_code == 404 - if user == 2: - assert ( - read_json(some_users / "some_users.json")["users"][user - 1]["sshKeys"] - == [] - ) - if user == 3: - "sshKeys" not in read_json(some_users / "some_users.json")["users"][user - 1] - - -def test_add_keys_of_nonexistent_user(authorized_client, some_users): - response = authorized_client.post( - "/services/ssh/keys/user4", json={"public_key": "ssh-rsa KEY user4@pc"} - ) - assert response.status_code == 404 - - def test_add_key_on_undefined_users(authorized_client, undefined_settings): response = authorized_client.post( "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user4@pc"} @@ -124,13 +102,6 @@ def test_add_key_on_undefined_users(authorized_client, undefined_settings): assert response.status_code == 404 -def test_delete_keys_of_nonexistent_user(authorized_client, some_users): - response = authorized_client.delete( - "/services/ssh/keys/user4", json={"public_key": "ssh-rsa KEY user4@pc"} - ) - assert response.status_code == 404 - - def test_delete_key_when_undefined_users(authorized_client, undefined_settings): response = authorized_client.delete( "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"} From bc45a48af37eb4b534928a92fc80ca842e3762de Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 09:40:03 +0000 Subject: [PATCH 101/130] test(ssh): json storage of user keys : reading --- tests/data/turned_on.json | 10 ++-- tests/test_ssh.py | 103 +++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/tests/data/turned_on.json b/tests/data/turned_on.json index 06e957a..2c98e77 100644 --- a/tests/data/turned_on.json +++ b/tests/data/turned_on.json @@ -37,19 +37,17 @@ { "username": "user1", "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] + "sshKeys": ["ssh-rsa KEY user1@pc"] }, { "username": "user2", "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] + "sshKeys": ["ssh-rsa KEY user2@pc"] }, { "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" + "hashedPassword": "HASHED_PASSWORD_3", + "sshKeys": ["ssh-rsa KEY user3@pc"] } ] } diff --git a/tests/test_ssh.py b/tests/test_ssh.py index d333eef..e65ebc0 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -245,12 +245,12 @@ def test_read_admin_keys_from_json(generic_userdata): with WriteUserData() as data: data["sshKeys"] = new_keys - get_user_by_username(admin_name).ssh_keys == new_keys + assert get_user_by_username(admin_name).ssh_keys == new_keys with WriteUserData() as data: del data["sshKeys"] - get_user_by_username(admin_name).ssh_keys == [] + assert get_user_by_username(admin_name).ssh_keys == [] def test_adding_admin_key_writes_json(generic_userdata): @@ -314,3 +314,102 @@ def test_remove_admin_key_on_undefined(generic_userdata): remove_ssh_key(admin_name, key1) admin_keys = get_user_by_username(admin_name).ssh_keys assert len(admin_keys) == 0 + + +############### USER KEYS + +regular_users = ["user1", "user2", "user3"] + + +def find_user_index_in_json_users(users: list, username: str) -> Optional[int]: + for i, user in enumerate(users): + if user["username"] == username: + return i + return None + + +@pytest.mark.parametrize("username", regular_users) +def test_read_user_keys_from_json(generic_userdata, username): + old_keys = [f"ssh-rsa KEY {username}@pc"] + assert get_user_by_username(username).ssh_keys == old_keys + new_keys = ["ssh-rsa KEY test@pc", "ssh-ed25519 KEY2 test@pc"] + + with WriteUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + data["users"][user_index]["sshKeys"] = new_keys + + assert get_user_by_username(username).ssh_keys == new_keys + + with WriteUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + del data["users"][user_index]["sshKeys"] + + assert get_user_by_username(username).ssh_keys == [] + + # deeper deletions are for user getter tests, not here + + +# @pytest.mark.parametrize("username", regular_users) +# def test_adding_user_key_writes_json(generic_userdata, regular_users): +# admin_name = "tester" + +# with WriteUserData() as data: +# del data["sshKeys"] +# key1 = "ssh-ed25519 KEY test@pc" +# key2 = "ssh-ed25519 KEY2 test@pc" +# create_ssh_key(admin_name, key1) + +# with ReadUserData() as data: +# assert "sshKeys" in data +# assert data["sshKeys"] == [key1] + +# create_ssh_key(admin_name, key2) + +# with ReadUserData() as data: +# assert "sshKeys" in data +# # order is irrelevant +# assert set(data["sshKeys"]) == set([key1, key2]) + + +# @pytest.mark.parametrize("username", regular_users) +# def test_removing_user_key_writes_json(generic_userdata, regular_users): +# # generic userdata has a a single root key +# admin_name = "tester" + +# admin_keys = get_user_by_username(admin_name).ssh_keys +# assert len(admin_keys) == 1 +# key1 = admin_keys[0] +# key2 = "ssh-rsa MYSUPERKEY admin@pc" + +# create_ssh_key(admin_name, key2) +# admin_keys = get_user_by_username(admin_name).ssh_keys +# assert len(admin_keys) == 2 + +# remove_ssh_key(admin_name, key2) + +# with ReadUserData() as data: +# assert "sshKeys" in data +# assert data["sshKeys"] == [key1] + +# remove_ssh_key(admin_name, key1) +# with ReadUserData() as data: +# assert "sshKeys" in data +# assert data["sshKeys"] == [] + + +# @pytest.mark.parametrize("username", regular_users) +# def test_remove_user_key_on_undefined(generic_userdata, regular_users): +# # generic userdata has a a single root key +# admin_name = "tester" + +# admin_keys = get_user_by_username(admin_name).ssh_keys +# assert len(admin_keys) == 1 +# key1 = admin_keys[0] + +# with WriteUserData() as data: +# del data["sshKeys"] + +# with pytest.raises(KeyNotFound): +# remove_ssh_key(admin_name, key1) +# admin_keys = get_user_by_username(admin_name).ssh_keys +# assert len(admin_keys) == 0 From 0669dc117b1161184081fd38513773569ffdeeae Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 09:49:27 +0000 Subject: [PATCH 102/130] test(ssh): user key storage test: adding --- tests/test_ssh.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index e65ebc0..5832a2a 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -349,30 +349,32 @@ def test_read_user_keys_from_json(generic_userdata, username): # deeper deletions are for user getter tests, not here -# @pytest.mark.parametrize("username", regular_users) -# def test_adding_user_key_writes_json(generic_userdata, regular_users): -# admin_name = "tester" +@pytest.mark.parametrize("username", regular_users) +def test_adding_user_key_writes_json(generic_userdata, username): -# with WriteUserData() as data: -# del data["sshKeys"] -# key1 = "ssh-ed25519 KEY test@pc" -# key2 = "ssh-ed25519 KEY2 test@pc" -# create_ssh_key(admin_name, key1) + with WriteUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + del data["users"][user_index]["sshKeys"] + key1 = "ssh-ed25519 KEY test@pc" + key2 = "ssh-ed25519 KEY2 test@pc" + create_ssh_key(username, key1) -# with ReadUserData() as data: -# assert "sshKeys" in data -# assert data["sshKeys"] == [key1] + with ReadUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + assert "sshKeys" in data["users"][user_index] + assert data["users"][user_index]["sshKeys"] == [key1] -# create_ssh_key(admin_name, key2) + create_ssh_key(username, key2) -# with ReadUserData() as data: -# assert "sshKeys" in data -# # order is irrelevant -# assert set(data["sshKeys"]) == set([key1, key2]) + with ReadUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + assert "sshKeys" in data["users"][user_index] + # order is irrelevant + assert set(data["users"][user_index]["sshKeys"]) == set([key1, key2]) # @pytest.mark.parametrize("username", regular_users) -# def test_removing_user_key_writes_json(generic_userdata, regular_users): +# def test_removing_user_key_writes_json(generic_userdata, username): # # generic userdata has a a single root key # admin_name = "tester" @@ -398,7 +400,7 @@ def test_read_user_keys_from_json(generic_userdata, username): # @pytest.mark.parametrize("username", regular_users) -# def test_remove_user_key_on_undefined(generic_userdata, regular_users): +# def test_remove_user_key_on_undefined(generic_userdata, username): # # generic userdata has a a single root key # admin_name = "tester" From 7377c6375a643c77fb25c8c71da67cc4870fc5da Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 09:57:35 +0000 Subject: [PATCH 103/130] test(ssh): user key storage test: removing --- tests/test_ssh.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 5832a2a..e2726d4 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -373,30 +373,31 @@ def test_adding_user_key_writes_json(generic_userdata, username): assert set(data["users"][user_index]["sshKeys"]) == set([key1, key2]) -# @pytest.mark.parametrize("username", regular_users) -# def test_removing_user_key_writes_json(generic_userdata, username): -# # generic userdata has a a single root key -# admin_name = "tester" +@pytest.mark.parametrize("username", regular_users) +def test_removing_user_key_writes_json(generic_userdata, username): + # generic userdata has a a single root key -# admin_keys = get_user_by_username(admin_name).ssh_keys -# assert len(admin_keys) == 1 -# key1 = admin_keys[0] -# key2 = "ssh-rsa MYSUPERKEY admin@pc" + user_keys = get_user_by_username(username).ssh_keys + assert len(user_keys) == 1 + key1 = user_keys[0] + key2 = "ssh-rsa MYSUPERKEY admin@pc" -# create_ssh_key(admin_name, key2) -# admin_keys = get_user_by_username(admin_name).ssh_keys -# assert len(admin_keys) == 2 + create_ssh_key(username, key2) + user_keys = get_user_by_username(username).ssh_keys + assert len(user_keys) == 2 -# remove_ssh_key(admin_name, key2) + remove_ssh_key(username, key2) -# with ReadUserData() as data: -# assert "sshKeys" in data -# assert data["sshKeys"] == [key1] + with ReadUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + assert "sshKeys" in data["users"][user_index] + assert data["users"][user_index]["sshKeys"] == [key1] -# remove_ssh_key(admin_name, key1) -# with ReadUserData() as data: -# assert "sshKeys" in data -# assert data["sshKeys"] == [] + remove_ssh_key(username, key1) + with ReadUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + assert "sshKeys" in data["users"][user_index] + assert data["users"][user_index]["sshKeys"] == [] # @pytest.mark.parametrize("username", regular_users) From 03feab76b0192c3d203e974e6de5d2b126499882 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 10:09:51 +0000 Subject: [PATCH 104/130] test(ssh): user key storage test: removing user keys on undefined --- tests/test_ssh.py | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index e2726d4..a688a63 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -12,6 +12,7 @@ from selfprivacy_api.actions.ssh import ( create_ssh_key, remove_ssh_key, KeyNotFound, + UserNotFound, ) from selfprivacy_api.actions.users import ( get_users, @@ -275,7 +276,7 @@ def test_adding_admin_key_writes_json(generic_userdata): def test_removing_admin_key_writes_json(generic_userdata): - # generic userdata has a a single root key + # generic userdata has a a single admin key admin_name = "tester" admin_keys = get_user_by_username(admin_name).ssh_keys @@ -300,7 +301,7 @@ def test_removing_admin_key_writes_json(generic_userdata): def test_remove_admin_key_on_undefined(generic_userdata): - # generic userdata has a a single root key + # generic userdata has a a single admin key admin_name = "tester" admin_keys = get_user_by_username(admin_name).ssh_keys @@ -375,7 +376,7 @@ def test_adding_user_key_writes_json(generic_userdata, username): @pytest.mark.parametrize("username", regular_users) def test_removing_user_key_writes_json(generic_userdata, username): - # generic userdata has a a single root key + # generic userdata has a a single user key user_keys = get_user_by_username(username).ssh_keys assert len(user_keys) == 1 @@ -400,19 +401,32 @@ def test_removing_user_key_writes_json(generic_userdata, username): assert data["users"][user_index]["sshKeys"] == [] -# @pytest.mark.parametrize("username", regular_users) -# def test_remove_user_key_on_undefined(generic_userdata, username): -# # generic userdata has a a single root key -# admin_name = "tester" +@pytest.mark.parametrize("username", regular_users) +def test_remove_user_key_on_undefined(generic_userdata, username): + # generic userdata has a a single user key + user_keys = get_user_by_username(username).ssh_keys + assert len(user_keys) == 1 + key1 = user_keys[0] -# admin_keys = get_user_by_username(admin_name).ssh_keys -# assert len(admin_keys) == 1 -# key1 = admin_keys[0] + with WriteUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + del data["users"][user_index]["sshKeys"] -# with WriteUserData() as data: -# del data["sshKeys"] + with pytest.raises(KeyNotFound): + remove_ssh_key(username, key1) -# with pytest.raises(KeyNotFound): -# remove_ssh_key(admin_name, key1) -# admin_keys = get_user_by_username(admin_name).ssh_keys -# assert len(admin_keys) == 0 + user_keys = get_user_by_username(username).ssh_keys + assert len(user_keys) == 0 + + with WriteUserData() as data: + user_index = find_user_index_in_json_users(data["users"], username) + del data["users"][user_index] + + with pytest.raises(UserNotFound): + remove_ssh_key(username, key1) + + with WriteUserData() as data: + del data["users"] + + with pytest.raises(UserNotFound): + remove_ssh_key(username, key1) From 04e3ee821f16fd12beea08535ef67032c099cbe0 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 10:11:37 +0000 Subject: [PATCH 105/130] test(ssh): remove the rest of rest ssh tests bc redundant --- .../test_rest_endpoints/services/data/gitkeep | 0 .../test_rest_endpoints/services/test_ssh.py | 109 ------------------ .../services/test_ssh/all_off.json | 57 --------- .../test_ssh/root_and_admin_have_keys.json | 57 --------- .../services/test_ssh/some_users.json | 76 ------------ .../services/test_ssh/turned_off.json | 51 -------- .../services/test_ssh/turned_on.json | 51 -------- .../services/test_ssh/undefined.json | 47 -------- .../services/test_ssh/undefined_values.json | 51 -------- 9 files changed, 499 deletions(-) delete mode 100644 tests/test_rest_endpoints/services/data/gitkeep delete mode 100644 tests/test_rest_endpoints/services/test_ssh.py delete mode 100644 tests/test_rest_endpoints/services/test_ssh/all_off.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/root_and_admin_have_keys.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/some_users.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/turned_off.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/turned_on.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/undefined.json delete mode 100644 tests/test_rest_endpoints/services/test_ssh/undefined_values.json diff --git a/tests/test_rest_endpoints/services/data/gitkeep b/tests/test_rest_endpoints/services/data/gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_rest_endpoints/services/test_ssh.py b/tests/test_rest_endpoints/services/test_ssh.py deleted file mode 100644 index cb91f96..0000000 --- a/tests/test_rest_endpoints/services/test_ssh.py +++ /dev/null @@ -1,109 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as file: - return json.load(file) - - -## FIXTURES ################################################### - - -@pytest.fixture -def ssh_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert not read_json(datadir / "turned_off.json")["ssh"]["enable"] - assert read_json(datadir / "turned_off.json")["ssh"]["passwordAuthentication"] - return datadir - - -@pytest.fixture -def ssh_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_off.json")["ssh"]["passwordAuthentication"] - assert read_json(datadir / "turned_on.json")["ssh"]["enable"] - return datadir - - -@pytest.fixture -def all_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "all_off.json") - assert not read_json(datadir / "all_off.json")["ssh"]["passwordAuthentication"] - assert not read_json(datadir / "all_off.json")["ssh"]["enable"] - return datadir - - -@pytest.fixture -def undefined_settings(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "ssh" not in read_json(datadir / "undefined.json") - return datadir - - -@pytest.fixture -def undefined_values(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined_values.json" - ) - assert "ssh" in read_json(datadir / "undefined_values.json") - assert "enable" not in read_json(datadir / "undefined_values.json")["ssh"] - assert ( - "passwordAuthentication" - not in read_json(datadir / "undefined_values.json")["ssh"] - ) - return datadir - - -@pytest.fixture -def root_and_admin_have_keys(mocker, datadir): - mocker.patch( - "selfprivacy_api.utils.USERDATA_FILE", - new=datadir / "root_and_admin_have_keys.json", - ) - assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"]["enable"] - assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"][ - "passwordAuthentication" - ] - assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"]["rootKeys"] == [ - "ssh-ed25519 KEY test@pc" - ] - assert read_json(datadir / "root_and_admin_have_keys.json")["sshKeys"] == [ - "ssh-rsa KEY test@pc" - ] - return datadir - - -@pytest.fixture -def some_users(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "some_users.json") - assert "users" in read_json(datadir / "some_users.json") - assert read_json(datadir / "some_users.json")["users"] == [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": ["ssh-rsa KEY user1@pc"], - }, - {"username": "user2", "hashedPassword": "HASHED_PASSWORD_2", "sshKeys": []}, - {"username": "user3", "hashedPassword": "HASHED_PASSWORD_3"}, - ] - return datadir - - -## /ssh/keys/{user} ###################################################### - - -def test_add_key_on_undefined_users(authorized_client, undefined_settings): - response = authorized_client.post( - "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user4@pc"} - ) - assert response.status_code == 404 - - -def test_delete_key_when_undefined_users(authorized_client, undefined_settings): - response = authorized_client.delete( - "/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"} - ) - assert response.status_code == 404 diff --git a/tests/test_rest_endpoints/services/test_ssh/all_off.json b/tests/test_rest_endpoints/services/test_ssh/all_off.json deleted file mode 100644 index 051d364..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/all_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": false, - "passwordAuthentication": false, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/root_and_admin_have_keys.json b/tests/test_rest_endpoints/services/test_ssh/root_and_admin_have_keys.json deleted file mode 100644 index c1691ea..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/root_and_admin_have_keys.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/some_users.json b/tests/test_rest_endpoints/services/test_ssh/some_users.json deleted file mode 100644 index df6380a..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/some_users.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - }, - { - "username": "user2", - "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] - }, - { - "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/turned_off.json b/tests/test_rest_endpoints/services/test_ssh/turned_off.json deleted file mode 100644 index 3856c80..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/turned_off.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": false, - "passwordAuthentication": true - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/turned_on.json b/tests/test_rest_endpoints/services/test_ssh/turned_on.json deleted file mode 100644 index e60c57f..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/turned_on.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/undefined.json b/tests/test_rest_endpoints/services/test_ssh/undefined.json deleted file mode 100644 index 7c9af37..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/undefined.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/services/test_ssh/undefined_values.json b/tests/test_rest_endpoints/services/test_ssh/undefined_values.json deleted file mode 100644 index b7b03d3..0000000 --- a/tests/test_rest_endpoints/services/test_ssh/undefined_values.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": {}, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 2f25329c434396168b9b374dcbc91c10a1044cb5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 22 Dec 2023 11:31:56 +0000 Subject: [PATCH 106/130] refactor(backup): remove a redundant constant --- selfprivacy_api/backup/tasks.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/selfprivacy_api/backup/tasks.py b/selfprivacy_api/backup/tasks.py index a948bff..6520c70 100644 --- a/selfprivacy_api/backup/tasks.py +++ b/selfprivacy_api/backup/tasks.py @@ -18,8 +18,6 @@ from selfprivacy_api.backup import Backups from selfprivacy_api.jobs import Jobs, JobStatus, Job -SNAPSHOT_CACHE_TTL_HOURS = 6 - SNAPSHOT_CACHE_TTL_HOURS = 6 @@ -35,9 +33,7 @@ def validate_datetime(dt: datetime) -> bool: # huey tasks need to return something @huey.task() -def start_backup( - service_id: str, reason: BackupReason = BackupReason.EXPLICIT -) -> bool: +def start_backup(service_id: str, reason: BackupReason = BackupReason.EXPLICIT) -> bool: """ The worker task that starts the backup process. """ From bcbe1ff50c3175994ea3182569f94aba6fb3f835 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 25 Dec 2023 13:49:36 +0000 Subject: [PATCH 107/130] refactor(dkim): do not use popen --- selfprivacy_api/utils/__init__.py | 43 +++++++------- tests/test_dkim.py | 95 ++++++------------------------- tests/test_graphql/test_system.py | 4 +- 3 files changed, 44 insertions(+), 98 deletions(-) diff --git a/selfprivacy_api/utils/__init__.py b/selfprivacy_api/utils/__init__.py index 5263b89..08bc61f 100644 --- a/selfprivacy_api/utils/__init__.py +++ b/selfprivacy_api/utils/__init__.py @@ -13,6 +13,7 @@ USERDATA_FILE = "/etc/nixos/userdata/userdata.json" TOKENS_FILE = "/etc/nixos/userdata/tokens.json" JOBS_FILE = "/etc/nixos/userdata/jobs.json" DOMAIN_FILE = "/var/domain" +DKIM_DIR = "/var/dkim/" class UserDataFiles(Enum): @@ -167,27 +168,31 @@ def parse_date(date_str: str) -> datetime.datetime: raise ValueError("Invalid date string") +def parse_dkim(dkim: str) -> str: + # extract key from file + dkim = dkim.split("(")[1] + dkim = dkim.split(")")[0] + # replace all quotes with nothing + dkim = dkim.replace('"', "") + # trim whitespace, remove newlines and tabs + dkim = dkim.strip() + dkim = dkim.replace("\n", "") + dkim = dkim.replace("\t", "") + # remove all redundant spaces + dkim = " ".join(dkim.split()) + return dkim + + def get_dkim_key(domain: str, parse: bool = True) -> typing.Optional[str]: """Get DKIM key from /var/dkim/.selector.txt""" - if os.path.exists("/var/dkim/" + domain + ".selector.txt"): - # Is this really neccessary to use Popen here? - cat_process = subprocess.Popen( - ["cat", "/var/dkim/" + domain + ".selector.txt"], stdout=subprocess.PIPE - ) - dkim = cat_process.communicate()[0] - if parse: - # Extract key from file - dkim = dkim.split(b"(")[1] - dkim = dkim.split(b")")[0] - # Replace all quotes with nothing - dkim = dkim.replace(b'"', b"") - # Trim whitespace, remove newlines and tabs - dkim = dkim.strip() - dkim = dkim.replace(b"\n", b"") - dkim = dkim.replace(b"\t", b"") - # Remove all redundant spaces - dkim = b" ".join(dkim.split()) - return str(dkim, "utf-8") + + dkim_path = os.path.join(DKIM_DIR, domain + ".selector.txt") + if os.path.exists(dkim_path): + with open(dkim_path, encoding="utf-8") as dkim_file: + dkim = dkim_file.read() + if parse: + dkim = parse_dkim(dkim) + return dkim return None diff --git a/tests/test_dkim.py b/tests/test_dkim.py index c9662d0..949bb19 100644 --- a/tests/test_dkim.py +++ b/tests/test_dkim.py @@ -1,68 +1,30 @@ import pytest -import typing +import os from os import path -from unittest.mock import DEFAULT from tests.conftest import global_data_dir from selfprivacy_api.utils import get_dkim_key, get_domain -import selfprivacy_api.utils as utils ############################################################################### - -class ProcessMock: - """Mock subprocess.Popen""" - - def __init__(self, args, **kwargs): - self.args = args - self.kwargs = kwargs - - def communicate(): - return ( - b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for test-domain.tld\n', - None, - ) - - -class NoFileMock(ProcessMock): - def communicate(): - return (b"", None) - - -def _path_exists_with_masked_paths(filepath, masked_paths: typing.List[str]): - if filepath in masked_paths: - return False - else: - # this will cause the mocker to return the standard path.exists output - # see https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect - return DEFAULT - - -def path_exists_func_but_with_masked_paths(masked_paths: typing.List[str]): - """ - Sometimes we do not want to pretend that no files exist at all, but that only specific files do not exist - This provides the needed path.exists function for some arbitrary list of masked paths - """ - return lambda x: _path_exists_with_masked_paths(x, masked_paths) +DKIM_FILE_CONTENT = b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for test-domain.tld\n' @pytest.fixture -def mock_all_paths_exist(mocker): - mock = mocker.patch("os.path.exists", autospec=True, return_value=True) - return mock +def dkim_file(mocker, domain_file, tmpdir): + domain = get_domain() + assert domain is not None + assert domain != "" + filename = domain + ".selector.txt" + dkim_path = path.join(tmpdir, filename) -@pytest.fixture -def mock_subproccess_popen_dkimfile(mocker): - mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) - return mock + with open(dkim_path, "wb") as file: + file.write(DKIM_FILE_CONTENT) - -@pytest.fixture -def mock_subproccess_popen(mocker): - mock = mocker.patch("subprocess.Popen", autospec=True, return_value=NoFileMock) - return mock + mocker.patch("selfprivacy_api.utils.DKIM_DIR", tmpdir) + return dkim_path @pytest.fixture @@ -74,46 +36,25 @@ def domain_file(mocker): @pytest.fixture -def mock_no_dkim_file(mocker): - """ - Should have domain mocks - """ - domain = utils.get_domain() - # try: - # domain = get_domain() - # except Exception as e: - # domain = "" - - masked_files = ["/var/dkim/" + domain + ".selector.txt"] - mock = mocker.patch( - "os.path.exists", - side_effect=path_exists_func_but_with_masked_paths(masked_files), - ) - return mock +def no_dkim_file(dkim_file): + os.remove(dkim_file) + assert path.exists(dkim_file) is False + return dkim_file ############################################################################### -def test_get_dkim_key( - mock_subproccess_popen_dkimfile, mock_all_paths_exist, domain_file -): +def test_get_dkim_key(domain_file, dkim_file): """Test DKIM key""" dkim_key = get_dkim_key("test-domain.tld") assert ( dkim_key == "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) - assert mock_subproccess_popen_dkimfile.call_args[0][0] == [ - "cat", - "/var/dkim/test-domain.tld.selector.txt", - ] -def test_no_dkim_key( - authorized_client, domain_file, mock_no_dkim_file, mock_subproccess_popen -): +def test_no_dkim_key(domain_file, no_dkim_file): """Test no DKIM key""" dkim_key = get_dkim_key("test-domain.tld") assert dkim_key is None - assert mock_subproccess_popen.called == False diff --git a/tests/test_graphql/test_system.py b/tests/test_graphql/test_system.py index b6b4362..c318fe7 100644 --- a/tests/test_graphql/test_system.py +++ b/tests/test_graphql/test_system.py @@ -6,7 +6,7 @@ import pytest from tests.common import generate_system_query, read_json from tests.test_graphql.common import assert_empty -from tests.test_dkim import mock_no_dkim_file +from tests.test_dkim import no_dkim_file, dkim_file @pytest.fixture @@ -338,7 +338,7 @@ def test_graphql_get_domain_no_dkim( domain_file, mock_get_ip4, mock_get_ip6, - mock_no_dkim_file, + no_dkim_file, turned_on, ): """Test no DKIM file situation gets properly handled""" From 4b2eda25f605330782d068d635b76fea7e3622dc Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 25 Dec 2023 14:38:59 +0000 Subject: [PATCH 108/130] test(service, backup): move dummy service fixtures to conftest.py --- tests/conftest.py | 64 +++++++++++++++++++++++++-- tests/test_backup.py | 2 - tests/test_block_device_utils.py | 1 - tests/test_common.py | 61 ------------------------- tests/test_graphql/test_api_backup.py | 1 - tests/test_graphql/test_services.py | 1 - tests/test_services.py | 2 - 7 files changed, 60 insertions(+), 72 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f058997..fddd32f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,12 +3,19 @@ # pylint: disable=unused-argument import os import pytest -from os import path - -from fastapi.testclient import TestClient -import os.path as path import datetime +from os import path +from os import makedirs +from typing import Generator +from fastapi.testclient import TestClient + +from selfprivacy_api.utils.huey import huey + +import selfprivacy_api.services as services +from selfprivacy_api.services import get_service_by_id, Service +from selfprivacy_api.services.test_service import DummyService + from selfprivacy_api.models.tokens.token import Token from selfprivacy_api.repositories.tokens.json_tokens_repository import ( JsonTokensRepository, @@ -19,6 +26,9 @@ from selfprivacy_api.repositories.tokens.redis_tokens_repository import ( from tests.common import read_json +TESTFILE_BODY = "testytest!" +TESTFILE_2_BODY = "testissimo!" + EMPTY_TOKENS_JSON = ' {"tokens": []}' @@ -147,3 +157,49 @@ def wrong_auth_client(tokens_file, huey_database, jobs_file): client = TestClient(app) client.headers.update({"Authorization": "Bearer WRONG_TOKEN"}) return client + + +@pytest.fixture() +def raw_dummy_service(tmpdir): + dirnames = ["test_service", "also_test_service"] + service_dirs = [] + for d in dirnames: + service_dir = path.join(tmpdir, d) + makedirs(service_dir) + service_dirs.append(service_dir) + + testfile_path_1 = path.join(service_dirs[0], "testfile.txt") + with open(testfile_path_1, "w") as file: + file.write(TESTFILE_BODY) + + testfile_path_2 = path.join(service_dirs[1], "testfile2.txt") + with open(testfile_path_2, "w") as file: + file.write(TESTFILE_2_BODY) + + # we need this to not change get_folders() much + class TestDummyService(DummyService, folders=service_dirs): + pass + + service = TestDummyService() + # assert pickle.dumps(service) is not None + return service + + +@pytest.fixture() +def dummy_service( + tmpdir, raw_dummy_service, generic_userdata +) -> Generator[Service, None, None]: + service = raw_dummy_service + + # register our service + services.services.append(service) + + huey.immediate = True + assert huey.immediate is True + + assert get_service_by_id(service.get_id()) is not None + service.enable() + yield service + + # cleanup because apparently it matters wrt tasks + services.services.remove(service) diff --git a/tests/test_backup.py b/tests/test_backup.py index bb9e217..036dd42 100644 --- a/tests/test_backup.py +++ b/tests/test_backup.py @@ -16,8 +16,6 @@ import tempfile from selfprivacy_api.utils.huey import huey -from tests.test_common import dummy_service, raw_dummy_service - from selfprivacy_api.services import Service, get_all_services from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import ServiceStatus diff --git a/tests/test_block_device_utils.py b/tests/test_block_device_utils.py index 0fa99f1..7a85c50 100644 --- a/tests/test_block_device_utils.py +++ b/tests/test_block_device_utils.py @@ -13,7 +13,6 @@ from selfprivacy_api.utils.block_devices import ( resize_block_device, ) from tests.common import read_json -from tests.test_common import dummy_service, raw_dummy_service SINGLE_LSBLK_OUTPUT = b""" { diff --git a/tests/test_common.py b/tests/test_common.py index 5c433a0..7dd3652 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,70 +1,9 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument -import json import os import pytest from selfprivacy_api.utils import WriteUserData, ReadUserData -from selfprivacy_api.utils.huey import huey - -from os import path -from os import makedirs -from typing import Generator - -# import pickle -import selfprivacy_api.services as services -from selfprivacy_api.services import get_service_by_id, Service -from selfprivacy_api.services.test_service import DummyService - - -TESTFILE_BODY = "testytest!" -TESTFILE_2_BODY = "testissimo!" - - -@pytest.fixture() -def raw_dummy_service(tmpdir): - dirnames = ["test_service", "also_test_service"] - service_dirs = [] - for d in dirnames: - service_dir = path.join(tmpdir, d) - makedirs(service_dir) - service_dirs.append(service_dir) - - testfile_path_1 = path.join(service_dirs[0], "testfile.txt") - with open(testfile_path_1, "w") as file: - file.write(TESTFILE_BODY) - - testfile_path_2 = path.join(service_dirs[1], "testfile2.txt") - with open(testfile_path_2, "w") as file: - file.write(TESTFILE_2_BODY) - - # we need this to not change get_folders() much - class TestDummyService(DummyService, folders=service_dirs): - pass - - service = TestDummyService() - # assert pickle.dumps(service) is not None - return service - - -@pytest.fixture() -def dummy_service( - tmpdir, raw_dummy_service, generic_userdata -) -> Generator[Service, None, None]: - service = raw_dummy_service - - # register our service - services.services.append(service) - - huey.immediate = True - assert huey.immediate is True - - assert get_service_by_id(service.get_id()) is not None - service.enable() - yield service - - # cleanup because apparently it matters wrt tasks - services.services.remove(service) def test_get_api_version(authorized_client): diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index 675c1b8..18d5d15 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -1,6 +1,5 @@ from os import path from tests.test_backup import backups -from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_backup_query diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index bd3e373..1c1374a 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -8,7 +8,6 @@ from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services.service import Service, ServiceStatus from selfprivacy_api.services.test_service import DummyService -from tests.test_common import raw_dummy_service, dummy_service from tests.common import generate_service_query from tests.test_graphql.common import assert_empty, assert_ok, get_data diff --git a/tests/test_services.py b/tests/test_services.py index 3addf05..09784e9 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -15,8 +15,6 @@ from selfprivacy_api.services.generic_service_mover import FolderMoveNames from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService -from tests.test_common import raw_dummy_service, dummy_service - def test_unimplemented_folders_raises(): with raises(NotImplementedError): From 6ade95bbf1a8ac55ce83b29399aa748326ac38d3 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 25 Dec 2023 16:36:40 +0000 Subject: [PATCH 109/130] test(service): refactor systemctl calltests --- tests/test_services_dkim.py | 67 ++++++++++++++----------------------- 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/tests/test_services_dkim.py b/tests/test_services_dkim.py index 02998c2..8b247e0 100644 --- a/tests/test_services_dkim.py +++ b/tests/test_services_dkim.py @@ -9,42 +9,24 @@ from selfprivacy_api.services.ocserv import Ocserv from selfprivacy_api.services.pleroma import Pleroma +def expected_status_call(service_name: str): + return ["systemctl", "show", service_name] + + def call_args_asserts(mocked_object): assert mocked_object.call_count == 7 - assert mocked_object.call_args_list[0][0][0] == [ - "systemctl", - "show", - "dovecot2.service", - ] - assert mocked_object.call_args_list[1][0][0] == [ - "systemctl", - "show", - "postfix.service", - ] - assert mocked_object.call_args_list[2][0][0] == [ - "systemctl", - "show", - "vaultwarden.service", - ] - assert mocked_object.call_args_list[3][0][0] == [ - "systemctl", - "show", - "gitea.service", - ] - assert mocked_object.call_args_list[4][0][0] == [ - "systemctl", - "show", - "phpfpm-nextcloud.service", - ] - assert mocked_object.call_args_list[5][0][0] == [ - "systemctl", - "show", - "ocserv.service", - ] - assert mocked_object.call_args_list[6][0][0] == [ - "systemctl", - "show", - "pleroma.service", + calls = [callargs[0][0] for callargs in mocked_object.call_args_list] + assert calls == [ + expected_status_call(service) + for service in [ + "dovecot2.service", + "postfix.service", + "vaultwarden.service", + "gitea.service", + "phpfpm-nextcloud.service", + "ocserv.service", + "pleroma.service", + ] ] @@ -74,7 +56,7 @@ SubState=exited @pytest.fixture -def mock_subproccess_popen(mocker): +def mock_popen_systemctl_service_ok(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, return_value=SUCCESSFUL_STATUS ) @@ -82,7 +64,7 @@ def mock_subproccess_popen(mocker): @pytest.fixture -def mock_broken_service(mocker): +def mock_popen_systemctl_service_not_ok(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, return_value=FAILED_STATUS ) @@ -91,21 +73,22 @@ def mock_broken_service(mocker): ############################################################################### -def test_dkim_key(authorized_client, mock_subproccess_popen): - assert MailServer.get_status() == ServiceStatus.ACTIVE + +def test_systemctl_ok(mock_popen_systemctl_service_ok): + assert MailServer.get_status() == ServiceStatus.ACTIVE assert Bitwarden.get_status() == ServiceStatus.ACTIVE assert Gitea.get_status() == ServiceStatus.ACTIVE assert Nextcloud.get_status() == ServiceStatus.ACTIVE assert Ocserv.get_status() == ServiceStatus.ACTIVE assert Pleroma.get_status() == ServiceStatus.ACTIVE - call_args_asserts(mock_subproccess_popen) + call_args_asserts(mock_popen_systemctl_service_ok) -def test_no_dkim_key(authorized_client, mock_broken_service): - assert MailServer.get_status() == ServiceStatus.FAILED +def test_systemctl_failed_service(mock_popen_systemctl_service_not_ok): + assert MailServer.get_status() == ServiceStatus.FAILED assert Bitwarden.get_status() == ServiceStatus.FAILED assert Gitea.get_status() == ServiceStatus.FAILED assert Nextcloud.get_status() == ServiceStatus.FAILED assert Ocserv.get_status() == ServiceStatus.FAILED assert Pleroma.get_status() == ServiceStatus.FAILED - call_args_asserts(mock_broken_service) + call_args_asserts(mock_popen_systemctl_service_not_ok) From fb15ef9388f09e6159eedaf27c7cda90ac49a889 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 25 Dec 2023 16:38:01 +0000 Subject: [PATCH 110/130] test(service): rename service systemctl calltests --- tests/{test_services_dkim.py => test_services_systemctl.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_services_dkim.py => test_services_systemctl.py} (100%) diff --git a/tests/test_services_dkim.py b/tests/test_services_systemctl.py similarity index 100% rename from tests/test_services_dkim.py rename to tests/test_services_systemctl.py From adcdbfb3687d16076f38d57cfccca1ac5f20fee5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 11:54:25 +0000 Subject: [PATCH 111/130] test(services): test mailserver dkim quirk --- tests/test_services.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_services.py b/tests/test_services.py index 09784e9..f3d6adc 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -9,12 +9,15 @@ from selfprivacy_api.utils.waitloop import wait_until_true from selfprivacy_api.services.bitwarden import Bitwarden from selfprivacy_api.services.pleroma import Pleroma +from selfprivacy_api.services.mailserver import MailServer from selfprivacy_api.services.owned_path import OwnedPath from selfprivacy_api.services.generic_service_mover import FolderMoveNames from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService +from tests.test_dkim import domain_file, dkim_file, no_dkim_file + def test_unimplemented_folders_raises(): with raises(NotImplementedError): @@ -145,3 +148,13 @@ def test_enabling_disabling_writes_json( dummy_service.disable() with ReadUserData() as data: assert data[dummy_service.get_id()]["enable"] is False + + +# more detailed testing of this is in test_graphql/test_system.py +def test_mailserver_with_dkim_returns_some_dns(dkim_file): + records = MailServer().get_dns_records() + assert len(records) > 0 + + +def test_mailserver_with_no_dkim_returns_no_dns(no_dkim_file): + assert MailServer().get_dns_records() == [] From e835173fea6209e3e5e21318eea1a51216ef5498 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 11:58:07 +0000 Subject: [PATCH 112/130] fix(users): handle no admin case --- selfprivacy_api/actions/users.py | 2 +- tests/test_graphql/test_users.py | 27 ++++++++++++++++++++++++- tests/test_rest_endpoints/test_users.py | 9 --------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/selfprivacy_api/actions/users.py b/selfprivacy_api/actions/users.py index bfc1756..10ba29b 100644 --- a/selfprivacy_api/actions/users.py +++ b/selfprivacy_api/actions/users.py @@ -58,7 +58,7 @@ def get_users( ) for user in user_data["users"] ] - if not exclude_primary: + if not exclude_primary and "username" in user_data.keys(): users.append( UserDataUser( username=user_data["username"], diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index e397600..bef02c3 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -6,6 +6,7 @@ from tests.common import ( generate_users_query, read_json, ) +from selfprivacy_api.utils import WriteUserData from tests.test_graphql.common import assert_empty, assert_errorcode invalid_usernames = [ @@ -89,6 +90,15 @@ def undefined_settings(mocker, datadir): return datadir +@pytest.fixture +def no_users_no_admin_nobody(undefined_settings): + datadir = undefined_settings + with WriteUserData() as data: + del data["username"] + del data["sshKeys"] + return datadir + + class ProcessMock: """Mock subprocess.Popen""" @@ -170,7 +180,7 @@ def test_graphql_get_no_users(authorized_client, no_users, mock_subprocess_popen ] -def test_graphql_get_users_undefined(authorized_client, undefined_settings): +def test_graphql_get_users_undefined_but_admin(authorized_client, undefined_settings): response = authorized_client.post( "/graphql", json={ @@ -187,6 +197,21 @@ def test_graphql_get_users_undefined(authorized_client, undefined_settings): ] +def test_graphql_get_users_undefined_no_admin( + authorized_client, no_users_no_admin_nobody +): + response = authorized_client.post( + "/graphql", + json={ + "query": generate_users_query([API_USERS_INFO]), + }, + ) + assert response.status_code == 200 + assert response.json().get("data") is not None + + assert len(response.json()["data"]["users"]["allUsers"]) == 0 + + API_GET_USERS = """ query TestUsers($username: String!) { users { diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index c7c5f5b..16f494c 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -112,15 +112,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### - -def test_get_undefined_users( - authorized_client, undefined_settings, mock_subprocess_popen -): - response = authorized_client.get("/users") - assert response.status_code == 200 - assert response.json() == [] - - # graphql tests still provide these fields even if with empty values From 2669e17c915e35061a6546797e30f74e030bc377 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 12:43:58 +0000 Subject: [PATCH 113/130] test(users): test adding users with missing (not just empty) fields --- tests/test_graphql/test_users.py | 147 +++++++++--------------- tests/test_rest_endpoints/test_users.py | 22 ---- 2 files changed, 57 insertions(+), 112 deletions(-) diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index bef02c3..2a2dd7f 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -7,7 +7,12 @@ from tests.common import ( read_json, ) from selfprivacy_api.utils import WriteUserData -from tests.test_graphql.common import assert_empty, assert_errorcode +from tests.test_graphql.common import ( + assert_empty, + assert_errorcode, + assert_ok, + get_data, +) invalid_usernames = [ "messagebus", @@ -368,118 +373,80 @@ mutation createUser($user: UserMutationInput!) { """ -def test_graphql_add_user_unauthorize(client, one_user, mock_subprocess_popen): - response = client.post( +def api_add_user_json(authorized_client, user_json: dict): + # lowlevel for deeper testing of edgecases + return authorized_client.post( "/graphql", json={ "query": API_CREATE_USERS_MUTATION, "variables": { - "user": { - "username": "user2", - "password": "12345678", - }, + "user": user_json, }, }, ) + + +def api_add_user(authorized_client, username, password): + response = api_add_user_json( + authorized_client, {"username": username, "password": password} + ) + output = get_data(response)["users"]["createUser"] + return output + + +def test_graphql_add_user_unauthorized(client, one_user, mock_subprocess_popen): + response = api_add_user_json(client, {"username": "user2", "password": "12345678"}) assert_empty(response) def test_graphql_add_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "user2", - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_user(authorized_client, "user2", password="12345678") + assert_ok(output, code=201) - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 201 - assert response.json()["data"]["users"]["createUser"]["success"] is True - - assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user2" - assert response.json()["data"]["users"]["createUser"]["user"]["sshKeys"] == [] + assert output["user"]["username"] == "user2" + assert output["user"]["sshKeys"] == [] -def test_graphql_add_undefined_settings( +def test_graphql_add_user_when_undefined_settings( authorized_client, undefined_settings, mock_subprocess_popen ): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "user2", - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_user(authorized_client, "user2", password="12345678") + assert_ok(output, code=201) - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 201 - assert response.json()["data"]["users"]["createUser"]["success"] is True - - assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user2" - assert response.json()["data"]["users"]["createUser"]["user"]["sshKeys"] == [] + assert output["user"]["username"] == "user2" + assert output["user"]["sshKeys"] == [] -def test_graphql_add_without_password( - authorized_client, one_user, mock_subprocess_popen -): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "user2", - "password": "", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None - - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 400 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert response.json()["data"]["users"]["createUser"]["user"] is None +users_witn_empty_fields = [ + {"username": "user2", "password": ""}, + {"username": "", "password": "12345678"}, + {"username": "", "password": ""}, +] -def test_graphql_add_without_both(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "", - "password": "", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None +@pytest.mark.parametrize("user_json", users_witn_empty_fields) +def test_graphql_add_with_empty_fields(authorized_client, one_user, user_json): + response = api_add_user_json(authorized_client, user_json) + output = get_data(response)["users"]["createUser"] - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 400 - assert response.json()["data"]["users"]["createUser"]["success"] is False + assert_errorcode(output, 400) - assert response.json()["data"]["users"]["createUser"]["user"] is None + assert output["user"] is None + + +users_witn_undefined_fields = [ + {"username": "user2"}, + {"password": "12345678"}, + {}, +] + + +@pytest.mark.parametrize("user_json", users_witn_undefined_fields) +def test_graphql_add_with_undefined_fields(authorized_client, one_user, user_json): + # checking that all fields are mandatory + response = api_add_user_json(authorized_client, user_json) + assert response.json()["errors"] is not None + assert response.json()["errors"] != [] @pytest.mark.parametrize("username", invalid_usernames) diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index 16f494c..2c325b3 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -112,28 +112,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### -# graphql tests still provide these fields even if with empty values - - -def test_post_without_username(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post("/users", json={"password": "password"}) - assert response.status_code == 422 - - -def test_post_without_password(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post("/users", json={"username": "user4"}) - assert response.status_code == 422 - - -def test_post_without_username_and_password( - authorized_client, one_user, mock_subprocess_popen -): - response = authorized_client.post("/users", json={}) - assert response.status_code == 422 - - -# end of BUT THERE ARE FIELDS! rant - # the final user is not in gql checks # I think maybe generate a bunch? @pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "№:%##$^&@$&^()_"]) From c470ec45e8cacb0e33481b414c859de0fd7926c5 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 13:06:44 +0000 Subject: [PATCH 114/130] test(users): test invalid usernames (and delete it from rest) --- tests/test_graphql/test_users.py | 130 +++++------------------- tests/test_rest_endpoints/test_users.py | 13 --- 2 files changed, 23 insertions(+), 120 deletions(-) diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index 2a2dd7f..96ecb85 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -430,7 +430,6 @@ def test_graphql_add_with_empty_fields(authorized_client, one_user, user_json): output = get_data(response)["users"]["createUser"] assert_errorcode(output, 400) - assert output["user"] is None @@ -445,6 +444,7 @@ users_witn_undefined_fields = [ def test_graphql_add_with_undefined_fields(authorized_client, one_user, user_json): # checking that all fields are mandatory response = api_add_user_json(authorized_client, user_json) + assert response.json()["errors"] is not None assert response.json()["errors"] != [] @@ -453,130 +453,46 @@ def test_graphql_add_with_undefined_fields(authorized_client, one_user, user_jso def test_graphql_add_system_username( authorized_client, one_user, mock_subprocess_popen, username ): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": username, - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_user(authorized_client, username, password="12345678") - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 409 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert response.json()["data"]["users"]["createUser"]["user"] is None + assert_errorcode(output, code=409) + assert output["user"] is None -def test_graphql_add_existing_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "user1", - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None +def test_graphql_add_existing_user(authorized_client, one_user): + output = api_add_user(authorized_client, "user1", password="12345678") - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 409 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user1" - assert ( - response.json()["data"]["users"]["createUser"]["user"]["sshKeys"][0] - == "ssh-rsa KEY user1@pc" - ) + assert_errorcode(output, code=409) + assert output["user"]["username"] == "user1" + assert output["user"]["sshKeys"][0] == "ssh-rsa KEY user1@pc" def test_graphql_add_main_user(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "tester", - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_user(authorized_client, "tester", password="12345678") - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 409 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert ( - response.json()["data"]["users"]["createUser"]["user"]["username"] == "tester" - ) - assert ( - response.json()["data"]["users"]["createUser"]["user"]["sshKeys"][0] - == "ssh-rsa KEY test@pc" - ) + assert_errorcode(output, code=409) + assert output["user"]["username"] == "tester" + assert output["user"]["sshKeys"][0] == "ssh-rsa KEY test@pc" def test_graphql_add_long_username(authorized_client, one_user, mock_subprocess_popen): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": "a" * 32, - "password": "12345678", - }, - }, - }, - ) - assert response.json().get("data") is not None + output = api_add_user(authorized_client, "a" * 32, password="12345678") - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 400 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert response.json()["data"]["users"]["createUser"]["user"] is None + assert_errorcode(output, code=400) + assert output["user"] is None -@pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "^-^"]) +# TODO: maybe make a username generating function to make a more comprehensive invalid username test +@pytest.mark.parametrize( + "username", ["", "1", "фыр", "user1@", "^-^", "№:%##$^&@$&^()_"] +) def test_graphql_add_invalid_username( authorized_client, one_user, mock_subprocess_popen, username ): - response = authorized_client.post( - "/graphql", - json={ - "query": API_CREATE_USERS_MUTATION, - "variables": { - "user": { - "username": username, - "password": "12345678", - }, - }, - }, - ) - assert response.status_code == 200 - assert response.json().get("data") is not None + output = api_add_user(authorized_client, username, password="12345678") - assert response.json()["data"]["users"]["createUser"]["message"] is not None - assert response.json()["data"]["users"]["createUser"]["code"] == 400 - assert response.json()["data"]["users"]["createUser"]["success"] is False - - assert response.json()["data"]["users"]["createUser"]["user"] is None + assert_errorcode(output, code=400) + assert output["user"] is None API_DELETE_USER_MUTATION = """ diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index 2c325b3..89f4331 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -111,19 +111,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### - -# the final user is not in gql checks -# I think maybe generate a bunch? -@pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "№:%##$^&@$&^()_"]) -def test_post_invalid_username( - authorized_client, one_user, mock_subprocess_popen, username -): - response = authorized_client.post( - "/users", json={"username": username, "password": "password"} - ) - assert response.status_code == 400 - - # gql counterpart is too weak def test_delete_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.delete("/users/user1") From 2e775dad90289f390b841d95d1ea25deacbc8fc9 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 13:44:39 +0000 Subject: [PATCH 115/130] fix(users): handle no admin name defined when adding a user --- selfprivacy_api/actions/users.py | 10 ++++++++++ selfprivacy_api/graphql/mutations/users_mutations.py | 6 ++++++ tests/test_graphql/test_users.py | 11 ++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/selfprivacy_api/actions/users.py b/selfprivacy_api/actions/users.py index 10ba29b..fafa84f 100644 --- a/selfprivacy_api/actions/users.py +++ b/selfprivacy_api/actions/users.py @@ -107,6 +107,12 @@ class PasswordIsEmpty(Exception): pass +class InvalidConfiguration(Exception): + """The userdata is broken""" + + pass + + def create_user(username: str, password: str): if password == "": raise PasswordIsEmpty("Password is empty") @@ -124,6 +130,10 @@ def create_user(username: str, password: str): with ReadUserData() as user_data: ensure_ssh_and_users_fields_exist(user_data) + if "username" not in user_data.keys(): + raise InvalidConfiguration( + "Broken config: Admin name is not defined. Consider recovery or add it manually" + ) if username == user_data["username"]: raise UserAlreadyExists("User already exists") if username in [user["username"] for user in user_data["users"]]: diff --git a/selfprivacy_api/graphql/mutations/users_mutations.py b/selfprivacy_api/graphql/mutations/users_mutations.py index 57825bc..7644b90 100644 --- a/selfprivacy_api/graphql/mutations/users_mutations.py +++ b/selfprivacy_api/graphql/mutations/users_mutations.py @@ -69,6 +69,12 @@ class UsersMutations: message=str(e), code=400, ) + except users_actions.InvalidConfiguration as e: + return UserMutationReturn( + success=False, + message=str(e), + code=400, + ) except users_actions.UserAlreadyExists as e: return UserMutationReturn( success=False, diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index 96ecb85..f3ba02d 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -467,7 +467,7 @@ def test_graphql_add_existing_user(authorized_client, one_user): assert output["user"]["sshKeys"][0] == "ssh-rsa KEY user1@pc" -def test_graphql_add_main_user(authorized_client, one_user, mock_subprocess_popen): +def test_graphql_add_main_user(authorized_client, one_user): output = api_add_user(authorized_client, "tester", password="12345678") assert_errorcode(output, code=409) @@ -475,6 +475,15 @@ def test_graphql_add_main_user(authorized_client, one_user, mock_subprocess_pope assert output["user"]["sshKeys"][0] == "ssh-rsa KEY test@pc" +def test_graphql_add_user_when_no_admin_defined( + authorized_client, no_users_no_admin_nobody +): + output = api_add_user(authorized_client, "tester", password="12345678") + + assert_errorcode(output, code=400) + assert output["user"] is None + + def test_graphql_add_long_username(authorized_client, one_user, mock_subprocess_popen): output = api_add_user(authorized_client, "a" * 32, password="12345678") From e7c89e3e3fe6350a2d7497d1313eb748db75ed99 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 14:31:50 +0000 Subject: [PATCH 116/130] test(users): delete a user and CHECK that it was deleted --- tests/test_graphql/test_users.py | 16 +++++++++++++++ tests/test_rest_endpoints/test_users.py | 10 ---------- tests/test_users.py | 26 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 tests/test_users.py diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index f3ba02d..99f5934 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -133,6 +133,17 @@ allUsers { """ +def api_all_users(authorized_client): + response = authorized_client.post( + "/graphql", + json={ + "query": generate_users_query([API_USERS_INFO]), + }, + ) + output = get_data(response)["users"]["allUsers"] + return output + + def test_graphql_get_users_unauthorized(client, some_users, mock_subprocess_popen): """Test wrong auth""" response = client.post( @@ -543,6 +554,11 @@ def test_graphql_delete_user(authorized_client, some_users, mock_subprocess_pope assert response.json()["data"]["users"]["deleteUser"]["message"] is not None assert response.json()["data"]["users"]["deleteUser"]["success"] is True + new_users = api_all_users(authorized_client) + assert len(new_users) == 3 + usernames = [user["username"] for user in new_users] + assert set(usernames) == set(["user2", "user3", "tester"]) + @pytest.mark.parametrize("username", ["", "def"]) def test_graphql_delete_nonexistent_users( diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index 89f4331..6123568 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -111,16 +111,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### -# gql counterpart is too weak -def test_delete_user(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.delete("/users/user1") - assert response.status_code == 200 - assert read_json(some_users / "some_users.json")["users"] == [ - {"username": "user2", "hashedPassword": "HASHED_PASSWORD_2", "sshKeys": []}, - {"username": "user3", "hashedPassword": "HASHED_PASSWORD_3"}, - ] - - def test_delete_main_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.delete("/users/tester") assert response.status_code == 400 diff --git a/tests/test_users.py b/tests/test_users.py new file mode 100644 index 0000000..2f613db --- /dev/null +++ b/tests/test_users.py @@ -0,0 +1,26 @@ +from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.actions.users import delete_user +""" + A place for user storage tests and other user tests that are not Graphql-specific. +""" + +# yes it is an incomplete suite. +# It was born in order to not lose things that REST API tests checked for +# In the future, user storage tests that are not dependent on actual API (graphql or otherwise) go here. + +def test_delete_user_writes_json(generic_userdata): + delete_user("user2") + with ReadUserData() as data: + assert data["users"] == [ + { + "username": "user1", + "hashedPassword": "HASHED_PASSWORD_1", + "sshKeys": ["ssh-rsa KEY user1@pc"] + }, + { + "username": "user3", + "hashedPassword": "HASHED_PASSWORD_3", + "sshKeys": ["ssh-rsa KEY user3@pc"] + } + ] + From ab081f6fbcd248da79bd4d4bb04e092828c86040 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 14:38:08 +0000 Subject: [PATCH 117/130] test(users): delete redundant rest admin deletion tests --- tests/test_rest_endpoints/test_users.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py index 6123568..f660126 100644 --- a/tests/test_rest_endpoints/test_users.py +++ b/tests/test_rest_endpoints/test_users.py @@ -111,9 +111,6 @@ def mock_subprocess_popen(mocker): ## TESTS ###################################################### -def test_delete_main_user(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.delete("/users/tester") - assert response.status_code == 400 def test_delete_just_delete(authorized_client, some_users, mock_subprocess_popen): From dcf6dd9ac5e9d7d59fdc9db5f727f61781a42baa Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 27 Dec 2023 14:46:52 +0000 Subject: [PATCH 118/130] test(users): delete rest user tests --- tests/test_rest_endpoints/test_users.py | 118 ------------------------ 1 file changed, 118 deletions(-) delete mode 100644 tests/test_rest_endpoints/test_users.py diff --git a/tests/test_rest_endpoints/test_users.py b/tests/test_rest_endpoints/test_users.py deleted file mode 100644 index f660126..0000000 --- a/tests/test_rest_endpoints/test_users.py +++ /dev/null @@ -1,118 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -import json -import pytest - - -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as file: - return json.load(file) - - -invalid_usernames = [ - "root", - "messagebus", - "postfix", - "polkituser", - "dovecot2", - "dovenull", - "nginx", - "postgres", - "systemd-journal-gateway", - "prosody", - "systemd-network", - "systemd-resolve", - "systemd-timesync", - "opendkim", - "rspamd", - "sshd", - "selfprivacy-api", - "restic", - "redis", - "pleroma", - "ocserv", - "nextcloud", - "memcached", - "knot-resolver", - "gitea", - "bitwarden_rs", - "vaultwarden", - "acme", - "virtualMail", - "nixbld1", - "nixbld2", - "nixbld29", - "nobody", -] - - -## FIXTURES ################################################### - - -@pytest.fixture -def no_users(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "no_users.json") - assert read_json(datadir / "no_users.json")["users"] == [] - return datadir - - -@pytest.fixture -def one_user(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "one_user.json") - assert read_json(datadir / "one_user.json")["users"] == [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": ["ssh-rsa KEY user1@pc"], - } - ] - return datadir - - -@pytest.fixture -def some_users(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "some_users.json") - assert read_json(datadir / "some_users.json")["users"] == [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": ["ssh-rsa KEY user1@pc"], - }, - {"username": "user2", "hashedPassword": "HASHED_PASSWORD_2", "sshKeys": []}, - {"username": "user3", "hashedPassword": "HASHED_PASSWORD_3"}, - ] - return datadir - - -@pytest.fixture -def undefined_settings(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "users" not in read_json(datadir / "undefined.json") - return datadir - - -class ProcessMock: - """Mock subprocess.Popen""" - - def __init__(self, args, **kwargs): - self.args = args - self.kwargs = kwargs - - def communicate(): - return (b"NEW_HASHED", None) - - returncode = 0 - - -@pytest.fixture -def mock_subprocess_popen(mocker): - mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) - return mock - - -## TESTS ###################################################### - - -def test_delete_just_delete(authorized_client, some_users, mock_subprocess_popen): - response = authorized_client.delete("/users") - assert response.status_code == 405 From 4a580e9b7b7eb28d994fdb06cc136214ad0a89e4 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 13:11:03 +0000 Subject: [PATCH 119/130] feature(system): better error handling for shell calls --- selfprivacy_api/actions/system.py | 54 +++++++++----- .../graphql/mutations/system_mutations.py | 70 +++++++++++++------ tests/test_graphql/test_system_nixos_tasks.py | 9 --- 3 files changed, 86 insertions(+), 47 deletions(-) diff --git a/selfprivacy_api/actions/system.py b/selfprivacy_api/actions/system.py index 853662f..f5e0dc0 100644 --- a/selfprivacy_api/actions/system.py +++ b/selfprivacy_api/actions/system.py @@ -2,7 +2,7 @@ import os import subprocess import pytz -from typing import Optional +from typing import Optional, List from pydantic import BaseModel from selfprivacy_api.utils import WriteUserData, ReadUserData @@ -58,36 +58,56 @@ def set_auto_upgrade_settings( user_data["autoUpgrade"]["allowReboot"] = allowReboot +class ShellException(Exception): + """Something went wrong when calling another process""" + + pass + + +def run_blocking(cmd: List[str], new_session: bool = False) -> str: + """Run a process, block until done, return output, complain if failed""" + process_handle = subprocess.Popen( + cmd, + shell=False, + start_new_session=new_session, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout_raw, stderr_raw = process_handle.communicate() + stdout = stdout_raw.decode("utf-8") + if stderr_raw is not None: + stderr = stderr_raw.decode("utf-8") + else: + stderr = "" + output = stdout + "\n" + stderr + if process_handle.returncode != 0: + raise ShellException( + f"Shell command failed, command array: {cmd}, output: {output}" + ) + return stdout + + def rebuild_system() -> int: """Rebuild the system""" - rebuild_result = subprocess.Popen( - ["systemctl", "start", "sp-nixos-rebuild.service"], start_new_session=True - ) - rebuild_result.communicate()[0] - return rebuild_result.returncode + run_blocking(["systemctl", "start", "sp-nixos-rebuild.service"], new_session=True) + return 0 def rollback_system() -> int: """Rollback the system""" - rollback_result = subprocess.Popen( - ["systemctl", "start", "sp-nixos-rollback.service"], start_new_session=True - ) - rollback_result.communicate()[0] - return rollback_result.returncode + run_blocking(["systemctl", "start", "sp-nixos-rollback.service"], new_session=True) + return 0 def upgrade_system() -> int: """Upgrade the system""" - upgrade_result = subprocess.Popen( - ["systemctl", "start", "sp-nixos-upgrade.service"], start_new_session=True - ) - upgrade_result.communicate()[0] - return upgrade_result.returncode + run_blocking(["systemctl", "start", "sp-nixos-upgrade.service"], new_session=True) + return 0 def reboot_system() -> None: """Reboot the system""" - subprocess.Popen(["reboot"], start_new_session=True) + run_blocking(["reboot"], new_session=True) def get_system_version() -> str: diff --git a/selfprivacy_api/graphql/mutations/system_mutations.py b/selfprivacy_api/graphql/mutations/system_mutations.py index b0cdae8..13ac16b 100644 --- a/selfprivacy_api/graphql/mutations/system_mutations.py +++ b/selfprivacy_api/graphql/mutations/system_mutations.py @@ -115,39 +115,67 @@ class SystemMutations: @strawberry.mutation(permission_classes=[IsAuthenticated]) def run_system_rebuild(self) -> GenericMutationReturn: - system_actions.rebuild_system() - return GenericMutationReturn( - success=True, - message="Starting rebuild system", - code=200, - ) + try: + system_actions.rebuild_system() + return GenericMutationReturn( + success=True, + message="Starting rebuild system", + code=200, + ) + except system_actions.ShellException as e: + return GenericMutationReturn( + success=False, + message=str(e), + code=500, + ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def run_system_rollback(self) -> GenericMutationReturn: system_actions.rollback_system() - return GenericMutationReturn( - success=True, - message="Starting rebuild system", - code=200, - ) + try: + return GenericMutationReturn( + success=True, + message="Starting rebuild system", + code=200, + ) + except system_actions.ShellException as e: + return GenericMutationReturn( + success=False, + message=str(e), + code=500, + ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def run_system_upgrade(self) -> GenericMutationReturn: system_actions.upgrade_system() - return GenericMutationReturn( - success=True, - message="Starting rebuild system", - code=200, - ) + try: + return GenericMutationReturn( + success=True, + message="Starting rebuild system", + code=200, + ) + except system_actions.ShellException as e: + return GenericMutationReturn( + success=False, + message=str(e), + code=500, + ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def reboot_system(self) -> GenericMutationReturn: system_actions.reboot_system() - return GenericMutationReturn( - success=True, - message="System reboot has started", - code=200, - ) + try: + return GenericMutationReturn( + success=True, + message="System reboot has started", + code=200, + ) + except system_actions.ShellException as e: + return GenericMutationReturn( + success=False, + message=str(e), + code=500, + ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def pull_repository_changes(self) -> GenericMutationReturn: diff --git a/tests/test_graphql/test_system_nixos_tasks.py b/tests/test_graphql/test_system_nixos_tasks.py index b292fda..6052e9f 100644 --- a/tests/test_graphql/test_system_nixos_tasks.py +++ b/tests/test_graphql/test_system_nixos_tasks.py @@ -23,15 +23,6 @@ class ProcessMock: returncode = 0 -class BrokenServiceMock(ProcessMock): - """Mock subprocess.Popen for broken service""" - - def communicate(): # pylint: disable=no-method-argument - return (b"Testing error", None) - - returncode = 3 - - @pytest.fixture def mock_subprocess_popen(mocker): mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock) From 46cc3171abb76acbef867cbb048c5445a8488404 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 13:12:32 +0000 Subject: [PATCH 120/130] test(system): test generic shell calls --- tests/test_system.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_system.py diff --git a/tests/test_system.py b/tests/test_system.py new file mode 100644 index 0000000..549692e --- /dev/null +++ b/tests/test_system.py @@ -0,0 +1,22 @@ +import pytest +from selfprivacy_api.actions.system import run_blocking, ShellException + +# uname is just an arbitrary command expected to be everywhere we care + + +def test_uname(): + output = run_blocking(["uname"]) + assert output is not None + + +def test_uname_new_session(): + output = run_blocking(["uname"], new_session=True) + assert output is not None + + +def test_uname_nonexistent_args(): + with pytest.raises(ShellException) as exception_info: + # uname: extra operand ‘sldfkjsljf’ + # Try 'uname --help' for more information + run_blocking(["uname", "isdyfhishfaisljhkeysmash"], new_session=True) + assert "extra operand" in exception_info.value.args[0] From d96739c9ae8c9463684f1a0a9dab91e2d7d15f8a Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 13:13:08 +0000 Subject: [PATCH 121/130] test(system): remove rest system tests --- tests/test_rest_endpoints/data/jobs.json | 1 - tests/test_rest_endpoints/test_system.py | 167 ------------------ tests/test_rest_endpoints/test_system/domain | 1 - .../test_system/no_values.json | 55 ------ .../test_system/turned_off.json | 57 ------ .../test_system/turned_on.json | 57 ------ .../test_system/undefined.json | 52 ------ .../test_users/no_users.json | 59 ------- .../test_users/one_user.json | 66 ------- .../test_users/some_users.json | 76 -------- .../test_users/undefined.json | 57 ------ 11 files changed, 648 deletions(-) delete mode 100644 tests/test_rest_endpoints/data/jobs.json delete mode 100644 tests/test_rest_endpoints/test_system.py delete mode 100644 tests/test_rest_endpoints/test_system/domain delete mode 100644 tests/test_rest_endpoints/test_system/no_values.json delete mode 100644 tests/test_rest_endpoints/test_system/turned_off.json delete mode 100644 tests/test_rest_endpoints/test_system/turned_on.json delete mode 100644 tests/test_rest_endpoints/test_system/undefined.json delete mode 100644 tests/test_rest_endpoints/test_users/no_users.json delete mode 100644 tests/test_rest_endpoints/test_users/one_user.json delete mode 100644 tests/test_rest_endpoints/test_users/some_users.json delete mode 100644 tests/test_rest_endpoints/test_users/undefined.json diff --git a/tests/test_rest_endpoints/data/jobs.json b/tests/test_rest_endpoints/data/jobs.json deleted file mode 100644 index 0967ef4..0000000 --- a/tests/test_rest_endpoints/data/jobs.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/test_rest_endpoints/test_system.py b/tests/test_rest_endpoints/test_system.py deleted file mode 100644 index f2b20db..0000000 --- a/tests/test_rest_endpoints/test_system.py +++ /dev/null @@ -1,167 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -# pylint: disable=missing-function-docstring - -import json -import os -import pytest -from selfprivacy_api.utils import get_domain - - -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as file: - return json.load(file) - - -@pytest.fixture -def domain_file(mocker, datadir): - mocker.patch("selfprivacy_api.utils.DOMAIN_FILE", datadir / "domain") - return datadir - - -@pytest.fixture -def turned_on(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json") - assert read_json(datadir / "turned_on.json")["autoUpgrade"]["enable"] == True - assert read_json(datadir / "turned_on.json")["autoUpgrade"]["allowReboot"] == True - assert read_json(datadir / "turned_on.json")["timezone"] == "Europe/Moscow" - return datadir - - -@pytest.fixture -def turned_off(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json") - assert read_json(datadir / "turned_off.json")["autoUpgrade"]["enable"] == False - assert read_json(datadir / "turned_off.json")["autoUpgrade"]["allowReboot"] == False - assert read_json(datadir / "turned_off.json")["timezone"] == "Europe/Moscow" - return datadir - - -@pytest.fixture -def undefined_config(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") - assert "autoUpgrade" not in read_json(datadir / "undefined.json") - assert "timezone" not in read_json(datadir / "undefined.json") - return datadir - - -@pytest.fixture -def no_values(mocker, datadir): - mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "no_values.json") - assert "enable" not in read_json(datadir / "no_values.json")["autoUpgrade"] - assert "allowReboot" not in read_json(datadir / "no_values.json")["autoUpgrade"] - return datadir - - -class ProcessMock: - """Mock subprocess.Popen""" - - def __init__(self, args, **kwargs): - self.args = args - self.kwargs = kwargs - - def communicate(): - return (b"", None) - - returncode = 0 - - -class BrokenServiceMock(ProcessMock): - """Mock subprocess.Popen""" - - def communicate(): - return (b"Testing error", None) - - returncode = 3 - - -@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_broken_service(mocker): - mock = mocker.patch( - "subprocess.Popen", autospec=True, return_value=BrokenServiceMock - ) - 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 - - -def test_system_rebuild_unauthorized(client, mock_subprocess_popen): - response = client.get("/system/configuration/apply") - assert response.status_code == 401 - assert mock_subprocess_popen.call_count == 0 - - -def test_system_rebuild(authorized_client, mock_subprocess_popen): - response = authorized_client.get("/system/configuration/apply") - assert response.status_code == 200 - assert mock_subprocess_popen.call_count == 1 - assert mock_subprocess_popen.call_args[0][0] == [ - "systemctl", - "start", - "sp-nixos-rebuild.service", - ] - - -def test_system_upgrade_unauthorized(client, mock_subprocess_popen): - response = client.get("/system/configuration/upgrade") - assert response.status_code == 401 - assert mock_subprocess_popen.call_count == 0 - - -def test_system_upgrade(authorized_client, mock_subprocess_popen): - response = authorized_client.get("/system/configuration/upgrade") - assert response.status_code == 200 - assert mock_subprocess_popen.call_count == 1 - assert mock_subprocess_popen.call_args[0][0] == [ - "systemctl", - "start", - "sp-nixos-upgrade.service", - ] - - -def test_system_rollback_unauthorized(client, mock_subprocess_popen): - response = client.get("/system/configuration/rollback") - assert response.status_code == 401 - assert mock_subprocess_popen.call_count == 0 - - -def test_system_rollback(authorized_client, mock_subprocess_popen): - response = authorized_client.get("/system/configuration/rollback") - assert response.status_code == 200 - assert mock_subprocess_popen.call_count == 1 - assert mock_subprocess_popen.call_args[0][0] == [ - "systemctl", - "start", - "sp-nixos-rollback.service", - ] - - -def test_reboot_system_unauthorized(client, mock_subprocess_popen): - response = client.get("/system/reboot") - assert response.status_code == 401 - assert mock_subprocess_popen.call_count == 0 - - -def test_reboot_system(authorized_client, mock_subprocess_popen): - response = authorized_client.get("/system/reboot") - assert response.status_code == 200 - assert mock_subprocess_popen.call_count == 1 - assert mock_subprocess_popen.call_args[0][0] == ["reboot"] diff --git a/tests/test_rest_endpoints/test_system/domain b/tests/test_rest_endpoints/test_system/domain deleted file mode 100644 index 3679d0d..0000000 --- a/tests/test_rest_endpoints/test_system/domain +++ /dev/null @@ -1 +0,0 @@ -test-domain.tld \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_system/no_values.json b/tests/test_rest_endpoints/test_system/no_values.json deleted file mode 100644 index 5c1431e..0000000 --- a/tests/test_rest_endpoints/test_system/no_values.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_system/turned_off.json b/tests/test_rest_endpoints/test_system/turned_off.json deleted file mode 100644 index 2336f36..0000000 --- a/tests/test_rest_endpoints/test_system/turned_off.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": false, - "allowReboot": false - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_system/turned_on.json b/tests/test_rest_endpoints/test_system/turned_on.json deleted file mode 100644 index 42999d8..0000000 --- a/tests/test_rest_endpoints/test_system/turned_on.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_system/undefined.json b/tests/test_rest_endpoints/test_system/undefined.json deleted file mode 100644 index 6b9f3fd..0000000 --- a/tests/test_rest_endpoints/test_system/undefined.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": true - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_users/no_users.json b/tests/test_rest_endpoints/test_users/no_users.json deleted file mode 100644 index 5929a79..0000000 --- a/tests/test_rest_endpoints/test_users/no_users.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_users/one_user.json b/tests/test_rest_endpoints/test_users/one_user.json deleted file mode 100644 index 6c553bc..0000000 --- a/tests/test_rest_endpoints/test_users/one_user.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_users/some_users.json b/tests/test_rest_endpoints/test_users/some_users.json deleted file mode 100644 index df6380a..0000000 --- a/tests/test_rest_endpoints/test_users/some_users.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "users": [ - { - "username": "user1", - "hashedPassword": "HASHED_PASSWORD_1", - "sshKeys": [ - "ssh-rsa KEY user1@pc" - ] - }, - { - "username": "user2", - "hashedPassword": "HASHED_PASSWORD_2", - "sshKeys": [ - ] - }, - { - "username": "user3", - "hashedPassword": "HASHED_PASSWORD_3" - } - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file diff --git a/tests/test_rest_endpoints/test_users/undefined.json b/tests/test_rest_endpoints/test_users/undefined.json deleted file mode 100644 index c1691ea..0000000 --- a/tests/test_rest_endpoints/test_users/undefined.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "api": { - "token": "TEST_TOKEN", - "enableSwagger": false - }, - "bitwarden": { - "enable": false - }, - "databasePassword": "PASSWORD", - "domain": "test.tld", - "hashedMasterPassword": "HASHED_PASSWORD", - "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, - "resticPassword": "PASS", - "ssh": { - "enable": true, - "passwordAuthentication": true, - "rootKeys": [ - "ssh-ed25519 KEY test@pc" - ] - }, - "username": "tester", - "gitea": { - "enable": false - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "autoUpgrade": { - "enable": true, - "allowReboot": true - }, - "timezone": "Europe/Moscow", - "sshKeys": [ - "ssh-rsa KEY test@pc" - ], - "dns": { - "provider": "CLOUDFLARE", - "apiKey": "TOKEN" - }, - "server": { - "provider": "HETZNER" - }, - "backup": { - "provider": "BACKBLAZE", - "accountId": "ID", - "accountKey": "KEY", - "bucket": "selfprivacy" - } -} \ No newline at end of file From 43d9d47aed76eb1ed2f5ba48f9041943a66e147b Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 13:34:06 +0000 Subject: [PATCH 122/130] feature(system): remove rest system code --- selfprivacy_api/app.py | 2 - selfprivacy_api/rest/system.py | 105 --------------------------------- 2 files changed, 107 deletions(-) delete mode 100644 selfprivacy_api/rest/system.py diff --git a/selfprivacy_api/app.py b/selfprivacy_api/app.py index a58301a..68cd814 100644 --- a/selfprivacy_api/app.py +++ b/selfprivacy_api/app.py @@ -11,7 +11,6 @@ from selfprivacy_api.graphql.schema import schema from selfprivacy_api.migrations import run_migrations from selfprivacy_api.rest import ( - system, users, api_auth, services, @@ -32,7 +31,6 @@ app.add_middleware( ) -app.include_router(system.router) app.include_router(users.router) app.include_router(api_auth.router) app.include_router(services.router) diff --git a/selfprivacy_api/rest/system.py b/selfprivacy_api/rest/system.py deleted file mode 100644 index 9933fb3..0000000 --- a/selfprivacy_api/rest/system.py +++ /dev/null @@ -1,105 +0,0 @@ -from typing import Optional -from fastapi import APIRouter, Body, Depends, HTTPException -from pydantic import BaseModel - -from selfprivacy_api.dependencies import get_token_header - -import selfprivacy_api.actions.system as system_actions - -router = APIRouter( - prefix="/system", - tags=["system"], - dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) - - -@router.get("/configuration/timezone") -async def get_timezone(): - """Get the timezone of the server""" - return system_actions.get_timezone() - - -class ChangeTimezoneRequestBody(BaseModel): - """Change the timezone of the server""" - - timezone: str - - -@router.put("/configuration/timezone") -async def change_timezone(timezone: ChangeTimezoneRequestBody): - """Change the timezone of the server""" - try: - system_actions.change_timezone(timezone.timezone) - except system_actions.InvalidTimezone as e: - raise HTTPException(status_code=400, detail=str(e)) - return {"timezone": timezone.timezone} - - -@router.get("/configuration/autoUpgrade") -async def get_auto_upgrade_settings(): - """Get the auto-upgrade settings""" - return system_actions.get_auto_upgrade_settings().dict() - - -class AutoUpgradeSettings(BaseModel): - """Settings for auto-upgrading user data""" - - enable: Optional[bool] = None - allowReboot: Optional[bool] = None - - -@router.put("/configuration/autoUpgrade") -async def set_auto_upgrade_settings(settings: AutoUpgradeSettings): - """Set the auto-upgrade settings""" - system_actions.set_auto_upgrade_settings(settings.enable, settings.allowReboot) - return "Auto-upgrade settings changed" - - -@router.get("/configuration/apply") -async def apply_configuration(): - """Apply the configuration""" - return_code = system_actions.rebuild_system() - return return_code - - -@router.get("/configuration/rollback") -async def rollback_configuration(): - """Rollback the configuration""" - return_code = system_actions.rollback_system() - return return_code - - -@router.get("/configuration/upgrade") -async def upgrade_configuration(): - """Upgrade the configuration""" - return_code = system_actions.upgrade_system() - return return_code - - -@router.get("/reboot") -async def reboot_system(): - """Reboot the system""" - system_actions.reboot_system() - return "System reboot has started" - - -@router.get("/version") -async def get_system_version(): - """Get the system version""" - return {"system_version": system_actions.get_system_version()} - - -@router.get("/pythonVersion") -async def get_python_version(): - """Get the Python version""" - return system_actions.get_python_version() - - -@router.get("/configuration/pull") -async def pull_configuration(): - """Pull the configuration""" - action_result = system_actions.pull_repository_changes() - if action_result.status == 0: - return action_result.dict() - raise HTTPException(status_code=500, detail=action_result.dict()) From 41cd876f576009fb04ac9142a56acc93c1b09582 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 13:42:23 +0000 Subject: [PATCH 123/130] feature(users): remove rest users code --- selfprivacy_api/app.py | 2 -- selfprivacy_api/rest/users.py | 62 ----------------------------------- 2 files changed, 64 deletions(-) delete mode 100644 selfprivacy_api/rest/users.py diff --git a/selfprivacy_api/app.py b/selfprivacy_api/app.py index 68cd814..be28e29 100644 --- a/selfprivacy_api/app.py +++ b/selfprivacy_api/app.py @@ -11,7 +11,6 @@ from selfprivacy_api.graphql.schema import schema from selfprivacy_api.migrations import run_migrations from selfprivacy_api.rest import ( - users, api_auth, services, ) @@ -31,7 +30,6 @@ app.add_middleware( ) -app.include_router(users.router) app.include_router(api_auth.router) app.include_router(services.router) app.include_router(graphql_app, prefix="/graphql") diff --git a/selfprivacy_api/rest/users.py b/selfprivacy_api/rest/users.py deleted file mode 100644 index ab4c6c9..0000000 --- a/selfprivacy_api/rest/users.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Users management module""" -from typing import Optional -from fastapi import APIRouter, Body, Depends, HTTPException -from pydantic import BaseModel - -import selfprivacy_api.actions.users as users_actions - -from selfprivacy_api.dependencies import get_token_header - -router = APIRouter( - prefix="/users", - tags=["users"], - dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) - - -@router.get("") -async def get_users(withMainUser: bool = False): - """Get the list of users""" - users: list[users_actions.UserDataUser] = users_actions.get_users( - exclude_primary=not withMainUser, exclude_root=True - ) - - return [user.username for user in users] - - -class UserInput(BaseModel): - """User input""" - - username: str - password: str - - -@router.post("", status_code=201) -async def create_user(user: UserInput): - try: - users_actions.create_user(user.username, user.password) - except users_actions.PasswordIsEmpty as e: - raise HTTPException(status_code=400, detail=str(e)) - except users_actions.UsernameForbidden as e: - raise HTTPException(status_code=409, detail=str(e)) - except users_actions.UsernameNotAlphanumeric as e: - raise HTTPException(status_code=400, detail=str(e)) - except users_actions.UsernameTooLong as e: - raise HTTPException(status_code=400, detail=str(e)) - except users_actions.UserAlreadyExists as e: - raise HTTPException(status_code=409, detail=str(e)) - - return {"result": 0, "username": user.username} - - -@router.delete("/{username}") -async def delete_user(username: str): - try: - users_actions.delete_user(username) - except users_actions.UserNotFound as e: - raise HTTPException(status_code=404, detail=str(e)) - except users_actions.UserIsProtected as e: - raise HTTPException(status_code=400, detail=str(e)) - - return {"result": 0, "username": username} From 3e1fbdd4aa8298ae97308bb8db36de06a83e5c94 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 14:28:33 +0000 Subject: [PATCH 124/130] feature(services): remove rest services code --- selfprivacy_api/app.py | 2 - selfprivacy_api/rest/services.py | 336 ------------------------------- 2 files changed, 338 deletions(-) delete mode 100644 selfprivacy_api/rest/services.py diff --git a/selfprivacy_api/app.py b/selfprivacy_api/app.py index be28e29..913305c 100644 --- a/selfprivacy_api/app.py +++ b/selfprivacy_api/app.py @@ -12,7 +12,6 @@ from selfprivacy_api.migrations import run_migrations from selfprivacy_api.rest import ( api_auth, - services, ) app = FastAPI() @@ -31,7 +30,6 @@ app.add_middleware( app.include_router(api_auth.router) -app.include_router(services.router) app.include_router(graphql_app, prefix="/graphql") diff --git a/selfprivacy_api/rest/services.py b/selfprivacy_api/rest/services.py deleted file mode 100644 index c6dc12e..0000000 --- a/selfprivacy_api/rest/services.py +++ /dev/null @@ -1,336 +0,0 @@ -"""Basic services legacy api""" -import base64 -from typing import Optional -from fastapi import APIRouter, Depends, HTTPException -from pydantic import BaseModel -from selfprivacy_api.actions.ssh import ( - InvalidPublicKey, - KeyAlreadyExists, - KeyNotFound, - create_ssh_key, - enable_ssh, - get_ssh_settings, - remove_ssh_key, - set_ssh_settings, -) -from selfprivacy_api.actions.users import UserNotFound, get_user_by_username - -from selfprivacy_api.dependencies import get_token_header -from selfprivacy_api.services.bitwarden import Bitwarden -from selfprivacy_api.services.gitea import Gitea -from selfprivacy_api.services.mailserver import MailServer -from selfprivacy_api.services.nextcloud import Nextcloud -from selfprivacy_api.services.ocserv import Ocserv -from selfprivacy_api.services.pleroma import Pleroma -from selfprivacy_api.services.service import ServiceStatus -from selfprivacy_api.utils import get_dkim_key, get_domain - -router = APIRouter( - prefix="/services", - tags=["services"], - dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) - - -def service_status_to_return_code(status: ServiceStatus): - """Converts service status object to return code for - compatibility with legacy api""" - if status == ServiceStatus.ACTIVE: - return 0 - elif status == ServiceStatus.FAILED: - return 1 - elif status == ServiceStatus.INACTIVE: - return 3 - elif status == ServiceStatus.OFF: - return 4 - else: - return 2 - - -@router.get("/status") -async def get_status(): - """Get the status of the services""" - mail_status = MailServer.get_status() - bitwarden_status = Bitwarden.get_status() - gitea_status = Gitea.get_status() - nextcloud_status = Nextcloud.get_status() - ocserv_stauts = Ocserv.get_status() - pleroma_status = Pleroma.get_status() - - return { - "imap": service_status_to_return_code(mail_status), - "smtp": service_status_to_return_code(mail_status), - "http": 0, - "bitwarden": service_status_to_return_code(bitwarden_status), - "gitea": service_status_to_return_code(gitea_status), - "nextcloud": service_status_to_return_code(nextcloud_status), - "ocserv": service_status_to_return_code(ocserv_stauts), - "pleroma": service_status_to_return_code(pleroma_status), - } - - -@router.post("/bitwarden/enable") -async def enable_bitwarden(): - """Enable Bitwarden""" - Bitwarden.enable() - return { - "status": 0, - "message": "Bitwarden enabled", - } - - -@router.post("/bitwarden/disable") -async def disable_bitwarden(): - """Disable Bitwarden""" - Bitwarden.disable() - return { - "status": 0, - "message": "Bitwarden disabled", - } - - -@router.post("/gitea/enable") -async def enable_gitea(): - """Enable Gitea""" - Gitea.enable() - return { - "status": 0, - "message": "Gitea enabled", - } - - -@router.post("/gitea/disable") -async def disable_gitea(): - """Disable Gitea""" - Gitea.disable() - return { - "status": 0, - "message": "Gitea disabled", - } - - -@router.get("/mailserver/dkim") -async def get_mailserver_dkim(): - """Get the DKIM record for the mailserver""" - domain = get_domain() - - dkim = get_dkim_key(domain, parse=False) - if dkim is None: - raise HTTPException(status_code=404, detail="DKIM record not found") - dkim = base64.b64encode(dkim.encode("utf-8")).decode("utf-8") - return dkim - - -@router.post("/nextcloud/enable") -async def enable_nextcloud(): - """Enable Nextcloud""" - Nextcloud.enable() - return { - "status": 0, - "message": "Nextcloud enabled", - } - - -@router.post("/nextcloud/disable") -async def disable_nextcloud(): - """Disable Nextcloud""" - Nextcloud.disable() - return { - "status": 0, - "message": "Nextcloud disabled", - } - - -@router.post("/ocserv/enable") -async def enable_ocserv(): - """Enable Ocserv""" - Ocserv.enable() - return { - "status": 0, - "message": "Ocserv enabled", - } - - -@router.post("/ocserv/disable") -async def disable_ocserv(): - """Disable Ocserv""" - Ocserv.disable() - return { - "status": 0, - "message": "Ocserv disabled", - } - - -@router.post("/pleroma/enable") -async def enable_pleroma(): - """Enable Pleroma""" - Pleroma.enable() - return { - "status": 0, - "message": "Pleroma enabled", - } - - -@router.post("/pleroma/disable") -async def disable_pleroma(): - """Disable Pleroma""" - Pleroma.disable() - return { - "status": 0, - "message": "Pleroma disabled", - } - - -@router.get("/restic/backup/list") -async def get_restic_backup_list(): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -@router.put("/restic/backup/create") -async def create_restic_backup(): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -@router.get("/restic/backup/status") -async def get_restic_backup_status(): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -@router.get("/restic/backup/reload") -async def reload_restic_backup(): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -class BackupRestoreInput(BaseModel): - backupId: str - - -@router.put("/restic/backup/restore") -async def restore_restic_backup(backup: BackupRestoreInput): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -class BackupConfigInput(BaseModel): - accountId: str - accountKey: str - bucket: str - - -@router.put("/restic/backblaze/config") -async def set_backblaze_config(backup_config: BackupConfigInput): - raise HTTPException( - status_code=410, - detail="This endpoint is deprecated, please use GraphQL API", - ) - - -@router.post("/ssh/enable") -async def rest_enable_ssh(): - """Enable SSH""" - enable_ssh() - return { - "status": 0, - "message": "SSH enabled", - } - - -@router.get("/ssh") -async def rest_get_ssh(): - """Get the SSH configuration""" - settings = get_ssh_settings() - return { - "enable": settings.enable, - "passwordAuthentication": settings.passwordAuthentication, - } - - -class SshConfigInput(BaseModel): - enable: Optional[bool] = None - passwordAuthentication: Optional[bool] = None - - -@router.put("/ssh") -async def rest_set_ssh(ssh_config: SshConfigInput): - """Set the SSH configuration""" - set_ssh_settings(ssh_config.enable, ssh_config.passwordAuthentication) - - return "SSH settings changed" - - -class SshKeyInput(BaseModel): - public_key: str - - -@router.put("/ssh/key/send", status_code=201) -async def rest_send_ssh_key(input: SshKeyInput): - """Send the SSH key""" - try: - create_ssh_key("root", input.public_key) - except KeyAlreadyExists as error: - raise HTTPException(status_code=409, detail="Key already exists") from error - except InvalidPublicKey as error: - raise HTTPException( - status_code=400, - detail="Invalid key type. Only ssh-ed25519 and ssh-rsa are supported", - ) from error - - return { - "status": 0, - "message": "SSH key sent", - } - - -@router.get("/ssh/keys/{username}") -async def rest_get_ssh_keys(username: str): - """Get the SSH keys for a user""" - user = get_user_by_username(username) - if user is None: - raise HTTPException(status_code=404, detail="User not found") - - return user.ssh_keys - - -@router.post("/ssh/keys/{username}", status_code=201) -async def rest_add_ssh_key(username: str, input: SshKeyInput): - try: - create_ssh_key(username, input.public_key) - except KeyAlreadyExists as error: - raise HTTPException(status_code=409, detail="Key already exists") from error - except InvalidPublicKey as error: - raise HTTPException( - status_code=400, - detail="Invalid key type. Only ssh-ed25519 and ssh-rsa are supported", - ) from error - except UserNotFound as error: - raise HTTPException(status_code=404, detail="User not found") from error - - return { - "message": "New SSH key successfully written", - } - - -@router.delete("/ssh/keys/{username}") -async def rest_delete_ssh_key(username: str, input: SshKeyInput): - try: - remove_ssh_key(username, input.public_key) - except KeyNotFound as error: - raise HTTPException(status_code=404, detail="Key not found") from error - except UserNotFound as error: - raise HTTPException(status_code=404, detail="User not found") from error - return {"message": "SSH key deleted"} From 02b10b5078b7abd4c02315c3862918f659f6a8f7 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 15:01:21 +0000 Subject: [PATCH 125/130] feature(auth): remove rest auth code --- selfprivacy_api/app.py | 4 - selfprivacy_api/rest/api_auth.py | 125 ------------------------------- 2 files changed, 129 deletions(-) delete mode 100644 selfprivacy_api/rest/api_auth.py diff --git a/selfprivacy_api/app.py b/selfprivacy_api/app.py index 913305c..64ca85a 100644 --- a/selfprivacy_api/app.py +++ b/selfprivacy_api/app.py @@ -10,9 +10,6 @@ from selfprivacy_api.dependencies import get_api_version from selfprivacy_api.graphql.schema import schema from selfprivacy_api.migrations import run_migrations -from selfprivacy_api.rest import ( - api_auth, -) app = FastAPI() @@ -29,7 +26,6 @@ app.add_middleware( ) -app.include_router(api_auth.router) app.include_router(graphql_app, prefix="/graphql") diff --git a/selfprivacy_api/rest/api_auth.py b/selfprivacy_api/rest/api_auth.py deleted file mode 100644 index 275dac3..0000000 --- a/selfprivacy_api/rest/api_auth.py +++ /dev/null @@ -1,125 +0,0 @@ -from datetime import datetime -from typing import Optional -from fastapi import APIRouter, Depends, HTTPException -from pydantic import BaseModel -from selfprivacy_api.actions.api_tokens import ( - CannotDeleteCallerException, - InvalidExpirationDate, - InvalidUsesLeft, - NotFoundException, - delete_api_token, - refresh_api_token, - get_api_recovery_token_status, - get_api_tokens_with_caller_flag, - get_new_api_recovery_key, - use_mnemonic_recovery_token, - delete_new_device_auth_token, - get_new_device_auth_token, - use_new_device_auth_token, -) - -from selfprivacy_api.dependencies import TokenHeader, get_token_header - - -router = APIRouter( - prefix="/auth", - tags=["auth"], - responses={404: {"description": "Not found"}}, -) - - -@router.get("/tokens") -async def rest_get_tokens(auth_token: TokenHeader = Depends(get_token_header)): - """Get the tokens info""" - return get_api_tokens_with_caller_flag(auth_token.token) - - -class DeleteTokenInput(BaseModel): - """Delete token input""" - - token_name: str - - -@router.delete("/tokens") -async def rest_delete_tokens( - token: DeleteTokenInput, auth_token: TokenHeader = Depends(get_token_header) -): - """Delete the tokens""" - try: - delete_api_token(auth_token.token, token.token_name) - except NotFoundException: - raise HTTPException(status_code=404, detail="Token not found") - except CannotDeleteCallerException: - raise HTTPException(status_code=400, detail="Cannot delete caller's token") - return {"message": "Token deleted"} - - -@router.post("/tokens") -async def rest_refresh_token(auth_token: TokenHeader = Depends(get_token_header)): - """Refresh the token""" - try: - new_token = refresh_api_token(auth_token.token) - except NotFoundException: - raise HTTPException(status_code=404, detail="Token not found") - return {"token": new_token} - - -@router.get("/recovery_token") -async def rest_get_recovery_token_status( - auth_token: TokenHeader = Depends(get_token_header), -): - return get_api_recovery_token_status() - - -class CreateRecoveryTokenInput(BaseModel): - expiration: Optional[datetime] = None - uses: Optional[int] = None - - -@router.post("/recovery_token") -async def rest_create_recovery_token( - limits: CreateRecoveryTokenInput = CreateRecoveryTokenInput(), - auth_token: TokenHeader = Depends(get_token_header), -): - try: - token = get_new_api_recovery_key(limits.expiration, limits.uses) - except InvalidExpirationDate as e: - raise HTTPException(status_code=400, detail=str(e)) - except InvalidUsesLeft as e: - raise HTTPException(status_code=400, detail=str(e)) - return {"token": token} - - -class UseTokenInput(BaseModel): - token: str - device: str - - -@router.post("/recovery_token/use") -async def rest_use_recovery_token(input: UseTokenInput): - token = use_mnemonic_recovery_token(input.token, input.device) - if token is None: - raise HTTPException(status_code=404, detail="Token not found") - return {"token": token} - - -@router.post("/new_device") -async def rest_new_device(auth_token: TokenHeader = Depends(get_token_header)): - token = get_new_device_auth_token() - return {"token": token} - - -@router.delete("/new_device") -async def rest_delete_new_device_token( - auth_token: TokenHeader = Depends(get_token_header), -): - delete_new_device_auth_token() - return {"token": None} - - -@router.post("/new_device/authorize") -async def rest_new_device_authorize(input: UseTokenInput): - token = use_new_device_auth_token(input.token, input.device) - if token is None: - raise HTTPException(status_code=404, detail="Token not found") - return {"message": "Device authorized", "token": token} From 3080f5a18b1a22b4457fd1f029d419a3d98aa7da Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Fri, 29 Dec 2023 15:19:17 +0000 Subject: [PATCH 126/130] feature(rest): remove rest --- selfprivacy_api/rest/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 selfprivacy_api/rest/__init__.py diff --git a/selfprivacy_api/rest/__init__.py b/selfprivacy_api/rest/__init__.py deleted file mode 100644 index e69de29..0000000 From b8d02231cf65b813878c3cb3e313a0a922bcb9fc Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 3 Jan 2024 15:46:48 +0000 Subject: [PATCH 127/130] fix(services): handle the async nature of moving. --- .../graphql/mutations/services_mutations.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/selfprivacy_api/graphql/mutations/services_mutations.py b/selfprivacy_api/graphql/mutations/services_mutations.py index ad3b1b9..9bacf66 100644 --- a/selfprivacy_api/graphql/mutations/services_mutations.py +++ b/selfprivacy_api/graphql/mutations/services_mutations.py @@ -160,6 +160,8 @@ class ServicesMutations: message="Service not found.", code=404, ) + # TODO: make serviceImmovable and BlockdeviceNotFound exceptions + # in the move_to_volume() function and handle them here if not service.is_movable(): return ServiceJobMutationReturn( success=False, @@ -176,7 +178,15 @@ class ServicesMutations: service=service_to_graphql_service(service), ) job = service.move_to_volume(volume) - if job.status == JobStatus.FINISHED: + if job.status in [JobStatus.CREATED, JobStatus.RUNNING]: + return ServiceJobMutationReturn( + success=True, + message="Started moving the service.", + code=200, + service=service_to_graphql_service(service), + job=job_to_api_job(job), + ) + elif job.status == JobStatus.FINISHED: return ServiceJobMutationReturn( success=True, message="Service moved.", From 8e551a8fe0d9dcb093c25c9814a52e80fb25ee2e Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 3 Jan 2024 17:47:13 +0000 Subject: [PATCH 128/130] refactor(services): use generic code for enabling and disabling --- .../services/bitwarden/__init__.py | 21 ------------------- .../services/nextcloud/__init__.py | 21 ------------------- selfprivacy_api/services/ocserv/__init__.py | 19 ----------------- selfprivacy_api/services/pleroma/__init__.py | 19 ----------------- 4 files changed, 80 deletions(-) diff --git a/selfprivacy_api/services/bitwarden/__init__.py b/selfprivacy_api/services/bitwarden/__init__.py index 0d1dfdc..1590729 100644 --- a/selfprivacy_api/services/bitwarden/__init__.py +++ b/selfprivacy_api/services/bitwarden/__init__.py @@ -58,11 +58,6 @@ class Bitwarden(Service): def get_backup_description() -> str: return "Password database, encryption certificate and attachments." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("bitwarden", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: """ @@ -76,22 +71,6 @@ class Bitwarden(Service): """ return get_service_status("vaultwarden.service") - @staticmethod - def enable(): - """Enable Bitwarden service.""" - with WriteUserData() as user_data: - if "bitwarden" not in user_data: - user_data["bitwarden"] = {} - user_data["bitwarden"]["enable"] = True - - @staticmethod - def disable(): - """Disable Bitwarden service.""" - with WriteUserData() as user_data: - if "bitwarden" not in user_data: - user_data["bitwarden"] = {} - user_data["bitwarden"]["enable"] = False - @staticmethod def stop(): subprocess.run(["systemctl", "stop", "vaultwarden.service"]) diff --git a/selfprivacy_api/services/nextcloud/__init__.py b/selfprivacy_api/services/nextcloud/__init__.py index 1703478..0da6dd9 100644 --- a/selfprivacy_api/services/nextcloud/__init__.py +++ b/selfprivacy_api/services/nextcloud/__init__.py @@ -53,11 +53,6 @@ class Nextcloud(Service): def get_backup_description() -> str: return "All the files and other data stored in Nextcloud." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("nextcloud", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: """ @@ -71,22 +66,6 @@ class Nextcloud(Service): """ return get_service_status("phpfpm-nextcloud.service") - @staticmethod - def enable(): - """Enable Nextcloud service.""" - with WriteUserData() as user_data: - if "nextcloud" not in user_data: - user_data["nextcloud"] = {} - user_data["nextcloud"]["enable"] = True - - @staticmethod - def disable(): - """Disable Nextcloud service.""" - with WriteUserData() as user_data: - if "nextcloud" not in user_data: - user_data["nextcloud"] = {} - user_data["nextcloud"]["enable"] = False - @staticmethod def stop(): """Stop Nextcloud service.""" diff --git a/selfprivacy_api/services/ocserv/__init__.py b/selfprivacy_api/services/ocserv/__init__.py index d9d59a0..a28358d 100644 --- a/selfprivacy_api/services/ocserv/__init__.py +++ b/selfprivacy_api/services/ocserv/__init__.py @@ -51,29 +51,10 @@ class Ocserv(Service): def get_backup_description() -> str: return "Nothing to backup." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("ocserv", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: return get_service_status("ocserv.service") - @staticmethod - def enable(): - with WriteUserData() as user_data: - if "ocserv" not in user_data: - user_data["ocserv"] = {} - user_data["ocserv"]["enable"] = True - - @staticmethod - def disable(): - with WriteUserData() as user_data: - if "ocserv" not in user_data: - user_data["ocserv"] = {} - user_data["ocserv"]["enable"] = False - @staticmethod def stop(): subprocess.run(["systemctl", "stop", "ocserv.service"], check=False) diff --git a/selfprivacy_api/services/pleroma/__init__.py b/selfprivacy_api/services/pleroma/__init__.py index b2540d8..1aae50e 100644 --- a/selfprivacy_api/services/pleroma/__init__.py +++ b/selfprivacy_api/services/pleroma/__init__.py @@ -50,29 +50,10 @@ class Pleroma(Service): def get_backup_description() -> str: return "Your Pleroma accounts, posts and media." - @staticmethod - def is_enabled() -> bool: - with ReadUserData() as user_data: - return user_data.get("pleroma", {}).get("enable", False) - @staticmethod def get_status() -> ServiceStatus: return get_service_status("pleroma.service") - @staticmethod - def enable(): - with WriteUserData() as user_data: - if "pleroma" not in user_data: - user_data["pleroma"] = {} - user_data["pleroma"]["enable"] = True - - @staticmethod - def disable(): - with WriteUserData() as user_data: - if "pleroma" not in user_data: - user_data["pleroma"] = {} - user_data["pleroma"]["enable"] = False - @staticmethod def stop(): subprocess.run(["systemctl", "stop", "pleroma.service"]) From 8e21e6d378c1eb02083dd06ade6c1dd2fd63241b Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 3 Jan 2024 19:19:29 +0000 Subject: [PATCH 129/130] feature(services): introduce 'modules' field in userdata and group services settings there --- selfprivacy_api/migrations/__init__.py | 2 + selfprivacy_api/migrations/modules_in_json.py | 50 +++++++++++++++ selfprivacy_api/services/service.py | 25 ++++---- tests/data/turned_on.json | 22 ++++--- tests/test_graphql/test_system/turned_on.json | 40 ++++++------ tests/test_migrations.py | 60 ++++++++++++++++++ tests/test_migrations/strays.json | 23 +++++++ tests/test_services.py | 61 +++++++++++++------ 8 files changed, 222 insertions(+), 61 deletions(-) create mode 100644 selfprivacy_api/migrations/modules_in_json.py create mode 100644 tests/test_migrations.py create mode 100644 tests/test_migrations/strays.json diff --git a/selfprivacy_api/migrations/__init__.py b/selfprivacy_api/migrations/__init__.py index 4aa932c..f2d1f0d 100644 --- a/selfprivacy_api/migrations/__init__.py +++ b/selfprivacy_api/migrations/__init__.py @@ -19,6 +19,7 @@ from selfprivacy_api.migrations.migrate_to_selfprivacy_channel import ( ) from selfprivacy_api.migrations.mount_volume import MountVolume from selfprivacy_api.migrations.providers import CreateProviderFields +from selfprivacy_api.migrations.modules_in_json import CreateModulesField from selfprivacy_api.migrations.prepare_for_nixos_2211 import ( MigrateToSelfprivacyChannelFrom2205, ) @@ -37,6 +38,7 @@ migrations = [ MigrateToSelfprivacyChannelFrom2205(), MigrateToSelfprivacyChannelFrom2211(), LoadTokensToRedis(), + CreateModulesField(), ] diff --git a/selfprivacy_api/migrations/modules_in_json.py b/selfprivacy_api/migrations/modules_in_json.py new file mode 100644 index 0000000..64ba7d3 --- /dev/null +++ b/selfprivacy_api/migrations/modules_in_json.py @@ -0,0 +1,50 @@ +from selfprivacy_api.migrations.migration import Migration +from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.services import get_all_services + + +def migrate_services_to_modules(): + with WriteUserData() as userdata: + if "modules" not in userdata.keys(): + userdata["modules"] = {} + + for service in get_all_services(): + name = service.get_id() + if name in userdata.keys(): + field_content = userdata[name] + userdata["modules"][name] = field_content + del userdata[name] + + +# If you ever want to get rid of modules field you will need to get rid of this migration +class CreateModulesField(Migration): + """introduce 'modules' (services) into userdata""" + + def get_migration_name(self): + return "modules_in_json" + + def get_migration_description(self): + return "Group service settings into a 'modules' field in userdata.json" + + def is_migration_needed(self) -> bool: + try: + with ReadUserData() as userdata: + for service in get_all_services(): + if service.get_id() in userdata.keys(): + return True + + if "modules" not in userdata.keys(): + return True + return False + except Exception as e: + print(e) + return False + + def migrate(self): + # Write info about providers to userdata.json + try: + migrate_services_to_modules() + print("Done") + except Exception as e: + print(e) + print("Error migrating service fields") diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index a53c028..b44f3a9 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -136,7 +136,7 @@ class Service(ABC): """ name = cls.get_id() with ReadUserData() as user_data: - return user_data.get(name, {}).get("enable", False) + return user_data.get("modules", {}).get(name, {}).get("enable", False) @staticmethod @abstractmethod @@ -144,24 +144,25 @@ class Service(ABC): """The status of the service, reported by systemd.""" pass - # But they do not really enable? + @classmethod + def _set_enable(cls, enable: bool): + name = cls.get_id() + with WriteUserData() as user_data: + if "modules" not in user_data: + user_data["modules"] = {} + if name not in user_data["modules"]: + user_data["modules"][name] = {} + user_data["modules"][name]["enable"] = enable + @classmethod def enable(cls): """Enable the service. Usually this means enabling systemd unit.""" - name = cls.get_id() - with WriteUserData() as user_data: - if name not in user_data: - user_data[name] = {} - user_data[name]["enable"] = True + cls._set_enable(True) @classmethod def disable(cls): """Disable the service. Usually this means disabling systemd unit.""" - name = cls.get_id() - with WriteUserData() as user_data: - if name not in user_data: - user_data[name] = {} - user_data[name]["enable"] = False + cls._set_enable(False) @staticmethod @abstractmethod diff --git a/tests/data/turned_on.json b/tests/data/turned_on.json index 2c98e77..1b6219d 100644 --- a/tests/data/turned_on.json +++ b/tests/data/turned_on.json @@ -1,15 +1,9 @@ { "api": {"token": "TEST_TOKEN", "enableSwagger": false}, - "bitwarden": {"enable": true}, "databasePassword": "PASSWORD", "domain": "test.tld", "hashedMasterPassword": "HASHED_PASSWORD", "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, "resticPassword": "PASS", "ssh": { "enable": true, @@ -17,16 +11,24 @@ "rootKeys": ["ssh-ed25519 KEY test@pc"] }, "username": "tester", - "gitea": {"enable": true}, - "ocserv": {"enable": true}, - "pleroma": {"enable": true}, - "jitsi": {"enable": true}, "autoUpgrade": {"enable": true, "allowReboot": true}, "useBinds": true, "timezone": "Europe/Moscow", "sshKeys": ["ssh-rsa KEY test@pc"], "dns": {"provider": "CLOUDFLARE", "apiKey": "TOKEN"}, "server": {"provider": "HETZNER"}, + "modules": { + "bitwarden": {"enable": true}, + "gitea": {"enable": true}, + "ocserv": {"enable": true}, + "pleroma": {"enable": true}, + "jitsi": {"enable": true}, + "nextcloud": { + "adminPassword": "ADMIN", + "databasePassword": "ADMIN", + "enable": true + } + }, "backup": { "provider": "BACKBLAZE", "accountId": "ID", diff --git a/tests/test_graphql/test_system/turned_on.json b/tests/test_graphql/test_system/turned_on.json index c6b758b..240c6c9 100644 --- a/tests/test_graphql/test_system/turned_on.json +++ b/tests/test_graphql/test_system/turned_on.json @@ -3,18 +3,10 @@ "token": "TEST_TOKEN", "enableSwagger": false }, - "bitwarden": { - "enable": true - }, "databasePassword": "PASSWORD", "domain": "test.tld", "hashedMasterPassword": "HASHED_PASSWORD", "hostname": "test-instance", - "nextcloud": { - "adminPassword": "ADMIN", - "databasePassword": "ADMIN", - "enable": true - }, "resticPassword": "PASS", "ssh": { "enable": true, @@ -24,17 +16,27 @@ ] }, "username": "tester", - "gitea": { - "enable": true - }, - "ocserv": { - "enable": true - }, - "pleroma": { - "enable": true - }, - "jitsi": { - "enable": true + "modules": { + "gitea": { + "enable": true + }, + "ocserv": { + "enable": true + }, + "pleroma": { + "enable": true + }, + "jitsi": { + "enable": true + }, + "nextcloud": { + "adminPassword": "ADMIN", + "databasePassword": "ADMIN", + "enable": true + }, + "bitwarden": { + "enable": true + } }, "autoUpgrade": { "enable": true, diff --git a/tests/test_migrations.py b/tests/test_migrations.py new file mode 100644 index 0000000..55f311a --- /dev/null +++ b/tests/test_migrations.py @@ -0,0 +1,60 @@ +import pytest + +from selfprivacy_api.migrations.modules_in_json import CreateModulesField +from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.services import get_all_services + + +@pytest.fixture() +def stray_services(mocker, datadir): + mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "strays.json") + return datadir + + +@pytest.fixture() +def empty_json(generic_userdata): + with WriteUserData() as data: + data.clear() + + with ReadUserData() as data: + assert len(data.keys()) == 0 + + return + + +def test_modules_empty_json(empty_json): + with ReadUserData() as data: + assert "modules" not in data.keys() + + assert CreateModulesField().is_migration_needed() + + CreateModulesField().migrate() + assert not CreateModulesField().is_migration_needed() + + with ReadUserData() as data: + assert "modules" in data.keys() + + +@pytest.mark.parametrize("modules_field", [True, False]) +def test_modules_stray_services(modules_field, stray_services): + if not modules_field: + with WriteUserData() as data: + del data["modules"] + assert CreateModulesField().is_migration_needed() + + CreateModulesField().migrate() + + for service in get_all_services(): + # assumes we do not tolerate previous format + assert service.is_enabled() + if service.get_id() == "email": + continue + with ReadUserData() as data: + assert service.get_id() in data["modules"].keys() + assert service.get_id() not in data.keys() + + assert not CreateModulesField().is_migration_needed() + + +def test_modules_no_migration_on_generic_data(generic_userdata): + assert not CreateModulesField().is_migration_needed() diff --git a/tests/test_migrations/strays.json b/tests/test_migrations/strays.json new file mode 100644 index 0000000..ee81350 --- /dev/null +++ b/tests/test_migrations/strays.json @@ -0,0 +1,23 @@ +{ + "bitwarden": { + "enable": true + }, + "nextcloud": { + "adminPassword": "ADMIN", + "databasePassword": "ADMIN", + "enable": true + }, + "gitea": { + "enable": true + }, + "ocserv": { + "enable": true + }, + "pleroma": { + "enable": true + }, + "jitsi": { + "enable": true + }, + "modules": {} +} diff --git a/tests/test_services.py b/tests/test_services.py index f3d6adc..65b4dc9 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -7,6 +7,8 @@ from pytest import raises from selfprivacy_api.utils import ReadUserData, WriteUserData from selfprivacy_api.utils.waitloop import wait_until_true +import selfprivacy_api.services as services_module + from selfprivacy_api.services.bitwarden import Bitwarden from selfprivacy_api.services.pleroma import Pleroma from selfprivacy_api.services.mailserver import MailServer @@ -15,6 +17,7 @@ from selfprivacy_api.services.generic_service_mover import FolderMoveNames from selfprivacy_api.services.test_service import DummyService from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService +from selfprivacy_api.services import get_enabled_services from tests.test_dkim import domain_file, dkim_file, no_dkim_file @@ -95,35 +98,49 @@ def test_foldermoves_from_ownedpaths(): def test_enabling_disabling_reads_json(dummy_service: DummyService): with WriteUserData() as data: - data[dummy_service.get_id()]["enable"] = False + data["modules"][dummy_service.get_id()]["enable"] = False assert dummy_service.is_enabled() is False with WriteUserData() as data: - data[dummy_service.get_id()]["enable"] = True + data["modules"][dummy_service.get_id()]["enable"] = True assert dummy_service.is_enabled() is True -@pytest.fixture(params=["normally_enabled", "deleted_attribute", "service_not_in_json"]) +# A helper to test undefined states. Used in fixtures below +def undefine_service_enabled_status(param, dummy_service): + if param == "deleted_attribute": + with WriteUserData() as data: + del data["modules"][dummy_service.get_id()]["enable"] + if param == "service_not_in_json": + with WriteUserData() as data: + del data["modules"][dummy_service.get_id()] + if param == "modules_not_in_json": + with WriteUserData() as data: + del data["modules"] + + +# May be defined or not +@pytest.fixture( + params=[ + "normally_enabled", + "deleted_attribute", + "service_not_in_json", + "modules_not_in_json", + ] +) def possibly_dubiously_enabled_service( dummy_service: DummyService, request ) -> DummyService: - if request.param == "deleted_attribute": - with WriteUserData() as data: - del data[dummy_service.get_id()]["enable"] - if request.param == "service_not_in_json": - with WriteUserData() as data: - del data[dummy_service.get_id()] + if request.param != "normally_enabled": + undefine_service_enabled_status(request.param, dummy_service) return dummy_service -# Yeah, idk yet how to dry it. -@pytest.fixture(params=["deleted_attribute", "service_not_in_json"]) +# Strictly UNdefined +@pytest.fixture( + params=["deleted_attribute", "service_not_in_json", "modules_not_in_json"] +) def undefined_enabledness_service(dummy_service: DummyService, request) -> DummyService: - if request.param == "deleted_attribute": - with WriteUserData() as data: - del data[dummy_service.get_id()]["enable"] - if request.param == "service_not_in_json": - with WriteUserData() as data: - del data[dummy_service.get_id()] + undefine_service_enabled_status(request.param, dummy_service) return dummy_service @@ -141,13 +158,13 @@ def test_enabling_disabling_writes_json( dummy_service.disable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is False + assert data["modules"][dummy_service.get_id()]["enable"] is False dummy_service.enable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is True + assert data["modules"][dummy_service.get_id()]["enable"] is True dummy_service.disable() with ReadUserData() as data: - assert data[dummy_service.get_id()]["enable"] is False + assert data["modules"][dummy_service.get_id()]["enable"] is False # more detailed testing of this is in test_graphql/test_system.py @@ -158,3 +175,7 @@ def test_mailserver_with_dkim_returns_some_dns(dkim_file): def test_mailserver_with_no_dkim_returns_no_dns(no_dkim_file): assert MailServer().get_dns_records() == [] + + +def test_services_enabled_by_default(generic_userdata): + assert set(get_enabled_services()) == set(services_module.services) From 2b21df9ad35855803ec227c56e88a8c962e97b39 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 3 Jan 2024 19:30:27 +0000 Subject: [PATCH 130/130] chore(version): bump version to 3.0, no Rest API --- selfprivacy_api/dependencies.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfprivacy_api/dependencies.py b/selfprivacy_api/dependencies.py index 05c9bdc..1dfc0a9 100644 --- a/selfprivacy_api/dependencies.py +++ b/selfprivacy_api/dependencies.py @@ -27,4 +27,4 @@ async def get_token_header( def get_api_version() -> str: """Get API version""" - return "2.4.3" + return "3.0.0" diff --git a/setup.py b/setup.py index 93637ff..36aa68e 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="selfprivacy_api", - version="2.4.3", + version="3.0.0", packages=find_packages(), scripts=[ "selfprivacy_api/app.py",