erplibre/test/test_todo.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

411 lines
14 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 MagicMock, patch
from script.todo.todo import (
ANDROID_DIR,
CONFIG_FILE,
CONFIG_OVERRIDE_FILE,
ENABLE_CRASH,
ERROR_LOG_PATH,
GRADLE_FILE,
LOGO_ASCII_FILE,
MOBILE_HOME_PATH,
STRINGS_FILE,
TODO,
VENV_ERPLIBRE,
)
from script.todo.version_manager import (
INSTALLED_ODOO_VERSION_FILE,
ODOO_VERSION_FILE,
VERSION_DATA_FILE,
get_odoo_version,
)
class TestTODOInit(unittest.TestCase):
def test_initial_attributes(self):
todo = TODO()
self.assertIsNone(todo.dir_path)
self.assertIsNone(todo.selected_file_path)
self.assertIsNotNone(todo.config_file)
self.assertIsNotNone(todo.execute)
self.assertIsNotNone(todo.kdbx_manager)
class TestFillHelpInfo(unittest.TestCase):
def setUp(self):
self.todo = TODO()
@patch("script.todo.todo.t")
def test_basic_help_info(self, mock_t):
mock_t.side_effect = lambda k: {
"command": "Command:",
"back": "Back",
}.get(k, k)
choices = [
{"prompt_description": "Option A"},
{"prompt_description": "Option B"},
]
result = self.todo.fill_help_info(choices)
self.assertIn("[1] Option A", result)
self.assertIn("[2] Option B", result)
self.assertIn("[0] Back", result)
@patch("script.todo.todo.t")
def test_with_prompt_description_key(self, mock_t):
mock_t.side_effect = lambda k: {
"command": "Command:",
"back": "Back",
"my_key": "Translated Description",
}.get(k, k)
choices = [
{
"prompt_description": "fallback",
"prompt_description_key": "my_key",
},
]
result = self.todo.fill_help_info(choices)
self.assertIn("[1] Translated Description", result)
@patch("script.todo.todo.t")
def test_empty_list(self, mock_t):
mock_t.side_effect = lambda k: {
"command": "Command:",
"back": "Back",
}.get(k, k)
result = self.todo.fill_help_info([])
self.assertIn("Command:", result)
self.assertIn("[0] Back", result)
self.assertNotIn("[1]", result)
class TestGetOdooVersion(unittest.TestCase):
def test_reads_version_data(self):
version_data = {
"odoo18.0_python3.12.10": {
"odoo_version": "18.0",
"python_version": "3.12.10",
"default": True,
"is_deprecated": False,
},
"odoo16.0_python3.10.18": {
"odoo_version": "16.0",
"python_version": "3.10.18",
"default": False,
"is_deprecated": False,
},
}
with tempfile.TemporaryDirectory() as tmpdir:
version_file = os.path.join(tmpdir, "version.json")
with open(version_file, "w") as f:
json.dump(version_data, f)
odoo_version_file = os.path.join(tmpdir, ".odoo-version")
with open(odoo_version_file, "w") as f:
f.write("18.0")
with patch(
"script.todo.version_manager.VERSION_DATA_FILE", version_file
), patch(
"script.todo.version_manager.INSTALLED_ODOO_VERSION_FILE",
os.path.join(tmpdir, "nonexistent.txt"),
), patch(
"script.todo.version_manager.ODOO_VERSION_FILE",
odoo_version_file,
):
versions, installed, odoo_current = get_odoo_version()
self.assertEqual(len(versions), 2)
self.assertEqual(odoo_current, "odoo18.0")
# Check erplibre_version was added
names = [v["erplibre_version"] for v in versions]
self.assertIn("odoo18.0_python3.12.10", names)
self.assertIn("odoo16.0_python3.10.18", names)
def test_installed_versions_read(self):
version_data = {
"odoo18.0_python3.12.10": {
"odoo_version": "18.0",
"python_version": "3.12.10",
"default": True,
"is_deprecated": False,
},
}
with tempfile.TemporaryDirectory() as tmpdir:
version_file = os.path.join(tmpdir, "version.json")
with open(version_file, "w") as f:
json.dump(version_data, f)
installed_file = os.path.join(tmpdir, "installed.txt")
with open(installed_file, "w") as f:
f.write("odoo18.0\nodoo16.0\n")
with patch(
"script.todo.version_manager.VERSION_DATA_FILE", version_file
), patch(
"script.todo.version_manager.INSTALLED_ODOO_VERSION_FILE",
installed_file,
), patch(
"script.todo.version_manager.ODOO_VERSION_FILE",
os.path.join(tmpdir, "nonexistent"),
):
versions, installed, odoo_current = get_odoo_version()
self.assertEqual(installed, ["odoo16.0", "odoo18.0"])
self.assertIsNone(odoo_current)
def test_no_version_data_raises(self):
with tempfile.TemporaryDirectory() as tmpdir:
version_file = os.path.join(tmpdir, "empty.json")
with open(version_file, "w") as f:
json.dump({}, f)
with patch(
"script.todo.version_manager.VERSION_DATA_FILE", version_file
):
with self.assertRaises(Exception):
get_odoo_version()
class TestOnDirSelected(unittest.TestCase):
@patch("script.todo.todo.todo_file_browser", create=True)
def test_sets_dir_path(self, mock_browser):
todo = TODO()
todo.on_dir_selected("/some/path")
self.assertEqual(todo.dir_path, "/some/path")
class TestExecuteFromConfiguration(unittest.TestCase):
def test_with_command(self):
todo = TODO()
todo.execute = MagicMock()
dct = {"command": "./run.sh"}
todo.execute_from_configuration(dct)
todo.execute.exec_command_live.assert_called()
def test_with_makefile_cmd(self):
todo = TODO()
todo.execute = MagicMock()
todo.execute.exec_command_live.return_value = 0
dct = {"makefile_cmd": "run_test"}
todo.execute_from_configuration(dct)
call_args = todo.execute.exec_command_live.call_args
self.assertIn("make run_test", call_args[0][0])
def test_makefile_cmd_ignored_when_flag(self):
todo = TODO()
todo.execute = MagicMock()
dct = {"makefile_cmd": "run_test"}
todo.execute_from_configuration(dct, ignore_makefile=True)
todo.execute.exec_command_live.assert_not_called()
def test_with_callback(self):
todo = TODO()
todo.execute = MagicMock()
callback = MagicMock()
dct = {"callback": callback}
todo.execute_from_configuration(dct)
callback.assert_called_once_with(dct)
def test_makefile_error_stops_execution(self):
todo = TODO()
todo.execute = MagicMock()
todo.execute.exec_command_live.return_value = 1
callback = MagicMock()
dct = {"makefile_cmd": "broken", "callback": callback}
todo.execute_from_configuration(dct)
callback.assert_not_called()
class TestConstants(unittest.TestCase):
def test_config_file_path(self):
self.assertEqual(CONFIG_FILE, "./script/todo/todo.json")
def test_config_override_path(self):
self.assertEqual(CONFIG_OVERRIDE_FILE, "./private/todo/todo.json")
def test_logo_path(self):
self.assertEqual(LOGO_ASCII_FILE, "./script/todo/logo_ascii.txt")
def test_venv_erplibre(self):
self.assertEqual(VENV_ERPLIBRE, ".venv.erplibre")
def test_file_error_path(self):
self.assertEqual(ERROR_LOG_PATH, ".erplibre.error.txt")
def test_version_data_file(self):
self.assertEqual(
VERSION_DATA_FILE,
os.path.join("conf", "supported_version_erplibre.json"),
)
def test_mobile_paths(self):
self.assertEqual(ANDROID_DIR, "android")
self.assertIn("mobile", MOBILE_HOME_PATH)
class TestDeployGitServer(unittest.TestCase):
def test_local_mode(self):
todo = TODO()
todo.execute = MagicMock()
todo._deploy_git_server(production_ready=False, action="init")
cmd = todo.execute.exec_command_live.call_args[0][0]
self.assertIn("--action init", cmd)
self.assertNotIn("--production-ready", cmd)
def test_production_mode(self):
todo = TODO()
todo.execute = MagicMock()
todo._deploy_git_server(production_ready=True, action="all")
cmd = todo.execute.exec_command_live.call_args[0][0]
self.assertIn("--production-ready", cmd)
self.assertIn("--action all", cmd)
class TestProcessKillGitDaemon(unittest.TestCase):
def test_calls_pkill(self):
todo = TODO()
todo.execute = MagicMock()
todo.process_kill_git_daemon()
cmd = todo.execute.exec_command_live.call_args[0][0]
self.assertIn("pkill", cmd)
self.assertIn("git daemon", cmd)
class TestExecuteUnitTests(unittest.TestCase):
def test_success_path(self):
todo = TODO()
todo.execute = MagicMock()
todo.execute.exec_command_live.return_value = (0, ["OK"])
with patch("builtins.print") as mock_print:
todo.execute_unit_tests()
cmd = todo.execute.exec_command_live.call_args[0][0]
self.assertIn("unittest discover", cmd)
def test_failure_path(self):
todo = TODO()
todo.execute = MagicMock()
todo.execute.exec_command_live.return_value = (1, ["FAIL"])
with patch("builtins.print") as mock_print:
todo.execute_unit_tests()
# Verify it was called - error handling path
class TestKdbxGetExtraCommandUser(unittest.TestCase):
def test_empty_kdbx_key(self):
todo = TODO()
result = todo.kdbx_manager.get_extra_command_user("")
self.assertEqual(result, "")
def test_none_kdbx_key(self):
todo = TODO()
result = todo.kdbx_manager.get_extra_command_user(None)
self.assertEqual(result, "")
def test_kdbx_not_available(self):
todo = TODO()
todo.kdbx_manager.get_kdbx = MagicMock(return_value=None)
result = todo.kdbx_manager.get_extra_command_user("some_key")
self.assertEqual(result, "")
class TestSetupClaudeCommit(unittest.TestCase):
def test_existing_file_skips(self):
todo = TODO()
with patch("os.path.exists", return_value=True), patch(
"builtins.print"
) as mock_print:
todo._setup_claude_commit()
# Should print exists message without asking for input
class TestSelectDatabase(unittest.TestCase):
@patch("script.todo.database_manager.click")
def test_select_database_returns_name(self, mock_click):
todo = TODO()
todo.db_manager._execute = MagicMock()
todo.db_manager._execute.exec_command_live.return_value = (
0,
["db_test", "db_prod"],
)
mock_click.prompt.return_value = "1"
result = todo.db_manager.select_database()
self.assertEqual(result, "db_test")
@patch("script.todo.database_manager.click")
def test_select_database_returns_false_on_zero(self, mock_click):
todo = TODO()
todo.db_manager._execute = MagicMock()
todo.db_manager._execute.exec_command_live.return_value = (
0,
["db_test"],
)
mock_click.prompt.return_value = "0"
result = todo.db_manager.select_database()
self.assertFalse(result)
class TestRestoreFromDatabase(unittest.TestCase):
@patch("builtins.input")
def test_restore_by_filename(self, mock_input):
todo = TODO()
todo.db_manager._execute = MagicMock()
todo.db_manager._execute.exec_command_live.return_value = (
0,
[],
)
# status="1" (by filename), db name default, no neutralize
mock_input.side_effect = ["1", "", "n", "n"]
todo.db_manager.restore_from_database()
cmd = todo.db_manager._execute.exec_command_live.call_args_list[0][0][
0
]
self.assertIn("db_restore.py", cmd)
@patch("builtins.input")
def test_restore_with_neutralize(self, mock_input):
todo = TODO()
todo.db_manager._execute = MagicMock()
todo.db_manager._execute.exec_command_live.return_value = (
0,
[],
)
mock_input.side_effect = ["1", "mydb", "y", "n"]
todo.db_manager.restore_from_database()
cmd = todo.db_manager._execute.exec_command_live.call_args_list[0][0][
0
]
self.assertIn("--neutralize", cmd)
self.assertIn("mydb_neutralize", cmd)
class TestCreateBackupFromDatabase(unittest.TestCase):
@patch("script.todo.database_manager.click")
@patch("builtins.input")
def test_creates_backup_command(self, mock_input, mock_click):
todo = TODO()
todo.db_manager._execute = MagicMock()
todo.db_manager._execute.exec_command_live.return_value = (
0,
["test_db"],
)
mock_click.prompt.return_value = "1"
# backup name input
mock_input.return_value = "backup.zip"
todo.db_manager.create_backup_from_database()
cmd = todo.db_manager._execute.exec_command_live.call_args_list[-1][0][
0
]
self.assertIn("--backup", cmd)
self.assertIn("test_db", cmd)
if __name__ == "__main__":
unittest.main()