Skip to content

Commit e13d720

Browse files
committed
test: add config tests
1 parent 1dea483 commit e13d720

1 file changed

Lines changed: 257 additions & 0 deletions

File tree

tests/test_config.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import json
2+
3+
import pytest
4+
5+
from vesskel.config import (
6+
CONFIG_SCHEMA_VERSION,
7+
ExtractionConfig,
8+
OutputConfig,
9+
PipelineConfig,
10+
load_pipeline_config,
11+
save_pipeline_config,
12+
)
13+
14+
15+
class TestExtractionConfig:
16+
def test_defaults(self):
17+
c = ExtractionConfig()
18+
assert c.branches is True
19+
assert c.branch_text is True
20+
assert c.summary is True
21+
assert c.fractal_dimension is False
22+
assert c.vessel_radius is False
23+
24+
def test_custom_values(self):
25+
c = ExtractionConfig(
26+
branches=False,
27+
branch_text=False,
28+
summary=False,
29+
fractal_dimension=True,
30+
vessel_radius=True,
31+
)
32+
assert c.branches is False
33+
assert c.branch_text is False
34+
assert c.summary is False
35+
assert c.fractal_dimension is True
36+
assert c.vessel_radius is True
37+
38+
def test_round_trip_dict(self):
39+
original = ExtractionConfig(
40+
branches=True,
41+
branch_text=False,
42+
summary=True,
43+
fractal_dimension=True,
44+
vessel_radius=False,
45+
)
46+
restored = ExtractionConfig.from_dict(original.to_dict())
47+
assert restored == original
48+
49+
def test_from_dict_defaults_on_missing_keys(self):
50+
c = ExtractionConfig.from_dict({})
51+
assert c == ExtractionConfig()
52+
53+
def test_from_dict_warns_on_unknown_keys(self, capsys):
54+
ExtractionConfig.from_dict({"branches": True, "foo": 1, "bar": 2})
55+
captured = capsys.readouterr()
56+
assert "ignored unknown keys" in captured.err
57+
assert "'bar'" in captured.err
58+
assert "'foo'" in captured.err
59+
60+
61+
class TestOutputConfig:
62+
def test_defaults(self):
63+
c = OutputConfig()
64+
assert c.write_skeleton_npy is True
65+
assert c.write_skeleton_png is False
66+
assert c.write_summary_csv is True
67+
assert c.write_branch_csv is False
68+
assert c.write_radius is False
69+
70+
def test_round_trip_dict(self):
71+
original = OutputConfig(
72+
write_skeleton_npy=False,
73+
write_skeleton_png=True,
74+
write_summary_csv=True,
75+
write_branch_csv=True,
76+
write_radius=True,
77+
)
78+
restored = OutputConfig.from_dict(original.to_dict())
79+
assert restored == original
80+
81+
def test_from_none_defaults(self):
82+
c = OutputConfig.from_dict(None)
83+
assert c == OutputConfig()
84+
85+
def test_from_empty_dict_defaults(self):
86+
c = OutputConfig.from_dict({})
87+
assert c == OutputConfig()
88+
89+
def test_from_dict_coerces_bools(self):
90+
c = OutputConfig.from_dict({"write_summary_csv": 0, "write_skeleton_png": 1})
91+
assert c.write_summary_csv is False
92+
assert c.write_skeleton_png is True
93+
94+
c = OutputConfig.from_dict({"write_summary_csv": 1, "write_skeleton_png": 0})
95+
assert c.write_summary_csv is True
96+
assert c.write_skeleton_png is False
97+
98+
def test_from_dict_falls_back_on_falsy(self):
99+
c = OutputConfig.from_dict(0)
100+
assert c == OutputConfig()
101+
c = OutputConfig.from_dict("")
102+
assert c == OutputConfig()
103+
104+
def test_from_dict_warns_on_unknown_keys(self, capsys):
105+
OutputConfig.from_dict({"write_summary_csv": True, "nope": 99})
106+
captured = capsys.readouterr()
107+
assert "ignored unknown keys" in captured.err
108+
assert "'nope'" in captured.err
109+
110+
111+
class TestPipelineConfig:
112+
def test_round_trip_dict(self):
113+
original = PipelineConfig(
114+
extraction=ExtractionConfig(fractal_dimension=True, vessel_radius=True),
115+
output=OutputConfig(write_branch_csv=True, write_radius=True),
116+
)
117+
as_dict = original.to_dict()
118+
restored = PipelineConfig.from_dict(as_dict)
119+
assert restored == original
120+
121+
def test_default_schema_version(self):
122+
c = PipelineConfig(
123+
extraction=ExtractionConfig(),
124+
output=OutputConfig(),
125+
)
126+
assert c.schema_version == CONFIG_SCHEMA_VERSION
127+
128+
def test_from_dict_preserves_schema_version_in_object(self):
129+
data = {
130+
"schema_version": CONFIG_SCHEMA_VERSION,
131+
"extraction": {},
132+
"output": {},
133+
}
134+
c = PipelineConfig.from_dict(data)
135+
assert c.schema_version == CONFIG_SCHEMA_VERSION
136+
137+
def test_from_dict_unsupported_schema_version(self):
138+
data = {
139+
"schema_version": 999,
140+
"extraction": {},
141+
"output": {},
142+
}
143+
with pytest.raises(ValueError, match="Unsupported schema_version"):
144+
PipelineConfig.from_dict(data)
145+
146+
def test_from_dict_rejects_none(self):
147+
with pytest.raises(ValueError, match="must be an object"):
148+
PipelineConfig.from_dict(None)
149+
150+
def test_from_dict_rejects_non_dict(self):
151+
with pytest.raises(ValueError, match="must be an object"):
152+
PipelineConfig.from_dict(["not", "a", "dict"])
153+
154+
def test_from_dict_rejects_non_dict_extraction(self):
155+
data = {
156+
"schema_version": CONFIG_SCHEMA_VERSION,
157+
"extraction": "bad",
158+
"output": {},
159+
}
160+
with pytest.raises(ValueError, match="'extraction' must be an object"):
161+
PipelineConfig.from_dict(data)
162+
163+
def test_from_dict_rejects_non_dict_output(self):
164+
data = {
165+
"schema_version": CONFIG_SCHEMA_VERSION,
166+
"extraction": {},
167+
"output": 123,
168+
}
169+
with pytest.raises(ValueError, match="'output' must be an object"):
170+
PipelineConfig.from_dict(data)
171+
172+
def test_from_dict_missing_schema_version_defaults(self):
173+
data = {"extraction": {}, "output": {}}
174+
c = PipelineConfig.from_dict(data)
175+
assert c.schema_version == CONFIG_SCHEMA_VERSION
176+
177+
def test_to_dict_structure(self):
178+
c = PipelineConfig(
179+
extraction=ExtractionConfig(),
180+
output=OutputConfig(),
181+
)
182+
d = c.to_dict()
183+
assert "schema_version" in d
184+
assert "extraction" in d
185+
assert "output" in d
186+
assert isinstance(d["extraction"], dict)
187+
assert isinstance(d["output"], dict)
188+
189+
def test_from_dict_warns_on_unknown_keys(self, capsys):
190+
data = {
191+
"schema_version": CONFIG_SCHEMA_VERSION,
192+
"extraction": {},
193+
"output": {},
194+
"unknown": "should warn",
195+
}
196+
PipelineConfig.from_dict(data)
197+
captured = capsys.readouterr()
198+
assert "ignored unknown keys" in captured.err
199+
assert "'unknown'" in captured.err
200+
201+
202+
class TestConfigFileIO:
203+
def test_save_and_load_round_trip(self, tmp_path):
204+
config = PipelineConfig(
205+
extraction=ExtractionConfig(vessel_radius=True),
206+
output=OutputConfig(write_skeleton_png=True, write_radius=True),
207+
)
208+
path = tmp_path / "config.json"
209+
save_pipeline_config(config, path)
210+
211+
loaded = load_pipeline_config(path)
212+
assert loaded == config
213+
214+
def test_save_creates_valid_json(self, tmp_path):
215+
config = PipelineConfig(
216+
extraction=ExtractionConfig(),
217+
output=OutputConfig(),
218+
)
219+
path = tmp_path / "pipeline.json"
220+
save_pipeline_config(config, path)
221+
222+
with path.open(encoding="utf-8") as f:
223+
raw = json.load(f)
224+
225+
assert raw["schema_version"] == CONFIG_SCHEMA_VERSION
226+
assert raw["extraction"]["branches"] is True
227+
assert raw["output"]["write_summary_csv"] is True
228+
229+
def test_load_missing_file(self, tmp_path):
230+
with pytest.raises(FileNotFoundError):
231+
load_pipeline_config(tmp_path / "nonexistent.json")
232+
233+
def test_load_malformed_json(self, tmp_path):
234+
path = tmp_path / "bad.json"
235+
path.write_text("{invalid", encoding="utf-8")
236+
with pytest.raises(json.JSONDecodeError):
237+
load_pipeline_config(path)
238+
239+
def test_save_to_string_path(self, tmp_path):
240+
config = PipelineConfig(
241+
extraction=ExtractionConfig(),
242+
output=OutputConfig(),
243+
)
244+
path = str(tmp_path / "str_save.json")
245+
save_pipeline_config(config, path)
246+
loaded = load_pipeline_config(path)
247+
assert loaded == config
248+
249+
def test_load_from_string_path(self, tmp_path):
250+
config = PipelineConfig(
251+
extraction=ExtractionConfig(),
252+
output=OutputConfig(),
253+
)
254+
path = tmp_path / "str_config.json"
255+
save_pipeline_config(config, path)
256+
loaded = load_pipeline_config(str(path))
257+
assert loaded == config

0 commit comments

Comments
 (0)