This PR adds support for migrating data from OpsGenie to Grafana IRM. Closes https://github.com/grafana/irm/issues/1179
344 lines
12 KiB
Python
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
|