Skip to content

Commit 1350050

Browse files
committed
Add unit tests for ACCESS_ESM_CMORiser driver class
1 parent 4738317 commit 1350050

2 files changed

Lines changed: 378 additions & 94 deletions

File tree

tests/unit/test_base.py

Lines changed: 154 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,181 @@
1-
from unittest.mock import patch
1+
"""
2+
Unit tests for the CMIP6_CMORiser base class.
3+
4+
These tests focus on the core functionality of the CMIP6_CMORiser class
5+
without requiring complex dependencies or data files.
6+
"""
7+
8+
from pathlib import Path
9+
from unittest.mock import Mock
210

311
import pytest
4-
import xarray as xr
512

6-
from access_mopper.base import BaseCMORiser
13+
from access_mopper.base import CMIP6_CMORiser
14+
715

16+
class TestCMIP6CMORiser:
17+
"""Unit tests for CMIP6_CMORiser base class."""
818

9-
class TestBaseCMORiser:
10-
"""Unit tests for BaseCMORiser class."""
19+
@pytest.fixture
20+
def mock_vocab(self):
21+
"""Mock CMIP6 vocabulary object."""
22+
vocab = Mock()
23+
vocab.get_table = Mock(return_value={"tas": {"units": "K"}})
24+
return vocab
1125

12-
def test_init_with_valid_params(self, mock_config, temp_dir):
26+
@pytest.fixture
27+
def mock_mapping(self):
28+
"""Mock variable mapping."""
29+
return {
30+
"CF standard Name": "air_temperature",
31+
"units": "K",
32+
"dimensions": {"time": "time", "lat": "lat", "lon": "lon"},
33+
"positive": None,
34+
}
35+
36+
def test_init_with_valid_params(self, mock_vocab, mock_mapping, temp_dir):
1337
"""Test initialization with valid parameters."""
14-
cmoriser = BaseCMORiser(
38+
cmoriser = CMIP6_CMORiser(
1539
input_paths=["test.nc"],
16-
compound_name="Amon.tas",
17-
output_path=temp_dir,
18-
**mock_config,
40+
output_path=str(temp_dir),
41+
cmor_name="tas",
42+
cmip6_vocab=mock_vocab,
43+
variable_mapping=mock_mapping,
1944
)
2045

21-
assert cmoriser.experiment_id == "historical"
22-
assert cmoriser.compound_name == "Amon.tas"
23-
assert cmoriser.mip_table == "Amon"
46+
assert cmoriser.input_paths == ["test.nc"]
47+
assert cmoriser.output_path == str(temp_dir)
2448
assert cmoriser.cmor_name == "tas"
49+
assert cmoriser.vocab == mock_vocab
50+
assert cmoriser.mapping == mock_mapping
51+
52+
def test_init_with_multiple_input_paths(self, mock_vocab, mock_mapping, temp_dir):
53+
"""Test initialization with multiple input files."""
54+
input_files = ["test1.nc", "test2.nc", "test3.nc"]
55+
cmoriser = CMIP6_CMORiser(
56+
input_paths=input_files,
57+
output_path=str(temp_dir),
58+
cmor_name="tas",
59+
cmip6_vocab=mock_vocab,
60+
variable_mapping=mock_mapping,
61+
)
62+
63+
assert cmoriser.input_paths == input_files
2564

26-
def test_init_with_invalid_compound_name(self, mock_config, temp_dir):
27-
"""Test initialization fails with invalid compound name."""
28-
with pytest.raises(ValueError, match="Invalid compound_name format"):
29-
BaseCMORiser(
30-
input_paths=["test.nc"],
31-
compound_name="invalid",
32-
output_path=temp_dir,
33-
**mock_config,
34-
)
35-
36-
def test_init_with_missing_required_params(self, temp_dir):
37-
"""Test initialization fails with missing required parameters."""
38-
with pytest.raises(TypeError):
39-
BaseCMORiser(
40-
input_paths=["test.nc"],
41-
compound_name="Amon.tas",
42-
output_path=temp_dir,
43-
# Missing required CMIP6 metadata
44-
)
45-
46-
@patch("access_mopper.base.xr.open_mfdataset")
47-
def test_load_data_single_file(
48-
self, mock_open_mfdataset, mock_netcdf_dataset, mock_config, temp_dir
65+
def test_init_with_single_input_path_string(
66+
self, mock_vocab, mock_mapping, temp_dir
4967
):
50-
"""Test loading data from single file."""
51-
mock_open_mfdataset.return_value = mock_netcdf_dataset
68+
"""Test initialization with single input path as string."""
69+
cmoriser = CMIP6_CMORiser(
70+
input_paths="single_file.nc",
71+
output_path=str(temp_dir),
72+
cmor_name="tas",
73+
cmip6_vocab=mock_vocab,
74+
variable_mapping=mock_mapping,
75+
)
76+
77+
assert cmoriser.input_paths == ["single_file.nc"]
5278

53-
cmoriser = BaseCMORiser(
79+
def test_init_with_drs_root(self, mock_vocab, mock_mapping, temp_dir):
80+
"""Test initialization with DRS root path."""
81+
drs_root = temp_dir / "drs"
82+
cmoriser = CMIP6_CMORiser(
5483
input_paths=["test.nc"],
55-
compound_name="Amon.tas",
56-
output_path=temp_dir,
57-
**mock_config,
84+
output_path=str(temp_dir),
85+
cmor_name="tas",
86+
cmip6_vocab=mock_vocab,
87+
variable_mapping=mock_mapping,
88+
drs_root=str(drs_root),
5889
)
5990

60-
result = cmoriser._load_input_data()
91+
assert cmoriser.drs_root == Path(drs_root)
92+
93+
def test_version_date_format(self, mock_vocab, mock_mapping, temp_dir):
94+
"""Test that version date is set correctly."""
95+
cmoriser = CMIP6_CMORiser(
96+
input_paths=["test.nc"],
97+
output_path=str(temp_dir),
98+
cmor_name="tas",
99+
cmip6_vocab=mock_vocab,
100+
variable_mapping=mock_mapping,
101+
)
61102

62-
mock_open_mfdataset.assert_called_once()
63-
assert isinstance(result, xr.Dataset)
103+
# Check that version_date is a string in YYYYMMDD format
104+
assert isinstance(cmoriser.version_date, str)
105+
assert len(cmoriser.version_date) == 8
106+
assert cmoriser.version_date.isdigit()
64107

65-
@patch("access_mopper.base.xr.open_mfdataset")
66-
def test_load_data_multiple_files(
67-
self, mock_open_mfdataset, mock_netcdf_dataset, mock_config, temp_dir
68-
):
69-
"""Test loading data from multiple files."""
70-
mock_open_mfdataset.return_value = mock_netcdf_dataset
71-
72-
cmoriser = BaseCMORiser(
73-
input_paths=["test1.nc", "test2.nc"],
74-
compound_name="Amon.tas",
75-
output_path=temp_dir,
76-
**mock_config,
108+
def test_type_mapping_attribute(self, mock_vocab, mock_mapping, temp_dir):
109+
"""Test that type_mapping is available as class attribute."""
110+
cmoriser = CMIP6_CMORiser(
111+
input_paths=["test.nc"],
112+
output_path=str(temp_dir),
113+
cmor_name="tas",
114+
cmip6_vocab=mock_vocab,
115+
variable_mapping=mock_mapping,
116+
)
117+
118+
# type_mapping should be available from utilities
119+
assert hasattr(cmoriser, "type_mapping")
120+
assert cmoriser.type_mapping is not None
121+
122+
def test_dataset_proxy_methods(self, mock_vocab, mock_mapping, temp_dir):
123+
"""Test that the CMORiser can proxy dataset operations."""
124+
# Create a mock dataset
125+
mock_dataset = Mock()
126+
mock_dataset.test_attr = "test_value"
127+
mock_dataset.__getitem__ = Mock(return_value="dataset_item")
128+
mock_dataset.__repr__ = Mock(return_value="<Dataset representation>")
129+
130+
cmoriser = CMIP6_CMORiser(
131+
input_paths=["test.nc"],
132+
output_path=str(temp_dir),
133+
cmor_name="tas",
134+
cmip6_vocab=mock_vocab,
135+
variable_mapping=mock_mapping,
77136
)
78137

79-
cmoriser._load_input_data()
138+
# Set the dataset
139+
cmoriser.ds = mock_dataset
80140

81-
mock_open_mfdataset.assert_called_once_with(
82-
["test1.nc", "test2.nc"],
83-
combine="by_coords",
84-
data_vars="minimal",
85-
coords="minimal",
86-
compat="override",
141+
# Test __getitem__ proxy
142+
result = cmoriser["test_key"]
143+
assert result == "dataset_item"
144+
mock_dataset.__getitem__.assert_called_with("test_key")
145+
146+
# Test __getattr__ proxy
147+
assert cmoriser.test_attr == "test_value"
148+
149+
# Test __setitem__ proxy
150+
cmoriser["new_key"] = "new_value"
151+
assert cmoriser.ds["new_key"] == "new_value"
152+
153+
# Test __repr__ proxy
154+
repr_result = repr(cmoriser)
155+
assert repr_result == "<Dataset representation>"
156+
157+
def test_dataset_none_initially(self, mock_vocab, mock_mapping, temp_dir):
158+
"""Test that dataset is None initially."""
159+
cmoriser = CMIP6_CMORiser(
160+
input_paths=["test.nc"],
161+
output_path=str(temp_dir),
162+
cmor_name="tas",
163+
cmip6_vocab=mock_vocab,
164+
variable_mapping=mock_mapping,
87165
)
88166

89-
def test_compound_name_parsing(self, mock_config, temp_dir):
90-
"""Test parsing of compound name into table and variable."""
91-
test_cases = [
92-
("Amon.tas", "Amon", "tas"),
93-
("Omon.tos", "Omon", "tos"),
94-
("day.pr", "day", "pr"),
95-
("6hrPlevPt.ua", "6hrPlevPt", "ua"),
96-
]
97-
98-
for compound_name, expected_table, expected_var in test_cases:
99-
cmoriser = BaseCMORiser(
100-
input_paths=["test.nc"],
101-
compound_name=compound_name,
102-
output_path=temp_dir,
103-
**mock_config,
104-
)
105-
assert cmoriser.mip_table == expected_table
106-
assert cmoriser.cmor_name == expected_var
107-
108-
def test_output_path_creation(self, mock_config, temp_dir):
109-
"""Test that output path is created if it doesn't exist."""
110-
non_existent_path = temp_dir / "new_output_dir"
111-
assert not non_existent_path.exists()
112-
113-
BaseCMORiser(
167+
assert cmoriser.ds is None
168+
169+
def test_getattr_fallback(self, mock_vocab, mock_mapping, temp_dir):
170+
"""Test __getattr__ behavior when dataset is None."""
171+
cmoriser = CMIP6_CMORiser(
114172
input_paths=["test.nc"],
115-
compound_name="Amon.tas",
116-
output_path=non_existent_path,
117-
**mock_config,
173+
output_path=str(temp_dir),
174+
cmor_name="tas",
175+
cmip6_vocab=mock_vocab,
176+
variable_mapping=mock_mapping,
118177
)
119178

120-
# This should create the directory
121-
assert non_existent_path.exists()
179+
# When ds is None, getattr should raise AttributeError
180+
with pytest.raises(AttributeError):
181+
_ = cmoriser.nonexistent_attribute

0 commit comments

Comments
 (0)