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>
140 lines
5.2 KiB
Python
140 lines
5.2 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 unittest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from script.maintenance.format_file_to_commit import (
|
|
execute_shell,
|
|
get_modified_files,
|
|
)
|
|
|
|
MOD = "script.maintenance.format_file_to_commit"
|
|
|
|
|
|
class TestExecuteShell(unittest.TestCase):
|
|
def test_successful_command(self):
|
|
code, output = execute_shell("echo hello")
|
|
self.assertEqual(code, 0)
|
|
self.assertEqual(output, "hello")
|
|
|
|
def test_failed_command(self):
|
|
code, output = execute_shell("exit 42")
|
|
self.assertEqual(code, 42)
|
|
|
|
def test_stderr_captured(self):
|
|
code, output = execute_shell("echo err >&2")
|
|
self.assertEqual(code, 0)
|
|
self.assertIn("err", output)
|
|
|
|
def test_empty_output(self):
|
|
code, output = execute_shell("true")
|
|
self.assertEqual(code, 0)
|
|
self.assertEqual(output, "")
|
|
|
|
def test_multiline_output(self):
|
|
code, output = execute_shell("echo 'a\nb'")
|
|
self.assertEqual(code, 0)
|
|
self.assertIn("a", output)
|
|
self.assertIn("b", output)
|
|
|
|
|
|
class TestGetModifiedFiles(unittest.TestCase):
|
|
def _mock_git(self, git_output):
|
|
"""Helper to mock subprocess.run for git status."""
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = git_output
|
|
mock_result.returncode = 0
|
|
return mock_result
|
|
|
|
def _no_repo_no_odoo(self, path):
|
|
"""Return False for repo bin and .odoo-version, True otherwise."""
|
|
if path in (".venv.erplibre/bin/repo",):
|
|
return False
|
|
return True
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists")
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_single_modified_file(self, mock_run, mock_exists, mock_isfile):
|
|
mock_exists.return_value = False
|
|
mock_run.return_value = self._mock_git(" M file.py")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 1)
|
|
self.assertEqual(files[0][0], "M")
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_added_file(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("A new_file.py")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(files[0][0], "A")
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_deleted_file_ignored(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("D deleted.py")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 0)
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_zip_file_ignored(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("M archive.zip")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 0)
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_tar_gz_file_ignored(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("M archive.tar.gz")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 0)
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_untracked_file(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("?? new_file.txt")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 1)
|
|
self.assertEqual(files[0][0], "??")
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_empty_git_status(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git("")
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 0)
|
|
|
|
@patch(f"{MOD}.os.path.isfile", return_value=False)
|
|
@patch(f"{MOD}.os.path.exists", return_value=False)
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_multiple_statuses(self, mock_run, mock_exists, mock_isfile):
|
|
mock_run.return_value = self._mock_git(
|
|
" M modified.py\nA added.py\n?? untracked.py"
|
|
)
|
|
files = get_modified_files()
|
|
self.assertIsNotNone(files)
|
|
self.assertEqual(len(files), 3)
|
|
|
|
@patch(f"{MOD}.subprocess.run")
|
|
def test_git_error_returns_none(self, mock_run):
|
|
import subprocess
|
|
|
|
mock_run.side_effect = subprocess.CalledProcessError(1, "git")
|
|
files = get_modified_files()
|
|
self.assertIsNone(files)
|