Coverage for tests/test_validation.py: 95.45%

44 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-17 17:18 +0200

1"""Tests for the `validation` module.""" 

2 

3from __future__ import annotations 

4 

5from inspect import Parameter 

6from typing import Any, Callable 

7 

8import pytest 

9 

10from duty.validation import _get_params_caster, cast_arg, to_bool 

11from tests.fixtures import validation as valfix 

12 

13 

14@pytest.mark.parametrize( 

15 ("value", "expected"), 

16 [ 

17 ("y", True), 

18 ("Y", True), 

19 ("yes", True), 

20 ("YES", True), 

21 ("on", True), 

22 ("ON", True), 

23 ("true", True), 

24 ("TRUE", True), 

25 ("anything else", True), 

26 ("-1", True), 

27 ("1", True), 

28 ("", False), 

29 ("n", False), 

30 ("N", False), 

31 ("no", False), 

32 ("NO", False), 

33 ("false", False), 

34 ("FALSE", False), 

35 ("off", False), 

36 ("OFF", False), 

37 ], 

38) 

39def test_bool_casting(value: str, expected: bool) -> None: 

40 """Check that we correctly cast string values to booleans. 

41 

42 Parameters: 

43 value: The value to cast. 

44 expected: The expected result. 

45 """ 

46 assert to_bool(value) == expected 

47 

48 

49class CustomType1: 

50 """Dummy type to test type-casting.""" 

51 

52 def __init__(self, value: str): # noqa: D107 

53 self.value = value 

54 

55 def __eq__(self, other: object): 

56 return self.value == other.value # type: ignore[attr-defined] 

57 

58 

59class CustomType2: 

60 """Dummy type to test type-casting.""" 

61 

62 def __init__(self, value, extra): # noqa: ANN001,D107 

63 ... # pragma: no cover 

64 

65 

66@pytest.mark.parametrize( 

67 ("arg", "annotation", "expected"), 

68 [ 

69 ("hello", Parameter.empty, "hello"), 

70 ("off", bool, False), 

71 ("on", bool, True), 

72 ("1", int, 1), 

73 ("1", float, 1.0), 

74 ("fie", str, "fie"), 

75 ("fih", CustomType1, CustomType1("fih")), 

76 ("foh", CustomType2, "foh"), 

77 ], 

78) 

79def test_cast_arg(arg: str, annotation: Any, expected: Any) -> None: 

80 """Check that arguments are properly casted given an annotation. 

81 

82 Parameters: 

83 arg: The argument value to cast. 

84 annotation: The annotation to use. 

85 expected: The expected result. 

86 """ 

87 assert cast_arg(arg, annotation) == expected 

88 

89 

90_parametrization = [ 

91 (valfix.no_params, (), {}, (), {}), 

92 (valfix.pos_or_kw_param, ("1",), {}, (1,), {}), 

93 (valfix.pos_or_kw_param, (), {"a": "1"}, (), {"a": 1}), 

94 (valfix.pos_or_kw_params, ("1", "2"), {}, (1, 2), {}), 

95 (valfix.pos_or_kw_params, ("1",), {"b": "2"}, (1,), {"b": 2}), 

96 (valfix.pos_or_kw_params, (), {"a": "1", "b": "2"}, (), {"a": 1, "b": 2}), 

97 (valfix.varpos_param, (), {}, (), {}), 

98 (valfix.varpos_param, ("1", "2"), {}, (1, 2), {}), 

99 (valfix.pos_and_varpos_param, ("1",), {}, (1,), {}), 

100 (valfix.pos_and_varpos_param, ("1", "2"), {}, (1, 2), {}), 

101 (valfix.pos_and_varpos_param, ("1", "2", "3"), {}, (1, 2, 3), {}), 

102 (valfix.kwonly_param, (), {"b": "1"}, (), {"b": 1}), 

103 (valfix.kwonly_param, ("2",), {"b": "1"}, (2,), {"b": 1}), 

104 (valfix.kwonly_param, ("2", "3"), {"b": "1"}, (2, 3), {"b": 1}), 

105 (valfix.varkw_param, ("1",), {}, (1,), {}), 

106 (valfix.varkw_param, ("1",), {"b": "2"}, (1,), {"b": 2}), 

107 (valfix.varkw_param, ("1",), {"b": "2", "c": "3"}, (1,), {"b": 2, "c": 3}), 

108 (valfix.varkw_no_annotation, (), {"a": "1"}, (), {"a": "1"}), 

109 (valfix.posonly_marker, ("1", "2"), {}, (1, 2), {}), 

110 (valfix.posonly_marker, ("1",), {"b": "2"}, (1,), {"b": 2}), 

111 (valfix.kwonly_marker, ("1",), {"b": "2"}, (1,), {"b": 2}), 

112 (valfix.kwonly_marker, (), {"a": "1", "b": "2"}, (), {"a": 1, "b": 2}), 

113 (valfix.only_markers, ("1",), {"b": "2", "c": "3"}, (1,), {"b": 2, "c": 3}), 

114 (valfix.only_markers, ("1", "2"), {"c": "3"}, (1, 2), {"c": 3}), 

115 (valfix.full, ("1", "2", "3", "4"), {"d": "5", "e": "6", "f": "7"}, (1, 2, 3, 4), {"d": 5, "e": 6, "f": 7}), 

116] 

117 

118 

119@pytest.mark.parametrize( 

120 ("func", "args", "kwargs", "expected_args", "expected_kwargs"), 

121 _parametrization, 

122) 

123def test_params_caster(func: Callable, args: tuple, kwargs: dict, expected_args: tuple, expected_kwargs: dict) -> None: 

124 """Test the whole parameters casting helper class. 

125 

126 Parameters: 

127 func: The function to work with. 

128 args: The positional arguments to cast. 

129 kwargs: The keyword arguments to cast. 

130 expected_args: The expected positional arguments result. 

131 expected_kwargs: The expected keyword arguments result. 

132 """ 

133 caster = _get_params_caster(func, *args, **kwargs) 

134 new_args, new_kwargs = caster.cast(*args, **kwargs) 

135 assert new_args == expected_args 

136 assert new_kwargs == expected_kwargs 

137 

138 

139def test_casting_based_on_default_value_type() -> None: 

140 """Test that we cast according to the default value type when there is no annotation.""" 

141 

142 def func(ctx, a=0): # noqa: ANN202, ANN001 

143 ... 

144 

145 caster = _get_params_caster(func, a="1") 

146 _, kwargs = caster.cast(a="1") 

147 assert kwargs == {"a": 1} 

148 

149 

150def test_validating_modern_annotations() -> None: 

151 """Test modern type annotations in function signatures.""" 

152 

153 def func(ctx, a: int | None = None): # noqa: ANN202, ANN001 

154 ... 

155 

156 caster = _get_params_caster(func, a=1) 

157 _, kwargs = caster.cast(a="1") 

158 assert kwargs == {"a": 1} 

159 caster = _get_params_caster(func, a=None) 

160 _, kwargs = caster.cast(a=None) 

161 assert kwargs == {"a": None} 

162 caster = _get_params_caster(func) 

163 _, kwargs = caster.cast() 

164 assert kwargs == {}