diff --git a/src/a2a_server/adcp_a2a_server.py b/src/a2a_server/adcp_a2a_server.py index 3661a841f..da003594c 100644 --- a/src/a2a_server/adcp_a2a_server.py +++ b/src/a2a_server/adcp_a2a_server.py @@ -61,6 +61,7 @@ from src.core.audit_logger import get_audit_logger from src.core.auth_utils import get_principal_from_token from src.core.config_loader import get_current_tenant +from src.core.database.models import PushNotificationConfig as DBPushNotificationConfig from src.core.domain_config import get_a2a_server_url, get_sales_agent_domain from src.core.schemas import ( GetSignalsRequest, @@ -99,8 +100,6 @@ ) from src.services.protocol_webhook_service import get_protocol_webhook_service -from src.core.database.models import PushNotificationConfig as DBPushNotificationConfig - # Configure logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) @@ -939,7 +938,7 @@ async def on_get_task_push_notification_config( from a2a.types import InvalidParamsError, TaskNotFoundError from src.core.database.database_session import get_db_session - + try: # Get authentication token auth_token = self._get_auth_token() @@ -1864,7 +1863,15 @@ async def _handle_update_media_buy_skill(self, parameters: dict, auth_token: str async def _handle_get_media_buy_delivery_skill(self, parameters: dict, auth_token: str) -> dict: """Handle explicit get_media_buy_delivery skill invocation (CRITICAL for monitoring). - Accepts media_buy_ids (plural, per AdCP v1.6.0 spec) or media_buy_id (singular, legacy). + Per AdCP spec, all parameters are optional: + - media_buy_ids (plural, per AdCP v1.6.0 spec) or media_buy_id (singular, legacy) + - buyer_refs: Filter by buyer reference IDs + - status_filter: Filter by status (active, pending, paused, completed, failed, all) + - start_date: Start date for reporting period (YYYY-MM-DD) + - end_date: End date for reporting period (YYYY-MM-DD) + + When no media_buy_ids are provided, returns delivery data for all media buys + the requester has access to, filtered by the provided criteria. """ try: # Create ToolContext from A2A auth info @@ -1881,18 +1888,19 @@ async def _handle_get_media_buy_delivery_skill(self, parameters: dict, auth_toke if media_buy_id: media_buy_ids = [media_buy_id] - # Validate that we have at least one ID - if not media_buy_ids: - return { - "success": False, - "message": "Missing required parameter: 'media_buy_ids' (or 'media_buy_id' for single buy)", - "required_parameters": ["media_buy_ids"], - "received_parameters": list(parameters.keys()), - } + # Extract other optional parameters + buyer_refs = parameters.get("buyer_refs") + status_filter = parameters.get("status_filter") + start_date = parameters.get("start_date") + end_date = parameters.get("end_date") - # Call core function directly with spec-compliant plural parameter + # Call core function with all parameters (all are optional per AdCP spec) response = core_get_media_buy_delivery_tool( media_buy_ids=media_buy_ids, + buyer_refs=buyer_refs, + status_filter=status_filter, + start_date=start_date, + end_date=end_date, context=self._tool_context_to_mcp_context(tool_context), ) diff --git a/tests/unit/test_a2a_parameter_mapping.py b/tests/unit/test_a2a_parameter_mapping.py index f6b523129..fa067a329 100644 --- a/tests/unit/test_a2a_parameter_mapping.py +++ b/tests/unit/test_a2a_parameter_mapping.py @@ -175,6 +175,50 @@ def test_get_media_buy_delivery_uses_plural_media_buy_ids(self): assert "media_buy_ids" in call_kwargs, "Should pass 'media_buy_ids' (plural) per AdCP spec" assert call_kwargs["media_buy_ids"] == parameters["media_buy_ids"] + def test_get_media_buy_delivery_optional_media_buy_ids(self): + """ + Test that get_media_buy_delivery works without media_buy_ids. + + Per AdCP spec, all parameters are optional. When media_buy_ids is omitted, + the server should return delivery data for all media buys the requester + has access to, filtered by the provided criteria (status_filter, dates, etc). + """ + from src.a2a_server.adcp_a2a_server import AdCPRequestHandler + + handler = AdCPRequestHandler() + + with ( + patch("src.a2a_server.adcp_a2a_server.get_principal_from_token") as mock_principal, + patch("src.a2a_server.adcp_a2a_server.get_current_tenant") as mock_tenant, + patch("src.a2a_server.adcp_a2a_server.core_get_media_buy_delivery_tool") as mock_delivery, + ): + mock_principal.return_value = "principal_123" + mock_tenant.return_value = {"tenant_id": "tenant_123"} + mock_delivery.return_value = {"media_buys": []} + + # AdCP request with filters but no media_buy_ids + parameters = { + "status_filter": "active", + "start_date": "2025-01-01", + "end_date": "2025-01-31", + } + + import asyncio + + result = asyncio.run( + handler._handle_get_media_buy_delivery_skill(parameters=parameters, auth_token="test_token") + ) + + # Verify core function was called with filters + mock_delivery.assert_called_once() + call_kwargs = mock_delivery.call_args.kwargs + + # Should pass None for media_buy_ids and include filters + assert call_kwargs["media_buy_ids"] is None, "media_buy_ids should be None when omitted" + assert call_kwargs["status_filter"] == "active", "Should pass status_filter" + assert call_kwargs["start_date"] == "2025-01-01", "Should pass start_date" + assert call_kwargs["end_date"] == "2025-01-31", "Should pass end_date" + def test_create_media_buy_validates_required_adcp_parameters(self): """ Test that create_media_buy validates required AdCP parameters.