diff --git a/swift_browser_ui/ui/health.py b/swift_browser_ui/ui/health.py index bde0babed..9e56b4686 100644 --- a/swift_browser_ui/ui/health.py +++ b/swift_browser_ui/ui/health.py @@ -113,7 +113,7 @@ async def get_upload_runner( _set_error_status(request, services, "swiftui-upload-runner") except Exception as e: request.app["Log"].info(f"Health failed for reason: {e}") - _set_error_status(request, services, "sswiftui-upload-runner") + _set_error_status(request, services, "swiftui-upload-runner") async def handle_health_check(request: aiohttp.web.Request) -> aiohttp.web.Response: diff --git a/tests/common/mockups.py b/tests/common/mockups.py index caa51b47e..861ed38f3 100644 --- a/tests/common/mockups.py +++ b/tests/common/mockups.py @@ -39,6 +39,7 @@ def setUp(self): "token": "test-token-1", "endpoint": "https://test-endpoint-1/v1/AUTH_test-id-1", "tainted": False, + "runner": "test-runner", }, } self.aiohttp_session_get_session_mock = unittest.mock.AsyncMock() @@ -72,6 +73,7 @@ def setUp(self): "has_trust": True, "upload_external_endpoint": "http://test-endpoint:9092/", "oidc_enabled": False, + "upload_internal_endpoint": "http://test-endpoint", } self.patch_setd = unittest.mock.patch( "swift_browser_ui.ui.api.setd", self.setd_mock @@ -112,14 +114,14 @@ def setUp(self): async def citer(_): yield self.mock_iter() - self.mock_client_json = {} + self.mock_client_json = {"status": "Ok"} self.mock_client_text = "" self.mock_client_response = types.SimpleNamespace( **{ "status": 200, "headers": {}, "cookie": {}, - "json": None, + "json": unittest.mock.AsyncMock(return_value=self.mock_client_json), "content": types.SimpleNamespace( **{ "iter_chunked": citer, diff --git a/tests/request/test_api.py b/tests/request/test_api.py index 77219b7f2..bc3f8f8f5 100644 --- a/tests/request/test_api.py +++ b/tests/request/test_api.py @@ -2,9 +2,9 @@ import unittest +import unittest.mock from types import SimpleNamespace - import aiohttp @@ -14,6 +14,10 @@ handle_user_made_request_listing, handle_container_request_listing, handle_user_share_request_delete, + handle_user_add_token, + handle_user_delete_token, + handle_user_list_tokens, + handle_health_check, ) @@ -28,20 +32,25 @@ def setUp(self): "db_conn": SimpleNamespace( **{ "add_request": unittest.mock.AsyncMock(), + "add_token": unittest.mock.AsyncMock(), "get_request_owned": unittest.mock.AsyncMock(), "get_request_made": unittest.mock.AsyncMock(), "get_request_container": unittest.mock.AsyncMock(), "delete_request": unittest.mock.AsyncMock(), + "get_tokens": unittest.mock.AsyncMock([]), + "revoke_token": unittest.mock.AsyncMock(), + "pool": None, } ) }, - "query": { - "owner": "AUTH_otherexample", - }, + "query": {"owner": "AUTH_otherexample", "token": "user_token"}, "match_info": { "container": "test", "user": "test", + "project": "test", + "id": "test", }, + "post": unittest.mock.AsyncMock(return_value={}), } ) @@ -50,33 +59,75 @@ def setUp(self): "swift_browser_ui.request.api.aiohttp.web.json_response", new=self.json_mock ) - async def test_endpoint_has_access_correct(self): - """Test the has-access endpoint for conformity.""" + async def test_endpoint_share_request_post_correct(self): + """Test the share_request_post endpoint for conformity.""" with self.patch_json_dump: await handle_share_request_post(self.mock_request) self.json_mock.assert_called_once() - async def test_endpoint_access_details_correct(self): - """Test the access-details endpoint for conformity.""" + async def test_endpoint_user_owned_request_listing_correct(self): + """Test the user_owned_request_listing endpoint for conformity.""" with self.patch_json_dump: await handle_user_owned_request_listing(self.mock_request) self.json_mock.assert_called_once() - async def test_endpoint_gave_access_correct(self): - """Test the gave-access endpoint for conformity.""" + async def test_endpoint_user_made_request_listing(self): + """Test the user_made_request_listing endpoint for conformity.""" with self.patch_json_dump: await handle_user_made_request_listing(self.mock_request) self.json_mock.assert_called_once() - async def test_endpoint_shared_details_correct(self): - """Test the shared_details endpoint for conformity.""" + async def test_endpoint_container_request_listing(self): + """Test the container_request_listing endpoint for conformity.""" with self.patch_json_dump: await handle_container_request_listing(self.mock_request) self.json_mock.assert_called_once() - async def test_endpoint_share_container_correct(self): - """Test the share_container endpoint for conformity.""" - self.json_mock.return_value.status = 200 + async def test_endpoint_user_share_request_delete(self): + """Test the user_share_request_delete endpoint for conformity.""" with self.patch_json_dump: resp = await handle_user_share_request_delete(self.mock_request) - print(resp) + self.assertEqual(resp.status, 200) + + async def test_endpoint_handle_user_add_token(self): + """Test the handle_user_add_token endpoint for conformity.""" + with self.patch_json_dump: + resp = await handle_user_add_token(self.mock_request) + self.assertEqual(resp.status, 200) + + # Also test when no token is present in query + del self.mock_request.query["token"] + with self.patch_json_dump: + with self.assertRaises(aiohttp.web.HTTPBadRequest): + await handle_user_add_token(self.mock_request) + + async def test_endpoint_handle_user_delete_token(self): + """Test the handle_user_delete_token endpoint for conformity.""" + with self.patch_json_dump: + resp = await handle_user_delete_token(self.mock_request) + self.assertEqual(resp.status, 200) + + async def test_endpoint_handle_user_list_tokens(self): + """Test the handle_user_list_tokens endpoint for conformity.""" + with self.patch_json_dump: + await handle_user_list_tokens(self.mock_request) + self.json_mock.assert_called_once() + + async def test_endpoint_handle_health_check(self): + """Test the handle_health_check endpoint for conformity.""" + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + self.mock_request.app["db_conn"].pool = {} + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + del self.mock_request.app["db_conn"].pool + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + calls = [ + unittest.mock.call({"status": "Ok"}), + unittest.mock.call({"status": "Degraded", "degraded": ["database"]}), + ] + self.json_mock.assert_has_calls(calls) diff --git a/tests/sharing/test_api.py b/tests/sharing/test_api.py index 24f1ccf42..8ce6d578b 100644 --- a/tests/sharing/test_api.py +++ b/tests/sharing/test_api.py @@ -3,8 +3,8 @@ from types import SimpleNamespace - import unittest +import unittest.mock import aiohttp.web @@ -17,6 +17,10 @@ edit_share_handler, delete_share_handler, delete_container_shares_handler, + handle_user_add_token, + handle_user_delete_token, + handle_user_list_tokens, + handle_health_check, ) @@ -31,6 +35,7 @@ def setUp(self): "db_conn": SimpleNamespace( **{ "add_share": unittest.mock.AsyncMock(), + "add_token": unittest.mock.AsyncMock(), "edit_share": unittest.mock.AsyncMock(), "delete_share": unittest.mock.AsyncMock(), "delete_container_shares": unittest.mock.AsyncMock(), @@ -38,6 +43,9 @@ def setUp(self): "get_shared_list": unittest.mock.AsyncMock(), "get_access_container_details": unittest.mock.AsyncMock(), "get_shared_container_details": unittest.mock.AsyncMock(), + "get_tokens": unittest.mock.AsyncMock([]), + "revoke_token": unittest.mock.AsyncMock(), + "pool": None, } ), }, @@ -47,8 +55,16 @@ def setUp(self): "container": "test-container-1", "access": "r,w,l", "address": "https://placeholder.os:443", + "token": "user_token", + }, + "match_info": { + "container": "test", + "user": "test", + "owner": "test", + "project": "test", + "id": "test", }, - "match_info": {"container": "test", "user": "test", "owner": "test"}, + "post": unittest.mock.AsyncMock(return_value={}), } ) @@ -100,8 +116,58 @@ async def test_endpoint_delete_share_correct(self): resp = await delete_share_handler(self.mock_request) self.assertEqual(resp.status, 204) + # Also test delete_share endpoint leads to bulk unshare without user key + with self.patch_json_dump: + alt_mock_request = self.mock_request + del alt_mock_request.query["user"] + resp = await delete_share_handler(alt_mock_request) + self.assertEqual(resp.status, 204) + async def test_endpoint_delete_container_shares_correct(self): """Test the delete_container_shares endpoint for conformity.""" with self.patch_json_dump: resp = await delete_container_shares_handler(self.mock_request) self.assertEqual(resp.status, 204) + + async def test_endpoint_handle_user_add_token(self): + """Test the handle_user_add_token endpoint for conformity.""" + with self.patch_json_dump: + resp = await handle_user_add_token(self.mock_request) + self.assertEqual(resp.status, 200) + + # Also test when no token is present in query + del self.mock_request.query["token"] + with self.patch_json_dump: + with self.assertRaises(aiohttp.web.HTTPBadRequest): + await handle_user_add_token(self.mock_request) + + async def test_endpoint_handle_user_delete_token(self): + """Test the handle_user_delete_token endpoint for conformity.""" + with self.patch_json_dump: + resp = await handle_user_delete_token(self.mock_request) + self.assertEqual(resp.status, 200) + + async def test_endpoint_handle_user_list_tokens(self): + """Test the handle_user_list_tokens endpoint for conformity.""" + with self.patch_json_dump: + await handle_user_list_tokens(self.mock_request) + self.json_mock.assert_called_once() + + async def test_endpoint_handle_health_check(self): + """Test the handle_health_check endpoint for conformity.""" + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + self.mock_request.app["db_conn"].pool = {} + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + del self.mock_request.app["db_conn"].pool + with self.patch_json_dump: + await handle_health_check(self.mock_request) + + calls = [ + unittest.mock.call({"status": "Ok"}), + unittest.mock.call({"status": "Degraded", "degraded": ["database"]}), + ] + self.json_mock.assert_has_calls(calls) diff --git a/tests/ui_unit/test_api.py b/tests/ui_unit/test_api.py index f1bf901d7..00901b644 100644 --- a/tests/ui_unit/test_api.py +++ b/tests/ui_unit/test_api.py @@ -611,13 +611,15 @@ def setUp(self): """.""" super().setUp() self.mock_request.match_info = { - "project": "test-project", + "project": "test-id-1", "container": "test-container", "object": "test-object", + "object_name": "test-object-name", } self.mock_request.query = { "from_container": "test-container-2", "from_project": "test-project-2", + "project": "test-project", } self.mock_request.query_string = ("&test-query=test-value§",) self.mock_request.remote = ("remote",) @@ -682,3 +684,21 @@ async def test_get_upload_session(self): ) self.session_open_mock.assert_awaited_once() self.sign_mock.assert_awaited_once() + + async def test_get_crypted_upload_session(self): + """Test get crypted upload session.""" + with self.p_get_sess, self.patch_runner_session, self.patch_setd, self.patch_sign: + await swift_browser_ui.ui.api.get_crypted_upload_session( + self.mock_request, + ) + self.session_open_mock.assert_awaited_once() + self.assertEqual(self.sign_mock.await_count, 2) + + async def test_close_upload_session(self): + """Test close upload session.""" + with self.p_get_sess, self.patch_runner_session, self.patch_setd, self.patch_sign: + resp = await swift_browser_ui.ui.api.close_upload_session( + self.mock_request, + ) + self.mock_client.delete.assert_called_once() + self.assertEqual(200, resp.status) diff --git a/tests/ui_unit/test_health.py b/tests/ui_unit/test_health.py new file mode 100644 index 000000000..8131014f2 --- /dev/null +++ b/tests/ui_unit/test_health.py @@ -0,0 +1,156 @@ +"""Module for testing ``swift_browser_ui.ui.health``.""" + + +import unittest + +import tests.common.mockups +import swift_browser_ui.ui.health + + +class HealthTestClass(tests.common.mockups.APITestBase): + """Test the Object Browser API.""" + + def setUp(self): + """Set up mocks.""" + super().setUp() + self.mock_services = {} + self.mock_api_params = {} + self.mock_performance = {} + + self.mock_setd = { + "upload_internal_endpoint": "http://test-endpoint", + "sharing_internal_endpoint": "http://test-endpoint", + "request_internal_endpoint": "http://test-endpoint", + } + self.setd = unittest.mock.patch("swift_browser_ui.ui.health.setd", self.mock_setd) + + async def test_get_x_account_sharing(self): + """Test getting x account sharing.""" + with self.setd: + await swift_browser_ui.ui.health.get_x_account_sharing( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.mock_client.get.assert_called_once() + self.assertEqual(self.mock_services["swift-x-account-sharing"], {"status": "Ok"}) + self.assertIn("time", self.mock_performance["swift-x-account-sharing"]) + first_time = self.mock_performance["swift-x-account-sharing"]["time"] + + self.mock_client_response.status = 503 + with self.setd: + await swift_browser_ui.ui.health.get_x_account_sharing( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.assertEqual( + self.mock_services["swift-x-account-sharing"], {"status": "Down"} + ) + self.assertNotEqual( + first_time, self.mock_performance["swift-x-account-sharing"]["time"] + ) + + async def test_get_swift_sharing(self): + """Test getting swift sharing.""" + with self.setd: + await swift_browser_ui.ui.health.get_swift_sharing( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.mock_client.get.assert_called_once() + self.assertEqual(self.mock_services["swift-sharing-request"], {"status": "Ok"}) + self.assertIn("time", self.mock_performance["swift-sharing-request"]) + first_time = self.mock_performance["swift-sharing-request"]["time"] + + self.mock_client_response.status = 503 + with self.setd: + await swift_browser_ui.ui.health.get_swift_sharing( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.assertEqual(self.mock_services["swift-sharing-request"], {"status": "Down"}) + self.assertNotEqual( + first_time, self.mock_performance["swift-sharing-request"]["time"] + ) + + async def test_get_upload_runner(self): + """Test getting upload runner.""" + with self.setd: + await swift_browser_ui.ui.health.get_upload_runner( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.mock_client.get.assert_called_once() + self.assertEqual(self.mock_services["swiftui-upload-runner"], {"status": "Ok"}) + self.assertIn("time", self.mock_performance["swiftui-upload-runner"]) + first_time = self.mock_performance["swiftui-upload-runner"]["time"] + + self.mock_client_response.status = 503 + with self.setd: + await swift_browser_ui.ui.health.get_upload_runner( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.assertEqual(self.mock_services["swiftui-upload-runner"], {"status": "Down"}) + self.assertNotEqual( + first_time, self.mock_performance["swiftui-upload-runner"]["time"] + ) + + async def test_some_exception(self): + """Test that an exception happens when getting one of the service statuses.""" + self.mock_client_response.json = None + with self.setd: + await swift_browser_ui.ui.health.get_upload_runner( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.assertEqual(self.mock_services["swiftui-upload-runner"], {"status": "Error"}) + + async def test_nonexistant_service(self): + """Test get_upload_runner when upload_internal_endpoint is not assigned.""" + self.mock_setd["upload_internal_endpoint"] = None + with self.setd: + await swift_browser_ui.ui.health.get_upload_runner( + self.mock_services, + self.mock_request, + self.mock_client, + self.mock_api_params, + self.mock_performance, + ) + self.assertEqual( + self.mock_services["swiftui-upload-runner"], {"status": "Nonexistent"} + ) + + async def test_handle_health_check(self): + """Test handling health check.""" + with self.setd, self.p_json_resp: + await swift_browser_ui.ui.health.handle_health_check(self.mock_request) + self.aiohttp_json_response_mock.assert_called() + status = self.aiohttp_json_response_mock.call_args_list[0][0][0]["status"] + self.assertEqual(status, "Ok") + + self.mock_client_response.status = 503 + with self.setd, self.p_json_resp: + await swift_browser_ui.ui.health.handle_health_check(self.mock_request) + status = self.aiohttp_json_response_mock.call_args_list[1][0][0]["status"] + self.assertEqual(status, "Partially down") diff --git a/tests/upload/test_upload.py b/tests/upload/test_upload.py new file mode 100644 index 000000000..5766722c3 --- /dev/null +++ b/tests/upload/test_upload.py @@ -0,0 +1,137 @@ +"""Unit tests for swift_browser_ui.upload.upload module.""" + +import unittest.mock + +import aiohttp.web + +from swift_browser_ui.upload.upload import ResumableFileUploadProxy +import tests.common.mockups + + +class UploadTestClass(tests.common.mockups.APITestBase): + """Test class for swift_browser_ui.upload.upload functions.""" + + def setUp(self): + """Set up mocks.""" + super().setUp() + self.mock_session = { + "endpoint": "https://test-endpoint-0/v1/AUTH_test-id", + "token": "test-token", + } + self.mock_query = { + "resumableChunkSize": 0, + "resumableTotalSize": 9999999999, + "resumableChunkNumber": 10, + "resumableTotalChunks": 10, + "resumableType": "", + "resumableIdentifier": "", + "resumableFilename": "", + "resumableRelativePath": "", + } + self.file_upload_proxy = ResumableFileUploadProxy( + self.mock_session, + self.mock_query, + self.mock_request.match_info, + self.mock_client, + ) + + async def test_create_container(self): + """Test creating a container for an upload.""" + self.mock_client_response.status = 200 + with self.assertRaises(aiohttp.web.HTTPForbidden): + await self.file_upload_proxy.a_create_container() + self.mock_client.head.assert_called_once() + self.mock_client.put.assert_called_once() + self.mock_client.head.reset_mock() + self.mock_client.put.reset_mock() + + self.mock_client_response.status = 201 + await self.file_upload_proxy.a_create_container() + self.assertEqual(self.mock_client.head.call_count, 2) + self.assertEqual(self.mock_client.put.call_count, 2) + + async def test_check_container(self): + """Test checking if container is allowed.""" + with self.assertRaises(aiohttp.web.HTTPBadRequest): + await self.file_upload_proxy.a_check_container() + self.mock_client.head.assert_called_once() + self.mock_client.head.reset_mock() + + self.mock_client_response.status = 201 + match_info = { + "project": "AUTH_test-id", + "container": "test-container", + } + new_proxy = ResumableFileUploadProxy( + self.mock_session, + self.mock_query, + match_info, + self.mock_client, + ) + await new_proxy.a_check_container() + self.assertEqual(self.mock_client.head.call_count, 3) + + async def test_check_segment(self): + """Test checking existence of a segment.""" + resp = await self.file_upload_proxy.a_check_segment(1) + self.mock_client.head.assert_called_once() + self.mock_client.head.reset_mock() + self.assertEqual(resp.status, 200) + + self.mock_client_response.status = 204 + resp = await self.file_upload_proxy.a_check_segment(1) + self.mock_client.head.assert_called_once() + self.assertEqual(resp.status, 204) + + async def test_add_manifest(self): + """Test adding manifest file.""" + with self.assertRaises(aiohttp.web.HTTPBadRequest): + await self.file_upload_proxy.a_add_manifest() + self.mock_client.put.assert_called_once() + self.mock_client.put.reset_mock() + + self.mock_client_response.status = 201 + await self.file_upload_proxy.a_add_manifest() + self.mock_client.put.assert_called_once() + + async def test_add_chunk(self): + """Test adding chunk.""" + self.mock_chunk_reader = unittest.mock.Mock() + self.file_upload_proxy.upload_file = unittest.mock.AsyncMock() + self.patch_coro_upload = unittest.mock.patch( + "swift_browser_ui.upload.upload.ResumableFileUploadProxy.upload_file", + self.file_upload_proxy.upload_file, + ) + + self.file_upload_proxy.done_chunks = {9} + resp = await self.file_upload_proxy.a_add_chunk( + self.mock_query, self.mock_chunk_reader + ) + self.assertEqual(resp.status, 200) + + self.file_upload_proxy.done_chunks = {} + resp = await self.file_upload_proxy.a_add_chunk( + self.mock_query, self.mock_chunk_reader + ) + self.file_upload_proxy.upload_file.assert_awaited_once() + self.assertEqual(resp.status, 201) + + async def test_upload_file(self): + """Test uploading non-segmented file.""" + self.file_upload_proxy.segmented = False + await self.file_upload_proxy.upload_file() + self.mock_client.put.assert_called_once() + self.mock_client_response.status = 408 + with self.assertRaises(aiohttp.web.HTTPRequestTimeout): + await self.file_upload_proxy.upload_file() + self.mock_client_response.status = 411 + with self.assertRaises(aiohttp.web.HTTPLengthRequired): + await self.file_upload_proxy.upload_file() + self.mock_client_response.status = 422 + with self.assertRaises(aiohttp.web.HTTPUnprocessableEntity): + await self.file_upload_proxy.upload_file() + + async def test_wait_for_chunk(self): + """Test the method doesn't get stuck in an infinite loop.""" + self.file_upload_proxy.done_chunks = {1} + await self.file_upload_proxy.a_wait_for_chunk(1)