from unittest.mock import MagicMock, Mock, patch import pytest from lib.pagerduty.resources.services import ( BusinessService, TechnicalService, _transform_service, _validate_component, fetch_service_dependencies, fetch_services, filter_services, get_all_technical_services_with_metadata, ) @pytest.fixture def mock_session(): """Create a mock API session.""" return MagicMock() @pytest.fixture def service_data(): """Basic service data fixture.""" return { "id": "SERVICE123", "name": "Test Service", "description": "A test service", "status": "active", "created_at": "2023-01-01T00:00:00Z", "updated_at": "2023-01-02T00:00:00Z", "html_url": "https://example.pagerduty.com/service/SERVICE123", "self": "https://api.pagerduty.com/services/SERVICE123", "escalation_policy": {"id": "EP123", "name": "Test Policy"}, "teams": [{"id": "TEAM1", "summary": "Team 1"}], } @pytest.fixture def sample_services(): """Sample service data for testing.""" return [ { "id": "P123", "name": "Production Service", "type": "service", "teams": [{"summary": "Platform Team"}], "escalation_policy": { "escalation_rules": [ { "targets": [ {"type": "user", "id": "U123"}, {"type": "user", "id": "U456"}, ] } ] }, }, { "id": "P456", "name": "Staging Service", "type": "service", "teams": [{"summary": "DevOps Team"}], "escalation_policy": { "escalation_rules": [{"targets": [{"type": "user", "id": "U789"}]}] }, }, { "id": "B123", "name": "Business Service", "type": "business_service", "teams": [{"summary": "Platform Team"}], }, ] @pytest.fixture def mock_services(): """Create mock services for dependency testing.""" service1 = TechnicalService({"id": "SERVICE1", "name": "Service 1"}) service2 = TechnicalService({"id": "SERVICE2", "name": "Service 2"}) return [service1, service2] @pytest.fixture def technical_service(): """Create a mock technical service for testing.""" service = Mock(spec=TechnicalService) service.name = "Test Service" service.description = "A test service" service.id = "P123456" service.status = "active" service.html_url = "https://pagerduty.com/services/P123456" service.self_url = "https://api.pagerduty.com/services/P123456" return service @pytest.fixture def business_service(): """Create a mock business service for testing.""" service = Mock(spec=BusinessService) service.name = "Test Business Service" service.description = "A test business service" service.id = "P789012" service.html_url = "https://pagerduty.com/services/P789012" service.self_url = "https://api.pagerduty.com/services/P789012" return service @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_TEAM", "Platform Team") def test_filter_services_by_team(sample_services): """Test filtering services by team.""" filtered = filter_services(sample_services) assert len(filtered) == 2 assert all( service["teams"][0]["summary"] == "Platform Team" for service in filtered ) @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_USERS", ["U123"]) def test_filter_services_by_users(sample_services): """Test filtering services by users in escalation policy.""" filtered = filter_services(sample_services) # Should include both the matching technical service and the business service assert len(filtered) == 2 # Verify the technical service with matching user is included assert any(service["id"] == "P123" for service in filtered) # Verify the business service is included (not filtered by users) assert any(service["type"] == "business_service" for service in filtered) @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_SERVICE_REGEX", "Prod.*") def test_filter_services_by_regex(sample_services): """Test filtering services by name regex pattern.""" filtered = filter_services(sample_services) assert len(filtered) == 1 assert filtered[0]["name"] == "Production Service" @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_TEAM", "") @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_USERS", []) def test_filter_services_no_filters(sample_services): """Test that no filters returns all services.""" filtered = filter_services(sample_services) assert len(filtered) == len(sample_services) @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_TEAM", "Platform Team") @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_USERS", ["U123"]) @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_SERVICE_REGEX", "Prod.*") def test_filter_services_multiple_filters(sample_services): """Test applying multiple filters together.""" filtered = filter_services(sample_services) assert len(filtered) == 1 assert filtered[0]["id"] == "P123" assert filtered[0]["teams"][0]["summary"] == "Platform Team" assert filtered[0]["name"] == "Production Service" @patch("lib.pagerduty.resources.services.PAGERDUTY_FILTER_USERS", ["U123"]) def test_filter_services_business_services(sample_services): """Test that business services are not filtered by user assignments.""" filtered = filter_services(sample_services) assert len(filtered) == 2 assert any(service["type"] == "business_service" for service in filtered) def test_technical_service_init(service_data): """Test TechnicalService initialization with basic fields.""" service = TechnicalService(service_data) assert service.id == "SERVICE123" assert service.name == "Test Service" assert service.description == "A test service" assert service.status == "active" assert service.created_at == "2023-01-01T00:00:00Z" assert service.updated_at == "2023-01-02T00:00:00Z" assert service.html_url == "https://example.pagerduty.com/service/SERVICE123" assert service.self_url == "https://api.pagerduty.com/services/SERVICE123" assert service.escalation_policy == {"id": "EP123", "name": "Test Policy"} assert service.teams == [{"id": "TEAM1", "summary": "Team 1"}] assert service.dependencies == [] assert service.raw_data == service_data def test_technical_service_str(): """Test string representation of the service.""" service = TechnicalService({"id": "SERVICE123", "name": "Test Service"}) assert str(service) == "TechnicalService(id=SERVICE123, name=Test Service)" def test_fetch_services(mock_session): """Test fetching services from PagerDuty API.""" mock_session.list_all.return_value = [ {"id": "SERVICE1", "name": "Service 1"}, {"id": "SERVICE2", "name": "Service 2"}, ] services = fetch_services(mock_session) # Verify API call mock_session.list_all.assert_called_once_with( "services", params={"include[]": ["integrations", "teams"]} ) # Verify results assert len(services) == 2 assert isinstance(services[0], TechnicalService) assert services[0].id == "SERVICE1" assert services[1].id == "SERVICE2" def test_fetch_services_without_includes(mock_session): """Test fetching services without including integrations or teams.""" mock_session.list_all.return_value = [{"id": "SERVICE1"}] services = fetch_services( mock_session, include_integrations=False, include_teams=False ) # Verify API call with no includes mock_session.list_all.assert_called_once_with("services", params={}) # Verify results assert len(services) == 1 assert isinstance(services[0], TechnicalService) def test_fetch_service_dependencies(mock_session, mock_services): """Test fetching service dependencies.""" # Mock the dependencies API call - only mock for the first service to simplify mock_session.get.side_effect = [ { "relationships": [{"supporting_service": {"id": "SERVICE2"}}] }, # First call returns SERVICE2 as a dependency {"relationships": []}, # Second call returns no dependencies ] fetch_service_dependencies(mock_session, mock_services) # Verify API calls - should be called for each service assert mock_session.get.call_count == 2 mock_session.get.assert_any_call("service_dependencies/technical_services/SERVICE1") mock_session.get.assert_any_call("service_dependencies/technical_services/SERVICE2") # Verify that service1 now has service2 as a dependency assert len(mock_services[0].dependencies) == 1 assert mock_services[0].dependencies[0] == mock_services[1] # Service2 should have no dependencies since the mock returned empty list assert len(mock_services[1].dependencies) == 0 @patch("lib.pagerduty.resources.services.fetch_service_dependencies") @patch("lib.pagerduty.resources.services.fetch_services") def test_get_all_technical_services_with_metadata(mock_fetch_services, mock_fetch_deps): """Test getting all services with their metadata.""" mock_session = MagicMock() mock_services = [MagicMock(), MagicMock()] mock_fetch_services.return_value = mock_services result = get_all_technical_services_with_metadata(mock_session) # Verify calls mock_fetch_services.assert_called_once_with(mock_session) mock_fetch_deps.assert_called_once_with(mock_session, mock_services) # Verify result assert result == mock_services def test_transform_technical_service(technical_service): """Test transforming a technical service.""" component = _transform_service(technical_service) # Verify the component structure assert component["apiVersion"] == "servicemodel.ext.grafana.com/v1alpha1" assert component["kind"] == "Component" assert component["metadata"]["name"] == "test-service" assert component["spec"]["type"] == "service" assert component["spec"]["description"] == "A test service" # Verify annotations annotations = component["metadata"]["annotations"] assert annotations["pagerduty.com/service-id"] == "P123456" assert annotations["pagerduty.com/status"] == "active" assert ( annotations["pagerduty.com/html-url"] == "https://pagerduty.com/services/P123456" ) assert ( annotations["pagerduty.com/api-url"] == "https://api.pagerduty.com/services/P123456" ) def test_transform_business_service(business_service): """Test transforming a business service.""" component = _transform_service(business_service) # Verify the component structure assert component["apiVersion"] == "servicemodel.ext.grafana.com/v1alpha1" assert component["kind"] == "Component" assert component["metadata"]["name"] == "test-business-service" assert component["spec"]["type"] == "business_service" assert component["spec"]["description"] == "A test business service" # Verify annotations annotations = component["metadata"]["annotations"] assert annotations["pagerduty.com/service-id"] == "P789012" assert ( annotations["pagerduty.com/html-url"] == "https://pagerduty.com/services/P789012" ) assert ( annotations["pagerduty.com/api-url"] == "https://api.pagerduty.com/services/P789012" ) def test_validate_component(): """Test component validation.""" # Test valid component valid_component = { "apiVersion": "servicemodel.ext.grafana.com/v1alpha1", "kind": "Component", "metadata": { "name": "test-service", "annotations": { "pagerduty.com/service-id": "P123456", "pagerduty.com/status": "active", }, }, "spec": {"type": "service", "description": "A test service"}, } errors = _validate_component(valid_component) assert errors == [] # Test missing required field invalid_component = valid_component.copy() del invalid_component["spec"] errors = _validate_component(invalid_component) assert "Missing required field: spec" in errors