From 247c155e9c5367858504c3d2a5aff41e7644376d Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 24 May 2025 15:27:35 +0800 Subject: [PATCH] test: add test suites of multi-options loader --- tests/test_cfg_opts.py | 12 ++-- tests/test_load_opts.py | 118 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 tests/test_load_opts.py diff --git a/tests/test_cfg_opts.py b/tests/test_cfg_opts.py index 4458209..b4ef000 100644 --- a/tests/test_cfg_opts.py +++ b/tests/test_cfg_opts.py @@ -19,6 +19,10 @@ def verify_config(data: dict, excepted: dict) -> None: """ Verify configure loading from different sequence formats. """ + data |= { + 'unknown': 'unknown_value', + 'another_unknown': 'another_unknown_value', + } files = { '.json': json.dumps(data), '.toml': toml.dumps(data), @@ -32,7 +36,7 @@ def verify_config(data: dict, excepted: dict) -> None: assert boot.load_from_config(fp.name) == excepted -def test_config_empty(): +def test_config_empty() -> None: """ Test configuration file in the empty case. """ @@ -47,7 +51,7 @@ def test_config_empty(): ('max_chat_message', 'max-chat-message'), ], ) -def test_config_single_int(name: str, cfg_tag: str): +def test_config_single_int(name: str, cfg_tag: str) -> None: """ Test configuration file of single integer option. """ @@ -66,7 +70,7 @@ def test_config_single_int(name: str, cfg_tag: str): ('listen_ipv6', 'listen-ipv6'), ], ) -def test_config_single_str(name: str, cfg_tag: str): +def test_config_single_str(name: str, cfg_tag: str) -> None: """ Test configuration file of single string option. """ @@ -86,7 +90,7 @@ def test_config_single_str(name: str, cfg_tag: str): ('persistent', 'persistent'), ], ) -def test_config_single_bool(name: str, cfg_tag: str): +def test_config_single_bool(name: str, cfg_tag: str) -> None: """ Test configuration file of single boolean option. """ diff --git a/tests/test_load_opts.py b/tests/test_load_opts.py new file mode 100644 index 0000000..8ee23fa --- /dev/null +++ b/tests/test_load_opts.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import pytest +import src.boot as boot +from unittest.mock import patch + + +@pytest.fixture(autouse=True) +def init_opts(): + boot.init_opts() + yield + + +@patch('src.boot.load_from_config') +@patch('src.boot.load_from_args') +@patch('src.boot.load_from_env') +def test_config_priority(mock_env, mock_arg, mock_cfg) -> None: + """ + Test configure file loading priority order. + """ + mock_env.return_value = {} + mock_arg.return_value = {} + boot.load_opts() + mock_cfg.assert_called_with('config.yml') # default value + + mock_env.return_value = {'config': 'env_config.yml'} + mock_arg.return_value = {} + boot.load_opts() + mock_cfg.assert_called_with('env_config.yml') # select from env + + mock_env.return_value = {'config': 'env_config.yml'} + mock_arg.return_value = {'config': 'arg_config.yml'} + boot.load_opts() + mock_cfg.assert_called_with('arg_config.yml') # higher priority than env + + +@patch('src.boot.load_from_config') +@patch('src.boot.load_from_args') +@patch('src.boot.load_from_env') +def test_merge_priority(mock_env, mock_arg, mock_cfg) -> None: + """ + Test the merge priority order among env, cli and config options. + """ + low_opts = { + 'config': 'LOW', + 'motd': 'LOW', + 'port': 12345, + 'max_username': 12345, + 'persistent': False, + 'enable_tls': True, + 'permanent_rooms': ['LOW_1', 'LOW_2'], + } + high_opts = { + 'config': 'HIGH', + 'salt': 'HIGH', + 'port': 23456, + 'max_chat_message': 23456, + 'persistent': True, + 'enable_stats': True, + 'permanent_rooms': ['HIGH_1', 'HIGH_2'], + } + expected_opts = { + 'config': 'HIGH', + 'motd': 'LOW', + 'salt': 'HIGH', + 'port': 23456, + 'max_username': 12345, + 'max_chat_message': 23456, + 'persistent': True, + 'enable_tls': True, + 'enable_stats': True, + 'permanent_rooms': ['HIGH_1', 'HIGH_2'], + } + + mock_env.return_value = low_opts + mock_cfg.return_value = high_opts + mock_arg.return_value = {} + assert boot.load_opts() == expected_opts # env < cfg + + mock_env.return_value = low_opts + mock_cfg.return_value = {} + mock_arg.return_value = high_opts + assert boot.load_opts() == expected_opts # env < arg + + mock_env.return_value = {} + mock_cfg.return_value = low_opts + mock_arg.return_value = high_opts + assert boot.load_opts() == expected_opts # cfg < arg + + +@patch('src.boot.load_from_config') +@patch('src.boot.load_from_args') +@patch('src.boot.load_from_env') +def test_bool_options(mock_env, mock_arg, mock_cfg) -> None: + """ + Test bool options handling and non-bool retention. + """ + mock_env.return_value = {} + mock_cfg.return_value = {} + + bool_opts = [ + 'random_salt', + 'isolate_rooms', + 'disable_chat', + 'disable_ready', + 'enable_stats', + 'enable_tls', + 'persistent', + ] + mock_arg.return_value = {x: True for x in bool_opts} + assert boot.load_opts() == mock_arg.return_value # bool options kept on True + + mock_arg.return_value = {x: False for x in bool_opts} + assert boot.load_opts() == {} # bool options ignored on False + + mock_arg.return_value = {'port': 0, 'salt': '', 'permanent_rooms': []} + assert boot.load_opts() == mock_arg.return_value # non-bool options should be kept