Skip to content

Commit

Permalink
feat: add unit tests on gitlab cmd + commons
Browse files Browse the repository at this point in the history
- Add unit tests on commons tools
- Add unit tests on commands (for Gitlab, missing for Git)
  • Loading branch information
ryshu committed Oct 7, 2022
1 parent f5c2e60 commit 26e1a0a
Show file tree
Hide file tree
Showing 20 changed files with 1,119 additions and 112 deletions.
4 changes: 3 additions & 1 deletion gitflow_toolbox/common/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ def project(self) -> Project:
# Use provided CI project, if not available, use project from current job
project_id = get_env("GITLAB_PROJECT_ID", "CI_PROJECT_ID")
self.__project = self.projects.get(project_id)
return self.__project

self.__project = self.projects.get(get_env("GITLAB_PROJECT_ID"))
# This line isn't covered by unit test to simplify singleton handling.
self.__project = self.projects.get(get_env("GITLAB_PROJECT_ID")) # pragma: no cover
return self.__project

@property
Expand Down
5 changes: 3 additions & 2 deletions gitflow_toolbox/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def diff(
click.echo(diff_output)
return diff_output
finally:
# Clean
# Clean created directory by git lib during clone_from
if os.path.isdir(project_from_clone_dir):
shutil.rmtree(project_from_clone_dir)
# These lines aren't tested by unit test because git doesn't impact file-system (mocked)
shutil.rmtree(project_from_clone_dir) # pragma: no cover
12 changes: 6 additions & 6 deletions gitflow_toolbox/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
@click.argument("target_branch", type=str)
@click.option(
"--from-gitlab",
"-f",
type=click.Choice(["current", "remote"], case_sensitive=False),
help="Flag which allow you to chose between the current gitlab or the remote gitlab for the FROM role."
" Defaults to 'remote'.",
Expand Down Expand Up @@ -46,8 +45,8 @@ def push(
to_gitlab (str, optional): destination gitlab [current/remote]. Defaults to 'remote'.
force (bool, optional): whether to force push. Defaults to False.
"""
to_gitlab = to_gitlab.lower()
from_gitlab = from_gitlab.lower()
to_gitlab = (to_gitlab or "remote").lower()
from_gitlab = (from_gitlab or "remote").lower()

gitlab_from = CurrentGitlab() if from_gitlab == "current" else RemoteGitlab()
gitlab_to = CurrentGitlab() if to_gitlab == "current" else RemoteGitlab()
Expand Down Expand Up @@ -77,7 +76,8 @@ def push(
click.echo(f"✨ Successfully pushed {from_gitlab} {source_branch} into {to_gitlab} {target_branch}")

finally:
# Clean
# Clean created directory by git lib during clone_from
if os.path.isdir(project_from_clone_dir):
click.echo(f"Removing cache {project_from_clone_dir}")
shutil.rmtree(project_from_clone_dir)
# These lines aren't tested by unit test because git doesn't impact file-system (mocked)
click.echo(f"Removing cache {project_from_clone_dir}") # pragma: no cover
shutil.rmtree(project_from_clone_dir) # pragma: no cover
25 changes: 10 additions & 15 deletions gitflow_toolbox/push_missing_tags.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
import shutil
import sys
import uuid

import click
Expand Down Expand Up @@ -37,8 +36,8 @@ def push_missing_tags(
from_gitlab (str, optional): source gitlab [current/remote]. Defaults to 'remote'.
to_gitlab (str, optional): destination gitlab [current/remote]. Defaults to 'remote'.
"""
to_gitlab = to_gitlab.lower()
from_gitlab = from_gitlab.lower()
to_gitlab = (to_gitlab or "remote").lower()
from_gitlab = (from_gitlab or "remote").lower()

gitlab_from = CurrentGitlab() if from_gitlab == "current" else RemoteGitlab()
gitlab_to = CurrentGitlab() if to_gitlab == "current" else RemoteGitlab()
Expand Down Expand Up @@ -68,19 +67,15 @@ def push_missing_tags(
# Push missing tags from target to source
for item in source_tags - target_tags:
click.echo(f"Pushing {from_gitlab} {item.name} into {to_gitlab} ...")
changes = repo_from.remotes.target.push(item)
for change in changes:
click.echo(change.summary)
if "rejected" in change.summary:
click.echo(f"❌ Couldn't push {item.name} to {to_gitlab}")
sys.exit(1)
click.echo(f"✨ Successfully pushed {from_gitlab} tags into {to_gitlab}")
repo_from.remotes.target.push(item)
click.echo(f"✨ Successfully pushed {from_gitlab} {item.name} tags into {to_gitlab}")

finally:
# Clean
# Clean created directory by git lib during clone_from
# These lines aren't tested by unit test because git doesn't impact file-system (mocked)
if os.path.isdir(project_from_clone_dir):
click.echo(f"Removing cache {project_from_clone_dir}")
shutil.rmtree(project_from_clone_dir)
click.echo(f"Removing cache {project_from_clone_dir}") # pragma: no cover
shutil.rmtree(project_from_clone_dir) # pragma: no cover
if os.path.isdir(project_to_clone_dir):
click.echo(f"Removing cache {project_to_clone_dir}")
shutil.rmtree(project_to_clone_dir)
click.echo(f"Removing cache {project_to_clone_dir}") # pragma: no cover
shutil.rmtree(project_to_clone_dir) # pragma: no cover
126 changes: 126 additions & 0 deletions gitflow_toolbox/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import itertools
from functools import reduce
from typing import Union
from unittest import mock


class DictObject:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)


class MockBranch:
def __init__(self, name: str = "BRANCH") -> None:
self.name = name


class MockMergeRequest:
def __init__(self, iid: str = "MR_ID", web_url: str = "MR_URL", state: str = "opened") -> None:
self.iid = iid
self.web_url = web_url
self.state = state


class MockBranchManager:
def __init__(self) -> None:
self.list = mock.MagicMock()
self.list.return_value = []
self.create = mock.MagicMock()

def reset_mock(self):
self.list.reset_mock()
self.create.reset_mock()
self.list.return_value = []


class MockMergeRequestManager:
def __init__(self) -> None:
self.list = mock.MagicMock()
self.list.return_value = []
self.create = mock.MagicMock()
self.create.return_value = MockMergeRequest()

def reset_mock(self):
self.list.reset_mock()
self.create.reset_mock()
self.list.return_value = []
self.create.return_value = MockMergeRequest()


class MockProject:
def __init__(self) -> None:
self.branches = MockBranchManager()
self.mergerequests = MockMergeRequestManager()
self.attributes = {
"http_url_to_repo": "https://sample.spikeelabs.fr",
"ssh_url_to_repo": "ssh_url_to_repo",
}


class MockGitRemoteInstance:
def __init__(self) -> None:
self.fetch = mock.MagicMock()
self.push = mock.MagicMock()


class MockGitRemote:
def __init__(self) -> None:
self.fetch = mock.MagicMock()

def reset(self):
self.fetch.reset_mock()

def create_remote(self, *args, **kwargs): # pylint: disable=W0613
if not hasattr(self, args[0]):
setattr(self, args[0], MockGitRemoteInstance())


class MockGitGit:
def __init__(self) -> None:
self.diff = mock.MagicMock()
self.diff.return_value = None

def reset(self):
self.diff.reset_mock()
self.diff.return_value = None


class MockGitRepo:
def __init__(self) -> None:
self.create_remote = mock.MagicMock()
self.remotes = MockGitRemote()
self.git = MockGitGit()
self.tags = mock.MagicMock()

self.create_remote.side_effect = self.__create_remote

def reset(self):
self.create_remote.reset_mock()
self.remotes.reset()
self.git.reset()
self.tags.reset_mock()

def __create_remote(self, *args, **kwargs):
self.remotes.create_remote(*args, **kwargs)


def append_title(*args: Union[str, list[str]]):
"""This is just an utils to rewrite title of test when we use parameterized.expend
It help use to understand which case of expended tests fail.
Yields:
Union[str, list[str]: Given argument rewrite properly
"""
if args and isinstance(args[0], itertools.product):
args = args[0]

for arg in list(args):
if isinstance(arg, tuple):
arg = list(arg)
if isinstance(arg, list):
arg.insert(0, reduce(lambda x, y: f"{x.replace('-', '')}_{y.replace('-', '')}", arg))
if isinstance(arg, str):
arg = arg.replace("-", "")

yield arg
31 changes: 31 additions & 0 deletions gitflow_toolbox/tests/test_check_branch_exists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from parameterized import parameterized

from gitflow_toolbox.tests.factories import MockBranch
from gitflow_toolbox.tests.testcases import GitflowTestCase


class CheckBranchExistsTests(GitflowTestCase):
@parameterized.expand(["--remote", "--current", "-r", "-c"])
def test_cli_check_branch_exist_success(self, flag: str):
self.project_mock.branches.reset_mock()
self.project_mock.branches.list.return_value = [MockBranch("target_branch")]

result = self.runner.invoke(
self.main_cli, ["check-branch-exists", "target_branch", flag], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0, result.output)
self.assertEqual(result.output, "Checking if target_branch branch exists...\nTrue\n")

self.project_mock.branches.list.assert_called_once_with()

@parameterized.expand(["--remote", "--current", "-r", "-c"])
def test_cli_check_branch_exist_failure(self, flag: str):
self.project_mock.branches.reset_mock()

result = self.runner.invoke(
self.main_cli, ["check-branch-exists", "target_branch", flag], catch_exceptions=False
)
self.assertEqual(result.exit_code, 1, result.output)
self.assertEqual(result.output, "Checking if target_branch branch exists...\nFalse\n")

self.project_mock.branches.list.assert_called_once_with()
49 changes: 49 additions & 0 deletions gitflow_toolbox/tests/test_check_mr_exists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import itertools

from parameterized import parameterized

from gitflow_toolbox.tests.factories import MockMergeRequest, append_title
from gitflow_toolbox.tests.testcases import GitflowTestCase


class CheckMrExistsTests(GitflowTestCase):
@parameterized.expand(
append_title(
itertools.product(
["--remote", "--current", "-r", "-c"], ["-s opened", "-s closed", "-s locked", "-s merged"]
)
)
)
def test_cli_check_mr_exist_success(self, _, remote: str, state: str):
self.project_mock.mergerequests.reset_mock()
self.project_mock.mergerequests.list.return_value = [MockMergeRequest()]

result = self.runner.invoke(
self.main_cli, ["check-mr-exists", "src", "dst", remote, *state.split(" ")], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0, result.output)
self.assertEqual(result.output, "Checking if an opened merge request from src to dst exists...\nTrue\n")

self.project_mock.mergerequests.list.assert_called_once_with(
state=state.split(" ")[1], source_branch="src", target_branch="dst"
)

@parameterized.expand(
append_title(
itertools.product(
["--remote", "--current", "-r", "-c"], ["-s opened", "-s closed", "-s locked", "-s merged"]
)
)
)
def test_cli_check_mr_exist_failure(self, _, remote: str, state: str):
self.project_mock.mergerequests.reset_mock()

result = self.runner.invoke(
self.main_cli, ["check-mr-exists", "src", "dst", remote, *state.split(" ")], catch_exceptions=False
)
self.assertEqual(result.exit_code, 1, result.output)
self.assertEqual(result.output, "Checking if an opened merge request from src to dst exists...\nFalse\n")

self.project_mock.mergerequests.list.assert_called_once_with(
state=state.split(" ")[1], source_branch="src", target_branch="dst"
)
21 changes: 21 additions & 0 deletions gitflow_toolbox/tests/test_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from parameterized import parameterized

from gitflow_toolbox.common.get_env import get_env
from gitflow_toolbox.common.gitlab import CurrentGitlab, RemoteGitlab
from gitflow_toolbox.tests.testcases import GitflowTestCase


class CommonTests(GitflowTestCase):
def test_get_env_raise_not_set_1_arg(self):
self.assertRaises(Exception, lambda: get_env("FIRST"))

def test_get_env_raise_not_set_3_args(self):
self.assertRaises(Exception, lambda: get_env("FIRST", "SECOND", "THIRD"))

def test_current_gitlab_project_authenticated_url(self):
self.assertEqual(CurrentGitlab().project_authenticated_url, "https://gitflow:[email protected]")

def test_remote_gitlab_project_authenticated_url(self):
self.assertEqual(
RemoteGitlab().project_authenticated_url, "https://gitflow:[email protected]"
)
49 changes: 49 additions & 0 deletions gitflow_toolbox/tests/test_diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import itertools
from unittest import mock

from parameterized import parameterized

from gitflow_toolbox.tests.factories import MockGitRepo, append_title
from gitflow_toolbox.tests.testcases import GitflowTestCase


class DiffTests(GitflowTestCase):
@parameterized.expand(
append_title(itertools.product(["--from-gitlab", "-f", "--to-gitlab", "-t"], ["current", "remote"]))
)
def test_diff_successful(self, _, flag_key: str, flag_val: str):
with mock.patch("git.Repo.clone_from") as mock_clone_repo:
mock_repo = MockGitRepo()
mock_clone_repo.return_value = mock_repo
mock_repo.git.diff.return_value = "diff"

result = self.runner.invoke(
self.main_cli, ["diff", "src", "dst", flag_key, flag_val], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0, result.output)
self.assertEqual(result.output, "diff\n")

mock_clone_repo.assert_called_once()
mock_repo.create_remote.assert_called_once()
mock_repo.remotes.target.fetch.assert_called_once_with("dst")
mock_repo.git.diff.assert_called_once()

@parameterized.expand(
append_title(itertools.product(["--from-gitlab", "-f", "--to-gitlab", "-t"], ["current", "remote"]))
)
def test_when_no_diff(self, _, flag_key: str, flag_val: str):
with mock.patch("git.Repo.clone_from") as mock_clone_repo:
mock_repo = MockGitRepo()
mock_clone_repo.return_value = mock_repo
mock_repo.git.diff.return_value = ""

result = self.runner.invoke(
self.main_cli, ["diff", "src", "dst", flag_key, flag_val], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0, result.output)
self.assertEqual(result.output, "")

mock_clone_repo.assert_called_once()
mock_repo.create_remote.assert_called_once()
mock_repo.remotes.target.fetch.assert_called_once_with("dst")
mock_repo.git.diff.assert_called_once()
Loading

0 comments on commit 26e1a0a

Please sign in to comment.