"""Tests for infrastructure endpoints.""" import pytest from fastapi.testclient import TestClient from unittest.mock import patch, AsyncMock, MagicMock from src.main import app @pytest.fixture def client(): """Create a test client.""" return TestClient(app) @pytest.fixture def mock_portainer(): """Create a mock Portainer client.""" mock = AsyncMock() return mock @pytest.fixture def mock_npm(): """Create a mock NPM client.""" mock = AsyncMock() return mock class TestInfrastructureHealth: """Test /infrastructure/health endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_health_returns_200(self, mock_get_npm, mock_get_portainer, client): """Health endpoint should return 200.""" mock_portainer = AsyncMock() mock_portainer.health_check.return_value = True mock_portainer.get_stacks.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.health_check.return_value = True mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/health") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_health_returns_connection_status(self, mock_get_npm, mock_get_portainer, client): """Health endpoint should return connection status for both services.""" mock_portainer = AsyncMock() mock_portainer.health_check.return_value = True mock_portainer.get_stacks.return_value = [{"Id": 1}, {"Id": 2}] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.health_check.return_value = True mock_npm.get_proxy_hosts.return_value = [{"id": 1}] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/health") data = response.json() assert "portainer_connected" in data assert "npm_connected" in data assert "total_stacks" in data assert "total_proxy_hosts" in data assert data["portainer_connected"] is True assert data["npm_connected"] is True assert data["total_stacks"] == 2 assert data["total_proxy_hosts"] == 1 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_health_handles_disconnected_services(self, mock_get_npm, mock_get_portainer, client): """Health should handle when services are disconnected.""" mock_portainer = AsyncMock() mock_portainer.health_check.return_value = False mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.health_check.return_value = False mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/health") data = response.json() assert data["portainer_connected"] is False assert data["npm_connected"] is False assert data["total_stacks"] == 0 assert data["total_proxy_hosts"] == 0 class TestInfrastructureServices: """Test /infrastructure/services endpoints.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_services_returns_200(self, mock_get_npm, mock_get_portainer, client): """List services should return 200.""" mock_portainer = AsyncMock() mock_portainer.get_stacks.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/services") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_services_returns_stack_info(self, mock_get_npm, mock_get_portainer, client): """List services should return stack information.""" mock_portainer = AsyncMock() mock_portainer.get_stacks.return_value = [ {"Id": 1, "Name": "stack1", "Status": 1, "EndpointId": 1}, {"Id": 2, "Name": "stack2", "Status": 2, "EndpointId": 1} ] mock_portainer.get_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/services") data = response.json() assert isinstance(data, list) assert len(data) == 2 assert data[0]["name"] == "stack1" assert data[1]["name"] == "stack2" class TestInfrastructurePorts: """Test /infrastructure/ports endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_ports_returns_200(self, mock_get_npm, mock_get_portainer, client): """List ports should return 200.""" mock_portainer = AsyncMock() mock_portainer.get_endpoints.return_value = [{"Id": 1}] mock_portainer.get_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/ports") assert response.status_code == 200 class TestInfrastructureDomains: """Test /infrastructure/domains endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_domains_returns_200(self, mock_get_npm, mock_get_portainer, client): """List domains should return 200.""" mock_portainer = AsyncMock() mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/domains") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_domains_returns_domain_info(self, mock_get_npm, mock_get_portainer, client): """List domains should return domain information.""" mock_portainer = AsyncMock() mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [ { "id": 1, "domain_names": ["example.com", "www.example.com"], "forward_host": "app", "forward_port": 8080, "ssl_certificate_id": 1 } ] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/domains") data = response.json() assert isinstance(data, list) # Each domain name should be a separate entry assert len(data) >= 1 class TestInfrastructureContainers: """Test /infrastructure/containers endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_list_containers_returns_200(self, mock_get_npm, mock_get_portainer, client): """List containers should return 200.""" mock_portainer = AsyncMock() mock_portainer.list_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/containers") assert response.status_code == 200 class TestInfrastructureWidgetData: """Test /infrastructure/widget-data endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_widget_data_returns_200(self, mock_get_npm, mock_get_portainer, client): """Widget data should return 200.""" mock_portainer = AsyncMock() mock_portainer.health_check.return_value = True mock_portainer.get_stacks.return_value = [] mock_portainer.list_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.health_check.return_value = True mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/widget-data") assert response.status_code == 200 class TestInfrastructureServiceGroups: """Test /infrastructure/service-groups endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_service_groups_returns_200(self, mock_get_npm, mock_get_portainer, client): """Service groups should return 200.""" mock_portainer = AsyncMock() mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/service-groups") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_service_groups_returns_group_data(self, mock_get_npm, mock_get_portainer, client): """Service groups should return group data.""" mock_portainer = AsyncMock() mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/service-groups") data = response.json() # Should return a dict or list of service groups assert isinstance(data, (dict, list)) class TestInfrastructureResources: """Test /infrastructure/resources endpoints.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_system_resources_returns_200(self, mock_get_npm, mock_get_portainer, client): """System resources should return 200.""" mock_portainer = AsyncMock() mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/resources/system") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_container_resources_returns_200(self, mock_get_npm, mock_get_portainer, client): """Container resources should return 200.""" mock_portainer = AsyncMock() mock_portainer.list_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/resources/containers") assert response.status_code == 200 class TestInfrastructureServiceStatus: """Test /infrastructure/services/{service}/status endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_service_status_returns_200(self, mock_get_npm, mock_get_portainer, client): """Service status should return 200.""" mock_portainer = AsyncMock() mock_portainer.get_stacks.return_value = [ {"Id": 1, "Name": "testservice", "Status": 1, "EndpointId": 1} ] mock_portainer.get_containers.return_value = [ {"Names": ["/testservice_app_1"], "State": "running"} ] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/services/testservice/status") assert response.status_code == 200 class TestInfrastructureContainerActions: """Test container action endpoints.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_get_container_logs_returns_200(self, mock_get_npm, mock_get_portainer, client): """Get container logs should return 200.""" mock_portainer = AsyncMock() mock_portainer.list_containers.return_value = [ {"Names": ["/testcontainer"], "Id": "abc123"} ] mock_portainer.get_endpoints.return_value = [{"Id": 1}] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/containers/testcontainer/logs") # Response depends on container existence assert response.status_code in [200, 404] @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_get_single_container_returns_200(self, mock_get_npm, mock_get_portainer, client): """Get single container should return 200.""" mock_portainer = AsyncMock() mock_portainer.inspect_container.return_value = { "Id": "abc123", "Name": "/testcontainer", "State": {"Status": "running"} } mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/containers/testcontainer") assert response.status_code == 200 class TestInfrastructureGetService: """Test /infrastructure/services/{name} endpoint.""" @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_get_service_returns_200(self, mock_get_npm, mock_get_portainer, client): """Get service should return 200.""" mock_portainer = AsyncMock() mock_portainer.get_stacks.return_value = [ {"Id": 1, "Name": "testservice", "Status": 1, "EndpointId": 1} ] mock_portainer.get_containers.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_npm.get_proxy_hosts.return_value = [] mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/services/testservice") assert response.status_code == 200 @patch("src.controllers.infrastructure_controller.get_portainer_client") @patch("src.controllers.infrastructure_controller.get_npm_client") def test_get_service_returns_404_for_missing(self, mock_get_npm, mock_get_portainer, client): """Get service should return 404 for non-existent service.""" mock_portainer = AsyncMock() mock_portainer.get_stacks.return_value = [] mock_get_portainer.return_value = mock_portainer mock_npm = AsyncMock() mock_get_npm.return_value = mock_npm response = client.get("/infrastructure/services/nonexistent") assert response.status_code == 404