File: test_config.py

package info (click to toggle)
python-asusrouter 1.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,856 kB
  • sloc: python: 20,497; makefile: 3
file content (348 lines) | stat: -rw-r--r-- 10,957 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""Tests for the configuration module."""

from __future__ import annotations

import concurrent.futures
import random
from typing import Any

import pytest

from asusrouter.config import (
    CONFIG_DEFAULT_BOOL,
    CONFIG_DEFAULT_INT,
    TYPES_DEFAULT,
    ARConfig,
    ARConfigKey as ARConfKey,
    safe_bool_config,
    safe_int_config,
)
from asusrouter.connection_config import (
    ARConnectionConfig,
    ARConnectionConfigKey as ARCCKey,
)

KEYS_BOOL = [
    ARConfKey.OPTIMISTIC_DATA,
    ARConfKey.OPTIMISTIC_TEMPERATURE,
    ARConfKey.ROBUST_BOOTTIME,
]

KEYS_INT = [
    ARCCKey.PORT,
]


@pytest.fixture(autouse=True)
def reset_config() -> None:
    """Reset the configuration before each test."""

    ARConfig.set(ARConfKey.OPTIMISTIC_DATA, CONFIG_DEFAULT_BOOL)
    ARConfig.set(ARConfKey.OPTIMISTIC_TEMPERATURE, CONFIG_DEFAULT_BOOL)
    ARConfig.set(ARConfKey.ROBUST_BOOTTIME, CONFIG_DEFAULT_BOOL)


class TestConfig:
    """Tests for the ARConfig class."""

    def test_custom_defaults(self) -> None:
        """Test that custom defaults can be set and retrieved correctly."""

        custom = {ARConfKey.OPTIMISTIC_DATA: True}
        config = type(ARConfig)(custom)
        assert config.get(ARConfKey.OPTIMISTIC_DATA) is True

    @pytest.mark.parametrize(
        "wrong_key",
        [
            "unknown_option",
            12345,
            ["a", "b"],
            object(),
            None,
        ],
    )
    def test_wrong_key(self, wrong_key: Any) -> None:
        """Test that setting/getting an unknown key raises KeyError."""

        with pytest.raises(KeyError):
            ARConfig.set(wrong_key, True)

        with pytest.raises(KeyError):
            ARConfig.get(wrong_key)

    def test_contains(self) -> None:
        """Test the __contains__ method for ARConfig."""

        for key in KEYS_BOOL:
            assert key in ARConfig
        assert "not_a_key" not in ARConfig  # type: ignore[operator]

    def test_keys(self) -> None:
        """Test that we can get the full list of configuration keys."""

        keys = ARConfig.keys()
        assert isinstance(keys, list)
        assert len(keys) > 0
        assert all(isinstance(key, ARConfKey) for key in keys)

    def test_keys_immutable(self) -> None:
        """Test that the keys list is immutable."""

        keys = ARConfig.keys()
        keys.append("something")  # type: ignore[arg-type]
        assert all(isinstance(key, ARConfKey) for key in ARConfig.keys())  # noqa: SIM118

    def test_keys_returns_all_enum_members(self) -> None:
        """Test that ARConfig.keys() returns all members of ARConfKey."""

        keys = set(ARConfig.keys())
        enum_keys = set(ARConfKey)
        assert keys == enum_keys

    def test_list(self) -> None:
        """Test that we can get the full list of configuration options set."""

        options = ARConfig.list()
        assert isinstance(options, list)
        assert len(options) > 0
        assert all(
            isinstance(option, tuple) and len(option) == 2  # noqa: PLR2004
            for option in options
        )

    def test_types(self) -> None:
        """Test that we can get the types of configuration keys."""

        types = ARConfig.types
        assert isinstance(types, dict)
        assert len(types) > 0
        assert all(isinstance(key, ARConfKey) for key in types)
        assert all(callable(converter) for converter in types.values())


class TestRegister:
    """Tests for the register method of ARConfig."""

    def test_register(self) -> None:
        """Test that we can register a new type for a configuration key."""

        def int_to_bool(val: Any) -> bool:
            """Convert a string to an integer to a boolean."""

            if not val:
                return CONFIG_DEFAULT_BOOL

            return bool(int(val))

        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, int_to_bool)
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, 1)
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is True
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, 0)
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is False

        # Restore original converter for cleanliness
        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, safe_bool_config)

    def test_register_overwrites_existing(self) -> None:
        """Test that register overwrites an existing converter."""

        def always_true(val: Any) -> bool:
            return True

        def always_false(val: Any) -> bool:
            return False

        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, always_true)
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, "anything")
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is True

        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, always_false)
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, "anything")
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is False

        # Restore original converter
        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, safe_bool_config)

    def test_register_unknown_key(self) -> None:
        """Test that register raises KeyError for unknown keys."""

        class FakeKey:
            pass

        with pytest.raises(KeyError):
            ARConfig.register(FakeKey(), lambda x: x)  # type: ignore[arg-type]

    def test_register_no_converter(self) -> None:
        """Test that register with no converter uses boolean conversion."""

        ARConfig.register(ARConfKey.OPTIMISTIC_DATA, None)
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, 1)
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is True
        ARConfig.set(ARConfKey.OPTIMISTIC_DATA, 0)
        assert ARConfig.get(ARConfKey.OPTIMISTIC_DATA) is False


class TestReset:
    """Tests for the reset method of ARConfig."""

    def test_reset(self) -> None:
        """Test that reset restores all configuration options and types."""

        # Change all values
        for key in KEYS_BOOL:
            ARConfig.set(key, not CONFIG_DEFAULT_BOOL)
            assert ARConfig.get(key) is not CONFIG_DEFAULT_BOOL

        # Change a type
        for key in KEYS_BOOL:
            ARConfig.register(key, lambda x: not x)
            assert ARConfig.get(key) is not CONFIG_DEFAULT_BOOL

        # Reset to defaults
        ARConfig.reset()
        for key in KEYS_BOOL:
            assert ARConfig.get(key) is CONFIG_DEFAULT_BOOL
            assert ARConfig.types[key] is TYPES_DEFAULT[key]


class TestThreadSafety:
    """Tests for thread safety of ARConfig."""

    def test_set_and_get(self) -> None:
        """Test thread safety for set and get."""

        def set_and_get(key: ARConfKey, val: Any) -> Any:
            """Set a value and return it."""

            ARConfig.set(key, val)
            return key, ARConfig.get(key)

        # Prepare a list of (key, value) pairs
        tasks = [
            (random.choice(KEYS_BOOL), random.choice([True, False]))  # noqa: S311
            for _ in range(100)
        ]

        # Run many concurrent set/get operations
        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
            results = list(executor.map(lambda kv: set_and_get(*kv), tasks))

        # Check that all results are as expected
        for (key, expected_val), (result_key, result_val) in zip(
            tasks, results
        ):
            assert key == result_key
            assert result_val == expected_val

    def test_register(self) -> None:
        """Test thread safety for register."""

        def register() -> None:
            """Register a type."""
            ARConfig.register(ARConfKey.OPTIMISTIC_DATA, safe_bool_config)

        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
            futures = [executor.submit(register) for _ in range(20)]
            for f in futures:
                f.result()

    def test_reset(self) -> None:
        """Test thread safety for reset."""

        def reset_config() -> None:
            """Reset the configuration."""
            ARConfig.reset()

        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
            futures = [executor.submit(reset_config) for _ in range(20)]
            for f in futures:
                f.result()


class TestConvert:
    """Tests for the configuration conversion functions."""

    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (True, True),
            (False, False),
            ("true", True),
            ("false", False),
            (1, True),
            (0, False),
            ("1", True),
            ("0", False),
            ("string", False),
            (None, False),
        ],
    )
    def test_safe_bool_config(self, value: Any, expected: bool) -> None:
        """Test that safe_bool_config converts values correctly."""

        assert safe_bool_config(value) is expected

    def test_safe_bool_config_default(self) -> None:
        """Test that safe_bool_config returns default when value is None."""

        assert safe_bool_config(None) is CONFIG_DEFAULT_BOOL

    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (1, 1),
            (0, 0),
            ("1", 1),
            ("0", 0),
            ("string", 0),
            (None, 0),
        ],
    )
    def test_safe_int_config(self, value: Any, expected: int) -> None:
        """Test that safe_int_config converts values correctly."""

        assert safe_int_config(value) == expected

    def test_safe_int_config_default(self) -> None:
        """Test that safe_int_config returns default when value is None."""

        assert safe_int_config(None) == CONFIG_DEFAULT_INT


class TestBoolConfig:
    """Tests for boolean configuration options."""

    @pytest.mark.parametrize("key", KEYS_BOOL)
    def test_default_bool(self, key: ARConfKey) -> None:
        """Test the default value of a boolean configuration key."""

        assert ARConfig.get(key) is CONFIG_DEFAULT_BOOL

    @pytest.mark.parametrize("key", KEYS_BOOL)
    @pytest.mark.parametrize("value", [True, False])
    def test_set_bool(self, key: ARConfKey, value: bool) -> None:
        """Test setting a boolean configuration key."""

        ARConfig.set(key, value)
        assert ARConfig.get(key) is value


class TestIntConfig:
    """Tests for integer configuration options."""

    @pytest.mark.parametrize("key", KEYS_INT)
    def test_default_int(self, key: ARConfKey) -> None:
        """Test the default value of an integer configuration key."""

        configs = ARConnectionConfig()
        assert configs.get(key) is CONFIG_DEFAULT_INT

    @pytest.mark.parametrize("key", KEYS_INT)
    @pytest.mark.parametrize("value", [1, 2, 3])
    def test_set_int(self, key: ARConfKey, value: int) -> None:
        """Test setting an integer configuration key."""

        configs = ARConnectionConfig()
        configs.set(key, value)
        assert configs.get(key) is value