#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import os
import tempfile
import unittest
from collections import OrderedDict
from unittest.mock import MagicMock, mock_open, patch
from script.git.git_tool import (
DEFAULT_PROJECT_NAME,
DEFAULT_REMOTE_URL,
DEFAULT_WEBSITE,
EL_GITHUB_TOKEN,
SOURCE_REPO_ADDONS_FILE,
GitTool,
RepoAttrs,
)
class TestRepoAttrs(unittest.TestCase):
def test_basic_attributes(self):
s = RepoAttrs(a=1, b="hello")
self.assertEqual(s.a, 1)
self.assertEqual(s.b, "hello")
def test_empty_struct(self):
s = RepoAttrs()
self.assertEqual(s.__dict__, {})
def test_override_existing(self):
s = RepoAttrs(x=10)
self.assertEqual(s.x, 10)
class TestGetUrl(unittest.TestCase):
def test_https_to_git(self):
url, url_https, url_git = GitTool.get_url(
"https://github.com/OCA/server-tools.git"
)
self.assertEqual(url, "https://github.com/OCA/server-tools.git")
self.assertEqual(url_https, "https://github.com/OCA/server-tools.git")
self.assertEqual(url_git, "git@github.com:OCA/server-tools.git")
def test_git_to_https(self):
url, url_https, url_git = GitTool.get_url(
"git@github.com:OCA/server-tools.git"
)
self.assertEqual(url_https, "https://github.com/OCA/server-tools.git")
self.assertEqual(url_git, "git@github.com:OCA/server-tools.git")
def test_https_without_git_suffix(self):
url, url_https, url_git = GitTool.get_url(
"https://github.com/ERPLibre/ERPLibre"
)
self.assertEqual(url_https, "https://github.com/ERPLibre/ERPLibre")
self.assertEqual(url_git, "git@github.com:ERPLibre/ERPLibre")
class TestGetTransformedRepoInfo(unittest.TestCase):
def setUp(self):
self.gt = GitTool()
def test_https_url_as_submodule(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
repo_path=".",
get_obj=False,
)
self.assertEqual(result["organization"], "OCA")
self.assertEqual(result["repo_name"], "server-tools")
self.assertEqual(result["project_name"], "server-tools.git")
self.assertEqual(result["path"], "addons/OCA_server-tools")
self.assertTrue(result["is_submodule"])
def test_git_url_as_submodule(self):
result = self.gt.get_transformed_repo_info_from_url(
"git@github.com:ERPLibre/erplibre_addons.git",
repo_path=".",
get_obj=False,
)
self.assertEqual(result["organization"], "ERPLibre")
self.assertEqual(result["repo_name"], "erplibre_addons")
def test_not_submodule(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
repo_path="/tmp/test",
get_obj=False,
is_submodule=False,
)
self.assertEqual(result["path"], "/tmp/test")
self.assertFalse(result["is_submodule"])
def test_get_obj_true_returns_struct(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/web.git",
get_obj=True,
)
self.assertIsInstance(result, RepoAttrs)
self.assertEqual(result.organization, "OCA")
def test_organization_force(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
get_obj=False,
organization_force="MyOrg",
)
self.assertEqual(result["organization"], "MyOrg")
self.assertEqual(result["original_organization"], "OCA")
self.assertIn("MyOrg", result["url_https"])
def test_custom_sub_path(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
get_obj=False,
sub_path="custom",
)
self.assertEqual(result["path"], "custom/OCA_server-tools")
def test_empty_sub_path(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
get_obj=False,
sub_path="",
)
self.assertEqual(result["path"], "server-tools")
def test_dot_sub_path(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools.git",
get_obj=False,
sub_path=".",
)
self.assertEqual(result["path"], "server-tools")
def test_revision_and_clone_depth(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/web.git",
get_obj=False,
revision="16.0",
clone_depth="1",
)
self.assertEqual(result["revision"], "16.0")
self.assertEqual(result["clone_depth"], "1")
def test_url_without_git_suffix(self):
result = self.gt.get_transformed_repo_info_from_url(
"https://github.com/OCA/server-tools",
get_obj=False,
)
self.assertEqual(result["repo_name"], "server-tools")
class TestDefaultProperties(unittest.TestCase):
def test_default_project_name(self):
gt = GitTool()
self.assertEqual(gt.default_project_name, DEFAULT_PROJECT_NAME)
def test_default_website(self):
gt = GitTool()
self.assertEqual(gt.default_website, DEFAULT_WEBSITE)
def test_default_remote_url(self):
gt = GitTool()
self.assertEqual(gt.default_remote_url, DEFAULT_REMOTE_URL)
@patch(
"builtins.open",
mock_open(read_data="18.0"),
)
def test_odoo_version(self):
gt = GitTool()
self.assertEqual(gt.odoo_version, "18.0")
@patch(
"builtins.open",
mock_open(read_data="16.0"),
)
def test_odoo_version_long(self):
gt = GitTool()
self.assertEqual(gt.odoo_version_long, "odoo16.0")
class TestStrInsert(unittest.TestCase):
def test_insert_middle(self):
result = GitTool.str_insert("abcdef", "XY", 3)
self.assertEqual(result, "abcXYdef")
def test_insert_beginning(self):
result = GitTool.str_insert("hello", "X", 0)
self.assertEqual(result, "Xhello")
def test_insert_end(self):
result = GitTool.str_insert("hello", "X", 5)
self.assertEqual(result, "helloX")
class TestGetProjectConfig(unittest.TestCase):
def test_reads_github_token(self):
content = (
"#!/bin/bash\n"
'EL_GITHUB_TOKEN="my_token_123"\n'
'OTHER_VAR="value"\n'
)
with tempfile.NamedTemporaryFile(
mode="w", suffix=".sh", delete=False, dir="/tmp"
) as f:
f.write(content)
f.flush()
tmpdir = os.path.dirname(f.name)
tmpname = os.path.basename(f.name)
try:
# We need env_var.sh in a directory
env_var_path = os.path.join(tmpdir, "env_var.sh")
os.rename(f.name, env_var_path)
result = GitTool.get_project_config(repo_path=tmpdir)
self.assertEqual(result[EL_GITHUB_TOKEN], "my_token_123")
finally:
if os.path.exists(env_var_path):
os.unlink(env_var_path)
class TestGetRepoInfoSubmodule(unittest.TestCase):
def test_parses_gitmodules(self):
gitmodules_content = (
'[submodule "addons/OCA_server-tools"]\n'
"\turl = https://github.com/OCA/server-tools.git\n"
"\tpath = addons/OCA_server-tools\n"
"\n"
'[submodule "addons/OCA_web"]\n'
"\turl = https://github.com/OCA/web.git\n"
"\tpath = addons/OCA_web\n"
)
gt = GitTool()
with tempfile.TemporaryDirectory() as tmpdir:
gitmodules_path = os.path.join(tmpdir, ".gitmodules")
with open(gitmodules_path, "w") as f:
f.write(gitmodules_content)
result = gt.get_repo_info_submodule(
repo_path=tmpdir, add_root=False
)
self.assertEqual(len(result), 2)
names = [r["name"] for r in result]
self.assertIn("addons/OCA_server-tools", names)
self.assertIn("addons/OCA_web", names)
def test_single_submodule(self):
gitmodules_content = (
'[submodule "addons/test"]\n'
"\turl = https://github.com/Test/repo.git\n"
"\tpath = addons/test\n"
)
gt = GitTool()
with tempfile.TemporaryDirectory() as tmpdir:
with open(os.path.join(tmpdir, ".gitmodules"), "w") as f:
f.write(gitmodules_content)
result = gt.get_repo_info_submodule(repo_path=tmpdir)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]["name"], "addons/test")
self.assertIn("https://", result[0]["url_https"])
self.assertIn("git@", result[0]["url_git"])
class TestGetManifestXmlInfo(unittest.TestCase):
def test_parses_manifest(self):
xml_content = """
"""
gt = GitTool()
with tempfile.NamedTemporaryFile(
mode="w", suffix=".xml", delete=False
) as f:
f.write(xml_content)
f.flush()
try:
remotes, projects, default_remote = gt.get_manifest_xml_info(
filename=f.name
)
self.assertIn("OCA", remotes)
self.assertIn("server-tools.git", projects)
self.assertEqual(default_remote["@remote"], "OCA")
finally:
os.unlink(f.name)
def test_empty_manifest(self):
xml_content = """
"""
gt = GitTool()
with tempfile.NamedTemporaryFile(
mode="w", suffix=".xml", delete=False
) as f:
f.write(xml_content)
f.flush()
try:
remotes, projects, default_remote = gt.get_manifest_xml_info(
filename=f.name
)
self.assertEqual(remotes, {})
self.assertEqual(projects, {})
self.assertIsNone(default_remote)
finally:
os.unlink(f.name)
class TestConstants(unittest.TestCase):
def test_file_source_repo_addons(self):
self.assertEqual(SOURCE_REPO_ADDONS_FILE, "source_repo_addons.csv")
def test_default_project_name(self):
self.assertEqual(DEFAULT_PROJECT_NAME, "ERPLibre")
def test_default_website(self):
self.assertEqual(DEFAULT_WEBSITE, "erplibre.ca")
if __name__ == "__main__":
unittest.main()