diff --git a/.flake8 b/.flake8 index 79ca792..4fabf78 100644 --- a/.flake8 +++ b/.flake8 @@ -6,5 +6,7 @@ max-complexity = 17 max-line-length = 120 application-import-names = scigateway_auth,test import-order-style = google -per-file-ignores = test/testutils.py:S105,B950,test/test_authenticationHandler.py:S105 +per-file-ignores = + test/**.py:S101 + test/mock_data.py:S105 enable-extensions=G diff --git a/test/keys/jwt-key b/test/keys/jwt-key index 2a48c1e..02c7366 100644 --- a/test/keys/jwt-key +++ b/test/keys/jwt-key @@ -1,51 +1,27 @@ ------BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY----- +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEApoPHYHDIhykwoz+Y5JabApfNViOTaC+SfiMv3C6S+RButKyfGBvO +IFtFJFKyY8IE93LWffK/6tf3a7yj4Y6fTrgaEYLDAfcuOQJTwdpXeVanroxAq0L380LWbV +PtZyYPg5QkoOyuvREqp0u3jZVPeUkudfUPm04z62LExn2buFjmcSpiKupPfEmNwqMTlyJb +MPlrJwrFKwS5DULXdCsHQAWTi0VkOimIdSX+Gn6kv5//vkkwtnjm6I2qmKIdJw4SDSKazy +YfDsRZsa43MyrdHMczP9LJaaOuqvksfSW/JMAuwQ50+viOvuIcbgn3PPes4WpIsXzT8tck +Hy+YloGd0QAAA7j0hCM+9IQjPgAAAAdzc2gtcnNhAAABAQCmg8dgcMiHKTCjP5jklpsCl8 +1WI5NoL5J+Iy/cLpL5EG60rJ8YG84gW0UkUrJjwgT3ctZ98r/q1/drvKPhjp9OuBoRgsMB +9y45AlPB2ld5VqeujECrQvfzQtZtU+1nJg+DlCSg7K69ESqnS7eNlU95SS519Q+bTjPrYs +TGfZu4WOZxKmIq6k98SY3CoxOXIlsw+WsnCsUrBLkNQtd0KwdABZOLRWQ6KYh1Jf4afqS/ +n/++STC2eObojaqYoh0nDhINIprPJh8OxFmxrjczKt0cxzM/0slpo66q+Sx9Jb8kwC7BDn +T6+I6+4hxuCfc896zhakixfNPy1yQfL5iWgZ3RAAAAAwEAAQAAAQAizLqf0KJQAQ89pt5s +s3782T3cKT420W5jtzXa5c7oh4MjdkofjzwSbhCyqSfICnXgQxtJojByw6TdWsTDMHvhxz +8HdtULdZ2u42oewuNyI+aSZ5RiVnIcYFUQ7y/X1U/7tzu6eAyCzfIkmYHocJJ17hHsLAWR +dxQJtxAZzYl8Sp29xTb7F0tcFlm4muEU/opjte92kxzH3I/ksoe7MNNVhUcIf8FAxxEe8F +k1lrodMOYub3I636h6H9epZvleXaXMTFSXOmyNt2NzVGHsryddUQCsl1d76GTc/K4uEDnx +O8PdPl98LtyNRumHhXiFngV7x6IelglauoRZ2BImIZfTAAAAgQC3sX1llfu3G4lny+PJAS +tQuGbag7w7YFjfCw7E10M++ZtJOlZPl8V58djExh9orvUudy2VwGD1uz7H5//Bd91RRhnW +QHjR7w6TpZSLexUNy7MaHQDC9ANLeydNAEDYy1OUs7yEVNC1ittfOdUTpRHx25wLmPK/Pm +/5jcCqGDEiAwAAAIEAzz5FcjK4J47LlV395DY2YswcZ/I6Uv9MykjdPBzTDjy/Y1TNR3Q0 +DD08M+0mkBMF3Jn486YUed5WGVpiyNsLGoFI4GlxbaTd2fnMNZYtWnojZNuRS3/z4ee+Y1 +o7S7N2oR1CSC2xF7YzOTeJqxgUfv8H78qWzGCzN4SW4KDVpH8AAACBAM2wiQ66odzzi0Na +lnGcuJTxv9LL8o9BGpQaIi5BxfV5RvG+Gozj27/veio2UR+7PwWATGAya9Irztig5Tvq6X +qW2QK2GFtlP1fPIy+jayGtQj/MBKICeR+3D6ARYrB/Q6UnH/GBASLUK/YcUxVU1CsmWfnC +2ahFM50fl9l/KFWvAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/test/keys/jwt-key.pub b/test/keys/jwt-key.pub index 9ca3df5..9abeeba 100644 --- a/test/keys/jwt-key.pub +++ b/test/keys/jwt-key.pub @@ -1 +1 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAgEAsjRzJiBUs6ywQ0e6qW1pBc6Zi49VSPvknqyJ49my+zicWg2Pq+vsfAKdbbZqd5jZ/bWfAKlSr8mnQDsoXrZlwHzitazouh2X62+06+orWWo8gRq7iO2bajSpsAp2HQQdNWaz8FLboaF01bR5LJeeDPjIVef98mdIU/suNS9UJv/zROMT0X2hjtiX9R/6fxNCUDrGKz6p47x7iRtjHiXg0nD4oseoc8hP585v23PEbPGCfL2Tnh9PaGCov/TOfG/dfFjFozmHDW6rxeM813CFz0YDKynyeWYLb+epwN6KT803gFVoSoINaZ5n6p2oh2OQfdUSSJUdy0/FcQ03a55ZHmaz3VFuckEanNmDv/Ou2nrZRmYq5/4BlYAo4FWUwZM4/eM94Dy9d32zrQpZV1l+o6JieR6sIL89mhKCdt7UvcVG8XD30Ob7tqVHSD6QxDqqUXkB1K+dRr/mUTIto7F1bkwP/HFF4AQPwM/d90pVxSSIpQokkGV570pu3AN+Z480vw/lQfaQOY2atOCjzEpWjkqNNCFQfd4o574HInNkMbcroH6vxU1vMuQ7/mPNWGgQxn364Ku/buO5ICl3YiLTV6Pz4EXmb/7rMTffqsX1guzHcilwbugnWDlm19pkdAOGsUTnB4hOSTmtgPuoHXCCStsL4EkHW0ucrrSS3qddPks= +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCmg8dgcMiHKTCjP5jklpsCl81WI5NoL5J+Iy/cLpL5EG60rJ8YG84gW0UkUrJjwgT3ctZ98r/q1/drvKPhjp9OuBoRgsMB9y45AlPB2ld5VqeujECrQvfzQtZtU+1nJg+DlCSg7K69ESqnS7eNlU95SS519Q+bTjPrYsTGfZu4WOZxKmIq6k98SY3CoxOXIlsw+WsnCsUrBLkNQtd0KwdABZOLRWQ6KYh1Jf4afqS/n/++STC2eObojaqYoh0nDhINIprPJh8OxFmxrjczKt0cxzM/0slpo66q+Sx9Jb8kwC7BDnT6+I6+4hxuCfc896zhakixfNPy1yQfL5iWgZ3R diff --git a/test/mock_data.py b/test/mock_data.py new file mode 100644 index 0000000..9a6b793 --- /dev/null +++ b/test/mock_data.py @@ -0,0 +1,59 @@ +""" +Mock data for use in tests. +""" + +VALID_ACCESS_TOKEN_NON_ADMIN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0LXNlc3Npb24taWQiLCJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiL" + "CJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6MjUzNDAyMzAwNzk5fQ.dwjtuXX76o4tE6db6bjef5CphiS3hrHPRYW7nuBz_nzjgqNLdOl6BobYbWZQ" + "AXhDo8JzDRhNxaIvbvk-NSf72utxqo6WeMOQSNZtdOuqewpVTFQoPe6dLorgpVdKepIfT8AAKKtXyaKtVxYPfEzJMzK8eZM3pK4yWmUjOgCixoZ322" + "_tSaZUUbqkEV7885ohSL_L5NxoJ7n5nV5n5gFoPFZUSvZzk_7NLZHPSXjAgTrprsvBsTH1ivi-yos4up4lNZ2pstOgPP0sYbQZoMJubB0dQcKefLEg" + "3JSu-laX_4l9geVHCAsYOFHjEPzH0X0Q4nDY5BO976PEEJcXAtfNKw" +) + +EXPIRED_ACCESS_TOKEN_NON_ADMIN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0LXNlc3Npb24taWQiLCJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiL" + "CJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6LTYyMTM1NTk2ODAwfQ.YcWGenSm0noq0TAaSZhb1ZVYFxuk-aXIX_XlPh-FoxsBxTZy1dnh-TKLRzom" + "wdzP0bMbcU3LZKRMYScpcRSSnANwzCFZLhPId0OBLOiyoBMXtK2xsMsYeapcKPbhvqYxDLGMB--n1uniKGUGnGTI3SwJhBup_6SLnjRMmw5fze_sb4" + "w1G88o8vcLK_Ii2D9ZauZTI-LVcy-C9z8Y6sGXxfGIceUlb8RtFz1RJwVT87wYvtqN72fzMVgjOv_AvYj5HjD2wHKpb50-0lxzonrc0sdQe_yAzjoa" + "b6RBeydd5JpP7zb-NTJunsX8vbtRq4fpjUTqiCsJGN8Q0g_2Soq7Ew" +) + +EXPECTED_ACCESS_TOKEN_NON_ADMIN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0LXNlc3Npb24taWQiLCJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiL" + "CJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6MTcwNTQ4NzQwMH0.jjjHhjXuixjJsqlfRI8vNR76yHIwEB_e1iwTOvLLHkzeb03HPQDpBOr4UtZCTuH" + "xy38hwKkYz9g-gdsyqn7_WuPkikmMm7Rwsb8G4DT10FnzxWvgBfAzR_ubc0jTDvuaaCj3GfNssnMzBrRVUcwyNOGvsz2N7wjdalK-anK-SJToxmThF" + "xmButHZl_x8NTEC5_KEnbf4roU0v5ZQ4gNfAn-2yTHHRyqrnXlVq9nse_85NC1Wz_zliBpQfN1yafPKae2HobPYeW_PhV4y3yLVJ8zlRED6y577XIm" + "qibAEQlGFyfhKzQZIVOBGUkAjvKiL_zUQv4RliHdd4INDt24LeA" +) + +EXPECTED_ACCESS_TOKEN_ADMIN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0LXNlc3Npb24taWQiLCJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiL" + "CJ1c2VySXNBZG1pbiI6dHJ1ZSwiZXhwIjoxNzA1NDg3NDAwfQ.MJRiZgFiD3X7Xb7Bcz5girfsTDXU3gtd06RDx0X22nCsJi6YCxD35mcVr4Bv6hdM" + "Qb81qkLazvi9GiNEze9Q1nKNfuyyXi_D6-9lpwKqYFkqfrE52NiZwin3yrT0gAVRHlWSOCNV4oUSeaTM8MYFt75rEU99LWptfzpEjsierDAB7lMgb6" + "HX6XtCa5MtZ1z7dBN4b5jVr4STnYIgpOazpV-sArpN5jB21eUNODr8BxCD287eHixEbur0yrzpJ-vbGCJdH2m_wdpbVJdbD98X-ydc_Mmz6LSukh1n" + "rNj77RuynVMceRF98fGVMb7OQ2QJZNhc8ZizqAxOjxAeHehG5A" +) + +VALID_REFRESH_TOKEN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiLCJleHAiOjI1MzQwMjMwMDc5OX0.LqLipT0jYgjro" + "u0BJPjNyvmNRPY_G_ltAIPj65ClYglnhJLX4qhTaHI71UYDJrxT4W1j4xIN0itcZ7kNG6SdDw_jn_PO4gOnWsDEMDB9IBWS17-mFYNwyP2eE8rGoHy" + "jwaWYzqyigPnxrsDjPJ9FTJ6anN36ktZ6t0Zj-CooWH0lDIvkd5j7-txziki56N1zqnIS3Hp_CMPgfjXQq4vmoBRIw29Tkbea-czMvAeQWpOhazj4R" + "hW4D-A2Dh1rKqZAXtvly6o05eyACv4-DAR0CuZFB3Bb1SZ_iXbFay6a6yD_E0ScIOMR2pplf6N9eWatDcfJ6UvqPjwxSMT50sFGYQ" +) + +EXPIRED_REFRESH_TOKEN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiLCJleHAiOi02MjEzNTU5NjgwMH0.kzCXNo_n3aZHF" + "jgZ7SGK3chBCD9-qKJmFUk3RzoO1DlolFKiHm82pIO82snNnmJCy9MLJvnpX43UHolo_MTubR19lwBCoc3c0kOaWv5KSmYIsPPPlHuwyetA8hPJ_Q1" + "G_9zfpiDSKsQhMbbGjp5FU1H0Io9Z_asV-F9sU_3rdPkWOV1IU8X2pJCbxpDtVaiySEin-MT_Pq3vyf145d__yy8WiruqVx1MvQfdme_UT8y5E73TW" + "a0LgrAUq11IYGirMgWYjtvZstXdkCJHKmF4VMruxZ6u_EeTdO513Btv-ab-V4G03_AHGBCv_gIcVADwxgr3FA6GRahtXW0aPOY-eA" +) + +EXPECTED_REFRESH_TOKEN = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QtdXNlcm5hbWUiLCJleHAiOjE3MDYwOTA0MDB9.WStj05wJxI0ogFAS" + "C-n2GimmKuUBWNXFB-ITj_uGztHaaTM-CVs74sB_GFFJuXNoCiaLDdDdyvvYUsjXSonvtaONsBIOsq0RUAyMMtt7-iYdT8eiWQANvb_w1KZnfuwBmP" + "f1wux2PCvCYrybuD7_uuB_1jOLbOr71sYxacYQQmPeRj-YZ6SXkbnHwEQ4CjhH0ygaQwB4IEjym34j6jvX4YunjCrGI6RkHOsMWHH2FNDPFndkdO2N" + "QN5FSaV_qDCx02M9P2m_hLj9XaojNcvbYDSBgCkSmM59g8GMbYeksJ1tH_oyzFsgz6PJSriG_1uBOjgX81LKZG0eWLQ61zvtkQ" +) + +MAINTENANCE_CONFIG_PATH = "path/to/config/test_config.json" +MAINTENANCE_STATE = {"test": "test"} diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..71e2ec1 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,19 @@ +[pytest] +env = + # (If using a proxy) The path prefix handled by a proxy that is not seen by the app. + API__ROOT_PATH= + # The allowed headers, origins, and methods for cross-origin requests. + API__ALLOWED_CORS_HEADERS=["*"] + API__ALLOWED_CORS_ORIGINS=["*"] + API__ALLOWED_CORS_METHODS=["*"] + AUTHENTICATION__PRIVATE_KEY_PATH=./test/keys/jwt-key + AUTHENTICATION__PUBLIC_KEY_PATH=./test/keys/jwt-key.pub + AUTHENTICATION__JWT_ALGORITHM=RS256 + AUTHENTICATION__ACCESS_TOKEN_VALIDITY_MINUTES=30 + AUTHENTICATION__REFRESH_TOKEN_VALIDITY_DAYS=7 + AUTHENTICATION__JWT_BLACKLIST=[] + AUTHENTICATION__ADMIN_USERS=[] + MAINTENANCE__MAINTENANCE_PATH=./maintenance/maintenance.json + MAINTENANCE__SCHEDULED_MAINTENANCE_PATH=./maintenance/scheduled_maintenance.json + ICAT_SERVER__URL=http://localhost/icat + ICAT_SERVER__CERTIFICATE_VALIDATION=true \ No newline at end of file diff --git a/test/test_ICATAuthenticator.py b/test/test_ICATAuthenticator.py deleted file mode 100644 index 9b5838f..0000000 --- a/test/test_ICATAuthenticator.py +++ /dev/null @@ -1,118 +0,0 @@ -from unittest import mock, TestCase - -from scigateway_auth.common.exceptions import AuthenticationError -from scigateway_auth.src.auth import ICATAuthenticator - - -class MockResponse(object): - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - -def mock_successful_icat_new_session_request(*args, **kwargs): - return MockResponse({"sessionId": "test"}, 200) - - -def mock_unsuccessful_icat_new_session_request(*args, **kwargs): - return MockResponse( - {"code": "SESSION", "message": "Error logging in. Please try again later"}, - 400, - ) - - -def mock_successful_icat_session_get_request(*args, **kwargs): - return MockResponse({"userName": "test name", "remainingMinutes": 60}, 200) - - -def mock_unsuccessful_icat_session_get_request(*args, **kwargs): - return MockResponse( - {"code": "SESSION", "message": "Unable to find user by sessionid: test"}, - 400, - ) - - -def mock_successful_icat_refresh_session_request(*args, **kwargs): - return MockResponse("", 204) - - -def mock_unsuccessful_icat_refresh_session_request(*args, **kwargs): - return MockResponse( - {"code": "SESSION", "message": "Unable to find user by sessionid: x"}, - 403, - ) - - -def mock_successful_icat_properties_request(*args, **kwargs): - return MockResponse({"authenticators": [{"mnemonic": "anon", "keys": []}]}, 200) - - -def mock_unsuccessful_icat_properties_request(*args, **kwargs): - return MockResponse({}, 500) - - -class TestICATAuthenticator(TestCase): - def setUp(self): - self.authenticator = ICATAuthenticator() - - @mock.patch( - "requests.put", - side_effect=mock_successful_icat_refresh_session_request, - ) - def test_refresh_success(self, mock_get): - result = self.authenticator.refresh("valid session id") - self.assertIsNone(result) - - @mock.patch( - "requests.put", - side_effect=mock_unsuccessful_icat_refresh_session_request, - ) - def test_refresh_failure(self, mock_get): - with self.assertRaises(AuthenticationError) as ctx: - self.authenticator.refresh("invalid session id") - self.assertEqual( - "The session ID was unable to be refreshed", - str(ctx.exception), - ) - - @mock.patch("requests.post", side_effect=mock_successful_icat_new_session_request) - def test_authenticate_with_good_response(self, mock_post): - result = self.authenticator.authenticate( - "test", - {"username": "valid", "password": "credentials"}, - ) - self.assertEqual(result, "test") - - @mock.patch("requests.post", side_effect=mock_unsuccessful_icat_new_session_request) - def test_authenticate_with_bad_response(self, mock_post): - with self.assertRaises(AuthenticationError) as ctx: - self.authenticator.authenticate( - "test", - {"username": "valid", "password": "credentials"}, - ) - self.assertEqual("Error logging in. Please try again later", str(ctx.exception)) - - @mock.patch("requests.get", side_effect=mock_successful_icat_session_get_request) - def test_get_username_with_good_response(self, mock_post): - result = self.authenticator.get_username("test") - self.assertEqual(result, "test name") - - @mock.patch("requests.get", side_effect=mock_unsuccessful_icat_session_get_request) - def test_get_username_with_bad_response(self, mock_post): - with self.assertRaises(AuthenticationError) as ctx: - self.authenticator.get_username("test") - self.assertEqual("Unable to find user by sessionid: test", str(ctx.exception)) - - @mock.patch("requests.get", side_effect=mock_successful_icat_properties_request) - def test_get_authenticators_with_good_response(self, mock_get): - result = self.authenticator.get_authenticators() - self.assertEqual(result, [{"mnemonic": "anon", "keys": []}]) - - @mock.patch("requests.get", side_effect=mock_unsuccessful_icat_properties_request) - def test_get_authenticators_with_bad_response(self, mock_get): - with self.assertRaises(KeyError) as ctx: - self.authenticator.get_authenticators() - self.assertEqual("'authenticators'", str(ctx.exception)) diff --git a/test/test_authentication.py b/test/test_authentication.py new file mode 100644 index 0000000..c42fd68 --- /dev/null +++ b/test/test_authentication.py @@ -0,0 +1,113 @@ +""" +Unit tests for the `ICATAuthenticator` class. +""" + +from unittest.mock import Mock, patch + +import pytest + +from scigateway_auth.common.config import config +from scigateway_auth.common.exceptions import ICATAuthenticationError +from scigateway_auth.common.schemas import UserCredentialsPostRequestSchema +from scigateway_auth.src.authentication import ICATAuthenticator + + +class TestICATAuthenticator: + """ + Test suite for the `ICATAuthenticator` class. + """ + + username = "test-username" + password = "test-password" # noqa: S105 + mnemonic = "test-mnemonic" + session_id = "test-session-id" + credentials = UserCredentialsPostRequestSchema(username=username, password=password) + + def create_mock_response(self, status_code: int, json_data: dict = None) -> Mock: + """ + Helper function to create a mock response with a given status code and JSON data. + + :param status_code: The HTTP status code to simulate. + :param json_data: The JSON data to return in the mock response. + :return: A mock object mimicking an HTTP response. + """ + mock_response = Mock() + mock_response.status_code = status_code + mock_response.json.return_value = json_data + return mock_response + + @patch("requests.post") + def test_authenticate_success(self, mock_post): + """ + Test that `authenticate` method successfully returns a session ID when authentication is successful. + """ + mock_post.return_value = self.create_mock_response(200, {"sessionId": self.session_id}) + session_id = ICATAuthenticator.authenticate(self.mnemonic, self.credentials) + assert session_id == self.session_id + + @patch("requests.post") + def test_authenticate_failure(self, mock_post): + """ + Test that `authenticate` method raises an `ICATAuthenticationError` on authentication failure. + """ + json_data = {"code": "SESSION", "message": "Error logging in. Please try again later"} + mock_post.return_value = self.create_mock_response(400, json_data) + with pytest.raises(ICATAuthenticationError) as exc: + ICATAuthenticator.authenticate(self.mnemonic, self.credentials) + assert str(exc.value) == json_data["message"] + + @patch("requests.get") + def test_get_username_success(self, mock_get): + """ + Test that `get_username` method successfully returns the username when provided a valid session ID. + """ + mock_get.return_value = self.create_mock_response(200, {"userName": self.username, "remainingMinutes": 60}) + username = ICATAuthenticator.get_username(self.session_id) + assert username == self.username + + @patch("requests.get") + def test_get_username_failure(self, mock_get): + """ + Test that `get_username` method raises an `ICATAuthenticationError` when the session ID is invalid. + """ + json_data = {"code": "SESSION", "message": f"Unable to find user by sessionid: {self.username}"} + mock_get.return_value = self.create_mock_response(400, json_data) + with pytest.raises(ICATAuthenticationError) as exc: + ICATAuthenticator.get_username("mocked_session_id") + assert str(exc.value) == json_data["message"] + + @patch("requests.get") + def test_get_authenticators(self, mock_get): + """ + Test that `get_authenticators` method returns a list of authenticators when the request is successful. + """ + json_data = {"authenticators": [{"mnemonic": "anon", "keys": []}]} + mock_get.return_value = self.create_mock_response(200, json_data) + authenticators = ICATAuthenticator.get_authenticators() + assert authenticators == json_data["authenticators"] + + @patch("requests.put") + def test_refresh_success(self, mock_put): + """ + Test that `refresh` method successfully completes without errors when the session ID is valid. + """ + mock_put.return_value = self.create_mock_response(204, {}) + ICATAuthenticator.refresh(self.session_id) + + mock_put.assert_called_once_with( + f"{config.icat_server.url}/session/{self.session_id}", + verify=config.icat_server.certificate_validation, + ) + + @patch("requests.put") + def test_refresh_failure(self, mock_put): + """ + Test that `refresh` method raises an `ICATAuthenticationError` when the session ID is invalid. + """ + mock_put.return_value = self.create_mock_response( + 403, + {"code": "SESSION", "message": "Unable to find user by sessionid: invalid-session-id"}, + ) + with pytest.raises(ICATAuthenticationError) as exc: + ICATAuthenticator.refresh("invalid-session-id") + assert str(exc.value) == "The session ID was unable to be refreshed" diff --git a/test/test_authenticationHandler.py b/test/test_authenticationHandler.py deleted file mode 100644 index 6575f8b..0000000 --- a/test/test_authenticationHandler.py +++ /dev/null @@ -1,165 +0,0 @@ -import datetime -from unittest import mock, TestCase - -from scigateway_auth.common.config import Config -from scigateway_auth.src.auth import AuthenticationHandler -from test.testutils import ( - EXPIRED_ACCESS_TOKEN, - EXPIRED_REFRESH_TOKEN, - NEW_REFRESH_TOKEN, - PRIVATE_KEY, - PUBLIC_KEY, - REFRESHED_ACCESS_TOKEN, - REFRESHED_NON_ADMIN_ACCESS_TOKEN, - VALID_ACCESS_TOKEN, - VALID_REFRESH_TOKEN, -) - - -class MockResponse(object): - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - -def mock_post_requests(*args, **kwargs): - return MockResponse({"sessionId": "test"}, 200) - - -def mock_get_requests(*args, **kwargs): - return MockResponse({"userName": "test name", "remainingMinutes": 60}, 200) - - -def mock_session_put_request_success(*args, **kwargs): - return MockResponse("", 204) - - -def mock_session_put_request_failure(*args, **kwargs): - return MockResponse( - { - "code": "SESSION", - "message": "Unable to find user by sessionid: " - "b764cb14-aba7-4ce1-a90e-74074dd3fe42", - }, - 403, - ) - - -def mock_datetime_now(*args, **kwargs): - return datetime.datetime(2020, 1, 8) - - -def mock_get_blacklist_with_tokens(*args, **kwargs): - if args[0] is Config.BLACKLIST: - return [VALID_REFRESH_TOKEN] - - -def mock_get_admin_users(*args, **kwargs): - if args[0] is Config.ADMIN_USERS: - return ["test name"] - - -@mock.patch("scigateway_auth.src.auth.ACCESS_TOKEN_VALID_FOR", 5) -@mock.patch("scigateway_auth.src.auth.REFRESH_TOKEN_VALID_FOR", 10080) -@mock.patch("scigateway_auth.src.auth.PRIVATE_KEY", PRIVATE_KEY) -@mock.patch("scigateway_auth.src.auth.PUBLIC_KEY", PUBLIC_KEY) -class TestAuthenticationHandler(TestCase): - def setUp(self): - self.handler = AuthenticationHandler() - - def test__pack_jwt(self): - token = self.handler._pack_jwt({"test": "test"}) - expected_token = ( - "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0ZXN0IjoidGV" - "zdCJ9.aCqysXBRNgBakmUa3NCksATw_CsNYkLU_AQoDl3DYTCFaIpEjJGzw-qfKkydLnQa" - "MK01WHdWqMrx9lft9RWSCGstNJAS1QzyVTNRcvIYGFo4GaCU8mtDuP94kCpWK-VXZmXmIg" - "z5pTszMfRs0vmfWQIahHrDbGfY_h36BycMgUwZH9VE1OeX0KaMmQHjIaG0-dUUEDO0XqSh" - "Rny6Ml2qdhQ0bE3oxM4pC8HCgb-sMQXdSImun1X4lSetRdjOcdJVDJZkV8eiN9xda9VYRn" - "lWZSmAR4j8IiF5sE9It5x-snSDwnAUfm_kfiPYROeUuZQNBv0db9B1GFdT9sFeoZjs6eap" - "nhWHwEMhVJVp7OkmSkDFimyPyXNGNq8LRY6UxonyWfzjHLvBvYSfZJBC4yye5zetj-Pc8u" - "frKrU7wXiHtkXiwxKk0rCQBe6LEvS8AGmNTZFa3olLEyzb_VgLSaFHDdSogFXAaDnBHWMc" - "Mr-77c35m6iW2WB2-FreedL5tbKw1MM51S0WFe9lVeGzsYx5fxG048iIuusLXg0AyORIVA" - "Q-2LDK1fX3VmKVULMOJdCsPIhwK8W9HUKPWlSqBEeITkEDkohCxGHHL2iOFQS52PGGfMzx" - "9ix8pa_MzPtC0MR1LfxnGNi8F5PUAE34yRi7ha3yA5J2ocMLeVl9lQ7M6ms" - ) - self.assertEqual(token, expected_token) - - @mock.patch("requests.post", side_effect=mock_post_requests) - @mock.patch("requests.get", side_effect=mock_get_requests) - @mock.patch("scigateway_auth.src.auth.current_time", side_effect=mock_datetime_now) - @mock.patch( - "scigateway_auth.src.auth.get_config_value", - side_effect=mock_get_admin_users, - ) - def test_get_access_token_admin_user( - self, - mock_post, - mock_get, - mock_now, - mock_admin_users, - ): - self.handler.set_mnemonic("anon") - token = self.handler.get_access_token() - expected_token = REFRESHED_ACCESS_TOKEN - self.assertEqual(token, expected_token) - - @mock.patch("requests.post", side_effect=mock_post_requests) - @mock.patch("requests.get", side_effect=mock_get_requests) - @mock.patch("scigateway_auth.src.auth.current_time", side_effect=mock_datetime_now) - def test_get_access_token_non_admin_user(self, mock_post, mock_get, mock_now): - self.handler.set_mnemonic("anon") - token = self.handler.get_access_token() - expected_token = REFRESHED_NON_ADMIN_ACCESS_TOKEN - self.assertEqual(token, expected_token) - - def test_verify_token_success(self): - token = VALID_ACCESS_TOKEN - result = self.handler.verify_token(token) - self.assertEqual(result, ("", 200)) - - def test_verify_token_error(self): - token = EXPIRED_ACCESS_TOKEN - result = self.handler.verify_token(token) - self.assertEqual(result, ("Unauthorized", 403)) - - @mock.patch("scigateway_auth.src.auth.current_time", side_effect=mock_datetime_now) - def test_get_refresh_token(self, mock_now): - token = self.handler.get_refresh_token() - expected_token = NEW_REFRESH_TOKEN - self.assertEqual(token, expected_token) - - @mock.patch("requests.put", side_effect=mock_session_put_request_success) - @mock.patch("scigateway_auth.src.auth.current_time", side_effect=mock_datetime_now) - def test_refresh_token_success(self, mock_put, mock_now): - refresh_token = VALID_REFRESH_TOKEN - access_token = EXPIRED_ACCESS_TOKEN - result = self.handler.refresh_token(refresh_token, access_token) - expected_access_token = REFRESHED_NON_ADMIN_ACCESS_TOKEN - self.assertEqual(result, (expected_access_token, 200)) - - def test_refresh_token_error_expired_refresh_token(self): - refresh_token = EXPIRED_REFRESH_TOKEN - access_token = REFRESHED_ACCESS_TOKEN - result = self.handler.refresh_token(refresh_token, access_token) - self.assertEqual(result, ("Refresh token was not valid", 403)) - - @mock.patch( - "scigateway_auth.src.auth.get_config_value", - side_effect=mock_get_blacklist_with_tokens, - ) - def test_refresh_token_error_blacklisted_refresh_token(self, mock_blacklist): - refresh_token = VALID_REFRESH_TOKEN - access_token = REFRESHED_ACCESS_TOKEN - result = self.handler.refresh_token(refresh_token, access_token) - self.assertEqual(result, ("Refresh token was not valid", 403)) - - @mock.patch("requests.put", side_effect=mock_session_put_request_failure) - @mock.patch("scigateway_auth.src.auth.current_time", side_effect=mock_datetime_now) - def test_refresh_token_error_access_token_refresh_failure(self, mock_put, mock_now): - refresh_token = VALID_REFRESH_TOKEN - access_token = REFRESHED_ACCESS_TOKEN - result = self.handler.refresh_token(refresh_token, access_token) - self.assertEqual(result, ("Unable to refresh token", 403)) diff --git a/test/test_config.py b/test/test_config.py deleted file mode 100644 index cb5c5a6..0000000 --- a/test/test_config.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from unittest import mock, TestCase - -from scigateway_auth.common.config import _load_config, Config, get_config_value - -CONFIG_VALUES = {"verify": True} - - -@mock.patch( - "builtins.open", - mock.mock_open(read_data=json.dumps(CONFIG_VALUES)), -) -class TestConfig(TestCase): - def test__load_config(self): - self.assertEqual(_load_config(), CONFIG_VALUES) - - def test__load_config_failure(self): - with mock.patch("builtins.open", mock.mock_open()) as mocked_file: - mocked_file.side_effect = IOError() - with self.assertRaises(SystemExit): - _load_config() - - def test_get_config_value(self): - self.assertEqual(get_config_value(Config.VERIFY), True) - - def test_get_config_value_missing(self): - with self.assertRaises(SystemExit): - get_config_value(Config.BLACKLIST) diff --git a/test/test_jwt_handler.py b/test/test_jwt_handler.py new file mode 100644 index 0000000..dba0bb5 --- /dev/null +++ b/test/test_jwt_handler.py @@ -0,0 +1,193 @@ +""" +Unit tests for the `JWTHandler` class. +""" + +from datetime import datetime, timezone +from unittest.mock import Mock, patch + +import pytest + +from scigateway_auth.common.exceptions import ( + BlacklistedJWTError, + ICATAuthenticationError, + InvalidJWTError, + JWTRefreshError, +) +from scigateway_auth.src.jwt_handler import JWTHandler +from test.mock_data import ( + EXPECTED_ACCESS_TOKEN_ADMIN, + EXPECTED_ACCESS_TOKEN_NON_ADMIN, + EXPECTED_REFRESH_TOKEN, + EXPIRED_ACCESS_TOKEN_NON_ADMIN, + EXPIRED_REFRESH_TOKEN, + VALID_ACCESS_TOKEN_NON_ADMIN, + VALID_REFRESH_TOKEN, +) + + +class TestJWTHandler: + """ + Unit tests for the `JWTHandler` class. + """ + + icat_username = "test-username" + icat_session_id = "test-session-id" + mock_icat_authenticator: Mock + jwt_handler = JWTHandler() + + def mock_datetime_now(self) -> datetime: + """ + Mock function to return a predefined datetime object. + + :return: Predefined datetime object. + """ + return datetime(2024, 1, 17, 10, 0, 0, 0, tzinfo=timezone.utc) + + @patch("scigateway_auth.src.jwt_handler.datetime") + def test_get_access_token_non_admin(self, mock_datetime): + """ + Test that `get_access_token` method successfully returns a JWT access token when user is not an admin. + """ + mock_datetime.now.return_value = self.mock_datetime_now() + + access_token = self.jwt_handler.get_access_token(self.icat_session_id, self.icat_username) + + assert access_token == EXPECTED_ACCESS_TOKEN_NON_ADMIN + + @patch("scigateway_auth.src.jwt_handler.config.authentication.admin_users", new=[icat_username]) + @patch("scigateway_auth.src.jwt_handler.datetime") + def test_get_access_token_admin(self, mock_datetime): + """ + Test that `get_access_token` method successfully returns a JWT access token when user is an admin. + """ + mock_datetime.now.return_value = self.mock_datetime_now() + + access_token = self.jwt_handler.get_access_token(self.icat_session_id, self.icat_username) + + assert access_token == EXPECTED_ACCESS_TOKEN_ADMIN + + @patch("scigateway_auth.src.jwt_handler.datetime") + def test_get_refresh_token(self, mock_datetime): + """ + Test that `get_refresh_token` method successfully returns a JWT refresh token. + """ + mock_datetime.now.return_value = self.mock_datetime_now() + + refresh_token = self.jwt_handler.get_refresh_token(self.icat_username) + + assert refresh_token == EXPECTED_REFRESH_TOKEN + + @patch("scigateway_auth.src.jwt_handler.ICATAuthenticator.refresh", return_value=None) + @patch("scigateway_auth.src.jwt_handler.datetime") + def test_refresh_access_token(self, mock_datetime, mock_icat_authenticator_refresh): + """ + Test that `refresh_access_token` method successfully returns a new JWT access token when provided a valid + refresh token. + """ + mock_datetime.now.return_value = self.mock_datetime_now() + + access_token = self.jwt_handler.refresh_access_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN, VALID_REFRESH_TOKEN) + + assert access_token == EXPECTED_ACCESS_TOKEN_NON_ADMIN + + @patch( + "scigateway_auth.src.jwt_handler.config.authentication.jwt_refresh_token_blacklist", + new=[VALID_REFRESH_TOKEN], + ) + def test_refresh_access_token_with_blacklisted_refresh_token(self): + """ + Test that `refresh_access_token` raises `BlacklistedJWTError` when attempting to refresh with a blacklisted + refresh token. + """ + with pytest.raises(BlacklistedJWTError) as exc: + self.jwt_handler.refresh_access_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN, VALID_REFRESH_TOKEN) + assert str(exc.value) == f"Attempted refresh from token in blacklist: {VALID_REFRESH_TOKEN}" + + def test_refresh_access_token_with_expired_refresh_token(self): + """ + Test that `refresh_access_token` raises `InvalidJWTError` when attempting to refresh with an expired refresh + token. + """ + with pytest.raises(InvalidJWTError) as exc: + self.jwt_handler.refresh_access_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN, EXPIRED_REFRESH_TOKEN) + assert str(exc.value) == "Invalid JWT token" + + def test_refresh_access_token_with_invalid_access_token(self): + """ + Test that `refresh_access_token` raises `JWTRefreshError` when attempting to refresh with an invalid access + token. + """ + with pytest.raises(JWTRefreshError) as exc: + self.jwt_handler.refresh_access_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN + "1", VALID_REFRESH_TOKEN) + assert str(exc.value) == "Unable to refresh access token" + + def test_refresh_access_token_with_with_non_matching_usernames(self): + """ + Test that `refresh_access_token` raises `JWTRefreshError` when access token and refresh token have non-matching + usernames. + """ + access_token = ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0LXNlc3Npb24taWQiLCJ1c2VybmFtZSI6InVzZXIxMjMiL" # noqa: S105 + "CJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6MTcwNTQ4NzQwMH0.J0ygnF1volh5cWl2xRLt1jahh1IAzYBDdXY5KN0IGpA_egAPXMiFjkF" + "EJYrFacz-1AA_NTU2cdHiKNpmPwqj_WxGDfe9eU6snhJSjQgLfPrs-Yd9DQpWEjYeA73gj6MW72qy44uBVd4BeqHYWGWbgRLwgb-l7WrYJ" + "zAd_AzTpn3-WCIgNXGp08o4fJ9d36YZQVRnAzKgYeBIMX13lFRXE0A1Roscjj94xG0EqTqLGwqngs2kbdFPT0vVmUMJeXeSEiIhZoDx3eB" + "jTcZnMEOqUODI9y8OKWq5csIT4L5HYrv0_5SR78BM4uv8-9E_Lqvsw64wN35fZ8EI-_gWwJ0qqA" + ) + + with pytest.raises(JWTRefreshError) as exc: + self.jwt_handler.refresh_access_token(access_token, VALID_REFRESH_TOKEN) + assert str(exc.value) == "Unable to refresh access token" + + @patch("scigateway_auth.src.jwt_handler.ICATAuthenticator.refresh", side_effect=ICATAuthenticationError) + def test_refresh_access_token_icat_authenticator_refresh_failure(self, mock_icat_authenticator_refresh): + """ + Test that `refresh_access_token` raises `JWTRefreshError` when `ICATAuthenticator.refresh` fails. + """ + with pytest.raises(JWTRefreshError) as exc: + self.jwt_handler.refresh_access_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN, VALID_REFRESH_TOKEN) + assert str(exc.value) == "Unable to refresh access token" + + def test_verify_token_with_access_token(self): + """ + Test that `verify_token` method successfully verifies a valid JWT access token. + """ + payload = self.jwt_handler.verify_token(VALID_ACCESS_TOKEN_NON_ADMIN) + + assert payload == { + "sessionId": self.icat_session_id, + "username": self.icat_username, + "userIsAdmin": False, + "exp": 253402300799, + } + + def test_verify_token_with_refresh_token(self): + """ + Test that `verify_token` method successfully verifies a valid JWT refresh token. + """ + payload = self.jwt_handler.verify_token(VALID_REFRESH_TOKEN) + + assert payload == {"username": self.icat_username, "exp": 253402300799} + + def test_verify_token_with_expired_access_token(self): + """ + Test that `verify_token` raises `InvalidJWTError` when verifying an expired access token. + """ + with pytest.raises(InvalidJWTError) as exc: + self.jwt_handler.verify_token(EXPIRED_ACCESS_TOKEN_NON_ADMIN) + assert str(exc.value) == "Invalid JWT token" + + def test_verify_token_with_expired_refresh_token(self): + """ + Test that `verify_token` raises `InvalidJWTError` when verifying an expired refresh token. + """ + with pytest.raises(InvalidJWTError) as exc: + self.jwt_handler.verify_token(EXPIRED_REFRESH_TOKEN) + assert str(exc.value) == "Invalid JWT token" + + def test_verify_token_with_invalid_token(self): + """ + Test that `verify_token` raises `InvalidJWTError` when verifying an invalid token. + """ + with pytest.raises(InvalidJWTError) as exc: + self.jwt_handler.verify_token(VALID_REFRESH_TOKEN + "1") + assert str(exc.value) == "Invalid JWT token" diff --git a/test/test_maintenance.py b/test/test_maintenance.py new file mode 100644 index 0000000..3fb3efb --- /dev/null +++ b/test/test_maintenance.py @@ -0,0 +1,77 @@ +""" +Unit tests for the `MaintenanceBase` class. +""" + +import json +from unittest.mock import mock_open, patch + +import pytest + +from scigateway_auth.common.exceptions import ( + InvalidMaintenanceFileError, + MaintenanceFileReadError, + MaintenanceFileWriteError, +) +from scigateway_auth.common.schemas import ScheduledMaintenanceStateSchema +from scigateway_auth.src.maintenance import MaintenanceBase + + +class TestMaintenanceBase: + """ + Unit tests for the `MaintenanceBase` class. + """ + + config_path = "path/to/config/test_maintenance.json" + maintenance_state = ScheduledMaintenanceStateSchema(show=False, message="test-message", severity="test-severity") + maintenance_base = MaintenanceBase(config_path, ScheduledMaintenanceStateSchema) + + @patch("builtins.open", mock_open(read_data=maintenance_state.model_dump_json())) + def test_get_maintenance_state(self): + """ + Test that `get_maintenance_state` successfully reads and returns the maintenance state from the file. + """ + state = self.maintenance_base.get_maintenance_state() + + assert state == self.maintenance_state + + @patch("builtins.open", side_effect=IOError) + def test_get_maintenance_state_file_read_error(self, mock_open_file): + """ + Test that `get_maintenance_state` raises `MaintenanceFileReadError` when an `IOError` occurs while trying to + read the file. + """ + with pytest.raises(MaintenanceFileReadError) as exc: + self.maintenance_base.get_maintenance_state() + assert str(exc.value) == "An error occurred while trying to find and read the scheduled maintenance file" + + @patch("builtins.open", mock_open(read_data=json.dumps({"show": True}))) + def test_get_maintenance_state_invalid_file(self): + """ + Test that `get_maintenance_state` raises `InvalidMaintenanceFileError` when the data in the file is invalid and + cannot be validated. + """ + with pytest.raises(InvalidMaintenanceFileError) as exc: + self.maintenance_base.get_maintenance_state() + assert str(exc.value) == "An error occurred while validating the data in the scheduled maintenance file" + + @patch("builtins.open", new_callable=mock_open) + @patch("json.dump") + def test_update_maintenance_state(self, mock_json_dump, mock_open_file): + """ + Test that `update_maintenance_state` successfully writes the maintenance state to the file. + """ + self.maintenance_base.update_maintenance_state(self.maintenance_state) + + mock_open_file.assert_called_once_with(self.config_path, "w") + mock_json_dump.assert_called_once_with(self.maintenance_state.model_dump(), mock_open_file()) + + @patch("builtins.open", side_effect=OSError) + def test_update_maintenance_state_file_write_error(self, mock_open_file): + """ + Test that `update_maintenance_state` raises `MaintenanceFileWriteError` when an `OSError` occurs while trying + to write the maintenance state to the file. + """ + with pytest.raises(MaintenanceFileWriteError) as exc: + self.maintenance_base.update_maintenance_state(self.maintenance_state) + assert str(exc.value) == "An error occurred while trying to find and update the scheduled maintenance file" + mock_open_file.assert_called_once_with(self.config_path, "w") diff --git a/test/test_maintenance_mode.py b/test/test_maintenance_mode.py deleted file mode 100644 index a2463f6..0000000 --- a/test/test_maintenance_mode.py +++ /dev/null @@ -1,111 +0,0 @@ -import json -from unittest import mock, TestCase - -from scigateway_auth.common.config import Config -from scigateway_auth.src import admin -from scigateway_auth.src.admin import MaintenanceMode -from test.testutils import ( - ACCESS_TOKEN_WITHOUT_ADMIN_INFO, - EXPIRED_ACCESS_TOKEN, - INVALID_ACCESS_TOKEN, - MAINTENANCE_CONFIG_PATH, - MAINTENANCE_STATE, - PUBLIC_KEY, - VALID_ACCESS_TOKEN, - VALID_NON_ADMIN_ACCESS_TOKEN, -) - - -def mock_get_blacklist_with_tokens(*args, **kwargs): - if args[0] is Config.BLACKLIST: - return [VALID_ACCESS_TOKEN] - - -def mock_get_blacklist_no_tokens(*args, **kwargs): - if args[0] is Config.BLACKLIST: - return [] - - -@mock.patch("scigateway_auth.src.admin.PUBLIC_KEY", PUBLIC_KEY) -@mock.patch( - "scigateway_auth.src.admin.MAINTENANCE_CONFIG_PATH", - MAINTENANCE_CONFIG_PATH, -) -class TestMaintenanceMode(TestCase): - def setUp(self): - self.maintenance_mode = MaintenanceMode() - - @mock.patch( - "builtins.open", - mock.mock_open(read_data=json.dumps(MAINTENANCE_STATE)), - ) - def test_get_state(self): - self.assertEqual(self.maintenance_mode.get_state(), MAINTENANCE_STATE) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_no_tokens, - ) - def test_set_state_valid_token(self, mock_blacklist): - mock_json_dump = mock.patch.object(admin.json, "dump").start() - with mock.patch("builtins.open", mock.mock_open()) as mocked_file: - result = self.maintenance_mode.set_state( - VALID_ACCESS_TOKEN, - MAINTENANCE_STATE, - ) - - mocked_file.assert_called_once_with(MAINTENANCE_CONFIG_PATH, "w") - mock_json_dump.assert_called_once_with( - MAINTENANCE_STATE, - mocked_file.return_value, - ) - self.assertEqual(result, ("Maintenance mode state successfully updated", 200)) - - def test_set_state_invalid_token(self): - result = self.maintenance_mode.set_state( - INVALID_ACCESS_TOKEN, - MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Access token was not valid", 403)) - - def test_set_state_expired_token(self): - result = self.maintenance_mode.set_state( - EXPIRED_ACCESS_TOKEN, - MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Access token was not valid", 403)) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_with_tokens, - ) - def test_set_state_blacklisted_token(self, mock_blacklist): - result = self.maintenance_mode.set_state(VALID_ACCESS_TOKEN, MAINTENANCE_STATE) - self.assertEqual(result, ("Access token was not valid", 403)) - - def test_set_state_valid_non_admin_token(self): - result = self.maintenance_mode.set_state( - VALID_NON_ADMIN_ACCESS_TOKEN, - MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Unauthorized", 403)) - - def test_set_state_token_without_admin_info(self): - result = self.maintenance_mode.set_state( - ACCESS_TOKEN_WITHOUT_ADMIN_INFO, - MAINTENANCE_CONFIG_PATH, - ) - self.assertEqual(result, ("Unauthorized", 403)) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_no_tokens, - ) - def test_set_state_file_update_failure(self, mock_blacklist): - with mock.patch("builtins.open", mock.mock_open()) as mocked_file: - mocked_file.side_effect = IOError() - result = self.maintenance_mode.set_state( - VALID_ACCESS_TOKEN, - MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Failed to update maintenance mode state", 500)) diff --git a/test/test_requires_mnemonic.py b/test/test_requires_mnemonic.py deleted file mode 100644 index 472258d..0000000 --- a/test/test_requires_mnemonic.py +++ /dev/null @@ -1,36 +0,0 @@ -import logging -from unittest import TestCase - -from scigateway_auth.common.exceptions import AuthenticationError, MissingMnemonicError -from scigateway_auth.src.auth import requires_mnemonic - - -class TestRequiresMnemonic(TestCase): - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - - @classmethod - def tearDownClass(cls): - logging.disable(logging.NOTSET) - - def test_missing_mnemonic(self): - @requires_mnemonic - def raise_missing_mnemonic_error(): - raise MissingMnemonicError() - - self.assertEqual(("Missing mnemonic", 400), raise_missing_mnemonic_error()) - - def test_authentication_error(self): - @requires_mnemonic - def raise_authentication_error(): - raise AuthenticationError("Test") - - self.assertEqual(("Test", 403), raise_authentication_error()) - - def test_general_exception(self): - @requires_mnemonic - def raise_exception(): - raise Exception - - self.assertEqual(("Something went wrong", 500), raise_exception()) diff --git a/test/test_scheduled_maintenance_mode.py b/test/test_scheduled_maintenance_mode.py deleted file mode 100644 index 702f71d..0000000 --- a/test/test_scheduled_maintenance_mode.py +++ /dev/null @@ -1,126 +0,0 @@ -import json -from unittest import mock, TestCase - -from scigateway_auth.common.config import Config -from scigateway_auth.src import admin -from scigateway_auth.src.admin import ScheduledMaintenanceMode -from test.testutils import ( - ACCESS_TOKEN_WITHOUT_ADMIN_INFO, - EXPIRED_ACCESS_TOKEN, - INVALID_ACCESS_TOKEN, - MAINTENANCE_CONFIG_PATH, - MAINTENANCE_STATE, - PUBLIC_KEY, - VALID_ACCESS_TOKEN, - VALID_NON_ADMIN_ACCESS_TOKEN, -) - -SCHEDULED_MAINTENANCE_STATE = MAINTENANCE_STATE -SCHEDULED_MAINTENANCE_CONFIG_PATH = MAINTENANCE_CONFIG_PATH - - -def mock_get_blacklist_with_tokens(*args, **kwargs): - if args[0] is Config.BLACKLIST: - return [VALID_ACCESS_TOKEN] - - -def mock_get_blacklist_no_tokens(*args, **kwargs): - if args[0] is Config.BLACKLIST: - return [] - - -@mock.patch("scigateway_auth.src.admin.PUBLIC_KEY", PUBLIC_KEY) -@mock.patch( - "scigateway_auth.src.admin.SCHEDULED_MAINTENANCE_CONFIG_PATH", - SCHEDULED_MAINTENANCE_CONFIG_PATH, -) -class TestScheduledMaintenanceMode(TestCase): - def setUp(self): - self.scheduled_maintenance_mode = ScheduledMaintenanceMode() - - @mock.patch( - "builtins.open", - mock.mock_open(read_data=json.dumps(SCHEDULED_MAINTENANCE_STATE)), - ) - def test_get_state(self): - self.assertEqual( - self.scheduled_maintenance_mode.get_state(), - SCHEDULED_MAINTENANCE_STATE, - ) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_no_tokens, - ) - def test_set_state_valid_token(self, mock_blacklist): - mock_json_dump = mock.patch.object(admin.json, "dump").start() - with mock.patch("builtins.open", mock.mock_open()) as mocked_file: - result = self.scheduled_maintenance_mode.set_state( - VALID_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - - mocked_file.assert_called_once_with(SCHEDULED_MAINTENANCE_CONFIG_PATH, "w") - mock_json_dump.assert_called_once_with( - SCHEDULED_MAINTENANCE_STATE, - mocked_file.return_value, - ) - self.assertEqual( - result, - ("Scheduled maintenance mode state successfully updated", 200), - ) - - def test_set_state_invalid_token(self): - result = self.scheduled_maintenance_mode.set_state( - INVALID_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Access token was not valid", 403)) - - def test_set_state_expired_token(self): - result = self.scheduled_maintenance_mode.set_state( - EXPIRED_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Access token was not valid", 403)) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_with_tokens, - ) - def test_set_state_blacklisted_token(self, mock_blacklist): - result = self.scheduled_maintenance_mode.set_state( - VALID_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Access token was not valid", 403)) - - def test_set_state_valid_non_admin_token(self): - result = self.scheduled_maintenance_mode.set_state( - VALID_NON_ADMIN_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - self.assertEqual(result, ("Unauthorized", 403)) - - def test_set_state_token_without_admin_info(self): - result = self.scheduled_maintenance_mode.set_state( - ACCESS_TOKEN_WITHOUT_ADMIN_INFO, - SCHEDULED_MAINTENANCE_CONFIG_PATH, - ) - self.assertEqual(result, ("Unauthorized", 403)) - - @mock.patch( - "scigateway_auth.src.admin.get_config_value", - side_effect=mock_get_blacklist_no_tokens, - ) - def test_set_state_file_update_failure(self, mock_blacklist): - with mock.patch("builtins.open", mock.mock_open()) as mocked_file: - mocked_file.side_effect = IOError() - result = self.scheduled_maintenance_mode.set_state( - VALID_ACCESS_TOKEN, - SCHEDULED_MAINTENANCE_STATE, - ) - self.assertEqual( - result, - ("Failed to update scheduled maintenance mode state", 500), - ) diff --git a/test/testutils.py b/test/testutils.py deleted file mode 100644 index e211fe4..0000000 --- a/test/testutils.py +++ /dev/null @@ -1,17 +0,0 @@ -with open("test/keys/jwt-key", "r") as f: - PRIVATE_KEY = f.read() - -with open("test/keys/jwt-key.pub", "r") as f: - PUBLIC_KEY = f.read() -VALID_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJ1c2VySXNBZG1pbiI6dHJ1ZSwiZXhwIjo5OTk5OTk5OTk5fQ.L2pR4ViBiUoZtEYat8boDyfs1RgVbHQS_-MNK7-QfdA4aEzfghJjm53EeySw88zyVpiZcNgO9owu400ywPDmKu0A88i82hhltEd0zIPLd3PfCUUe4NlzZ5aNavuemrQ3FAztc9c0GIJFVBwPkItLmwyL47uTgSBJB_a5y51tVh61g_FLg3Bb7vYwyTyjxIUhrzwc4mXfpeybQae5fcQKJzE3MbZxHEtGYhL9p7ukaaFl1UnGeGba_CALD3kZBLFge50eobiDUx_RTpUHHRrOq4prtiBXZk7LH1xrBIVhEXqJX04aoS-0N7cxtJ5RUcOzLYKNAHo0eBVjIj6cly7aaz_DDEIjDQb51FvvwQBXBN33XOikABrI6wjkIT9wox7Vlh605e5VjyqlPI1UdHDpvW2yISxpF0_c8wfLQF5yotw9ETlmwSPyr_nA4BlpUFg7YxyOLOcF8DlB7-egQuUSLHm3FGi18dIIrPFZaIF0YjlZysLNdGxSqHzMMyLzQ1kDh8IDoJD8ylKvw8ElKHg7gs9j620J-hKBI8fdfISMEtU9-U05lNuT9wpP0Z3QcjgeoXiEtqYZN7mkD3DJVzCq4SB3oRWupo5gmrHEq_z7ElYHN4aO4cOJxw_kgFuiNjLqTFTp08C8lUkcsOYB5ZPHUYnmFqGk6XARe_9KJkjLV5I" -INVALID_ACCESS_TOKEN = VALID_ACCESS_TOKEN + "1" -VALID_NON_ADMIN_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6OTk5OTk5OTk5OX0.ZrvLcWueXfix-dOtuFWdpZuUZAM8WrhtqUffEfZhLJPDbCe-I_D3Bcikznhtkb1k8-Kcj9MiLdUcaFXT-SvyYpc_c2Zj690FMVqfrR9yjaGGWG-2Lx2izedjSFoNu7zT779hmDanzTFNtLzSZeSU8GhXXSsrzgY2FwcDMNHiCcKb6JlcsrmSRIKQCiKK4k_VadJ7e7EioCN9tVAGiORwOy81r1m5RqSui6y8SvYzTFXSbDwjTErTVUfdIsUcN9_i_WEMn1sPztVPeQ_0o3sW0XsIxK-peuiqAqnX67OQpNQtUoMd_RtY603U9zdGujCDW-zYz-vIzWhUu5GNyFaA_1zJIuS-Ga3gGS-QXrLTYrosBTCpK0OCZp8mM4Guue7obKa5zeEL-Cq2-WlrLuPMlxCPrPH1qu_pMtYO-yaMIrfX6Xt7g-0p2ZQVIaR-vesXJC6MTIO-bqq8rIv7ToImf774gY2Fg5EiNnhkJAml8AaKfSynWsRoV7YsXoCnlqGLCwE4-RyRoq-rmm4ms1zEzwl38ydPWUzNqQMTkwqj5eYsRw-ncfVssnP0VcZ5HCcHIXKHOVuHrxqc5wJg-MkhCfJazOb8pjyDZg6fW4Omsr54Rthz3R7jeska0AbinZzAlPJSwdx9ouPxvdAD2hWxAPnp9Ii16gn50V38eMLm3wA" -EXPIRED_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6MTU3ODQ0MTU5OX0.cZh9eezkKpuUhx49NQORXm9Nq6VaMNA9hI_XtRlwr9wO2RkX6g4DE0I9ls9QDp8mNs1ZrKolM1aAa-3xi6GiHQBK9hQFSVZK8UYd_wG4AbnuBUmWypSKB3AD_fUtNeCv1NxOdKQPbx6CgKguWN3DmjAFrc84nrrX-FHGx5IsmiD_TeSudqBHiG7Kqbn4FWhVmyzcwTs4d0eCtTpJNGaLcNNp5WuR8GX0CnCZdgM6eFM8pvoAZGJ_lXEQ3ayCY-4CShWinAa2G1x_mwgd0-y5KhmjU1DdY6G0qqlmeFBO4Qz6LSoEjp5KHiSTSRNLVr9FIHYJY5v2MhZ9Jt-bp3e7mDEyR8M8RtnVRGAtn_KKovh0e5v2AgFnUsOVYtj3g_gr5lbiwpVY9cNa24HJqPxSb9WnjBLWYHcyhLCZKDTtZ0xdCDeQrO772rCpfb2tSif6LijQJRuhJzvjdcHJgZNeFDoF0bGha_brqRqOo4ayIn-BrfuBmN4ShplLs_Q_vGg76do7Px1QVc4_1mrXgIdl9xDCtv-z80E7Y6LzEPtEGwfoyqLJWhyF5HRYIUgJSakK6ZtZsFGMIC3uCCMZGD5KZDUcc_UKZOIZZ9mlDXyPkIehZ1AoQszZKJbkvoDf9xYLQLpdf7-0vIIhqqaIM2VjOmKD-BQjUvrfyd-Olv_sPAw" -REFRESHED_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJ1c2VySXNBZG1pbiI6dHJ1ZSwiZXhwIjoxNTc4NDQxOTAwfQ.BlwipnOq1Au2IhzRdExiJc9UN8iQ1w9tAAxX2NEZicNoAazGBj7V4YLrDIQJIlnd3e1RUjlQzyb9P1aw4WOLwilZIoNCRHqGZxYwy1qZIrEyhGqdI545k-QKG1hlXc1xK9A6eOopNWtrFd1gvxELHgdNihTPGEbcGGO_NRoXs89KZpFgdmEd_GxsYksGCO5f1UTHbkptZ2zoYso9gLcWRpUX7j27I2EgNdbYkQ9DIglYWnLkEIzvVhE96CQzn_Pea7X1GHTWsLYD5jpFobwM8wc-F_e_UhV375cXH36N41f12_RmTbO5WVV0luLSeKHZCLyqpVCdrOM0hL5ERVdvtRx6TJSF_nrt84ewwtt1XeAQYY_InPaobN4xvUxpF7jW-FmaV-WVIv8D8qDRTtmRisyJjRHlDopRHWnfHe1oV-32_y27VcVRXqUn-GQudRqteTG6ngY1jaREvZLFANGzrvwEFKBLaKGkljt9KFQ7QuknXXbjKSmitcxfps6RNVjAJALxi-aEinKC3d7fgVm09Sg6X_tG0funABIzENQwSGtTCZYfcnryL7D4V0mU8BuviLQbmA9yRmIOZjHfBYzTEA3MySe3fPGPj8Z5a8Z2jqbXpub7pT2RsNIxD4SqijoZBnO4ZmPMiBkUoTXaq8v2Rk2Q97oepqKkl85xb6G4tX8" -REFRESHED_NON_ADMIN_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJ1c2VySXNBZG1pbiI6ZmFsc2UsImV4cCI6MTU3ODQ0MTkwMH0.ew2pVkjd1YD3gha-kjw50lrtqMyhxZZT6nwYzQXM7PfjykjvhJRWS6wUXeYSeT8lGwLLZDXdaLY3nCMM8UK6ERckIvte2-BFFx12h4ZktWMLcN0tSRaKJU6mp3wrHTlVQ-iEQOtQ1GOYeZdryGbjotrq3IdRY2iuOpmTGqSRhSuAO262BEPpUxm-bgUqE9V7j9lnSRBA_GZ0OPssxOmnKrXqVoyi3zmrAcWf-Ci409JPmdrczPy6dBABHt8ayuqtY3nc8jtB45N9o97_mvfbeP2Vsf4ndFt7i5mOhib1Ytgj_TZaB1_aErBBk3VAq9uKgb9EIEJSFFj2es_kMjmCkPKRP-Bpm_caIp5Agopoocg6vc4iUnRRpJGPWSJNcBeWDoI_8aC03MME9gIuqrCRdeXJyjRRl4pEDkdi2QchV2rm1miD6uRy-ho4jBGydiylA-yZCPexnyT13Pxom63c2WqX0muHTHGD3kMCkoO0RN_aF2s5kkKkvVrRHuk2_MNg1KX03mZ1jMp3y6KNHXJuY6REZqUtufVvVNxjx-AZJajw2EkmfTMCtldtAwc54EL-i7rFc9CmIbiXFsEZK1oKyj8WOqLtN_b-DVdrzx5D6s7SNlDu9ahhdzSII2CL3_QzJNJlOiCI3dfp73L9CmMZoPz5CJiRxnwzOfitVScoOX4" -NEW_REFRESH_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1NzkwNDY0MDB9.pAQEve08F-fDXHZO4YeHziqO9MesmISs2LRZUTUGJHBCoJcB5tTpSgHq-XMhKkA4iVPmsN1sQ5rD6Ahr9wvKLQLTqMgDf-Gc7AVIDZ79I1D74o1wDUMEC5WUll9sUtcUuImAl2KQHWK-i74uOkZGv7ibzGZFMREoW9KApA9yCc15KeZgSF9XoKxyAkr2T1WYH2kzT9DRUn5ddZgjDJHjlJhUuZUSdDqAXKT4dYZabP48xZmjSBrnr7MdoS5x2jhmanBIWFtbuuAK1L9MRd0uY1RAcrueUjoDEfjoT56R9nju4rcRL5QGaArJSQpNEel-mxqHqEZpjE3Me1AGjUFT494v95Rg2KG2aFGIfxcMMaEd5dNU2BlhGlviQK5KDuLuBULra_0aiFm8TRVHjTWE9uIKI0ZS5Vzl4umu3c1YAbWXAolBxXoH8tYnuvVYI-HCdwd7f_B1BPrAmPBhSRLaBrQwqU_EJtit5XSi9Pm4Y-c93EmzT0sFZk9Z8-xjw0Sr2hbNwGsd5gSN_YPUZH-r9XZ6xy1a9HRVKS0UVWq2P5oSHtrafl95OZ_WbSd4Hh0cOnuf6chz4AWM6JP-tlOYMas5D387a9el0Pl3nl3xO9pk7oUATeeMlm1Vx2nt7bvsbgQwBhfY5OnGrW9reem1zZTEm4SpJTraKLleGh1UGd8" -VALID_REFRESH_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjk5OTk5OTk5OTl9.dgakoOk09sfdjTpPvC28RQbmJFt_F2eWq4AuBGyp9Z6ri1XHycRNiv-4ewixw_uZaD57Y_Xr5tJuoSlUEfVL0f2R-X-AHhGb1aYsY6Gk0hIBupYkLM-sEOQNLGffK7ayd58k8ay_GtjxuLcmsqqBdCWYM3nr0JCDpsnBYaRXFvHQKThOk7BEJPFmlEk06w0C8OW35GDn9EPl_ArKKyUDm2H1Wbgjn7mVrVmpcoUyXmaDMJ63ZOMvm82j-3y2H2Bye0Ezlc2h4DLPH9KB3CuwdArwaka_ybA8_DVah4poWvZXxS3vNS5cLBGnKPwRSjo4VHcgilpqeM63nrelfqjEg2MP5-CDnUro0MrtoHg1VoF0zLTFuDrdx4YkD---uML0e3K8_UhCG1ipj-WwA9oUpQ0SEZ8KnlNKXRxutuyei3U7Q9c60Joye7sldmAVWDL6W8EnUPyGKAYyMSWDbzHANzHCAaBl5GJSuiiHFyjVli9PFJJPcwJb4hXZybjaPNsmeiHnZUPCY_ESuyFQYJpo4Ot5rbYabBkVVvTz5BK3RXVAwecxEGrzsRyl_vLOKn_D1T6_gQkHmPI99ySDNIo7qL7N9P5aLxwJTGYl9xczsQtuUXe1PurjQSj78b4ioU8ImPC1AYwGJzRHDgeM3q04nx3tFPOwLmOdRZwEBmcGXWk" -EXPIRED_REFRESH_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjEwMDAwMDAwMDB9.SWuznEIymHglPP4rSNjlVjY5kAwNKQEgiVsXxItxCgrdhNCrhadKLYTV0EFs3oEfylCSpkrQF1nfhlXhOhuUVvWQJR6itxVDxMfyS_YkDSLyrdeiEbpYcQ-dNv7mPKdPOdcwqnh_f0w5Fg961zoGAYLxpdCN67hvIkW1RXMX4n8ZGV_kQ9vdLpg3Jx4x29iSzAKF5ZBdocwUZZaYojpVdsIvv3cdoxS0LRShgKowYwdyKp8xWVacV8wS4stUh9rpk70MSVjlec4ecAaDqMxI0WyO3bVxtXsKj9bGlbzyUvhxEMRFyuyYtpMIXspuvblqqDJZkCiMqbrJxtJxXirNzwJYqKwgsjJxzrcsJygWi4GnVUykaOvvIznZ9iJmaVsMFrXDjLhQDxsoRAQuodpHdE4tWzpXWSQgMLHscjvFqfIgybX0JQCeOXMtXVgIj3Y8a2MvsmEYMmqwypYyRLrF4LsjTi9A9vu3Zo5RU5Lbwqbwvp-Auan-_4Ef5aBKgYCa1CaMP-nRZMMz8esdeEwx_xQ21FX1zZt-VMt-bC_J-b_9Ym7gpizvi-nrymF-yNLS8HF519NM1TlV0H_4Kc4uI6WESlGcM9TyEMh8yoy2tMMzURyahy8h98hIG5H0sP1fadZ_RdYxhqR79VDISxugVcXmdltMlMRmFrRSwf1ENXk" -ACCESS_TOKEN_WITHOUT_ADMIN_INFO = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzZXNzaW9uSWQiOiJ0ZXN0IiwidXNlcm5hbWUiOiJ0ZXN0IG5hbWUiLCJleHAiOjk5OTk5OTk5OTl9.beDpd6SrucSThtzszb1sfr4xieVpWzoZ7uVRj6vOeIy-MI0yAfn7M-PnDOdZoyOMJbGEP_BWNo3e4zpiyv9wxx-TVL_EKccdre92seYPFI8pOnAm0OlmbM9hOwSSQuhAuqSAzxSupU0V2CPXN9YG_xOFFhrpAgNIltyqcpkv0VArxYbniJtGEJt_WTwn15XO1oEDK7-3w7CNidQ2_BUrp0ZfYDVjgJXpaK6-op_7KLUBqXzZtAIbf2Z97O05sZk5Iv0citk9Mqh3a17CUajNDJAQAwBuUomwIvqm0flB3SriEfrUx7tteNlH8ziZ1Vs3b9Dg6hjvF0jDkC8BL2LZoI6eYP7iVIyS-ZwGEjnE9qiBqblP9E4ENVygCfP7A1SDG8XsCmkIt1lXFKM8qgjfPaompOI8rY5mJEwgbZTJmDCPpzJ9ethJmpuZbuDOQYjI1occkIeeOkzJsHnm3vkv4TTZPgifckUlIJdXaDFGGLZYC05cMFB2x-DdOQuzX1Dvr4OAKQH_g5vEibXhH3amMqRBNlHZlmc0WundBmo5hJFSUvR7oWa0nprWk1iPUlMuijyedTyVk51tJqVe1orDg2W2yp7ehdgAWJiWAwOpYL4Tbj-rypOq12tqnemaS5eykG0Ybo7fNG_4hTbTiX4SsPIhEusVte7-y-vpNVbwefE" -MAINTENANCE_CONFIG_PATH = "path/to/config/test_config.json" -MAINTENANCE_STATE = {"test": "test"}