265 lines
9.0 KiB
Python

from unittest.mock import MagicMock, patch
import click
import pytest
from docker import DockerClient
from datahub.cli.docker_cli import (
_check_upgrade_and_show_instructions,
check,
download_compose_files,
get_github_file_url,
)
@pytest.fixture
def mock_click():
with patch("datahub.cli.docker_cli.click") as mock:
yield mock
@pytest.fixture
def mock_docker_client():
with patch("datahub.cli.docker_check.get_docker_client") as mock:
client = MagicMock(spec=DockerClient)
mock.return_value.__enter__.return_value = client
yield client
@pytest.fixture
def click_context():
"""Create a Click context for testing commands"""
ctx = click.Context(check)
ctx.obj = {} # Initialize empty context object
return ctx
def test_check_healthy(mock_click, mock_docker_client, click_context):
# Setup mock Docker client responses
# Mock containers
container1 = MagicMock()
container1.name = "datahub-mysql-1"
container1.status = "running"
container1.attrs = {"State": {"Health": {"Status": "healthy"}}}
container1.labels = {
"com.docker.compose.service": "mysql",
"com.docker.compose.project.config_files": "docker-compose.yml",
}
container2 = MagicMock()
container2.name = "datahub-frontend-1"
container2.status = "running"
container2.attrs = {"State": {"Health": {"Status": "healthy"}}}
container2.labels = {
"com.docker.compose.service": "frontend",
"com.docker.compose.project.config_files": "docker-compose.yml",
}
mock_docker_client.containers.list.return_value = [container1, container2]
mock_docker_client.containers.get.return_value = container1
mock_docker_client.containers.get.side_effect = lambda name: next(
(c for c in [container1, container2] if c.name == name), None
)
# Mock networks
network = MagicMock()
network.name = "datahub_network"
mock_docker_client.networks.list.return_value = [network]
# Mock volumes
volume = MagicMock()
volume.name = "datahub_mysqldata"
mock_docker_client.volumes.list.return_value = [volume]
# Mock compose file
with (
patch(
"datahub.cli.docker_check._get_services_from_compose",
return_value={"mysql", "frontend"},
),
patch(
"datahub.cli.docker_check._get_volumes_from_compose",
return_value={"datahub_mysqldata"},
),
):
# Execute
click_context.invoke(check)
# Verify
mock_click.secho.assert_called_once_with("✔ No issues detected", fg="green")
def test_check_unhealthy(mock_click, mock_docker_client, click_context):
# Setup mock Docker client responses
# Mock containers with unhealthy status
container1 = MagicMock()
container1.name = "datahub-mysql-1"
container1.status = "running"
container1.attrs = {"State": {"Health": {"Status": "unhealthy"}}}
container1.labels = {
"com.docker.compose.service": "mysql",
"com.docker.compose.project.config_files": "docker-compose.yml",
}
mock_docker_client.containers.list.return_value = [container1]
mock_docker_client.containers.get.return_value = container1
# Mock networks
network = MagicMock()
network.name = "datahub_network"
mock_docker_client.networks.list.return_value = [network]
# Mock volumes
volume = MagicMock()
volume.name = "datahub_mysqldata"
mock_docker_client.volumes.list.return_value = [volume]
# Mock compose file
with (
patch(
"datahub.cli.docker_check._get_services_from_compose",
return_value={"mysql"},
),
patch(
"datahub.cli.docker_check._get_volumes_from_compose",
return_value={"datahub_mysqldata"},
),
):
# Execute and verify
with pytest.raises(Exception) as exc_info:
click_context.invoke(check)
assert "issues were detected" in str(exc_info.value)
def test_check_missing_containers(mock_click, mock_docker_client, click_context):
# Setup mock Docker client responses
# Return empty container list to simulate missing containers
mock_docker_client.containers.list.return_value = []
# Mock networks
network = MagicMock()
network.name = "datahub_network"
mock_docker_client.networks.list.return_value = [network]
# Mock volumes
volume = MagicMock()
volume.name = "datahub_mysqldata"
mock_docker_client.volumes.list.return_value = [volume]
# Mock compose file
with (
patch(
"datahub.cli.docker_check._get_services_from_compose",
return_value={"mysql", "frontend"},
),
patch(
"datahub.cli.docker_check._get_volumes_from_compose",
return_value={"datahub_mysqldata"},
),
):
# Execute and verify
with pytest.raises(Exception) as exc_info:
click_context.invoke(check)
assert "issues were detected" in str(exc_info.value)
def test_get_github_file_url_default():
"""Test get_github_file_url with default environment (no DOCKER_COMPOSE_BASE set)"""
with patch.dict("os.environ", {}, clear=True):
result = get_github_file_url("v1.0.0")
expected = "https://raw.githubusercontent.com/datahub-project/datahub/v1.0.0/docker/quickstart/docker-compose.quickstart-profile.yml"
assert result == expected
def test_get_github_file_url_custom_base():
"""Test get_github_file_url with custom DOCKER_COMPOSE_BASE environment variable"""
custom_base = "https://github.com/my-fork/datahub/my-branch"
with patch.dict("os.environ", {"DOCKER_COMPOSE_BASE": custom_base}):
result = get_github_file_url("v2.0.0")
expected = (
f"{custom_base}/docker/quickstart/docker-compose.quickstart-profile.yml"
)
assert result == expected
def test_download_compose_files_404_error():
"""Test download_compose_files when quickstart_download_response.status_code is 404"""
# Mock the requests.Session and its get method
mock_response = MagicMock()
mock_response.status_code = 404
mock_session = MagicMock()
mock_session.get.return_value = mock_response
with (
patch("datahub.cli.docker_cli.requests.Session", return_value=mock_session),
patch("datahub.cli.docker_cli.tempfile.NamedTemporaryFile") as mock_tempfile,
):
# Setup tempfile mock
mock_tempfile_instance = MagicMock()
mock_tempfile_instance.__enter__.return_value = mock_tempfile_instance
mock_tempfile_instance.__exit__.return_value = None
mock_tempfile_instance.name = "/tmp/test.yml"
mock_tempfile.return_value = mock_tempfile_instance
# Test that ClickException is raised with the correct message
with pytest.raises(click.ClickException) as exc_info:
download_compose_files(None, [], "v1.0.0")
expected_message = "Could not find quickstart compose file for version v1.0.0. Please try a different version or check the version exists at https://github.com/datahub-project/datahub/releases"
assert str(exc_info.value) == expected_message
def test_check_upgrade_and_show_instructions_upgrade_not_supported():
"""Test _check_upgrade_and_show_instructions when check_upgrade_supported returns False"""
# Mock the dependencies
mock_status = MagicMock()
mock_status.is_ok.return_value = (
True # Status is OK, so migration instructions will be shown
)
with (
patch(
"datahub.cli.docker_cli.check_docker_quickstart", return_value=mock_status
),
patch("datahub.cli.docker_cli.check_upgrade_supported", return_value=False),
patch(
"datahub.cli.docker_cli.show_migration_instructions"
) as mock_show_migration,
):
# Call the function
result = _check_upgrade_and_show_instructions([])
# Verify that show_migration_instructions was called (since status.is_ok() returns True)
mock_show_migration.assert_called_once()
# Verify that the function returns False
assert result is False
def test_check_upgrade_and_show_instructions_upgrade_not_supported_repair():
"""Test _check_upgrade_and_show_instructions when check_upgrade_supported returns False and status is not OK"""
# Mock the dependencies
mock_status = MagicMock()
mock_status.is_ok.return_value = (
False # Status is not OK, so repair instructions will be shown
)
with (
patch(
"datahub.cli.docker_cli.check_docker_quickstart", return_value=mock_status
),
patch("datahub.cli.docker_cli.check_upgrade_supported", return_value=False),
patch("datahub.cli.docker_cli.show_repair_instructions") as mock_show_repair,
):
# Call the function
result = _check_upgrade_and_show_instructions([])
# Verify that show_repair_instructions was called (since status.is_ok() returns False)
mock_show_repair.assert_called_once()
# Verify that the function returns False
assert result is False