diff --git a/README.md b/README.md index 741e4448..3775121e 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,41 @@ pip install adcp > **Note**: This client requires Python 3.10 or later and supports both synchronous and asynchronous workflows. +## Quick Start: Test Helpers + +The fastest way to get started is using the pre-configured test agents: + +```python +from adcp.testing import test_agent +from adcp.types.generated import GetProductsRequest + +# Zero configuration - just import and use! +result = await test_agent.get_products( + GetProductsRequest( + brief="Coffee subscription service", + promoted_offering="Premium coffee deliveries" + ) +) + +if result.success: + print(f"Found {len(result.data.products)} products") +``` + +Test helpers include: +- **`test_agent`**: Pre-configured MCP test agent (ready to use) +- **`test_agent_a2a`**: Pre-configured A2A test agent +- **`creative_agent`**: Reference creative agent for preview functionality +- **`test_agent_client`**: Multi-agent client with both protocols +- **`create_test_agent()`**: Factory for custom test configurations + +> **Note**: Test agents are rate-limited and for testing/examples only. DO NOT use in production. + +See [examples/test_helpers_demo.py](examples/test_helpers_demo.py) for more examples. + ## Quick Start: Distributed Operations +For production use, configure your own agents: + ```python from adcp import ADCPMultiAgentClient, AgentConfig, GetProductsRequest @@ -75,6 +108,50 @@ async with ADCPMultiAgentClient( ## Features +### Test Helpers + +Pre-configured test agents for instant prototyping and testing: + +```python +from adcp.testing import test_agent, test_agent_a2a, creative_agent, test_agent_client, create_test_agent +from adcp.types.generated import GetProductsRequest, PreviewCreativeRequest + +# 1. Single agent (MCP) +result = await test_agent.get_products( + GetProductsRequest(brief="Coffee brands") +) + +# 2. Single agent (A2A) +result = await test_agent_a2a.get_products( + GetProductsRequest(brief="Coffee brands") +) + +# 3. Creative agent (preview functionality) +result = await creative_agent.preview_creative( + PreviewCreativeRequest( + manifest={"format_id": "banner_300x250", "assets": {...}} + ) +) + +# 4. Multi-agent (parallel execution) +results = await test_agent_client.get_products( + GetProductsRequest(brief="Coffee brands") +) + +# 5. Custom configuration +from adcp.client import ADCPClient +config = create_test_agent(id="my-test", timeout=60.0) +client = ADCPClient(config) +``` + +**Use cases:** +- Quick prototyping and experimentation +- Example code and documentation +- Integration testing without mock servers +- Learning AdCP concepts + +**Important:** Test agents are public, rate-limited, and for testing only. Never use in production. + ### Full Protocol Support - **A2A Protocol**: Native support for Agent-to-Agent protocol - **MCP Protocol**: Native support for Model Context Protocol diff --git a/examples/test_helpers_demo.py b/examples/test_helpers_demo.py new file mode 100755 index 00000000..b2fe39ce --- /dev/null +++ b/examples/test_helpers_demo.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Test Helpers Demo - Using Pre-configured Test Agents. + +This example shows how to use the built-in test helpers for quick testing and examples. +""" + +from __future__ import annotations + +import asyncio + +from adcp.client import ADCPMultiAgentClient +from adcp.testing import ( + create_test_agent, + test_agent, + test_agent_a2a, + test_agent_client, +) +from adcp.types.generated import GetProductsRequest, ListCreativeFormatsRequest + + +async def simplest_example() -> None: + """Example 1: Simplest Possible Usage. + + Use the pre-configured test agent directly - no setup needed! + """ + print("šŸŽÆ Example 1: Simplest Usage with test_agent") + print("=" * 43) + print() + + try: + # Just import and use - that's it! + result = await test_agent.get_products( + GetProductsRequest( + brief="Premium coffee subscription service", + promoted_offering="Artisan coffee deliveries", + ) + ) + + if result.success and result.data: + print(f"āœ… Success! Found {len(result.data.products)} products") + print(" Protocol: MCP") + print() + else: + print(f"āŒ Error: {result.error}") + print() + except Exception as e: + print(f"āŒ Network error: {e}") + print() + + +async def protocol_comparison() -> None: + """Example 2: Testing Both Protocols. + + Use both A2A and MCP test agents to compare behavior. + """ + print("šŸ”„ Example 2: Protocol Comparison (A2A vs MCP)") + print("=" * 46) + print() + + request = GetProductsRequest( + brief="Sustainable fashion brands", + promoted_offering="Eco-friendly clothing", + ) + + try: + print("Testing MCP protocol...") + mcp_result = await test_agent.get_products(request) + print(f" MCP: {'āœ…' if mcp_result.success else 'āŒ'}") + + print("Testing A2A protocol...") + a2a_result = await test_agent_a2a.get_products(request) + print(f" A2A: {'āœ…' if a2a_result.success else 'āŒ'}") + print() + except Exception as e: + print(f"āŒ Error: {e}") + print() + + +async def multi_agent_example() -> None: + """Example 3: Multi-Agent Testing. + + Use the test_agent_client for parallel operations. + """ + print("🌐 Example 3: Multi-Agent Operations") + print("=" * 36) + print() + + try: + print(f"Testing with {len(test_agent_client.agent_ids)} agents in parallel...") + + # Run the same query on both agents in parallel + results = await test_agent_client.get_products( + GetProductsRequest( + brief="Tech gadgets for remote work", + promoted_offering="Ergonomic workspace solutions", + ) + ) + + print("\nResults:") + for i, result in enumerate(results, 1): + print(f" {i}. {'āœ…' if result.success else 'āŒ'}") + print() + except Exception as e: + print(f"āŒ Error: {e}") + print() + + +async def custom_test_agent() -> None: + """Example 4: Custom Test Agent Configuration. + + Create a custom test agent with modifications. + """ + print("āš™ļø Example 4: Custom Test Agent Configuration") + print("=" * 46) + print() + + # Create a custom config with your own ID + custom_config = create_test_agent( + id="my-custom-test", + timeout=60.0, + ) + + print("Created custom config:") + print(f" ID: {custom_config.id}") + print(f" Protocol: {custom_config.protocol}") + print(f" URI: {custom_config.agent_uri}") + print(f" Timeout: {custom_config.timeout}s") + print() + + # Use it with a client + client = ADCPMultiAgentClient([custom_config]) + agent = client.agent("my-custom-test") + + try: + result = await agent.get_products( + GetProductsRequest( + brief="Travel packages", + promoted_offering="European vacations", + ) + ) + + print(f"Result: {'āœ… Success' if result.success else 'āŒ Failed'}") + print() + except Exception as e: + print(f"āŒ Error: {e}") + print() + finally: + await client.close() + + +async def various_operations() -> None: + """Example 5: Testing Different Operations. + + Show various ADCP operations with test agents. + """ + print("šŸŽ¬ Example 5: Various ADCP Operations") + print("=" * 37) + print() + + try: + # Get products + print("1. Getting products...") + products = await test_agent.get_products( + GetProductsRequest( + brief="Coffee brands", + promoted_offering="Premium coffee", + ) + ) + success = "āœ…" if products.success else "āŒ" + count = len(products.data.products) if products.data else 0 + print(f" {success} Products: {count}") + + # List creative formats + print("2. Listing creative formats...") + formats = await test_agent.list_creative_formats( + ListCreativeFormatsRequest() + ) + success = "āœ…" if formats.success else "āŒ" + count = len(formats.data.formats) if formats.data else 0 + print(f" {success} Formats: {count}") + + print() + except Exception as e: + print(f"āŒ Error: {e}") + print() + + +async def main() -> None: + """Main function - run all examples.""" + print("\nšŸ“š ADCP Test Helpers - Demo Examples") + print("=" * 37) + print("These examples show how to use pre-configured test agents\n") + + await simplest_example() + await protocol_comparison() + await multi_agent_example() + await custom_test_agent() + await various_operations() + + print("šŸ’” Key Takeaways:") + print(" • test_agent = Pre-configured MCP test agent (ready to use!)") + print(" • test_agent_a2a = Pre-configured A2A test agent") + print(" • test_agent_client = Multi-agent client with both protocols") + print(" • create_test_agent() = Create custom test configurations") + print(" • Perfect for examples, docs, and quick testing") + print("\nāš ļø Remember: Test agents are rate-limited and for testing only!") + print(" DO NOT use in production applications.\n") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 7cd15d1c..64338f9c 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -18,6 +18,19 @@ ADCPWebhookError, ADCPWebhookSignatureError, ) + +# Test helpers +from adcp.testing import ( + CREATIVE_AGENT_CONFIG, + TEST_AGENT_A2A_CONFIG, + TEST_AGENT_MCP_CONFIG, + TEST_AGENT_TOKEN, + create_test_agent, + creative_agent, + test_agent, + test_agent_a2a, + test_agent_client, +) from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus, WebhookMetadata from adcp.types.generated import ( ActivateSignalError, @@ -145,6 +158,16 @@ "TaskResult", "TaskStatus", "WebhookMetadata", + # Test helpers + "test_agent", + "test_agent_a2a", + "creative_agent", + "test_agent_client", + "create_test_agent", + "TEST_AGENT_TOKEN", + "TEST_AGENT_MCP_CONFIG", + "TEST_AGENT_A2A_CONFIG", + "CREATIVE_AGENT_CONFIG", # Exceptions "ADCPError", "ADCPConnectionError", diff --git a/src/adcp/testing/__init__.py b/src/adcp/testing/__init__.py new file mode 100644 index 00000000..fc8dc95a --- /dev/null +++ b/src/adcp/testing/__init__.py @@ -0,0 +1,30 @@ +"""Test helpers for AdCP client library. + +Provides pre-configured test agents for examples and quick testing. +""" + +from __future__ import annotations + +from adcp.testing.test_helpers import ( + CREATIVE_AGENT_CONFIG, + TEST_AGENT_A2A_CONFIG, + TEST_AGENT_MCP_CONFIG, + TEST_AGENT_TOKEN, + create_test_agent, + creative_agent, + test_agent, + test_agent_a2a, + test_agent_client, +) + +__all__ = [ + "test_agent", + "test_agent_a2a", + "creative_agent", + "test_agent_client", + "create_test_agent", + "TEST_AGENT_TOKEN", + "TEST_AGENT_MCP_CONFIG", + "TEST_AGENT_A2A_CONFIG", + "CREATIVE_AGENT_CONFIG", +] diff --git a/src/adcp/testing/test_helpers.py b/src/adcp/testing/test_helpers.py new file mode 100644 index 00000000..ba549755 --- /dev/null +++ b/src/adcp/testing/test_helpers.py @@ -0,0 +1,225 @@ +"""Test agent helpers for easy examples and quick testing. + +These provide pre-configured access to AdCP's public test agent. +""" + +from __future__ import annotations + +from typing import Any + +from adcp.client import ADCPClient, ADCPMultiAgentClient +from adcp.types.core import AgentConfig, Protocol + +# Public test agent auth token +# This token is public and rate-limited, for testing/examples only. +TEST_AGENT_TOKEN = "1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ" + +# Public test agent configuration - MCP protocol +TEST_AGENT_MCP_CONFIG = AgentConfig( + id="test-agent-mcp", + agent_uri="https://test-agent.adcontextprotocol.org/mcp/", + protocol=Protocol.MCP, + auth_token=TEST_AGENT_TOKEN, +) + +# Public test agent configuration - A2A protocol +TEST_AGENT_A2A_CONFIG = AgentConfig( + id="test-agent-a2a", + agent_uri="https://test-agent.adcontextprotocol.org", + protocol=Protocol.A2A, + auth_token=TEST_AGENT_TOKEN, +) + +# Reference creative agent configuration - MCP protocol +# No authentication required for the reference creative agent +CREATIVE_AGENT_CONFIG = AgentConfig( + id="creative-agent", + agent_uri="https://creative.adcontextprotocol.org/mcp", + protocol=Protocol.MCP, +) + + +def _create_test_agent_client() -> ADCPClient: + """Create pre-configured test agent client using MCP protocol. + + Returns: + ADCPClient instance configured for the public test agent + + Note: + This agent is rate-limited and intended for testing/examples only. + The auth token is public and may be rotated without notice. + DO NOT use in production applications. + """ + return ADCPClient(TEST_AGENT_MCP_CONFIG) + + +def _create_test_agent_a2a_client() -> ADCPClient: + """Create pre-configured test agent client using A2A protocol. + + Returns: + ADCPClient instance configured for the public test agent + + Note: + This agent is rate-limited and intended for testing/examples only. + The auth token is public and may be rotated without notice. + DO NOT use in production applications. + """ + return ADCPClient(TEST_AGENT_A2A_CONFIG) + + +def _create_creative_agent_client() -> ADCPClient: + """Create pre-configured creative agent client. + + Returns: + ADCPClient instance configured for the reference creative agent + + Note: + The reference creative agent is public and requires no authentication. + It provides creative preview functionality for testing and examples. + """ + return ADCPClient(CREATIVE_AGENT_CONFIG) + + +def _create_test_multi_agent_client() -> ADCPMultiAgentClient: + """Create multi-agent client with both test agents configured. + + Returns: + ADCPMultiAgentClient with both MCP and A2A test agents + + Note: + This client is rate-limited and intended for testing/examples only. + DO NOT use in production applications. + """ + return ADCPMultiAgentClient([TEST_AGENT_MCP_CONFIG, TEST_AGENT_A2A_CONFIG]) + + +# Pre-configured test agent client using MCP protocol. +# Ready to use for examples, documentation, and quick testing. +# +# Example: +# ```python +# from adcp.testing import test_agent +# +# # Simple get_products call +# result = await test_agent.get_products( +# GetProductsRequest( +# brief="Coffee subscription service for busy professionals", +# promoted_offering="Premium monthly coffee deliveries" +# ) +# ) +# +# if result.success: +# print(f"Found {len(result.data.products)} products") +# ``` +# +# Note: +# This agent is rate-limited and intended for testing/examples only. +# The auth token is public and may be rotated without notice. +# DO NOT use in production applications. +test_agent: ADCPClient = _create_test_agent_client() + +# Pre-configured test agent client using A2A protocol. +# Identical functionality to test_agent but uses A2A instead of MCP. +# +# Example: +# ```python +# from adcp.testing import test_agent_a2a +# +# result = await test_agent_a2a.get_products( +# GetProductsRequest( +# brief="Sustainable fashion brands", +# promoted_offering="Eco-friendly clothing" +# ) +# ) +# ``` +# +# Note: +# This agent is rate-limited and intended for testing/examples only. +# The auth token is public and may be rotated without notice. +# DO NOT use in production applications. +test_agent_a2a: ADCPClient = _create_test_agent_a2a_client() + +# Pre-configured reference creative agent. +# Provides creative preview functionality without authentication. +# +# Example: +# ```python +# from adcp.testing import creative_agent +# from adcp.types.generated import PreviewCreativeRequest +# +# result = await creative_agent.preview_creative( +# PreviewCreativeRequest( +# manifest={ +# "format_id": "banner_300x250", +# "assets": {...} +# } +# ) +# ) +# ``` +# +# Note: +# The reference creative agent is public and requires no authentication. +# Perfect for testing creative rendering and preview functionality. +creative_agent: ADCPClient = _create_creative_agent_client() + +# Multi-agent client with both test agents configured. +# Useful for testing multi-agent patterns and protocol comparisons. +# +# Example: +# ```python +# from adcp.testing import test_agent_client +# +# # Access individual agents +# mcp_agent = test_agent_client.agent("test-agent-mcp") +# a2a_agent = test_agent_client.agent("test-agent-a2a") +# +# # Use for parallel operations +# results = await test_agent_client.get_products( +# GetProductsRequest( +# brief="Premium coffee brands", +# promoted_offering="Artisan coffee" +# ) +# ) +# ``` +# +# Note: +# This client is rate-limited and intended for testing/examples only. +# DO NOT use in production applications. +test_agent_client: ADCPMultiAgentClient = _create_test_multi_agent_client() + + +def create_test_agent(**overrides: Any) -> AgentConfig: + """Create a custom test agent configuration. + + Useful when you need to modify the default test agent setup. + + Args: + **overrides: Keyword arguments to override default config values + + Returns: + Complete agent configuration + + Example: + ```python + from adcp.testing import create_test_agent + from adcp.client import ADCPClient + + # Use default test agent with custom ID + config = create_test_agent(id="my-test-agent") + client = ADCPClient(config) + ``` + + Example: + ```python + # Use A2A protocol instead of MCP + from adcp.types.core import Protocol + + config = create_test_agent( + protocol=Protocol.A2A, + agent_uri="https://test-agent.adcontextprotocol.org" + ) + ``` + """ + base_config = TEST_AGENT_MCP_CONFIG.model_dump() + base_config.update(overrides) + return AgentConfig(**base_config) diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..c208d4ed --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,179 @@ +"""Tests for the test helpers module.""" + +from __future__ import annotations + +from adcp.client import ADCPClient, ADCPMultiAgentClient +from adcp.testing import ( + CREATIVE_AGENT_CONFIG, + TEST_AGENT_A2A_CONFIG, + TEST_AGENT_MCP_CONFIG, + TEST_AGENT_TOKEN, + create_test_agent, + creative_agent, + test_agent, + test_agent_a2a, + test_agent_client, +) +from adcp.types.core import Protocol + + +def test_exports_from_testing_module(): + """Test that all expected exports are available from testing module.""" + # These imports should work without errors + from adcp.testing import ( + TEST_AGENT_A2A_CONFIG, + TEST_AGENT_MCP_CONFIG, + TEST_AGENT_TOKEN, + create_test_agent, + test_agent, + test_agent_a2a, + test_agent_client, + ) + + assert test_agent is not None + assert test_agent_a2a is not None + assert test_agent_client is not None + assert callable(create_test_agent) + assert isinstance(TEST_AGENT_TOKEN, str) + assert TEST_AGENT_MCP_CONFIG is not None + assert TEST_AGENT_A2A_CONFIG is not None + + +def test_test_agent_token(): + """Test that TEST_AGENT_TOKEN is a valid string.""" + assert isinstance(TEST_AGENT_TOKEN, str) + assert len(TEST_AGENT_TOKEN) > 0 + assert TEST_AGENT_TOKEN == "1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ" + + +def test_mcp_config_structure(): + """Test TEST_AGENT_MCP_CONFIG has correct structure.""" + assert TEST_AGENT_MCP_CONFIG.id == "test-agent-mcp" + assert TEST_AGENT_MCP_CONFIG.protocol == Protocol.MCP + # AgentConfig validator strips trailing slashes for consistency + assert TEST_AGENT_MCP_CONFIG.agent_uri == "https://test-agent.adcontextprotocol.org/mcp" + assert TEST_AGENT_MCP_CONFIG.auth_token is not None + + +def test_a2a_config_structure(): + """Test TEST_AGENT_A2A_CONFIG has correct structure.""" + assert TEST_AGENT_A2A_CONFIG.id == "test-agent-a2a" + assert TEST_AGENT_A2A_CONFIG.protocol == Protocol.A2A + assert TEST_AGENT_A2A_CONFIG.agent_uri == "https://test-agent.adcontextprotocol.org" + assert TEST_AGENT_A2A_CONFIG.auth_token is not None + + +def test_test_agent_is_adcp_client(): + """Test that test_agent is an ADCPClient instance.""" + assert isinstance(test_agent, ADCPClient) + assert hasattr(test_agent, "get_products") + assert hasattr(test_agent, "list_creative_formats") + assert callable(test_agent.get_products) + assert callable(test_agent.list_creative_formats) + + +def test_test_agent_a2a_is_adcp_client(): + """Test that test_agent_a2a is an ADCPClient instance.""" + assert isinstance(test_agent_a2a, ADCPClient) + assert hasattr(test_agent_a2a, "get_products") + assert hasattr(test_agent_a2a, "list_creative_formats") + assert callable(test_agent_a2a.get_products) + assert callable(test_agent_a2a.list_creative_formats) + + +def test_test_agent_client_is_multi_agent(): + """Test that test_agent_client is an ADCPMultiAgentClient instance.""" + assert isinstance(test_agent_client, ADCPMultiAgentClient) + assert hasattr(test_agent_client, "agent") + assert hasattr(test_agent_client, "agents") + assert callable(test_agent_client.agent) + assert len(test_agent_client.agent_ids) == 2 + + +def test_test_agent_client_provides_access_to_both_agents(): + """Test that test_agent_client provides access to both MCP and A2A agents.""" + mcp_agent = test_agent_client.agent("test-agent-mcp") + a2a_agent = test_agent_client.agent("test-agent-a2a") + + assert mcp_agent is not None + assert a2a_agent is not None + assert isinstance(mcp_agent, ADCPClient) + assert isinstance(a2a_agent, ADCPClient) + assert hasattr(mcp_agent, "get_products") + assert hasattr(a2a_agent, "get_products") + + +def test_create_test_agent_default(): + """Test that create_test_agent creates valid config with defaults.""" + config = create_test_agent() + + assert config.id == "test-agent-mcp" + assert config.protocol == Protocol.MCP + assert config.auth_token is not None + + +def test_create_test_agent_with_overrides(): + """Test that create_test_agent allows overrides.""" + config = create_test_agent( + id="custom-test-agent", + timeout=60.0, + ) + + assert config.id == "custom-test-agent" + assert config.timeout == 60.0 + assert config.protocol == Protocol.MCP # unchanged + assert config.auth_token is not None # retained + + +def test_create_test_agent_protocol_override(): + """Test that create_test_agent allows protocol override.""" + config = create_test_agent( + protocol=Protocol.A2A, + agent_uri="https://test-agent.adcontextprotocol.org", + ) + + assert config.protocol == Protocol.A2A + assert config.agent_uri == "https://test-agent.adcontextprotocol.org" + + +def test_test_agent_config_match(): + """Test that test_agent uses TEST_AGENT_MCP_CONFIG.""" + assert test_agent.agent_config.id == TEST_AGENT_MCP_CONFIG.id + assert test_agent.agent_config.protocol == TEST_AGENT_MCP_CONFIG.protocol + + +def test_test_agent_a2a_config_match(): + """Test that test_agent_a2a uses TEST_AGENT_A2A_CONFIG.""" + assert test_agent_a2a.agent_config.id == TEST_AGENT_A2A_CONFIG.id + assert test_agent_a2a.agent_config.protocol == TEST_AGENT_A2A_CONFIG.protocol + + +def test_agent_ids_in_test_agent_client(): + """Test that test_agent_client has correct agent IDs.""" + agent_ids = test_agent_client.agent_ids + assert "test-agent-mcp" in agent_ids + assert "test-agent-a2a" in agent_ids + + +def test_creative_agent_config_structure(): + """Test CREATIVE_AGENT_CONFIG has correct structure.""" + assert CREATIVE_AGENT_CONFIG.id == "creative-agent" + assert CREATIVE_AGENT_CONFIG.protocol == Protocol.MCP + assert CREATIVE_AGENT_CONFIG.agent_uri == "https://creative.adcontextprotocol.org/mcp" + # Creative agent requires no authentication + assert CREATIVE_AGENT_CONFIG.auth_token is None + + +def test_creative_agent_is_adcp_client(): + """Test that creative_agent is an ADCPClient instance.""" + assert isinstance(creative_agent, ADCPClient) + assert hasattr(creative_agent, "preview_creative") + assert hasattr(creative_agent, "list_creative_formats") + assert callable(creative_agent.preview_creative) + assert callable(creative_agent.list_creative_formats) + + +def test_creative_agent_config_match(): + """Test that creative_agent uses CREATIVE_AGENT_CONFIG.""" + assert creative_agent.agent_config.id == CREATIVE_AGENT_CONFIG.id + assert creative_agent.agent_config.protocol == CREATIVE_AGENT_CONFIG.protocol