diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 5c0a74ab5..39f552579 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,8 +1,9 @@ import logging import unittest.mock as umock -from typing import List +from typing import Generator, List + +import pytest -import helpers.hsystem as hsystem import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore import helpers.test.test_lib_tasks as httestlib @@ -36,17 +37,23 @@ def _extract_commands_from_call(calls: List[umock._Call]) -> List[str]: # ############################################################################# -# TestDockerBuildLocalImage1 +# _DockerFlowTestHelper # ############################################################################# -class TestDockerBuildLocalImage1(hunitest.TestCase): +class _DockerFlowTestHelper(hunitest.TestCase): + """ + Helper test class to perform common setup, teardown logic and assertion + checks for Docker flow tests. + """ - def setUp(self) -> None: - """ - Set up test environment and initialize all necessary mocks. - """ - super().setUp() + @pytest.fixture(autouse=True) + def setup_teardown_test(self) -> Generator: + self.set_up_test() + yield + self.tear_down_test() + + def set_up_test(self) -> None: # Mock system calls. self.system_patcher = umock.patch("helpers.hsystem.system") self.mock_system = self.system_patcher.start() @@ -63,20 +70,63 @@ def setUp(self) -> None: "helpers.lib_tasks_docker.docker_login" ) self.mock_docker_login = self.docker_login_patcher.start() + # Mock environment variable. + self.env_patcher = umock.patch.dict( + "os.environ", {"CSFY_ECR_BASE_PATH": "test.ecr.path"} + ) + self.env_patcher.start() # - self.user = hsystem.get_user_name() + self.patchers = [ + self.system_patcher, + self.run_patcher, + self.version_patcher, + self.docker_login_patcher, + self.env_patcher, + ] + # Test inputs. + self.mock_ctx = httestlib._build_mock_context_returning_ok() + self.test_version = "1.0.0" + self.test_base_image = "test-registry.com/test-image" + self.test_multi_arch = "linux/amd64,linux/arm64" - def tearDown(self) -> None: + def tear_down_test(self) -> None: """ Clean up test environment by stopping all mocks after each test case. """ - self.system_patcher.stop() - self.run_patcher.stop() - self.version_patcher.stop() - self.docker_login_patcher.stop() - super().tearDown() + for patcher in self.patchers: + patcher.stop() + + def _check_docker_command_output( + self, exp: str, call_args_list: List[umock._Call] + ) -> None: + """ + Check that the sequence of commands from mock calls matches the + expected string. + + :param exp: expected command string + :param call_args_list: list of mock call objects + """ + actual_cmds = _extract_commands_from_call(call_args_list) + actual_cmds = "\n".join(actual_cmds) + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) - def test_docker_build_single_arch(self) -> None: + +# ############################################################################# +# Test_docker_build_local_image1 +# ############################################################################# + + +class Test_docker_build_local_image1(_DockerFlowTestHelper): + + def test_docker_build_local_image_single_arch(self) -> None: """ Test building a local Docker image with single architecture. @@ -84,20 +134,14 @@ def test_docker_build_single_arch(self) -> None: generated for building a local Docker image with single architecture. """ - # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" # Call tested function. hltadore.docker_build_local_image( - mock_ctx, - test_version, + self.mock_ctx, + self.test_version, cache=False, - base_image=test_base_image, + base_image=self.test_base_image, poetry_mode="update", ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.dev .dockerignore @@ -114,17 +158,9 @@ def test_docker_build_single_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) - def test_docker_build_multi_arch(self) -> None: + def test_docker_build_local_image_multi_arch(self) -> None: """ Test building a local Docker image with multiple architectures. @@ -132,23 +168,15 @@ def test_docker_build_multi_arch(self) -> None: generated for building a local Docker image with multiple architectures. """ - # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" - test_multi_arch = "linux/amd64,linux/arm64" # Call tested function. hltadore.docker_build_local_image( - mock_ctx, - test_version, + self.mock_ctx, + self.test_version, cache=False, - base_image=test_base_image, + base_image=self.test_base_image, poetry_mode="update", - multi_arch=test_multi_arch, + multi_arch=self.test_multi_arch, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.dev .dockerignore docker buildx create \ @@ -173,12 +201,148 @@ def test_docker_build_multi_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, + self._check_docker_command_output(exp, self.mock_run.call_args_list) + + +# ############################################################################# +# Test_docker_build_prod_image1 +# ############################################################################# + + +class Test_docker_build_prod_image1(_DockerFlowTestHelper): + + def test_docker_build_prod_image(self) -> None: + """ + Test building a prod Docker image with single architecture. + + This test verifies that the correct sequence of commands is + generated for building a prod Docker image. + """ + # Call tested function. + hltadore.docker_build_prod_image( + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, + cache=False, + ) + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-1.0.0 \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=test.ecr.path \ + . + docker tag test-registry.com/test-image:prod-1.0.0 test-registry.com/test-image:prod + docker image ls test-registry.com/test-image:prod + """ + self._check_docker_command_output(exp, self.mock_run.call_args_list) + + def test_docker_build_multi_arch_prod_image(self) -> None: + """ + Test building a prod Docker image with multiple architectures. + + This test verifies that the correct sequence of commands is + generated for building a multi-arch prod Docker image. + """ + # Call tested function. + hltadore.docker_build_multi_arch_prod_image( + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, + cache=False, + multi_arch=self.test_multi_arch, + ) + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + docker buildx create \ + --name multiarch_builder \ + --driver docker-container \ + --bootstrap \ + && \ + docker buildx use multiarch_builder + tar -czh . | DOCKER_BUILDKIT=0 \ + time \ + docker buildx build \ + --no-cache \ + --push \ + --platform linux/amd64,linux/arm64 \ + --build-arg VERSION=1.0.0 --build-arg ECR_BASE_PATH=test.ecr.path \ + --tag test-registry.com/test-image:prod-1.0.0 \ + --file devops/docker_build/prod.Dockerfile \ + - + docker pull test-registry.com/test-image:prod-1.0.0 + docker image ls test-registry.com/test-image:prod-1.0.0 + """ + self._check_docker_command_output(exp, self.mock_run.call_args_list) + + def test_docker_build_prod_image_with_candidate_tag(self) -> None: + """ + Test building a prod Docker image with candidate mode using tag. + + This test verifies that the correct sequence of commands is + generated for building a prod image with candidate mode using + tag. + """ + test_tag = "test_tag" + # Call tested function. + hltadore.docker_build_prod_image( + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, + cache=False, + candidate=True, + tag=test_tag, + ) + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-test_tag \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=test.ecr.path \ + . + docker image ls test-registry.com/test-image:prod-test_tag + """ + self._check_docker_command_output(exp, self.mock_run.call_args_list) + + def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: + """ + Test building a prod Docker image with candidate mode using user tag. + + This test verifies that the correct sequence of commands is + generated for building a prod image with candidate mode using + user tag and tag. + """ + test_user_tag = "test_user" + test_tag = "test_tag" + # Call tested function. + hltadore.docker_build_prod_image( + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, + cache=False, + candidate=True, + user_tag=test_user_tag, + tag=test_tag, ) + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-test_user-test_tag \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=test.ecr.path \ + . + docker image ls test-registry.com/test-image:prod-test_user-test_tag + """ + self._check_docker_command_output(exp, self.mock_run.call_args_list)