"""Unit tests for DockerDAO class."""

import unittest
from unittest.mock import Mock, MagicMock, patch, call
from typing import Dict, Any
import logging

import docker
from docker.errors import DockerException, APIError, NotFound
from docker.models.services import Service as DockerServiceObject
from docker.models.containers import Container as DockerContainer
from docker.models.images import Image as DockerImage

import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../src')))

from blackant.docker.dao import (
    DockerDAO,
    DockerConnectionError,
    ImageConfig,
    ResourceConfig,
    ServiceConfig,
)

# Disable logging during tests to avoid cluttering test output
logging.disable(logging.CRITICAL)


class TestDockerDAO(unittest.TestCase):
    """Test cases for DockerDAO class."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.mock_http_client = MagicMock()
        self.registry_config = {
            "url": "test-registry.com",
            "username": "test-user",
            "password": "test-pass",
        }

    @patch("blackant.docker.dao.docker.from_env")
    def test_init_without_docker_host(self, mock_from_env):
        """Test initialization without specifying docker host."""
        mock_from_env.return_value = self.mock_docker_client
        
        dao = DockerDAO()
        
        mock_from_env.assert_called_once()
        self.assertEqual(dao.docker_client, self.mock_docker_client)
        self.assertEqual(dao.registry_config, {})

    @patch("blackant.docker.dao.docker.DockerClient")
    def test_init_with_docker_host(self, mock_docker_client_class):
        """Test initialization with specific docker host."""
        mock_docker_client_class.return_value = self.mock_docker_client
        docker_host = "tcp://localhost:2375"
        
        dao = DockerDAO(docker_host=docker_host)
        
        mock_docker_client_class.assert_called_once_with(base_url=docker_host)
        self.assertEqual(dao.docker_client, self.mock_docker_client)

    @patch("blackant.docker.dao.docker.from_env")
    def test_init_with_registry_config(self, mock_from_env):
        """Test initialization with registry configuration."""
        mock_from_env.return_value = self.mock_docker_client
        
        dao = DockerDAO(registry_config=self.registry_config)
        
        self.mock_docker_client.login.assert_called_once_with(
            registry="test-registry.com",
            username="test-user",
            password="test-pass",
        )

    @patch("blackant.docker.dao.docker.from_env")
    def test_init_docker_connection_error(self, mock_from_env):
        """Test initialization failure when Docker daemon is not available."""
        mock_from_env.side_effect = DockerException("Cannot connect to Docker daemon")
        
        with self.assertRaises(DockerConnectionError) as context:
            DockerDAO()
        
        self.assertIn("Cannot connect to Docker daemon", str(context.exception))

    @patch("blackant.docker.dao.docker.from_env")
    def test_registry_login_failure(self, mock_from_env):
        """Test registry login failure."""
        mock_from_env.return_value = self.mock_docker_client
        self.mock_docker_client.login.side_effect = APIError("Login failed")
        
        with self.assertRaises(DockerConnectionError) as context:
            DockerDAO(registry_config=self.registry_config)
        
        self.assertIn("Registry login failed", str(context.exception))


class TestDockerDAOContainerOperations(unittest.TestCase):
    """Test cases for DockerDAO container operations."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.dao = self._create_dao()

    def _create_dao(self):
        """Helper to create DockerDAO instance with mocked client."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            return DockerDAO()

    def test_get_containers_all(self):
        """Test getting all containers."""
        mock_containers = [MagicMock(), MagicMock()]
        self.mock_docker_client.containers.list.return_value = mock_containers
        
        result = self.dao.get_containers(all_containers=True)
        
        self.mock_docker_client.containers.list.assert_called_once_with(all=True)
        self.assertEqual(result, mock_containers)

    def test_get_containers_running_only(self):
        """Test getting running containers only."""
        mock_containers = [MagicMock()]
        self.mock_docker_client.containers.list.return_value = mock_containers
        
        result = self.dao.get_containers(all_containers=False)
        
        self.mock_docker_client.containers.list.assert_called_once_with(all=False)
        self.assertEqual(result, mock_containers)

    def test_get_containers_api_error(self):
        """Test get_containers with API error."""
        self.mock_docker_client.containers.list.side_effect = APIError("API Error")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_containers()
        
        self.assertIn("Failed to list containers", str(context.exception))

    def test_get_container_success(self):
        """Test getting a specific container by ID."""
        mock_container = MagicMock()
        self.mock_docker_client.containers.get.return_value = mock_container
        container_id = "test-container-id"
        
        result = self.dao.get_container(container_id)
        
        self.mock_docker_client.containers.get.assert_called_once_with(container_id)
        self.assertEqual(result, mock_container)

    def test_get_container_not_found(self):
        """Test get_container when container doesn't exist."""
        container_id = "non-existent-container"
        self.mock_docker_client.containers.get.side_effect = NotFound("Container not found")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_container(container_id)
        
        self.assertIn(f"Container {container_id} not found", str(context.exception))

    def test_get_container_api_error(self):
        """Test get_container with API error."""
        container_id = "test-container"
        self.mock_docker_client.containers.get.side_effect = APIError("API Error")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_container(container_id)
        
        self.assertIn("Failed to get container", str(context.exception))


class TestDockerDAOImageOperations(unittest.TestCase):
    """Test cases for DockerDAO image operations."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.dao = self._create_dao()

    def _create_dao(self):
        """Helper to create DockerDAO instance with mocked client."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            return DockerDAO()

    def test_get_images_all(self):
        """Test getting all images."""
        mock_images = [MagicMock(), MagicMock()]
        self.mock_docker_client.images.list.return_value = mock_images
        
        result = self.dao.get_images(all_images=True)
        
        self.mock_docker_client.images.list.assert_called_once_with(all=True)
        self.assertEqual(result, mock_images)

    def test_get_images_tagged_only(self):
        """Test getting tagged images only."""
        mock_images = [MagicMock()]
        self.mock_docker_client.images.list.return_value = mock_images
        
        result = self.dao.get_images(all_images=False)
        
        self.mock_docker_client.images.list.assert_called_once_with(all=False)
        self.assertEqual(result, mock_images)

    def test_get_images_api_error(self):
        """Test get_images with API error."""
        self.mock_docker_client.images.list.side_effect = APIError("API Error")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_images()
        
        self.assertIn("Failed to list images", str(context.exception))

    def test_pull_image_success(self):
        """Test pulling an image successfully."""
        mock_image = MagicMock()
        self.mock_docker_client.images.pull.return_value = mock_image
        image_name = "nginx"
        tag = "latest"
        
        result = self.dao.pull_image(image_name, tag)
        
        self.mock_docker_client.images.pull.assert_called_once_with(image_name, tag=tag)
        self.assertEqual(result, mock_image)

    def test_pull_image_custom_tag(self):
        """Test pulling an image with custom tag."""
        mock_image = MagicMock()
        self.mock_docker_client.images.pull.return_value = mock_image
        image_name = "python"
        tag = "3.9-slim"
        
        result = self.dao.pull_image(image_name, tag)
        
        self.mock_docker_client.images.pull.assert_called_once_with(image_name, tag=tag)
        self.assertEqual(result, mock_image)

    def test_pull_image_not_found(self):
        """Test pull_image when image doesn't exist."""
        image_name = "non-existent-image"
        tag = "latest"
        self.mock_docker_client.images.pull.side_effect = NotFound("Image not found")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.pull_image(image_name, tag)
        
        self.assertIn(f"Image {image_name}:{tag} not found", str(context.exception))

    def test_pull_image_api_error(self):
        """Test pull_image with API error."""
        image_name = "test-image"
        tag = "latest"
        self.mock_docker_client.images.pull.side_effect = APIError("API Error")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.pull_image(image_name, tag)
        
        self.assertIn("Failed to pull image", str(context.exception))


class TestDockerDAONodeOperations(unittest.TestCase):
    """Test cases for DockerDAO node operations."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.dao = self._create_dao()

    def _create_dao(self):
        """Helper to create DockerDAO instance with mocked client."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            return DockerDAO()

    def test_get_nodes_all(self):
        """Test getting all nodes."""
        mock_nodes = [MagicMock(), MagicMock()]
        self.mock_docker_client.nodes.list.return_value = mock_nodes
        
        result = self.dao.get_nodes()
        
        self.mock_docker_client.nodes.list.assert_called_once()
        self.assertEqual(result, mock_nodes)

    def test_get_nodes_by_name(self):
        """Test getting nodes by exact name match."""
        mock_node1 = MagicMock()
        mock_node1.attrs = {"Description": {"Hostname": "worker-1"}}
        mock_node2 = MagicMock()
        mock_node2.attrs = {"Description": {"Hostname": "worker-2"}}
        mock_nodes = [mock_node1, mock_node2]
        self.mock_docker_client.nodes.list.return_value = mock_nodes
        
        result = self.dao.get_nodes(node_name="worker-1")
        
        self.assertEqual(len(result), 1)
        self.assertEqual(result[0], mock_node1)

    def test_get_nodes_by_id(self):
        """Test getting nodes by ID."""
        mock_nodes = [MagicMock()]
        self.mock_docker_client.nodes.list.return_value = mock_nodes
        node_id = "node123"
        
        result = self.dao.get_nodes(node_id=node_id)
        
        self.mock_docker_client.nodes.list.assert_called_once_with(filters={"id": node_id})
        self.assertEqual(result, mock_nodes)

    def test_get_nodes_api_error(self):
        """Test get_nodes with API error."""
        self.mock_docker_client.nodes.list.side_effect = APIError("API Error")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_nodes()
        
        self.assertIn("Failed to list nodes", str(context.exception))

    def test_get_node_success(self):
        """Test getting a specific node by ID."""
        mock_node = MagicMock()
        self.mock_docker_client.nodes.get.return_value = mock_node
        node_id = "test-node-id"
        
        result = self.dao.get_node(node_id)
        
        self.mock_docker_client.nodes.get.assert_called_once_with(node_id)
        self.assertEqual(result, mock_node)

    def test_get_node_not_found(self):
        """Test get_node when node doesn't exist."""
        node_id = "non-existent-node"
        self.mock_docker_client.nodes.get.side_effect = NotFound("Node not found")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_node(node_id)
        
        self.assertIn(f"Node {node_id} not found", str(context.exception))

    def test_get_node_ip_manager(self):
        """Test getting IP address for manager node."""
        mock_node = MagicMock()
        mock_node.attrs = {
            "ManagerStatus": {"Addr": "192.168.1.100:2377"}
        }
        self.mock_docker_client.nodes.get.return_value = mock_node
        
        result = self.dao.get_node_ip("node-id")
        
        self.assertEqual(result, "192.168.1.100")

    def test_get_node_ip_worker(self):
        """Test getting IP address for worker node."""
        mock_node = MagicMock()
        mock_node.attrs = {
            "Status": {"Addr": "192.168.1.101:2377"}
        }
        self.mock_docker_client.nodes.get.return_value = mock_node
        
        result = self.dao.get_node_ip("node-id")
        
        self.assertEqual(result, "192.168.1.101")

    def test_get_node_ip_not_found(self):
        """Test get_node_ip when node doesn't exist."""
        self.mock_docker_client.nodes.get.side_effect = NotFound("Node not found")
        
        result = self.dao.get_node_ip("non-existent-node")
        
        self.assertIsNone(result)

    def test_get_node_ip_no_address(self):
        """Test get_node_ip when node has no IP address."""
        mock_node = MagicMock()
        mock_node.attrs = {}
        self.mock_docker_client.nodes.get.return_value = mock_node
        
        result = self.dao.get_node_ip("node-id")
        
        self.assertIsNone(result)


class TestDockerDAOServiceOperations(unittest.TestCase):
    """Test cases for DockerDAO service operations."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.dao = self._create_dao()
        self.service_config = ServiceConfig(
            name="test-service",
            image=ImageConfig(
                container="myregistry",
                name="myapp",
                tag="v1.0",
            ),
            service_type="web",
            networks=["test-network"],
            environments={"ENV_VAR": "value"},
            resource_config=ResourceConfig(
                cpu_limit=2,
                ram_limit=2048,
            ),
        )

    def _create_dao(self):
        """Helper to create DockerDAO instance with mocked client."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            return DockerDAO()

    @patch("blackant.docker.dao.docker.types.ServiceMode")
    @patch("blackant.docker.dao.docker.types.Resources")
    def test_create_service_success(self, mock_resources_class, mock_service_mode_class):
        """Test creating a service successfully."""
        mock_service = MagicMock()
        mock_service.id = "service-123"
        self.mock_docker_client.services.create.return_value = mock_service
        mock_resources = MagicMock()
        mock_resources_class.return_value = mock_resources
        mock_service_mode = MagicMock()
        mock_service_mode_class.return_value = mock_service_mode
        
        # Mock pull_image
        with patch.object(self.dao, 'pull_image'):
            result = self.dao.create_service(self.service_config)
        
        self.assertEqual(result, mock_service)
        mock_resources_class.assert_called_once_with(
            cpu_limit=2000000000,  # 2 * 1000000000
            mem_limit=2048000000,  # 2048 * 1000000
        )

    def test_create_service_api_error(self):
        """Test create_service with API error."""
        self.mock_docker_client.services.create.side_effect = APIError("API Error")
        
        with patch.object(self.dao, 'pull_image'):
            with self.assertRaises(DockerConnectionError) as context:
                self.dao.create_service(self.service_config)
        
        self.assertIn("Failed to create service", str(context.exception))

    def test_get_service_success(self):
        """Test getting a service by ID."""
        mock_service = MagicMock()
        self.mock_docker_client.services.get.return_value = mock_service
        service_id = "test-service-id"
        
        result = self.dao.get_service(service_id)
        
        self.mock_docker_client.services.get.assert_called_once_with(service_id)
        self.assertEqual(result, mock_service)

    def test_get_service_not_found(self):
        """Test get_service when service doesn't exist."""
        service_id = "non-existent-service"
        self.mock_docker_client.services.get.side_effect = NotFound("Service not found")
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_service(service_id)
        
        self.assertIn(f"Service {service_id} not found", str(context.exception))

    def test_is_service_running_true(self):
        """Test is_service_running returns True when service has running tasks."""
        mock_service = MagicMock()
        mock_task = {"Status": {"State": "running"}}
        mock_service.tasks.return_value = [mock_task]
        
        with patch.object(self.dao, 'get_service', return_value=mock_service):
            result = self.dao.is_service_running("service-id")
        
        self.assertTrue(result)

    def test_is_service_running_false(self):
        """Test is_service_running returns False when service has no running tasks."""
        mock_service = MagicMock()
        mock_task = {"Status": {"State": "failed"}}
        mock_service.tasks.return_value = [mock_task]
        
        with patch.object(self.dao, 'get_service', return_value=mock_service):
            result = self.dao.is_service_running("service-id")
        
        self.assertFalse(result)

    def test_is_service_running_not_found(self):
        """Test is_service_running returns False when service doesn't exist."""
        with patch.object(self.dao, 'get_service', side_effect=DockerConnectionError("Not found")):
            result = self.dao.is_service_running("non-existent-service")
        
        self.assertFalse(result)

    def test_delete_service_success(self):
        """Test deleting a service successfully."""
        mock_service = MagicMock()
        self.mock_docker_client.services.get.return_value = mock_service
        service_id = "test-service"
        
        self.dao.delete_service(service_id)
        
        self.mock_docker_client.services.get.assert_called_once_with(service_id)
        mock_service.remove.assert_called_once()

    def test_delete_service_not_found(self):
        """Test delete_service when service doesn't exist."""
        service_id = "non-existent-service"
        self.mock_docker_client.services.get.side_effect = NotFound("Service not found")
        
        # Should not raise exception, just log warning
        self.dao.delete_service(service_id)
        
        self.mock_docker_client.services.get.assert_called_once_with(service_id)

    def test_delete_service_api_error(self):
        """Test delete_service with API error."""
        mock_service = MagicMock()
        self.mock_docker_client.services.get.return_value = mock_service
        mock_service.remove.side_effect = APIError("API Error")
        service_id = "test-service"
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.delete_service(service_id)
        
        self.assertIn("Failed to delete service", str(context.exception))


class TestDockerDAOUtilityMethods(unittest.TestCase):
    """Test cases for DockerDAO utility methods."""

    def setUp(self):
        """Set up test fixtures."""
        self.mock_docker_client = MagicMock()
        self.dao = self._create_dao()

    def _create_dao(self):
        """Helper to create DockerDAO instance with mocked client."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            return DockerDAO()

    def test_node_selector_default(self):
        """Test node selector with default values."""
        key, value = self.dao._node_selector("test-service")
        
        self.assertEqual(key, "type")
        self.assertEqual(value, "calculation_worker")

    def test_node_selector_with_preferred_node(self):
        """Test node selector with preferred node that exists."""
        mock_node = MagicMock()
        mock_node.attrs = {"Spec": {"Labels": {"worker_label": "gpu-node"}}}
        
        with patch.object(self.dao, 'get_nodes', return_value=[mock_node]):
            key, value = self.dao._node_selector("test-service", "gpu-node")
        
        self.assertEqual(key, "worker_label")
        self.assertEqual(value, "gpu-node")

    def test_node_selector_preferred_not_available(self):
        """Test node selector when preferred node is not available."""
        mock_node = MagicMock()
        mock_node.attrs = {"Spec": {"Labels": {"worker_label": "cpu-node"}}}
        
        with patch.object(self.dao, 'get_nodes', return_value=[mock_node]):
            key, value = self.dao._node_selector("test-service", "gpu-node")
        
        self.assertEqual(key, "type")
        self.assertEqual(value, "calculation_worker")

    def test_any_task_running_true(self):
        """Test _any_task_running returns True when task is running."""
        mock_service = MagicMock()
        mock_service.tasks.return_value = [
            {"Status": {"State": "pending"}},
            {"Status": {"State": "running"}},
        ]
        
        result = self.dao._any_task_running(mock_service)
        
        self.assertTrue(result)

    def test_any_task_running_false(self):
        """Test _any_task_running returns False when no tasks are running."""
        mock_service = MagicMock()
        mock_service.tasks.return_value = [
            {"Status": {"State": "failed"}},
            {"Status": {"State": "complete"}},
        ]
        
        result = self.dao._any_task_running(mock_service)
        
        self.assertFalse(result)

    def test_any_task_running_exception(self):
        """Test _any_task_running handles exceptions gracefully."""
        mock_service = MagicMock()
        mock_service.tasks.side_effect = Exception("Task error")
        
        result = self.dao._any_task_running(mock_service)
        
        self.assertFalse(result)

    @patch("blackant.docker.dao.time.sleep")
    def test_wait_for_service_ready_success(self, mock_sleep):
        """Test wait_for_service_ready when service becomes ready."""
        mock_service = MagicMock()
        mock_service.name = "test-service"
        
        # First call returns False, second returns True
        with patch.object(self.dao, '_any_task_running', side_effect=[False, True]):
            result = self.dao.wait_for_service_ready(mock_service, timeout=10)
        
        self.assertTrue(result)
        mock_sleep.assert_called_once()

    @patch("blackant.docker.dao.time.sleep")
    def test_wait_for_service_ready_timeout(self, mock_sleep):
        """Test wait_for_service_ready when service doesn't become ready."""
        mock_service = MagicMock()
        mock_service.name = "test-service"
        
        with patch.object(self.dao, '_any_task_running', return_value=False):
            result = self.dao.wait_for_service_ready(mock_service, timeout=2)
        
        self.assertFalse(result)
        self.assertEqual(mock_sleep.call_count, 2)

    def test_get_swarm_info_success(self):
        """Test getting Swarm info successfully."""
        mock_attrs = {"ID": "swarm-123", "Version": {"Index": 10}}
        self.mock_docker_client.swarm.attrs = mock_attrs
        
        result = self.dao.get_swarm_info()
        
        self.assertEqual(result, mock_attrs)

    def test_get_swarm_info_api_error(self):
        """Test get_swarm_info with API error."""
        # Create a property mock that raises an exception when accessed
        type(self.mock_docker_client.swarm).attrs = property(
            lambda x: (_ for _ in ()).throw(APIError("API Error"))
        )
        
        with self.assertRaises(DockerConnectionError) as context:
            self.dao.get_swarm_info()
        
        self.assertIn("Failed to get Swarm info", str(context.exception))

    def test_context_manager(self):
        """Test DockerDAO as context manager."""
        with patch("blackant.docker.dao.docker.from_env") as mock_from_env:
            mock_from_env.return_value = self.mock_docker_client
            
            with DockerDAO() as dao:
                self.assertIsNotNone(dao)
            
            # Check if close was called if it exists
            if hasattr(self.mock_docker_client, 'close'):
                self.mock_docker_client.close.assert_called_once()


class TestDockerDAODataClasses(unittest.TestCase):
    """Test cases for DockerDAO data classes."""

    def test_image_config_defaults(self):
        """Test ImageConfig with default values."""
        config = ImageConfig()
        
        self.assertEqual(config.container, "")
        self.assertEqual(config.name, "")
        self.assertEqual(config.tag, "stable")

    def test_image_config_custom(self):
        """Test ImageConfig with custom values."""
        config = ImageConfig(
            container="myregistry",
            name="myapp",
            tag="v2.0",
        )
        
        self.assertEqual(config.container, "myregistry")
        self.assertEqual(config.name, "myapp")
        self.assertEqual(config.tag, "v2.0")

    def test_resource_config_defaults(self):
        """Test ResourceConfig with default values."""
        config = ResourceConfig()
        
        self.assertFalse(config.use_gpu)
        self.assertEqual(config.cpu_limit, 1)
        self.assertEqual(config.ram_limit, 1024)
        self.assertEqual(config.disk_limit, 1)

    def test_resource_config_custom(self):
        """Test ResourceConfig with custom values."""
        config = ResourceConfig(
            use_gpu=True,
            cpu_limit=4,
            ram_limit=8192,
            disk_limit=10,
        )
        
        self.assertTrue(config.use_gpu)
        self.assertEqual(config.cpu_limit, 4)
        self.assertEqual(config.ram_limit, 8192)
        self.assertEqual(config.disk_limit, 10)

    def test_service_config_defaults(self):
        """Test ServiceConfig with default values."""
        config = ServiceConfig()
        
        self.assertEqual(config.name, "")
        self.assertIsInstance(config.image, ImageConfig)
        self.assertEqual(config.service_type, "unknown")
        self.assertEqual(config.networks, [])
        self.assertEqual(config.environments, {})
        self.assertIsNone(config.parent)
        self.assertIsInstance(config.resource_config, ResourceConfig)
        self.assertEqual(config.node_label, "calculation_worker")

    def test_service_config_custom(self):
        """Test ServiceConfig with custom values."""
        image_config = ImageConfig(container="registry", name="app", tag="latest")
        resource_config = ResourceConfig(cpu_limit=2, ram_limit=4096)
        
        config = ServiceConfig(
            name="my-service",
            image=image_config,
            service_type="api",
            networks=["net1", "net2"],
            environments={"KEY": "value"},
            parent=123,
            resource_config=resource_config,
            node_label="gpu-worker",
        )
        
        self.assertEqual(config.name, "my-service")
        self.assertEqual(config.image, image_config)
        self.assertEqual(config.service_type, "api")
        self.assertEqual(config.networks, ["net1", "net2"])
        self.assertEqual(config.environments, {"KEY": "value"})
        self.assertEqual(config.parent, 123)
        self.assertEqual(config.resource_config, resource_config)
        self.assertEqual(config.node_label, "gpu-worker")


def setUpModule():
    """Module-level setup - disable logging."""
    logging.disable(logging.CRITICAL)


def tearDownModule():
    """Module-level teardown - re-enable logging."""
    logging.disable(logging.NOTSET)


if __name__ == "__main__":
    unittest.main()