|
| 1 | +from unittest import IsolatedAsyncioTestCase |
| 2 | +from unittest.mock import patch, MagicMock |
| 3 | + |
| 4 | +from fastapi import HTTPException |
| 5 | + |
| 6 | +from daemon import daemon |
| 7 | +from tests._utils import import_module, AsyncMock, mock_dict |
| 8 | + |
| 9 | + |
| 10 | +class TestDaemon(IsolatedAsyncioTestCase): |
| 11 | + def get_decorated_function(self, fastapi_patch: MagicMock, decorator_name, *decorator_args, **decorator_kwargs): |
| 12 | + functions = [] |
| 13 | + decorator = MagicMock(side_effect=functions.append) |
| 14 | + getattr(fastapi_patch(), decorator_name).side_effect = ( |
| 15 | + lambda *args, **kwargs: decorator if (args, kwargs) == (decorator_args, decorator_kwargs) else MagicMock() |
| 16 | + ) |
| 17 | + fastapi_patch.reset_mock() |
| 18 | + |
| 19 | + module = import_module("daemon.daemon") |
| 20 | + |
| 21 | + decorator.assert_called_once() |
| 22 | + self.assertEqual(1, len(functions)) |
| 23 | + return module, functions[0] |
| 24 | + |
| 25 | + @patch("daemon.endpoints.register_collections") |
| 26 | + @patch("fastapi.FastAPI") |
| 27 | + async def test__global_vars(self, fastapi_patch: MagicMock, register_collections_patch: MagicMock): |
| 28 | + module = import_module("daemon.daemon") |
| 29 | + |
| 30 | + fastapi_patch.assert_called_once_with(title="Python Daemon") |
| 31 | + self.assertEqual(fastapi_patch(), module.app) |
| 32 | + register_collections_patch.assert_called_once_with(fastapi_patch()) |
| 33 | + self.assertEqual(register_collections_patch(), module.endpoints) |
| 34 | + |
| 35 | + @patch("daemon.database.db") |
| 36 | + @patch("fastapi.FastAPI") |
| 37 | + async def test__db_session(self, fastapi_patch: MagicMock, db_patch: MagicMock): |
| 38 | + _, db_session = self.get_decorated_function(fastapi_patch, "middleware", "http") |
| 39 | + |
| 40 | + events = [] |
| 41 | + db_patch.create_session.side_effect = lambda: events.append(0) |
| 42 | + expected = MagicMock() |
| 43 | + call_next = AsyncMock(side_effect=lambda _: events.append(1) or expected) |
| 44 | + request = MagicMock() |
| 45 | + db_patch.commit = AsyncMock(side_effect=lambda: events.append(2)) |
| 46 | + db_patch.close = AsyncMock(side_effect=lambda: events.append(3)) |
| 47 | + |
| 48 | + result = await db_session(request, call_next) |
| 49 | + |
| 50 | + self.assertEqual([0, 1, 2, 3], events) |
| 51 | + call_next.assert_called_once_with(request) |
| 52 | + self.assertEqual(expected, result) |
| 53 | + |
| 54 | + @patch("fastapi.FastAPI") |
| 55 | + async def test__on_startup__no_tables(self, fastapi_patch: MagicMock): |
| 56 | + module, on_startup = self.get_decorated_function(fastapi_patch, "on_event", "startup") |
| 57 | + |
| 58 | + module.SQL_CREATE_TABLES = False |
| 59 | + db = module.db = AsyncMock() |
| 60 | + |
| 61 | + await on_startup() |
| 62 | + |
| 63 | + db.create_tables.assert_not_called() |
| 64 | + |
| 65 | + @patch("fastapi.FastAPI") |
| 66 | + async def test__on_startup__create_tables(self, fastapi_patch: MagicMock): |
| 67 | + module, on_startup = self.get_decorated_function(fastapi_patch, "on_event", "startup") |
| 68 | + |
| 69 | + module.SQL_CREATE_TABLES = True |
| 70 | + db = module.db = AsyncMock() |
| 71 | + |
| 72 | + await on_startup() |
| 73 | + |
| 74 | + db.create_tables.assert_called_once_with() |
| 75 | + |
| 76 | + @patch("daemon.endpoint_collection.format_docs") |
| 77 | + @patch("daemon.schemas.daemon.EndpointCollectionModel") |
| 78 | + @patch("daemon.utils.responses") |
| 79 | + @patch("daemon.authorization.HTTPAuthorization") |
| 80 | + @patch("fastapi.params.Depends") |
| 81 | + @patch("fastapi.FastAPI") |
| 82 | + async def test__daemon_endpoints( |
| 83 | + self, |
| 84 | + fastapi_patch: MagicMock, |
| 85 | + depends_patch: MagicMock, |
| 86 | + httpauthorization_patch: MagicMock, |
| 87 | + responses_patch: MagicMock, |
| 88 | + endpoint_collection_model_patch: MagicMock, |
| 89 | + format_docs_patch: MagicMock, |
| 90 | + ): |
| 91 | + format_docs_patch.side_effect = lambda f: setattr(f, "docs_formatted", True) or f # noqa: B010 |
| 92 | + module, daemon_endpoints = self.get_decorated_function( |
| 93 | + fastapi_patch, |
| 94 | + "get", |
| 95 | + "/daemon/endpoints", |
| 96 | + name="List Daemon Endpoints", |
| 97 | + tags=["daemon"], |
| 98 | + dependencies=[[depends_patch(), depends_patch.reset_mock()][0]], |
| 99 | + responses=[responses_patch(), responses_patch.reset_mock()][0], |
| 100 | + ) |
| 101 | + |
| 102 | + httpauthorization_patch.assert_called_once_with() |
| 103 | + depends_patch.assert_called_once_with(httpauthorization_patch()) |
| 104 | + responses_patch.assert_called_once_with(list[endpoint_collection_model_patch]) |
| 105 | + self.assertEqual(True, daemon_endpoints.docs_formatted) |
| 106 | + |
| 107 | + module.endpoints = MagicMock() |
| 108 | + self.assertEqual(module.endpoints, await daemon_endpoints()) |
| 109 | + |
| 110 | + @patch("daemon.daemon.JSONResponse") |
| 111 | + @patch("daemon.daemon.HTTPException") |
| 112 | + async def test__make_exception(self, httpexception_patch: MagicMock, jsonresponse_patch: MagicMock): |
| 113 | + status_code = MagicMock() |
| 114 | + kwargs = mock_dict(5, True) |
| 115 | + |
| 116 | + result = daemon._make_exception(status_code, **kwargs) |
| 117 | + |
| 118 | + httpexception_patch.assert_called_once_with(status_code) |
| 119 | + jsonresponse_patch.assert_called_once_with( |
| 120 | + {**kwargs, "error": f"{status_code} {httpexception_patch().detail}"}, |
| 121 | + status_code, |
| 122 | + ) |
| 123 | + self.assertEqual(jsonresponse_patch(), result) |
| 124 | + |
| 125 | + @patch("daemon.exceptions.api_exception.APIException") |
| 126 | + @patch("fastapi.FastAPI") |
| 127 | + async def test__handle_api_exception(self, fastapi_patch: MagicMock, apiexception_patch: MagicMock): |
| 128 | + _, handle_api_exception = self.get_decorated_function(fastapi_patch, "exception_handler", apiexception_patch) |
| 129 | + exception = MagicMock() |
| 130 | + |
| 131 | + result = await handle_api_exception(..., exception) |
| 132 | + |
| 133 | + exception.make_response.assert_called_once_with() |
| 134 | + self.assertEqual(exception.make_response(), result) |
| 135 | + |
| 136 | + @patch("fastapi.exceptions.HTTPException") |
| 137 | + @patch("fastapi.FastAPI") |
| 138 | + async def test__handle_http_exception(self, fastapi_patch: MagicMock, httpexception_patch: MagicMock): |
| 139 | + module, handle_http_exception = self.get_decorated_function( |
| 140 | + fastapi_patch, |
| 141 | + "exception_handler", |
| 142 | + httpexception_patch, |
| 143 | + ) |
| 144 | + exception = MagicMock() |
| 145 | + _make_exception, module._make_exception = module._make_exception, MagicMock() |
| 146 | + |
| 147 | + result = await handle_http_exception(..., exception) |
| 148 | + |
| 149 | + module._make_exception.assert_called_once_with(exception.status_code) |
| 150 | + self.assertEqual(module._make_exception(), result) |
| 151 | + module._make_exception = _make_exception |
| 152 | + |
| 153 | + @patch("fastapi.exceptions.RequestValidationError") |
| 154 | + @patch("fastapi.FastAPI") |
| 155 | + async def test__handle_unprocessable_entity( |
| 156 | + self, |
| 157 | + fastapi_patch: MagicMock, |
| 158 | + request_validation_error_patch: MagicMock, |
| 159 | + ): |
| 160 | + module, handle_unprocessable_entity = self.get_decorated_function( |
| 161 | + fastapi_patch, |
| 162 | + "exception_handler", |
| 163 | + request_validation_error_patch, |
| 164 | + ) |
| 165 | + exception = MagicMock() |
| 166 | + _make_exception, module._make_exception = module._make_exception, MagicMock() |
| 167 | + |
| 168 | + result = await handle_unprocessable_entity(..., exception) |
| 169 | + |
| 170 | + exception.errors.assert_called_once_with() |
| 171 | + module._make_exception.assert_called_once_with(422, detail=exception.errors()) |
| 172 | + self.assertEqual(module._make_exception(), result) |
| 173 | + module._make_exception = _make_exception |
| 174 | + |
| 175 | + @patch("fastapi.FastAPI") |
| 176 | + async def test__handle_internal_server_error(self, fastapi_patch: MagicMock): |
| 177 | + module, handle_internal_server_error = self.get_decorated_function( |
| 178 | + fastapi_patch, |
| 179 | + "exception_handler", |
| 180 | + Exception, |
| 181 | + ) |
| 182 | + exception = MagicMock() |
| 183 | + _make_exception, module._make_exception = module._make_exception, MagicMock() |
| 184 | + |
| 185 | + result = await handle_internal_server_error(..., exception) |
| 186 | + |
| 187 | + module._make_exception.assert_called_once_with(500) |
| 188 | + self.assertEqual(module._make_exception(), result) |
| 189 | + module._make_exception = _make_exception |
| 190 | + |
| 191 | + @patch("fastapi.FastAPI") |
| 192 | + async def test__handle_not_found(self, fastapi_patch: MagicMock): |
| 193 | + module, handle_not_found = self.get_decorated_function( |
| 194 | + fastapi_patch, |
| 195 | + "get", |
| 196 | + "/{_:path}", |
| 197 | + include_in_schema=False, |
| 198 | + ) |
| 199 | + |
| 200 | + with self.assertRaises(HTTPException) as context: |
| 201 | + await handle_not_found() |
| 202 | + |
| 203 | + self.assertEqual(404, context.exception.status_code) |
0 commit comments