erplibre/test/test_config_file.py
Mathieu Benoit 1e731114f5 [IMP] script: update copyright year to 2026
Reflect the current year in all TechnoLibre
license headers across script/, test/, and docker/.

Generated by Claude Code 2.1.74 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-03-11 23:16:05 -04:00

262 lines
8.6 KiB
Python

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import json
import os
import tempfile
import unittest
from unittest.mock import patch
from script.config.config_file import ConfigFile
class TestDeepMergeWithLists(unittest.TestCase):
def setUp(self):
self.cfg = ConfigFile()
def test_empty_dicts(self):
result = self.cfg.deep_merge_with_lists({}, {})
self.assertEqual(result, {})
def test_dest_only(self):
result = self.cfg.deep_merge_with_lists(
{"a": 1, "b": 2}, {}
)
self.assertEqual(result, {"a": 1, "b": 2})
def test_src_only(self):
result = self.cfg.deep_merge_with_lists(
{}, {"a": 1, "b": 2}
)
self.assertEqual(result, {"a": 1, "b": 2})
def test_simple_merge(self):
result = self.cfg.deep_merge_with_lists(
{"a": 1}, {"b": 2}
)
self.assertEqual(result, {"a": 1, "b": 2})
def test_src_overrides_dest_string(self):
result = self.cfg.deep_merge_with_lists(
{"a": "old"}, {"a": "new"}
)
self.assertEqual(result, {"a": "new"})
def test_empty_src_string_keeps_dest(self):
result = self.cfg.deep_merge_with_lists(
{"a": "old"}, {"a": ""}
)
self.assertEqual(result, {"a": "old"})
def test_nested_dict_merge(self):
dest = {"a": {"x": 1, "y": 2}}
src = {"a": {"y": 3, "z": 4}}
result = self.cfg.deep_merge_with_lists(dest, src)
self.assertEqual(result, {"a": {"x": 1, "y": 3, "z": 4}})
def test_deeply_nested_dict_merge(self):
dest = {"a": {"b": {"c": 1}}}
src = {"a": {"b": {"d": 2}}}
result = self.cfg.deep_merge_with_lists(dest, src)
self.assertEqual(result, {"a": {"b": {"c": 1, "d": 2}}})
def test_list_replace_strategy(self):
result = self.cfg.deep_merge_with_lists(
{"a": [1, 2]}, {"a": [3, 4]}, list_strategy="replace"
)
self.assertEqual(result, {"a": [3, 4]})
def test_list_extend_strategy(self):
result = self.cfg.deep_merge_with_lists(
{"a": [1, 2]}, {"a": [3, 4]}, list_strategy="extend"
)
self.assertEqual(result, {"a": [1, 2, 3, 4]})
def test_dest_dict_not_mutated(self):
dest = {"a": {"x": 1}}
src = {"a": {"y": 2}}
self.cfg.deep_merge_with_lists(dest, src)
self.assertEqual(dest, {"a": {"x": 1}})
def test_src_overrides_non_string_non_dict_non_list(self):
result = self.cfg.deep_merge_with_lists(
{"a": 1}, {"a": 2}
)
self.assertEqual(result, {"a": 2})
class TestGetConfig(unittest.TestCase):
def setUp(self):
self.cfg = ConfigFile()
self.tmpdir = tempfile.mkdtemp()
def tearDown(self):
for f in os.listdir(self.tmpdir):
os.remove(os.path.join(self.tmpdir, f))
os.rmdir(self.tmpdir)
def _write_json(self, filename, data):
path = os.path.join(self.tmpdir, filename)
with open(path, "w") as f:
json.dump(data, f)
return path
def test_get_config_base_only(self):
base_path = self._write_json(
"base.json", {"instance": [{"name": "test"}]}
)
with patch(
"script.config.config_file.CONFIG_FILE", base_path
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
os.path.join(self.tmpdir, "nonexistent1.json"),
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
os.path.join(self.tmpdir, "nonexistent2.json"),
):
result = self.cfg.get_config("instance")
self.assertEqual(result, [{"name": "test"}])
def test_get_config_returns_none_for_missing_key(self):
base_path = self._write_json("base.json", {"a": 1})
with patch(
"script.config.config_file.CONFIG_FILE", base_path
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
os.path.join(self.tmpdir, "nonexistent1.json"),
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
os.path.join(self.tmpdir, "nonexistent2.json"),
):
result = self.cfg.get_config("missing")
self.assertIsNone(result)
def test_get_config_override_merges(self):
base_path = self._write_json(
"base.json",
{"instance": [{"name": "base"}]},
)
override_path = self._write_json(
"override.json",
{"instance": [{"name": "override"}]},
)
with patch(
"script.config.config_file.CONFIG_FILE", base_path
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
override_path,
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
os.path.join(self.tmpdir, "nonexistent.json"),
):
result = self.cfg.get_config("instance")
# Lists with extend: base + override
self.assertEqual(
result,
[{"name": "base"}, {"name": "override"}],
)
def test_get_config_private_merges(self):
base_path = self._write_json(
"base.json",
{"data": {"key": "base_val"}},
)
private_path = self._write_json(
"private.json",
{"data": {"key": "private_val"}},
)
with patch(
"script.config.config_file.CONFIG_FILE", base_path
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
os.path.join(self.tmpdir, "nonexistent.json"),
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
private_path,
):
result = self.cfg.get_config("data")
self.assertEqual(result, {"key": "private_val"})
def test_get_config_all_three_files(self):
base_path = self._write_json(
"base.json",
{"items": [1], "meta": {"a": "base"}},
)
override_path = self._write_json(
"override.json",
{"items": [2], "meta": {"b": "override"}},
)
private_path = self._write_json(
"private.json",
{"items": [3], "meta": {"a": "private"}},
)
with patch(
"script.config.config_file.CONFIG_FILE", base_path
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
override_path,
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
private_path,
):
result_items = self.cfg.get_config("items")
result_meta = self.cfg.get_config("meta")
# Merge order: base + private, then + override
# Lists extend: [1] + [3] = [1,3], then [1,3] + [2] = [1,3,2]
self.assertEqual(result_items, [1, 3, 2])
# Dict merge: {a: base} + {a: private} = {a: private}
# then {a: private} + {b: override} = {a: private, b: override}
self.assertEqual(
result_meta, {"a": "private", "b": "override"}
)
def test_no_config_files_exist(self):
with patch(
"script.config.config_file.CONFIG_FILE",
os.path.join(self.tmpdir, "nope1.json"),
), patch(
"script.config.config_file.CONFIG_OVERRIDE_FILE",
os.path.join(self.tmpdir, "nope2.json"),
), patch(
"script.config.config_file.CONFIG_OVERRIDE_PRIVATE_FILE",
os.path.join(self.tmpdir, "nope3.json"),
):
result = self.cfg.get_config("anything")
self.assertIsNone(result)
class TestGetConfigValue(unittest.TestCase):
def setUp(self):
self.cfg = ConfigFile()
def test_nested_value(self):
with patch.object(
self.cfg,
"get_config",
return_value={"level1": {"level2": "found"}},
):
result = self.cfg.get_config_value(
["root", "level1", "level2"]
)
self.assertEqual(result, "found")
def test_single_key(self):
with patch.object(
self.cfg,
"get_config",
return_value={"key": "value"},
):
result = self.cfg.get_config_value(["root"])
self.assertEqual(result, {"key": "value"})
class TestGetLogoAsciiFilePath(unittest.TestCase):
def test_returns_logo_path(self):
cfg = ConfigFile()
result = cfg.get_logo_ascii_file_path()
self.assertEqual(result, "./script/todo/logo_ascii.txt")
if __name__ == "__main__":
unittest.main()