forked from OvertureMaps/schema
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_cli_functions.py
More file actions
257 lines (197 loc) · 9.03 KB
/
Copy pathtest_cli_functions.py
File metadata and controls
257 lines (197 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
"""Tests for CLI helper functions (load_input, perform_validation)."""
import io
import json
from pathlib import Path
import pytest
import yaml
from click.exceptions import UsageError
from conftest import build_feature
from overture.schema.cli.commands import load_input, perform_validation, resolve_types
from pydantic import ValidationError
class TestLoadInput:
"""Tests for load_input function.
Note: Happy-path file and stdin loading are covered by integration tests
in test_cli_commands.py. These tests focus on error cases and edge cases.
"""
def test_load_input_file_not_found(self) -> None:
"""Test that load_input raises UsageError when file doesn't exist."""
with pytest.raises(UsageError) as exc_info:
load_input(Path("/nonexistent/path/to/file.yaml"))
assert "is not a file" in str(exc_info.value)
def test_load_input_path_is_directory(
self, cli_runner: pytest.FixtureRequest
) -> None:
"""Test that load_input raises UsageError when path is a directory.
Note: cli_runner provides isolated filesystem for test file creation.
"""
# Create a directory
Path("testdir").mkdir()
with pytest.raises(UsageError) as exc_info:
load_input(Path("testdir"))
assert "is not a file" in str(exc_info.value)
def test_load_input_invalid_yaml(self, cli_runner: pytest.FixtureRequest) -> None:
"""Test that load_input raises YAMLError for invalid YAML.
Note: cli_runner provides isolated filesystem for test file creation.
"""
invalid_yaml = "test.yaml"
with open(invalid_yaml, "w") as f:
f.write("invalid: yaml: content: [")
with pytest.raises(yaml.YAMLError):
load_input(Path(invalid_yaml))
def test_load_input_handles_json(self, cli_runner: pytest.FixtureRequest) -> None:
"""Test that load_input can parse JSON files.
Note: cli_runner provides isolated filesystem for test file creation.
"""
json_file = "test.json"
feature = build_feature()
with open(json_file, "w") as f:
f.write(json.dumps(feature))
data, source_name = load_input(Path(json_file))
assert isinstance(data, dict)
assert data["id"] == "test"
assert source_name == json_file
def test_load_input_handles_list(self, cli_runner: pytest.FixtureRequest) -> None:
"""Test that load_input can parse YAML lists.
Note: cli_runner provides isolated filesystem for test file creation.
"""
list_file = "list.yaml"
feature1 = build_feature(id="test1")
feature2 = build_feature(id="test2")
with open(list_file, "w") as f:
f.write(yaml.dump([feature1, feature2]))
data, source_name = load_input(Path(list_file))
assert isinstance(data, list)
assert len(data) == 2
assert data[0]["id"] == "test1"
@pytest.mark.parametrize(
"extension",
[".txt", ".csv", ".xml", ".data", ""],
)
def test_load_input_warns_unexpected_extension(
self,
cli_runner: pytest.FixtureRequest,
capsys: pytest.CaptureFixture,
extension: str,
) -> None:
"""Test that load_input warns about unexpected file extensions.
Note: cli_runner provides isolated filesystem for test file creation.
"""
filename = f"data{extension}"
feature = build_feature()
with open(filename, "w") as f:
f.write(json.dumps(feature))
load_input(Path(filename))
captured = capsys.readouterr()
assert "Warning" in captured.err
assert "unexpected extension" in captured.err
assert filename in captured.err
@pytest.mark.parametrize(
"extension",
[".json", ".yaml", ".yml", ".geojson"],
)
def test_load_input_no_warning_expected_extension(
self,
cli_runner: pytest.FixtureRequest,
capsys: pytest.CaptureFixture,
extension: str,
) -> None:
"""Test that load_input does not warn for expected file extensions.
Note: cli_runner provides isolated filesystem for test file creation.
"""
filename = f"data{extension}"
feature = build_feature()
with open(filename, "w") as f:
f.write(json.dumps(feature))
load_input(Path(filename))
captured = capsys.readouterr()
assert captured.err == ""
def test_load_input_binary_file(self, cli_runner: pytest.FixtureRequest) -> None:
"""Test graceful failure on binary files.
Note: cli_runner provides isolated filesystem for test file creation.
"""
binary_file = "binary.dat"
with open(binary_file, "wb") as f:
f.write(b"\x00\x01\x02\xff\xfe")
with pytest.raises((yaml.YAMLError, UnicodeDecodeError)):
load_input(Path(binary_file))
def test_load_input_unicode_filenames(
self, cli_runner: pytest.FixtureRequest
) -> None:
"""Test files with unicode names.
Note: cli_runner provides isolated filesystem for test file creation.
"""
unicode_filename = "données_測試_🏢.json"
feature = build_feature()
with open(unicode_filename, "w", encoding="utf-8") as f:
f.write(json.dumps(feature))
data, source_name = load_input(Path(unicode_filename))
assert isinstance(data, dict)
assert data["id"] == "test"
assert source_name == unicode_filename
def test_load_input_jsonl_from_stdin(
self, cli_runner: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test that load_input handles newline-delimited JSON (JSONL) from stdin.
JSONL format is commonly used for streaming GeoJSON features where each line
is a complete JSON object/feature.
"""
feature1 = build_feature(id="test1")
feature2 = build_feature(id="test2")
jsonl_input = f"{json.dumps(feature1)}\n{json.dumps(feature2)}\n"
# Mock stdin with JSONL content
monkeypatch.setattr("sys.stdin", io.StringIO(jsonl_input))
data, source_name = load_input(Path("-"))
assert source_name == "<stdin>"
assert isinstance(data, list)
assert len(data) == 2
assert data[0]["id"] == "test1"
assert data[1]["id"] == "test2"
class TestPerformValidation:
"""Tests for perform_validation function.
Note: Happy-path validation (single features, lists, FeatureCollections, flat format)
is covered by integration tests in test_cli_commands.py. These tests focus on edge
cases and validation logic specific to the function.
"""
def test_perform_validation_raises_for_invalid_single_feature(self) -> None:
"""Test that perform_validation raises ValidationError for single invalid feature."""
data = build_feature(id=None) # Missing required 'id'
model_type = resolve_types(False, None, ("buildings",), ())
with pytest.raises(ValidationError) as exc_info:
perform_validation(data, model_type)
errors = exc_info.value.errors()
assert any("id" in error.get("loc", ()) for error in errors)
def test_perform_validation_raises_for_invalid_list_item(self) -> None:
"""Test that perform_validation raises ValidationError for invalid list item."""
feature1 = build_feature(id="test1")
feature2 = build_feature(
id=None, coordinates=[[[2, 2], [3, 2], [3, 3], [2, 3], [2, 2]]]
)
data = [feature1, feature2]
model_type = resolve_types(False, None, ("buildings",), ())
with pytest.raises(ValidationError) as exc_info:
perform_validation(data, model_type)
errors = exc_info.value.errors()
# Check that error location includes list index 1
assert any(1 in error.get("loc", ()) for error in errors)
def test_perform_validation_empty_list(self) -> None:
"""Test validating an empty list (edge case)."""
data: list[dict[str, object]] = []
model_type = resolve_types(False, None, ("buildings",), ())
# Should not raise
perform_validation(data, model_type)
def test_perform_validation_empty_feature_collection(self) -> None:
"""Test validating an empty FeatureCollection (edge case)."""
data = {"type": "FeatureCollection", "features": []}
model_type = resolve_types(False, None, ("buildings",), ())
# Should not raise
perform_validation(data, model_type)
def test_perform_validation_with_different_themes(self) -> None:
"""Test validating features from different themes."""
data = build_feature(theme="buildings", type="building")
# Should work with buildings theme
buildings_type = resolve_types(False, None, ("buildings",), ())
perform_validation(data, buildings_type)
# Should fail with wrong theme
places_type = resolve_types(False, None, ("places",), ())
with pytest.raises(ValidationError):
perform_validation(data, places_type)