Skip to content

Commit 2298416

Browse files
authored
Refactor boolean parsing to use a dedicated function and add tests (#2015)
1 parent ef2277f commit 2298416

2 files changed

Lines changed: 102 additions & 2 deletions

File tree

pyoverkiz/types.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
StateType = str | int | float | bool | dict[str, Any] | list[Any] | None
1212

1313

14-
DATA_TYPE_TO_PYTHON: dict[DataType, Callable[[Any], StateType]] = {
14+
def _parse_bool(value: str) -> bool:
15+
return value.lower() in ("true", "1")
16+
17+
18+
DATA_TYPE_TO_PYTHON: dict[DataType, Callable[[str], StateType]] = {
1519
DataType.INTEGER: int,
1620
DataType.DATE: int,
1721
DataType.FLOAT: float,
18-
DataType.BOOLEAN: bool,
22+
DataType.BOOLEAN: _parse_bool,
1923
DataType.JSON_ARRAY: json.loads,
2024
DataType.JSON_OBJECT: json.loads,
2125
}

tests/test_models.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,102 @@ def test_invalid_json_string_raises(self):
703703
value="[not-valid-json",
704704
)
705705

706+
@pytest.mark.parametrize(
707+
("raw", "expected"),
708+
[
709+
("true", True),
710+
("True", True),
711+
("TRUE", True),
712+
("1", True),
713+
("false", False),
714+
("False", False),
715+
("FALSE", False),
716+
("0", False),
717+
("", False),
718+
("yes", False),
719+
("no", False),
720+
],
721+
)
722+
def test_boolean_string_casting(self, raw: str, expected: bool):
723+
"""Cloud API returns booleans as strings; only 'true'/'1' are truthy."""
724+
state = EventState(name="state", type=DataType.BOOLEAN, value=raw)
725+
726+
assert state.value is expected
727+
728+
def test_boolean_native_passthrough(self):
729+
"""Local API returns native booleans; they must not be re-cast."""
730+
true_state = EventState(name="state", type=DataType.BOOLEAN, value=True)
731+
false_state = EventState(name="state", type=DataType.BOOLEAN, value=False)
732+
733+
assert true_state.value is True
734+
assert false_state.value is False
735+
736+
@pytest.mark.parametrize(
737+
("raw", "data_type", "expected"),
738+
[
739+
("42", DataType.INTEGER, 42),
740+
("-1", DataType.INTEGER, -1),
741+
("0", DataType.INTEGER, 0),
742+
("3.14", DataType.FLOAT, pytest.approx(3.14)),
743+
("-1.5", DataType.FLOAT, pytest.approx(-1.5)),
744+
("0.0", DataType.FLOAT, pytest.approx(0.0)),
745+
("0", DataType.FLOAT, pytest.approx(0.0)),
746+
("12345", DataType.DATE, 12345),
747+
("0", DataType.DATE, 0),
748+
("[1, 2]", DataType.JSON_ARRAY, [1, 2]),
749+
("[]", DataType.JSON_ARRAY, []),
750+
('{"foo": 1}', DataType.JSON_OBJECT, {"foo": 1}),
751+
("{}", DataType.JSON_OBJECT, {}),
752+
],
753+
)
754+
def test_other_type_casting(self, raw: str, data_type: DataType, expected):
755+
"""Non-boolean string values are cast correctly."""
756+
state = EventState(name="state", type=data_type, value=raw)
757+
758+
assert state.value == expected
759+
760+
@pytest.mark.parametrize(
761+
("raw", "data_type"),
762+
[
763+
("abc", DataType.INTEGER),
764+
("abc", DataType.FLOAT),
765+
],
766+
)
767+
def test_invalid_numeric_string_raises(self, raw: str, data_type: DataType):
768+
"""Non-numeric strings raise ValueError for numeric types."""
769+
with pytest.raises(ValueError, match="abc"):
770+
EventState(name="state", type=data_type, value=raw)
771+
772+
def test_string_type_not_cast(self):
773+
"""STRING type values are left as-is, not passed through any caster."""
774+
state = EventState(name="state", type=DataType.STRING, value="hello")
775+
776+
assert state.value == "hello"
777+
778+
def test_empty_string_type_not_cast(self):
779+
"""Empty STRING type values are preserved."""
780+
state = EventState(name="state", type=DataType.STRING, value="")
781+
782+
assert state.value == ""
783+
784+
@pytest.mark.parametrize(
785+
("value", "data_type"),
786+
[
787+
(42, DataType.INTEGER),
788+
(0, DataType.INTEGER),
789+
(3.14, DataType.FLOAT),
790+
(0.0, DataType.FLOAT),
791+
({"foo": 1}, DataType.JSON_OBJECT),
792+
([1, 2], DataType.JSON_ARRAY),
793+
],
794+
)
795+
def test_native_value_not_cast(self, value: object, data_type: DataType):
796+
"""Already-typed values (from local API) skip casting entirely."""
797+
state = EventState(name="state", type=data_type, value=value)
798+
799+
assert state.value == value
800+
assert type(state.value) is type(value)
801+
706802

707803
def test_command_to_payload_omits_none():
708804
"""Command.to_payload omits None fields from the resulting payload."""

0 commit comments

Comments
 (0)