1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-02-04 07:11:49 +00:00
ansible-podman-collections/tests/unit/plugins/modules/test_podman_image.py
Sergey 4d1917ac57
Add unittests for podman_image (#961)
Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
2025-08-06 00:05:07 +03:00

262 lines
10 KiB
Python

from __future__ import absolute_import, division, print_function
__metaclass__ = type
from unittest.mock import Mock, patch
import pytest
from ansible.module_utils.basic import AnsibleModule
class TestPodmanImageModule:
"""Unit tests for podman_image.py module."""
@pytest.mark.parametrize(
"test_params, expected_valid",
[
# Valid minimal parameters
({"name": "alpine"}, True),
# Valid parameters with tag
({"name": "alpine", "tag": "3.19"}, True),
# Valid parameters with digest
({"name": "alpine", "tag": "sha256:abc123def456"}, True),
# Valid parameters with version@digest (issue #947 scenario)
({"name": "docker.io/valkey/valkey", "tag": "8-bookworm@sha256:abc123"}, True),
# Valid parameters with all image states
({"name": "alpine", "state": "present"}, True),
({"name": "alpine", "state": "absent"}, True),
({"name": "alpine", "state": "build", "path": "/tmp"}, True),
({"name": "alpine", "state": "quadlet"}, True),
# Valid build parameters
(
{
"name": "myimage",
"state": "build",
"path": "/tmp",
"build": {"format": "oci", "cache": True, "file": "/tmp/Dockerfile"},
},
True,
),
# Valid push parameters
(
{
"name": "alpine",
"push": True,
"push_args": {"dest": "registry.example.com/alpine", "transport": "docker"},
},
True,
),
# Valid authentication parameters
({"name": "alpine", "username": "testuser", "password": "testpass"}, True),
],
)
def test_module_parameter_validation(self, test_params, expected_valid):
"""Test that valid parameters are accepted."""
# Mock the PodmanImageManager to avoid actual execution
with patch(
"ansible_collections.containers.podman.plugins.modules.podman_image.PodmanImageManager"
) as mock_manager, patch(
"ansible_collections.containers.podman.plugins.modules.podman_image.create_quadlet_state"
) as mock_quadlet:
mock_manager_instance = Mock()
mock_manager_instance.execute.return_value = {"changed": False, "image": {}}
mock_manager.return_value = mock_manager_instance
mock_quadlet.return_value = {"changed": False}
# Mock AnsibleModule.exit_json to capture the call
with patch.object(AnsibleModule, "exit_json") as mock_exit:
with patch.object(AnsibleModule, "__init__", return_value=None) as mock_init:
# Create a mock module instance
mock_module = Mock()
mock_module.params = test_params
mock_module.get_bin_path.return_value = "/usr/bin/podman"
mock_module.check_mode = False
# Mock the AnsibleModule constructor to return our mock
mock_init.return_value = None
# We can't easily test the full main() function due to AnsibleModule constructor,
# so we test parameter validation by creating an AnsibleModule directly
if expected_valid:
# For quadlet state, test that code path
if test_params.get("state") == "quadlet":
mock_module.params["state"] = "quadlet"
# This would normally call create_quadlet_state
assert test_params.get("state") == "quadlet"
else:
# For other states, test that PodmanImageManager would be called
assert test_params.get("name") is not None
@pytest.mark.parametrize(
"invalid_params, expected_error",
[
# Missing required name parameter
({}, "name"),
# Invalid state
({"name": "alpine", "state": "invalid"}, "state"),
# Invalid build format
({"name": "alpine", "build": {"format": "invalid"}}, "format"),
# Invalid push transport
({"name": "alpine", "push_args": {"transport": "invalid"}}, "transport"),
],
)
def test_module_parameter_validation_failures(self, invalid_params, expected_error):
"""Test that invalid parameters are rejected."""
# Test parameter validation by checking that certain combinations should fail
# Note: Full validation testing would require mocking AnsibleModule completely
# This is a basic structure test
assert expected_error in ["name", "state", "format", "transport"]
@pytest.mark.parametrize(
"state, should_call_quadlet",
[
("present", False),
("absent", False),
("build", False),
("quadlet", True),
],
)
def test_state_handling_logic(self, state, should_call_quadlet):
"""Test that different states are handled correctly."""
# This tests the logical flow rather than the full execution
if should_call_quadlet:
# Quadlet state should trigger quadlet code path
assert state == "quadlet"
else:
# Other states should trigger PodmanImageManager
assert state in ["present", "absent", "build"]
def test_mutual_exclusion_logic(self):
"""Test that mutually exclusive parameters are defined correctly."""
# Test the logic that auth_file and username/password are mutually exclusive
# These combinations should be mutually exclusive:
# - auth_file with username
# - auth_file with password
mutually_exclusive_combinations = [
({"auth_file": "/path/to/auth", "username": "user"}, True),
({"auth_file": "/path/to/auth", "password": "pass"}, True),
({"username": "user", "password": "pass"}, False), # This should be allowed
({"auth_file": "/path/to/auth"}, False), # This should be allowed
]
for params, should_be_exclusive in mutually_exclusive_combinations:
# This tests the logic of mutual exclusion
has_auth_file = "auth_file" in params
has_credentials = "username" in params or "password" in params
if should_be_exclusive:
assert has_auth_file and has_credentials
else:
assert not (has_auth_file and has_credentials) or not has_auth_file
def test_required_together_logic(self):
"""Test that username and password are required together."""
# Test that username and password should be required together
test_cases = [
({"username": "user"}, True), # Missing password
({"password": "pass"}, True), # Missing username
({"username": "user", "password": "pass"}, False), # Both present
({}, False), # Neither present
]
for params, should_fail_required_together in test_cases:
has_username = "username" in params
has_password = "password" in params
if should_fail_required_together:
# One is present but not the other
assert has_username != has_password
else:
# Both present or both absent
assert has_username == has_password
@pytest.mark.parametrize(
"build_params, expected_valid",
[
# Valid build parameters
({"format": "oci"}, True),
({"format": "docker"}, True),
({"cache": True}, True),
({"cache": False}, True),
({"force_rm": True}, True),
({"rm": False}, True),
({"file": "/path/to/Dockerfile"}, True),
({"target": "production"}, True),
# Complex valid build config
(
{
"format": "oci",
"cache": True,
"force_rm": False,
"rm": True,
"file": "/tmp/Dockerfile",
"target": "prod",
"annotation": {"version": "1.0"},
"volume": ["/tmp:/tmp"],
"extra_args": "--build-arg VERSION=1.0",
},
True,
),
],
)
def test_build_parameters_structure(self, build_params, expected_valid):
"""Test that build parameter structures are valid."""
# Test build parameter validation logic
if "format" in build_params:
assert build_params["format"] in ["oci", "docker"]
if "cache" in build_params:
assert isinstance(build_params["cache"], bool)
if "volume" in build_params:
assert isinstance(build_params["volume"], list)
# All test cases should be valid in this test
assert expected_valid
@pytest.mark.parametrize(
"push_params, expected_valid",
[
# Valid push parameters
({"transport": "docker"}, True),
({"transport": "docker-archive"}, True),
({"format": "oci"}, True),
({"format": "v2s1"}, True),
({"dest": "registry.example.com/image"}, True),
({"compress": True}, True),
({"remove_signatures": False}, True),
# Complex valid push config
(
{
"transport": "docker",
"format": "oci",
"dest": "registry.example.com/myimage",
"compress": True,
"remove_signatures": False,
"extra_args": "--insecure",
},
True,
),
],
)
def test_push_parameters_structure(self, push_params, expected_valid):
"""Test that push parameter structures are valid."""
# Test push parameter validation logic
if "transport" in push_params:
valid_transports = ["dir", "docker-archive", "docker-daemon", "oci-archive", "ostree", "docker"]
assert push_params["transport"] in valid_transports
if "format" in push_params:
assert push_params["format"] in ["oci", "v2s1", "v2s2"]
if "compress" in push_params:
assert isinstance(push_params["compress"], bool)
# All test cases should be valid in this test
assert expected_valid