oncall-engine/tools/migrators/lib/tests/pagerduty/resources/test_services.py
Joey Orlando e4728ea69f
feat: add opsgenie to migrator script (#5495)
This PR adds support for migrating data from OpsGenie to Grafana IRM.

Closes https://github.com/grafana/irm/issues/1179
2025-04-07 08:47:27 -04:00

344 lines
12 KiB
Python

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