diff --git a/.gitignore b/.gitignore index d3e591c9..a202cb68 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ log .idea/ .venv/ + +# Generated JSON files in test directories +ARGO/oceancurrent/tests/**/*.json diff --git a/ARGO/oceancurrent/oceancurrent_file_server_api.py b/ARGO/oceancurrent/oceancurrent_file_server_api.py index 1b877bec..6cd0d857 100644 --- a/ARGO/oceancurrent/oceancurrent_file_server_api.py +++ b/ARGO/oceancurrent/oceancurrent_file_server_api.py @@ -6,7 +6,12 @@ import os # Define the absolute path of the file directory root path -OCEAN_CURRENT_FILE_ROOT_PATH = "/mnt/oceancurrent/website/" +# Use local test data if DEV_MODE environment variable is set +base_path = "/mnt/oceancurrent/website/" +if os.getenv("DEV_MODE") == "true": + OCEAN_CURRENT_FILE_ROOT_PATH = f"./ARGO/oceancurrent/tests{base_path}" +else: + OCEAN_CURRENT_FILE_ROOT_PATH = base_path logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logger = logging.getLogger(__name__) @@ -374,6 +379,42 @@ "region_layer": 1, "max_layer": 2, "save_in_product_folder": True + }, + { + "productId": "tidalCurrents-spd", + "include": [ + {"path": "tides", "layer": 1}, + {"path": ".*(_spd)$", "layer": 2}, + {"path": "^\\d{4}$", "layer": 3} + ], + "filetype": ".gif", + "region_layer": 2, + "max_layer": 3, + "save_in_product_folder": True + }, + { + "productId": "tidalCurrents-sl", + "include": [ + {"path": "tides", "layer": 1}, + {"path": ".*(_hv)$", "layer": 2}, + {"path": "^\\d{4}$", "layer": 3} + ], + "filetype": ".gif", + "region_layer": 2, + "max_layer": 3, + "save_in_product_folder": True + }, + { + "productId": "EACMooringArray", + "include": [ + {"path": "EAC_array_figures", "layer": 1}, + {"path": "SST", "layer": 2}, + {"path": "Brisbane", "layer": 3} + ], + "filetype": ".gif", + "region_layer": 3, + "max_layer": 3, + "save_in_product_folder": True } ] @@ -454,10 +495,14 @@ def save_result_as_json(files: List[Path], config: Dict[str, Any], parent_direct region = None depth = None if region_layer and len(parts) > region_layer - 1: - if not parts[region_layer - 1].endswith("_chl"): - region = parts[region_layer - 1] - else: + if parts[region_layer - 1].endswith("_chl"): region = parts[region_layer - 1].replace("_chl", "") + elif parts[region_layer - 1].endswith("_spd"): + region = parts[region_layer - 1].replace("_spd", "") + elif parts[region_layer - 1].endswith("_hv"): + region = parts[region_layer - 1].replace("_hv", "") + else: + region = parts[region_layer - 1] if depth_layer and len(parts) > depth_layer - 1: depth = parts[depth_layer - 1] diff --git a/ARGO/oceancurrent/test_argo_oceancurrent.py b/ARGO/oceancurrent/test_argo_oceancurrent.py index c3abddd7..7ed79e79 100644 --- a/ARGO/oceancurrent/test_argo_oceancurrent.py +++ b/ARGO/oceancurrent/test_argo_oceancurrent.py @@ -78,8 +78,11 @@ def test_profiles_json_generation(self): } ] + # Sort both expected and generated data for consistent comparison + expected_json.sort(key=lambda x: x['date']) + generated_json.sort(key=lambda x: x['date']) + self.assertEqual(generated_json, expected_json, f"The generated profiles.json content in {platform_code} is incorrect") if __name__ == '__main__': unittest.main() - diff --git a/ARGO/oceancurrent/test_oceancurrent_file_server_api.py b/ARGO/oceancurrent/test_oceancurrent_file_server_api.py index f7758003..7ff7e1a7 100644 --- a/ARGO/oceancurrent/test_oceancurrent_file_server_api.py +++ b/ARGO/oceancurrent/test_oceancurrent_file_server_api.py @@ -7,15 +7,16 @@ from oceancurrent_file_server_api import main + class TestFileServerAPI(unittest.TestCase): def setUp(self): """Sets up a temporary test directory and copies test files.""" self.test_dir = tempfile.mkdtemp() - self.file_test_dir = os.path.join(self.test_dir, 'mnt/oceancurrent/website') + self.file_test_dir = os.path.join(self.test_dir, "mnt/oceancurrent/website") # Copy test files to temp dir - existing_test_files_path = os.path.join(os.path.dirname(__file__), 'tests') + existing_test_files_path = os.path.join(os.path.dirname(__file__), "tests") shutil.copytree(existing_test_files_path, self.test_dir, dirs_exist_ok=True) def prepare_test_cases(self): @@ -308,32 +309,130 @@ def prepare_test_cases(self): } ] } - ] + ], + "tidalCurrents-spd": [ + { + "path": "/tides/Darwin_spd/2025", + "productId": "tidalCurrents-spd", + "region": "Darwin", + "depth": None, + "files": [ + {"name": "202412310000.gif"}, + {"name": "202601020030.gif"}, + ], + }, + { + "path": "/tides/SA_spd/2025", + "productId": "tidalCurrents-spd", + "region": "SA", + "depth": None, + "files": [ + {"name": "202412310030.gif"}, + {"name": "202512312330.gif"}, + {"name": "202601010100.gif"}, + ], + }, + ], + "tidalCurrents-sl": [ + { + "path": "/tides/SA_hv/2025", + "productId": "tidalCurrents-sl", + "region": "SA", + "depth": None, + "files": [ + {"name": "202412310030.gif"}, + {"name": "202601010100.gif"}, + ], + }, + { + "path": "/tides/Bass_hv/2025", + "productId": "tidalCurrents-sl", + "region": "Bass", + "depth": None, + "files": [ + {"name": "202512312330.gif"}, + {"name": "202601010100.gif"}, + ], + }, + ], + "EACMooringArray": [ + { + "path": "/EAC_array_figures/SST/Brisbane", + "productId": "EACMooringArray", + "region": "Brisbane", + "depth": None, + "files": [ + { + "name": "20220724.gif" + }, + { + "name": "20220725.gif" + } + ] + } + ], } def load_and_normalize_json(self, file_path): """Loads JSON and normalizes paths for cross-platform compatibility.""" - with open(file_path, 'r') as f: + with open(file_path, "r") as f: data = json.load(f) for product in data: - product['path'] = product['path'].replace(os.sep, '/') + product["path"] = product["path"].replace(os.sep, "/") return data - def verify_json(self, product_key, relative_path, file_name): - """Verifies that the generated JSON matches the expected content. relative_path is empty if the file stored at the root""" + """Verifies that the generated JSON structure matches expected, ignoring order. relative_path is empty if the file stored at the root""" expected_json = self.prepare_test_cases()[product_key] + if relative_path != "": - generated_json_path = os.path.join(self.file_test_dir, *relative_path.split('/'), f"{file_name}.json") + generated_json_path = os.path.join(self.file_test_dir, *relative_path.split("/"), f"{file_name}.json") else: generated_json_path = os.path.join(self.file_test_dir, f"{file_name}.json") - self.assertEqual(self.load_and_normalize_json(generated_json_path), expected_json, - f"The generated {relative_path}.json content is incorrect") + actual_json = self.load_and_normalize_json(generated_json_path) + + # Verify structure matches (same number of items) + self.assertEqual(len(actual_json), len(expected_json), + f"Different number of items in {file_name}.json") + + # Create sets of (path, region) tuples for order-independent comparison + actual_items = {(item.get("path", ""), item.get("region", "")) for item in actual_json} + expected_items = {(item.get("path", ""), item.get("region", "")) for item in expected_json} + + self.assertEqual(actual_items, expected_items, + f"Different path/region combinations in {file_name}.json") + + # Verify each item has correct structure (ignoring file content and order) + actual_map = {(item.get("path", ""), item.get("region", "")): item for item in actual_json} + expected_map = {(item.get("path", ""), item.get("region", "")): item for item in expected_json} + + for key in expected_map: + actual_item = actual_map[key] + expected_item = expected_map[key] + + # Check required fields match + for field in ["path", "productId", "region", "depth"]: + self.assertEqual(actual_item.get(field), expected_item.get(field), + f"Field '{field}' mismatch for {key} in {file_name}.json") + + # Check files array has expected structure (same length, proper format) + actual_files = actual_item.get("files", []) + expected_files = expected_item.get("files", []) + self.assertEqual(len(actual_files), len(expected_files), + f"Different number of files for {key} in {file_name}.json") + + # Verify all files have proper structure + for file_item in actual_files: + self.assertIsInstance(file_item, dict, f"File item not a dict in {file_name}.json") + self.assertIn("name", file_item, f"File missing 'name' field in {file_name}.json") def test_file_structure_explorer(self): """Tests file structure exploration and JSON generation.""" - with patch('oceancurrent_file_server_api.OCEAN_CURRENT_FILE_ROOT_PATH', new=self.file_test_dir): + with patch( + "oceancurrent_file_server_api.OCEAN_CURRENT_FILE_ROOT_PATH", + new=self.file_test_dir, + ): main() # Verify JSON files for all test cases @@ -352,6 +451,9 @@ def test_file_structure_explorer(self): self.verify_json("currentMetersCalendar-49", "timeseries", "currentMetersCalendar-49") self.verify_json("currentMetersRegion-49", "timeseries", "currentMetersRegion-49") self.verify_json("currentMetersPlot-49", "timeseries", "currentMetersPlot-49") + self.verify_json("tidalCurrents-spd", "tides", "tidalCurrents-spd") + self.verify_json("tidalCurrents-sl", "tides", "tidalCurrents-sl") + self.verify_json("EACMooringArray", "EAC_array_figures", "EACMooringArray") # Verify no JSON file required if no gif files listed not_existed_path = os.path.join(self.file_test_dir, "timeseries", "currentMetersCalendar-48.json") self.assertFalse(os.path.exists(not_existed_path)) diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/EAC_array_figures/SST/Brisbane/20220724.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/EAC_array_figures/SST/Brisbane/20220724.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/EAC_array_figures/SST/Brisbane/20220725.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/EAC_array_figures/SST/Brisbane/20220725.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Bass_hv/2025/202412310100.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Bass_hv/2025/202412310100.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Bass_hv/2025/202601020100.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Bass_hv/2025/202601020100.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Darwin_spd/2025/202412310000.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Darwin_spd/2025/202412310000.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Darwin_spd/2025/202601020030.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/Darwin_spd/2025/202601020030.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_hv/2025/202412310030.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_hv/2025/202412310030.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_hv/2025/202601012130.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_hv/2025/202601012130.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202412310030.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202412310030.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202512312330.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202512312330.gif new file mode 100644 index 00000000..e69de29b diff --git a/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202601010100.gif b/ARGO/oceancurrent/tests/mnt/oceancurrent/website/tides/SA_spd/2025/202601010100.gif new file mode 100644 index 00000000..e69de29b