|
1 | 1 | import json |
| 2 | +import urllib.parse |
| 3 | +import warnings |
2 | 4 | from io import BytesIO, TextIOWrapper |
3 | 5 | from pathlib import Path |
4 | | -from unittest.mock import Mock, patch |
| 6 | +from unittest.mock import ANY, MagicMock, Mock, mock_open, patch |
5 | 7 |
|
6 | 8 | import numpy as np |
7 | 9 | import pandas as pd |
|
15 | 17 | NullHandler, |
16 | 18 | PersistentDict, |
17 | 19 | ResultCollector, |
| 20 | + load_previous_engineroom_results, |
| 21 | +) |
| 22 | +from fourinsight.engineroom.utils._core import ( |
| 23 | + _build_download_url, |
| 24 | + _download_and_save_file, |
| 25 | + _get_all_previous_file_names, |
18 | 26 | ) |
19 | 27 |
|
20 | 28 | REMOTE_FILE_PATH = Path(__file__).parent / "testdata/a_test_file.json" |
| 29 | +API_BASE_URL = "https://api.4insight.io" |
21 | 30 |
|
22 | 31 |
|
23 | 32 | @pytest.fixture |
@@ -55,6 +64,25 @@ def azure_blob_handler_mocked(mock_from_connection_string): |
55 | 64 | return handler |
56 | 65 |
|
57 | 66 |
|
| 67 | +@pytest.fixture |
| 68 | +def previous_file_names(): |
| 69 | + filenames = [ |
| 70 | + { |
| 71 | + "fileName": "config.json", |
| 72 | + "navigableFileName": "config.json", |
| 73 | + "safeName": "config.json", |
| 74 | + }, |
| 75 | + { |
| 76 | + "fileName": "SN00569 - 23AT/2024-12-02_131637/sensor_info/sensor_info.csv", |
| 77 | + "navigableFileName": "SN00569 - 23AT*2024-12-02_131637*sensor_info*sensor_info.csv", |
| 78 | + "safeName": urllib.parse.quote( |
| 79 | + "SN00569 - 23AT*2024-12-02_131637*sensor_info*sensor_info.csv" |
| 80 | + ), |
| 81 | + }, |
| 82 | + ] |
| 83 | + return filenames |
| 84 | + |
| 85 | + |
58 | 86 | class Test_BaseHandler: |
59 | 87 | def test__init__(self): |
60 | 88 | handler = BaseHandler() |
@@ -1210,3 +1238,147 @@ def test_delete_rows_truncate_int_both_none(self): |
1210 | 1238 | ).astype({"a": "float64", "b": "string", "c": "Int64", "d": "float64"}) |
1211 | 1239 |
|
1212 | 1240 | pd.testing.assert_frame_equal(df_out, df_expect) |
| 1241 | + |
| 1242 | + |
| 1243 | +def test__build_download_url(previous_file_names): |
| 1244 | + app_id = "12345" |
| 1245 | + for i in range(len(previous_file_names)): |
| 1246 | + navigable_filename = previous_file_names[i]["navigableFileName"] |
| 1247 | + safe_name = previous_file_names[i]["safeName"] |
| 1248 | + url = _build_download_url(app_id, navigable_filename) |
| 1249 | + assert ( |
| 1250 | + url |
| 1251 | + == f"{API_BASE_URL}/v1.0/Applications/{app_id}/results/{safe_name}/download" |
| 1252 | + ) |
| 1253 | + |
| 1254 | + |
| 1255 | +class Test__download_and_save_file: |
| 1256 | + def setup_method(self): |
| 1257 | + # Common mocks |
| 1258 | + self.mock_session = MagicMock() |
| 1259 | + self.mock_response = MagicMock() |
| 1260 | + self.mock_response.content = b"this is the files" |
| 1261 | + self.mock_session.get.return_value = self.mock_response |
| 1262 | + |
| 1263 | + self.url = "https://4insight.io/engineroom/result1.csv" |
| 1264 | + self.path = Path("output/results1.csv") |
| 1265 | + |
| 1266 | + @patch("fourinsight.engineroom.utils._core.open", new_callable=mock_open) |
| 1267 | + @patch.object(Path, "mkdir") |
| 1268 | + def test_download_success(self, mock_mkdir, mock_file): |
| 1269 | + _download_and_save_file(self.mock_session, self.url, self.path) |
| 1270 | + |
| 1271 | + self.mock_session.get.assert_called_once_with(self.url) |
| 1272 | + self.mock_response.raise_for_status.assert_called_once() |
| 1273 | + mock_file.assert_called_once_with(self.path, "wb") |
| 1274 | + mock_file().write.assert_called_once_with(b"this is the files") |
| 1275 | + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) |
| 1276 | + |
| 1277 | + @patch("fourinsight.engineroom.utils._core.open", new_callable=mock_open) |
| 1278 | + @patch.object(Path, "mkdir") |
| 1279 | + def test_raises_exception_on_http_error(self, mock_mkdir, mock_file): |
| 1280 | + self.mock_response.raise_for_status.side_effect = RuntimeError("HTTP error") |
| 1281 | + |
| 1282 | + with pytest.raises(RuntimeError, match="HTTP error"): |
| 1283 | + _download_and_save_file(self.mock_session, self.url, self.path) |
| 1284 | + |
| 1285 | + @patch("fourinsight.engineroom.utils._core.open", new_callable=mock_open) |
| 1286 | + @patch.object(Path, "mkdir") |
| 1287 | + def test_creates_directory_if_missing(self, mock_mkdir, mock_file): |
| 1288 | + _download_and_save_file(self.mock_session, self.url, self.path) |
| 1289 | + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) |
| 1290 | + |
| 1291 | + |
| 1292 | +class Test__get_all_previous_file_names: |
| 1293 | + def setup_method(self): |
| 1294 | + # Common mocks |
| 1295 | + self.mock_session = MagicMock() |
| 1296 | + self.mock_response = MagicMock() |
| 1297 | + |
| 1298 | + self.mock_response.raise_for_status.return_value = None |
| 1299 | + self.mock_session.get.return_value = self.mock_response |
| 1300 | + |
| 1301 | + self.url = "https://4insight.io/engineroom/result1.csv" |
| 1302 | + self.path = Path("output/results1.csv") |
| 1303 | + |
| 1304 | + def test_successful_response(self, previous_file_names): |
| 1305 | + self.mock_response.json.return_value = previous_file_names |
| 1306 | + app_id = "app123" |
| 1307 | + results = _get_all_previous_file_names(app_id, self.mock_session) |
| 1308 | + assert results == previous_file_names |
| 1309 | + self.mock_session.get.assert_called_once_with( |
| 1310 | + f"{API_BASE_URL}/v1.0/Applications/{app_id}/results" |
| 1311 | + ) |
| 1312 | + self.mock_response.raise_for_status.assert_called_once() |
| 1313 | + |
| 1314 | + def test_empty_results_returns_warning(self): |
| 1315 | + self.mock_response.json.return_value = [] |
| 1316 | + app_id = "app123" |
| 1317 | + with pytest.warns( |
| 1318 | + UserWarning, match=f"No results found for application ID {app_id}." |
| 1319 | + ): |
| 1320 | + _get_all_previous_file_names(app_id, self.mock_session) |
| 1321 | + |
| 1322 | + |
| 1323 | +class Test_load_previous_engineroom_results: |
| 1324 | + def setup_method(self): |
| 1325 | + self.mock_session = MagicMock() |
| 1326 | + self.mock_response = MagicMock() |
| 1327 | + |
| 1328 | + self.mock_response.raise_for_status.return_value = None |
| 1329 | + self.mock_session.get.return_value = self.mock_response |
| 1330 | + |
| 1331 | + self.url = "https://4insight.io/engineroom/result1.csv" |
| 1332 | + self.path = Path("output/results1.csv") |
| 1333 | + |
| 1334 | + @patch("fourinsight.engineroom.utils._core._download_and_save_file") |
| 1335 | + @patch("fourinsight.engineroom.utils._core._get_all_previous_file_names") |
| 1336 | + def test_download_all( |
| 1337 | + self, |
| 1338 | + mock__get_all_previous_file_names, |
| 1339 | + mock__download_and_save_file, |
| 1340 | + previous_file_names, |
| 1341 | + ): |
| 1342 | + mock__get_all_previous_file_names.return_value = previous_file_names |
| 1343 | + |
| 1344 | + load_previous_engineroom_results("app123", self.mock_session, download_all=True) |
| 1345 | + assert mock__get_all_previous_file_names.call_count == 1 |
| 1346 | + assert mock__download_and_save_file.call_count == len(previous_file_names) |
| 1347 | + for i in range(len(previous_file_names)): |
| 1348 | + mock__download_and_save_file.assert_any_call( |
| 1349 | + self.mock_session, |
| 1350 | + ANY, |
| 1351 | + Path("output") / previous_file_names[i]["fileName"], |
| 1352 | + ) |
| 1353 | + |
| 1354 | + @patch("fourinsight.engineroom.utils._core._download_and_save_file") |
| 1355 | + @patch("fourinsight.engineroom.utils._core._get_all_previous_file_names") |
| 1356 | + def test_download_single_file( |
| 1357 | + self, |
| 1358 | + mock__get_all_previous_file_names, |
| 1359 | + mock__download_and_save_file, |
| 1360 | + previous_file_names, |
| 1361 | + ): |
| 1362 | + mock__get_all_previous_file_names.return_value = previous_file_names |
| 1363 | + |
| 1364 | + load_previous_engineroom_results( |
| 1365 | + "app123", self.mock_session, previous_file_names[1]["fileName"] |
| 1366 | + ) |
| 1367 | + assert mock__get_all_previous_file_names.call_count == 1 |
| 1368 | + mock__download_and_save_file.assert_called_once_with( |
| 1369 | + self.mock_session, ANY, Path("output") / previous_file_names[1]["fileName"] |
| 1370 | + ) |
| 1371 | + |
| 1372 | + @patch("fourinsight.engineroom.utils._core._get_all_previous_file_names") |
| 1373 | + def test_raise_when_file_not_found( |
| 1374 | + self, mock__get_all_previous_file_names, previous_file_names |
| 1375 | + ): |
| 1376 | + mock__get_all_previous_file_names.return_value = previous_file_names |
| 1377 | + |
| 1378 | + with pytest.warns( |
| 1379 | + UserWarning, |
| 1380 | + match="missing_file.json not found in application app123 results.", |
| 1381 | + ): |
| 1382 | + load_previous_engineroom_results( |
| 1383 | + "app123", self.mock_session, "missing_file.json" |
| 1384 | + ) |
0 commit comments