Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ log
.idea/

.venv/

# Generated JSON files in test directories
ARGO/oceancurrent/tests/**/*.json
53 changes: 49 additions & 4 deletions ARGO/oceancurrent/oceancurrent_file_server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Copy link

Copilot AI Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DEV_MODE feature introduces a hardcoded relative path that may not work reliably across different execution contexts. Consider using an absolute path or making the test data location configurable through an environment variable to improve maintainability.

Suggested change
OCEAN_CURRENT_FILE_ROOT_PATH = f"./ARGO/oceancurrent/tests{base_path}"
test_data_path = os.getenv("OCEAN_CURRENT_TEST_DATA_PATH")
if test_data_path:
OCEAN_CURRENT_FILE_ROOT_PATH = os.path.join(test_data_path, base_path.lstrip("/"))
else:
# Use absolute path relative to this file for test data
OCEAN_CURRENT_FILE_ROOT_PATH = str((Path(__file__).parent / "tests" / base_path.lstrip("/")).resolve())

Copilot uses AI. Check for mistakes.
else:
OCEAN_CURRENT_FILE_ROOT_PATH = base_path

logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -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
}
]

Expand Down Expand Up @@ -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]

Expand Down
5 changes: 4 additions & 1 deletion ARGO/oceancurrent/test_argo_oceancurrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

124 changes: 113 additions & 11 deletions ARGO/oceancurrent/test_oceancurrent_file_server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading