Coverage for tests / unit / tools / core / test_option_validators.py: 100%
83 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for option_validators module."""
3from __future__ import annotations
5from typing import Any
7import pytest
8from assertpy import assert_that
10from lintro.tools.core.option_validators import (
11 filter_none_options,
12 normalize_str_or_list,
13 validate_bool,
14 validate_int,
15 validate_list,
16 validate_positive_int,
17 validate_str,
18)
20# =============================================================================
21# validate_bool tests
22# =============================================================================
25@pytest.mark.parametrize(
26 "value",
27 [
28 pytest.param(True, id="true"),
29 pytest.param(False, id="false"),
30 pytest.param(None, id="none"),
31 ],
32)
33def test_validate_bool_accepts_valid_values(value: bool | None) -> None:
34 """Accept True, False, and None values.
36 Args:
37 value: The boolean value to validate.
38 """
39 # Should not raise - test passes if no exception
40 validate_bool(value, "test")
43@pytest.mark.parametrize(
44 ("value", "description"),
45 [
46 pytest.param("true", "string", id="string"),
47 pytest.param(1, "integer", id="int"),
48 ],
49)
50def test_validate_bool_rejects_invalid_values(value: Any, description: str) -> None:
51 """Reject non-boolean values like {description}.
53 Args:
54 value: The invalid value to test.
55 description: Description of the test case.
56 """
57 with pytest.raises(ValueError, match="must be a boolean"):
58 validate_bool(value, "test")
61# =============================================================================
62# validate_str tests
63# =============================================================================
66@pytest.mark.parametrize(
67 "value",
68 [
69 pytest.param("hello", id="non_empty_string"),
70 pytest.param("", id="empty_string"),
71 pytest.param(None, id="none"),
72 ],
73)
74def test_validate_str_accepts_valid_values(value: str | None) -> None:
75 """Accept string and None values.
77 Args:
78 value: The string value to validate.
79 """
80 # Should not raise - test passes if no exception
81 validate_str(value, "test")
84@pytest.mark.parametrize(
85 ("value", "description"),
86 [
87 pytest.param(123, "integer", id="int"),
88 pytest.param(["a", "b"], "list", id="list"),
89 ],
90)
91def test_validate_str_rejects_invalid_values(value: Any, description: str) -> None:
92 """Reject non-string values like {description}.
94 Args:
95 value: The invalid value to test.
96 description: Description of the test case.
97 """
98 with pytest.raises(ValueError, match="must be a string"):
99 validate_str(value, "test")
102# =============================================================================
103# validate_int tests
104# =============================================================================
107@pytest.mark.parametrize(
108 "value",
109 [
110 pytest.param(42, id="positive_int"),
111 pytest.param(0, id="zero"),
112 pytest.param(-5, id="negative_int"),
113 pytest.param(None, id="none"),
114 ],
115)
116def test_validate_int_accepts_valid_values(value: int | None) -> None:
117 """Accept integer and None values.
119 Args:
120 value: The integer value to validate.
121 """
122 # Should not raise - test passes if no exception
123 validate_int(value, "test")
126@pytest.mark.parametrize(
127 ("value", "description"),
128 [
129 pytest.param("42", "string", id="string"),
130 pytest.param(3.14, "float", id="float"),
131 ],
132)
133def test_validate_int_rejects_invalid_values(value: Any, description: str) -> None:
134 """Reject non-integer values like {description}.
136 Args:
137 value: The invalid value to test.
138 description: Description of the test case.
139 """
140 with pytest.raises(ValueError, match="must be an integer"):
141 validate_int(value, "test")
144def test_validate_int_with_min_value() -> None:
145 """Accept values at or above min_value."""
146 validate_int(5, "test", min_value=5) # Equal to min
147 validate_int(10, "test", min_value=5) # Above min
150def test_validate_int_rejects_below_min_value() -> None:
151 """Reject values below min_value."""
152 with pytest.raises(ValueError, match="must be at least 5"):
153 validate_int(4, "test", min_value=5)
156def test_validate_int_with_max_value() -> None:
157 """Accept values at or below max_value."""
158 validate_int(10, "test", max_value=10) # Equal to max
159 validate_int(5, "test", max_value=10) # Below max
162def test_validate_int_rejects_above_max_value() -> None:
163 """Reject values above max_value."""
164 with pytest.raises(ValueError, match="must be at most 10"):
165 validate_int(11, "test", max_value=10)
168def test_validate_int_with_range() -> None:
169 """Accept values within min and max range."""
170 validate_int(5, "test", min_value=1, max_value=10)
171 validate_int(1, "test", min_value=1, max_value=10)
172 validate_int(10, "test", min_value=1, max_value=10)
175def test_validate_int_none_with_range() -> None:
176 """Accept None even when range is specified."""
177 validate_int(None, "test", min_value=1, max_value=10)
180# =============================================================================
181# validate_positive_int tests
182# =============================================================================
185@pytest.mark.parametrize(
186 "value",
187 [
188 pytest.param(42, id="positive_int"),
189 pytest.param(None, id="none"),
190 ],
191)
192def test_validate_positive_int_accepts_valid_values(value: int | None) -> None:
193 """Accept positive integer and None values.
195 Args:
196 value: The positive integer value to validate.
197 """
198 # Should not raise - test passes if no exception
199 validate_positive_int(value, "test")
202@pytest.mark.parametrize(
203 ("value", "match_pattern"),
204 [
205 pytest.param(0, "must be positive", id="zero"),
206 pytest.param(-5, "must be positive", id="negative"),
207 pytest.param("42", "must be an integer", id="string"),
208 ],
209)
210def test_validate_positive_int_rejects_invalid_values(
211 value: Any,
212 match_pattern: str,
213) -> None:
214 """Reject zero, negative, and non-integer values.
216 Args:
217 value: The invalid value to test.
218 match_pattern: Pattern expected in the error message.
219 """
220 with pytest.raises(ValueError, match=match_pattern):
221 validate_positive_int(value, "test")
224# =============================================================================
225# validate_list tests
226# =============================================================================
229@pytest.mark.parametrize(
230 "value",
231 [
232 pytest.param([1, 2, 3], id="non_empty_list"),
233 pytest.param([], id="empty_list"),
234 pytest.param(None, id="none"),
235 ],
236)
237def test_validate_list_accepts_valid_values(value: list[Any] | None) -> None:
238 """Accept list and None values.
240 Args:
241 value: The list value to validate.
242 """
243 # Should not raise - test passes if no exception
244 validate_list(value, "test")
247@pytest.mark.parametrize(
248 ("value", "description"),
249 [
250 pytest.param("a,b,c", "string", id="string"),
251 pytest.param((1, 2, 3), "tuple", id="tuple"),
252 ],
253)
254def test_validate_list_rejects_invalid_values(value: Any, description: str) -> None:
255 """Reject non-list values like {description}.
257 Args:
258 value: The invalid value to test.
259 description: Description of the test case.
260 """
261 with pytest.raises(ValueError, match="must be a list"):
262 validate_list(value, "test")
265# =============================================================================
266# normalize_str_or_list tests
267# =============================================================================
270@pytest.mark.parametrize(
271 ("value", "expected"),
272 [
273 pytest.param(None, None, id="none_returns_none"),
274 pytest.param("hello", ["hello"], id="string_returns_list"),
275 pytest.param(["a", "b"], ["a", "b"], id="list_returns_list"),
276 ],
277)
278def test_normalize_str_or_list_valid_values(
279 value: str | list[str] | None,
280 expected: list[str] | None,
281) -> None:
282 """Normalize string to list and pass through list/None.
284 Args:
285 value: The input value to normalize.
286 expected: The expected normalized result.
287 """
288 result = normalize_str_or_list(value, "test")
289 if expected is None:
290 assert_that(result).is_none()
291 else:
292 assert_that(result).is_equal_to(expected)
295@pytest.mark.parametrize(
296 ("value", "description"),
297 [
298 pytest.param(123, "integer", id="int"),
299 pytest.param({"key": "value"}, "dict", id="dict"),
300 ],
301)
302def test_normalize_str_or_list_rejects_invalid_values(
303 value: Any,
304 description: str,
305) -> None:
306 """Reject non-string/list values like {description}.
308 Args:
309 value: The invalid value to test.
310 description: Description of the test case.
311 """
312 with pytest.raises(ValueError, match="must be a string or list"):
313 normalize_str_or_list(value, "test")
316# =============================================================================
317# filter_none_options tests
318# =============================================================================
321def test_filter_none_options_filters_none_values() -> None:
322 """Filter out None values."""
323 result = filter_none_options(a=1, b=None, c="hello", d=None)
324 assert_that(result).is_equal_to({"a": 1, "c": "hello"})
327def test_filter_none_options_empty_input() -> None:
328 """Return empty dict for empty input."""
329 result = filter_none_options()
330 assert_that(result).is_empty()
333def test_filter_none_options_all_none() -> None:
334 """Return empty dict when all values are None."""
335 result = filter_none_options(a=None, b=None)
336 assert_that(result).is_empty()
339def test_filter_none_options_no_none() -> None:
340 """Return all values when none are None."""
341 result = filter_none_options(a=1, b=2, c=3)
342 assert_that(result).is_equal_to({"a": 1, "b": 2, "c": 3})
345def test_filter_none_options_preserves_falsy_values() -> None:
346 """Preserve False, 0, empty string (not None)."""
347 result = filter_none_options(a=False, b=0, c="", d=None)
348 assert_that(result).is_equal_to({"a": False, "b": 0, "c": ""})