1import configparser
2from dataclasses import dataclass
3from datetime import datetime
4from decimal import Decimal
5from pathlib import Path
6
7import pytest
8
9from mako.testing._config import ConfigValueTypeError
10from mako.testing._config import MissingConfig
11from mako.testing._config import MissingConfigItem
12from mako.testing._config import MissingConfigSection
13from mako.testing._config import ReadsCfg
14from mako.testing.assertions import assert_raises_message_with_given_cause
15from mako.testing.assertions import assert_raises_with_given_cause
16
17PATH_TO_TEST_CONFIG = Path(__file__).parent / "dummy.cfg"
18
19
20@dataclass
21class BasicConfig(ReadsCfg):
22    int_value: int
23    bool_value: bool
24    float_value: float
25    str_value: str
26
27    section_header = "basic_values"
28
29
30@dataclass
31class BooleanConfig(ReadsCfg):
32    yes: bool
33    one: bool
34    true: bool
35    on: bool
36    no: bool
37    zero: bool
38    false: bool
39    off: bool
40
41    section_header = "boolean_values"
42
43
44@dataclass
45class UnsupportedTypesConfig(ReadsCfg):
46    decimal_value: Decimal
47    datetime_value: datetime
48
49    section_header = "additional_types"
50
51
52@dataclass
53class SupportedTypesConfig(ReadsCfg):
54    decimal_value: Decimal
55    datetime_value: datetime
56
57    section_header = "additional_types"
58    converters = {
59        Decimal: lambda v: Decimal(str(v)),
60        datetime: lambda v: datetime.fromisoformat(v),
61    }
62
63
64@dataclass
65class NonexistentSectionConfig(ReadsCfg):
66    some_value: str
67    another_value: str
68
69    section_header = "i_dont_exist"
70
71
72@dataclass
73class TypeMismatchConfig(ReadsCfg):
74    int_value: int
75
76    section_header = "type_mismatch"
77
78
79@dataclass
80class MissingItemConfig(ReadsCfg):
81    present_item: str
82    missing_item: str
83
84    section_header = "missing_item"
85
86
87class BasicConfigTest:
88    @pytest.fixture(scope="class")
89    def config(self):
90        return BasicConfig.from_cfg_file(PATH_TO_TEST_CONFIG)
91
92    def test_coercions(self, config):
93        assert isinstance(config.int_value, int)
94        assert isinstance(config.bool_value, bool)
95        assert isinstance(config.float_value, float)
96        assert isinstance(config.str_value, str)
97
98    def test_values(self, config):
99        assert config.int_value == 15421
100        assert config.bool_value == True
101        assert config.float_value == 14.01
102        assert config.str_value == "Ceci n'est pas une chaîne"
103
104    def test_error_on_loading_from_nonexistent_file(self):
105        assert_raises_with_given_cause(
106            MissingConfig,
107            FileNotFoundError,
108            BasicConfig.from_cfg_file,
109            "./n/o/f/i/l/e/h.ere",
110        )
111
112    def test_error_on_loading_from_nonexistent_section(self):
113        assert_raises_with_given_cause(
114            MissingConfigSection,
115            configparser.NoSectionError,
116            NonexistentSectionConfig.from_cfg_file,
117            PATH_TO_TEST_CONFIG,
118        )
119
120
121class BooleanConfigTest:
122    @pytest.fixture(scope="class")
123    def config(self):
124        return BooleanConfig.from_cfg_file(PATH_TO_TEST_CONFIG)
125
126    def test_values(self, config):
127        assert config.yes is True
128        assert config.one is True
129        assert config.true is True
130        assert config.on is True
131        assert config.no is False
132        assert config.zero is False
133        assert config.false is False
134        assert config.off is False
135
136
137class UnsupportedTypesConfigTest:
138    @pytest.fixture(scope="class")
139    def config(self):
140        return UnsupportedTypesConfig.from_cfg_file(PATH_TO_TEST_CONFIG)
141
142    def test_values(self, config):
143        assert config.decimal_value == "100001.01"
144        assert config.datetime_value == "2021-12-04 00:05:23.283"
145
146
147class SupportedTypesConfigTest:
148    @pytest.fixture(scope="class")
149    def config(self):
150        return SupportedTypesConfig.from_cfg_file(PATH_TO_TEST_CONFIG)
151
152    def test_values(self, config):
153        assert config.decimal_value == Decimal("100001.01")
154        assert config.datetime_value == datetime(2021, 12, 4, 0, 5, 23, 283000)
155
156
157class TypeMismatchConfigTest:
158    def test_error_on_load(self):
159        assert_raises_message_with_given_cause(
160            ConfigValueTypeError,
161            "Wrong value type for int_value",
162            ValueError,
163            TypeMismatchConfig.from_cfg_file,
164            PATH_TO_TEST_CONFIG,
165        )
166
167
168class MissingItemConfigTest:
169    def test_error_on_load(self):
170        assert_raises_message_with_given_cause(
171            MissingConfigItem,
172            "No config item for missing_item",
173            configparser.NoOptionError,
174            MissingItemConfig.from_cfg_file,
175            PATH_TO_TEST_CONFIG,
176        )
177