Skip to content

Commit 3b51fb8

Browse files
committed
test: add unit tests for GeoZarr validation script
Tests subprocess execution, timeout handling, error cases, and CLI options including file output and verbose mode.
1 parent 7e1ce6d commit 3b51fb8

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"""Tests for validate_geozarr.py - GeoZarr compliance validation."""
2+
3+
import json
4+
import subprocess
5+
6+
import pytest
7+
8+
from scripts.validate_geozarr import main, validate_geozarr
9+
10+
11+
class TestValidateGeozarr:
12+
"""Test validation logic."""
13+
14+
def test_successful_validation(self, mocker):
15+
"""Validation passes when subprocess exits 0."""
16+
mock_run = mocker.patch("scripts.validate_geozarr.subprocess.run")
17+
mock_run.return_value = mocker.Mock(
18+
returncode=0,
19+
stdout="All checks passed",
20+
stderr="",
21+
)
22+
23+
result = validate_geozarr("s3://bucket/dataset.zarr")
24+
25+
assert result["valid"] is True
26+
assert result["exit_code"] == 0
27+
assert "All checks passed" in result["stdout"]
28+
mock_run.assert_called_once_with(
29+
["eopf-geozarr", "validate", "s3://bucket/dataset.zarr"],
30+
capture_output=True,
31+
text=True,
32+
timeout=300,
33+
)
34+
35+
def test_failed_validation(self, mocker):
36+
"""Validation fails when subprocess exits non-zero."""
37+
mock_run = mocker.patch("scripts.validate_geozarr.subprocess.run")
38+
mock_run.return_value = mocker.Mock(
39+
returncode=1,
40+
stdout="",
41+
stderr="Missing required attribute: spatial_ref",
42+
)
43+
44+
result = validate_geozarr("s3://bucket/invalid.zarr")
45+
46+
assert result["valid"] is False
47+
assert result["exit_code"] == 1
48+
assert "Missing required attribute" in result["stderr"]
49+
50+
def test_verbose_flag_passed(self, mocker):
51+
"""Verbose flag is passed to subprocess."""
52+
mock_run = mocker.patch("scripts.validate_geozarr.subprocess.run")
53+
mock_run.return_value = mocker.Mock(returncode=0, stdout="", stderr="")
54+
55+
validate_geozarr("s3://bucket/dataset.zarr", verbose=True)
56+
57+
mock_run.assert_called_once_with(
58+
["eopf-geozarr", "validate", "s3://bucket/dataset.zarr", "--verbose"],
59+
capture_output=True,
60+
text=True,
61+
timeout=300,
62+
)
63+
64+
def test_timeout_handling(self, mocker):
65+
"""Handles subprocess timeout gracefully."""
66+
mock_run = mocker.patch("scripts.validate_geozarr.subprocess.run")
67+
mock_run.side_effect = subprocess.TimeoutExpired(
68+
cmd=["eopf-geozarr", "validate"], timeout=300
69+
)
70+
71+
result = validate_geozarr("s3://bucket/large.zarr")
72+
73+
assert result["valid"] is False
74+
assert result["exit_code"] == -1
75+
assert "timeout" in result["error"].lower()
76+
77+
def test_subprocess_exception(self, mocker):
78+
"""Handles subprocess exceptions."""
79+
mock_run = mocker.patch("scripts.validate_geozarr.subprocess.run")
80+
mock_run.side_effect = FileNotFoundError("eopf-geozarr not found")
81+
82+
result = validate_geozarr("s3://bucket/dataset.zarr")
83+
84+
assert result["valid"] is False
85+
assert result["exit_code"] == -1
86+
assert "not found" in result["error"]
87+
88+
89+
class TestMainCLI:
90+
"""Test CLI interface."""
91+
92+
def test_basic_validation(self, mocker):
93+
"""Basic validation without options."""
94+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
95+
mock_validate.return_value = {
96+
"valid": True,
97+
"exit_code": 0,
98+
"stdout": "OK",
99+
"stderr": "",
100+
}
101+
mocker.patch("sys.argv", ["validate_geozarr.py", "s3://bucket/dataset.zarr"])
102+
103+
with pytest.raises(SystemExit) as exc_info:
104+
main()
105+
106+
assert exc_info.value.code == 0
107+
mock_validate.assert_called_once_with("s3://bucket/dataset.zarr", False)
108+
109+
def test_with_item_id(self, mocker):
110+
"""Includes item ID in output."""
111+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
112+
mock_validate.return_value = {"valid": True, "exit_code": 0}
113+
mocker.patch(
114+
"sys.argv",
115+
["validate_geozarr.py", "s3://bucket/dataset.zarr", "--item-id", "test-item-123"],
116+
)
117+
118+
with pytest.raises(SystemExit) as exc_info:
119+
main()
120+
121+
assert exc_info.value.code == 0
122+
123+
def test_with_output_file(self, mocker, tmp_path):
124+
"""Writes results to output file."""
125+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
126+
mock_validate.return_value = {"valid": True, "exit_code": 0}
127+
128+
output_file = tmp_path / "results.json"
129+
mocker.patch(
130+
"sys.argv",
131+
["validate_geozarr.py", "s3://bucket/dataset.zarr", "--output", str(output_file)],
132+
)
133+
134+
with pytest.raises(SystemExit):
135+
main()
136+
137+
assert output_file.exists()
138+
data = json.loads(output_file.read_text())
139+
assert data["validation"]["valid"] is True
140+
141+
def test_verbose_flag(self, mocker):
142+
"""Verbose flag is passed through."""
143+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
144+
mock_validate.return_value = {"valid": True, "exit_code": 0}
145+
mocker.patch("sys.argv", ["validate_geozarr.py", "s3://bucket/dataset.zarr", "--verbose"])
146+
147+
with pytest.raises(SystemExit):
148+
main()
149+
150+
mock_validate.assert_called_once_with("s3://bucket/dataset.zarr", True)
151+
152+
def test_failed_validation_exits_1(self, mocker):
153+
"""Failed validation exits with code 1."""
154+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
155+
mock_validate.return_value = {"valid": False, "exit_code": 1}
156+
mocker.patch("sys.argv", ["validate_geozarr.py", "s3://bucket/invalid.zarr"])
157+
158+
with pytest.raises(SystemExit) as exc_info:
159+
main()
160+
161+
assert exc_info.value.code == 1
162+
163+
def test_creates_output_directory(self, mocker, tmp_path):
164+
"""Creates output directory if it doesn't exist."""
165+
mock_validate = mocker.patch("scripts.validate_geozarr.validate_geozarr")
166+
mock_validate.return_value = {"valid": True, "exit_code": 0}
167+
168+
nested_output = tmp_path / "deep" / "nested" / "results.json"
169+
mocker.patch(
170+
"sys.argv",
171+
["validate_geozarr.py", "s3://bucket/dataset.zarr", "--output", str(nested_output)],
172+
)
173+
174+
with pytest.raises(SystemExit):
175+
main()
176+
177+
assert nested_output.exists()
178+
assert nested_output.parent.exists()

0 commit comments

Comments
 (0)