diff --git a/README.md b/README.md index 3775121..b56b0d4 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,10 @@ if result.success: ``` Test helpers include: -- **`test_agent`**: Pre-configured MCP test agent (ready to use) -- **`test_agent_a2a`**: Pre-configured A2A test agent +- **`test_agent`**: Pre-configured MCP test agent with authentication +- **`test_agent_a2a`**: Pre-configured A2A test agent with authentication +- **`test_agent_no_auth`**: Pre-configured MCP test agent WITHOUT authentication +- **`test_agent_a2a_no_auth`**: Pre-configured A2A test agent WITHOUT authentication - **`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 @@ -113,32 +115,47 @@ async with ADCPMultiAgentClient( 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.testing import ( + test_agent, test_agent_a2a, + test_agent_no_auth, test_agent_a2a_no_auth, + creative_agent, test_agent_client, create_test_agent +) from adcp.types.generated import GetProductsRequest, PreviewCreativeRequest -# 1. Single agent (MCP) +# 1. Single agent with authentication (MCP) result = await test_agent.get_products( GetProductsRequest(brief="Coffee brands") ) -# 2. Single agent (A2A) +# 2. Single agent with authentication (A2A) result = await test_agent_a2a.get_products( GetProductsRequest(brief="Coffee brands") ) -# 3. Creative agent (preview functionality) +# 3. Single agent WITHOUT authentication (MCP) +# Useful for testing unauthenticated behavior +result = await test_agent_no_auth.get_products( + GetProductsRequest(brief="Coffee brands") +) + +# 4. Single agent WITHOUT authentication (A2A) +result = await test_agent_a2a_no_auth.get_products( + GetProductsRequest(brief="Coffee brands") +) + +# 5. Creative agent (preview functionality, no auth required) result = await creative_agent.preview_creative( PreviewCreativeRequest( manifest={"format_id": "banner_300x250", "assets": {...}} ) ) -# 4. Multi-agent (parallel execution) +# 6. Multi-agent (parallel execution with both protocols) results = await test_agent_client.get_products( GetProductsRequest(brief="Coffee brands") ) -# 5. Custom configuration +# 7. Custom configuration from adcp.client import ADCPClient config = create_test_agent(id="my-test", timeout=60.0) client = ADCPClient(config) @@ -148,6 +165,7 @@ client = ADCPClient(config) - Quick prototyping and experimentation - Example code and documentation - Integration testing without mock servers +- Testing authentication behavior (comparing auth vs no-auth results) - Learning AdCP concepts **Important:** Test agents are public, rate-limited, and for testing only. Never use in production. @@ -417,6 +435,46 @@ uvx adcp --json myagent get_products '{"brief":"TV ads"}' uvx adcp --debug myagent get_products '{"brief":"TV ads"}' ``` +### Using Test Agents from CLI + +The CLI provides easy access to public test agents without configuration: + +```bash +# Use test agent with authentication (MCP) +uvx adcp https://test-agent.adcontextprotocol.org/mcp/ \ + --auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ \ + get_products '{"brief":"Coffee brands"}' + +# Use test agent WITHOUT authentication (MCP) +uvx adcp https://test-agent.adcontextprotocol.org/mcp/ \ + get_products '{"brief":"Coffee brands"}' + +# Use test agent with authentication (A2A) +uvx adcp --protocol a2a \ + --auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ \ + https://test-agent.adcontextprotocol.org \ + get_products '{"brief":"Coffee brands"}' + +# Save test agent for easier access +uvx adcp --save-auth test-agent https://test-agent.adcontextprotocol.org/mcp/ mcp +# Enter token when prompted: 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ + +# Now use saved config +uvx adcp test-agent get_products '{"brief":"Coffee brands"}' + +# Use creative agent (no auth required) +uvx adcp https://creative.adcontextprotocol.org/mcp \ + preview_creative @creative_manifest.json +``` + +**Test Agent Details:** +- **URL (MCP)**: `https://test-agent.adcontextprotocol.org/mcp/` +- **URL (A2A)**: `https://test-agent.adcontextprotocol.org` +- **Auth Token**: `1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ` (optional, public token) +- **Rate Limited**: For testing only, not for production +- **No Auth Mode**: Omit `--auth` flag to test unauthenticated behavior +``` + ### Configuration Management ```bash diff --git a/examples/test_helpers_demo.py b/examples/test_helpers_demo.py index b2fe39c..2b38082 100755 --- a/examples/test_helpers_demo.py +++ b/examples/test_helpers_demo.py @@ -13,7 +13,9 @@ create_test_agent, test_agent, test_agent_a2a, + test_agent_a2a_no_auth, test_agent_client, + test_agent_no_auth, ) from adcp.types.generated import GetProductsRequest, ListCreativeFormatsRequest @@ -148,12 +150,57 @@ async def custom_test_agent() -> None: await client.close() +async def auth_vs_no_auth_comparison() -> None: + """Example 5: Authenticated vs Unauthenticated Requests. + + Compare behavior between authenticated and unauthenticated test agents. + Useful for testing how agents handle different authentication states. + """ + print("🔐 Example 5: Authentication Comparison") + print("=" * 39) + print() + + request = GetProductsRequest( + brief="Coffee subscription service", + promoted_offering="Premium coffee", + ) + + try: + # Test with authentication + print("Testing WITH authentication (MCP)...") + auth_result = await test_agent.get_products(request) + auth_success = "✅" if auth_result.success else "❌" + auth_count = len(auth_result.data.products) if auth_result.data else 0 + print(f" {auth_success} With Auth: {auth_count} products") + + # Test without authentication + print("Testing WITHOUT authentication (MCP)...") + no_auth_result = await test_agent_no_auth.get_products(request) + no_auth_success = "✅" if no_auth_result.success else "❌" + no_auth_count = len(no_auth_result.data.products) if no_auth_result.data else 0 + print(f" {no_auth_success} No Auth: {no_auth_count} products") + + # Compare results + print() + if auth_count != no_auth_count: + print(" 💡 Note: Different results with/without auth!") + print(f" Auth returned {auth_count} products") + print(f" No auth returned {no_auth_count} products") + else: + print(" 💡 Note: Same results with/without auth") + + print() + except Exception as e: + print(f"❌ Error: {e}") + print() + + async def various_operations() -> None: - """Example 5: Testing Different Operations. + """Example 6: Testing Different Operations. Show various ADCP operations with test agents. """ - print("🎬 Example 5: Various ADCP Operations") + print("🎬 Example 6: Various ADCP Operations") print("=" * 37) print() @@ -195,11 +242,14 @@ async def main() -> None: await protocol_comparison() await multi_agent_example() await custom_test_agent() + await auth_vs_no_auth_comparison() 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 = Pre-configured MCP test agent with auth") + print(" • test_agent_a2a = Pre-configured A2A test agent with auth") + print(" • test_agent_no_auth = Pre-configured MCP test agent WITHOUT auth") + print(" • test_agent_a2a_no_auth = Pre-configured A2A test agent WITHOUT auth") 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") diff --git a/src/adcp/testing/__init__.py b/src/adcp/testing/__init__.py index fc8dc95..1e56ed2 100644 --- a/src/adcp/testing/__init__.py +++ b/src/adcp/testing/__init__.py @@ -8,23 +8,31 @@ from adcp.testing.test_helpers import ( CREATIVE_AGENT_CONFIG, TEST_AGENT_A2A_CONFIG, + TEST_AGENT_A2A_NO_AUTH_CONFIG, TEST_AGENT_MCP_CONFIG, + TEST_AGENT_MCP_NO_AUTH_CONFIG, TEST_AGENT_TOKEN, create_test_agent, creative_agent, test_agent, test_agent_a2a, + test_agent_a2a_no_auth, test_agent_client, + test_agent_no_auth, ) __all__ = [ "test_agent", "test_agent_a2a", + "test_agent_no_auth", + "test_agent_a2a_no_auth", "creative_agent", "test_agent_client", "create_test_agent", "TEST_AGENT_TOKEN", "TEST_AGENT_MCP_CONFIG", "TEST_AGENT_A2A_CONFIG", + "TEST_AGENT_MCP_NO_AUTH_CONFIG", + "TEST_AGENT_A2A_NO_AUTH_CONFIG", "CREATIVE_AGENT_CONFIG", ] diff --git a/src/adcp/testing/test_helpers.py b/src/adcp/testing/test_helpers.py index ba54975..b369294 100644 --- a/src/adcp/testing/test_helpers.py +++ b/src/adcp/testing/test_helpers.py @@ -30,6 +30,20 @@ auth_token=TEST_AGENT_TOKEN, ) +# Public test agent configuration (no auth) - MCP protocol +TEST_AGENT_MCP_NO_AUTH_CONFIG = AgentConfig( + id="test-agent-mcp-no-auth", + agent_uri="https://test-agent.adcontextprotocol.org/mcp/", + protocol=Protocol.MCP, +) + +# Public test agent configuration (no auth) - A2A protocol +TEST_AGENT_A2A_NO_AUTH_CONFIG = AgentConfig( + id="test-agent-a2a-no-auth", + agent_uri="https://test-agent.adcontextprotocol.org", + protocol=Protocol.A2A, +) + # Reference creative agent configuration - MCP protocol # No authentication required for the reference creative agent CREATIVE_AGENT_CONFIG = AgentConfig( @@ -67,6 +81,34 @@ def _create_test_agent_a2a_client() -> ADCPClient: return ADCPClient(TEST_AGENT_A2A_CONFIG) +def _create_test_agent_no_auth_client() -> ADCPClient: + """Create pre-configured test agent client (no auth) using MCP protocol. + + Returns: + ADCPClient instance configured for the public test agent without authentication + + Note: + This agent is rate-limited and intended for testing scenarios where no auth is provided. + Useful for testing behavior differences between authenticated and unauthenticated requests. + DO NOT use in production applications. + """ + return ADCPClient(TEST_AGENT_MCP_NO_AUTH_CONFIG) + + +def _create_test_agent_a2a_no_auth_client() -> ADCPClient: + """Create pre-configured test agent client (no auth) using A2A protocol. + + Returns: + ADCPClient instance configured for the public test agent without authentication + + Note: + This agent is rate-limited and intended for testing scenarios where no auth is provided. + Useful for testing behavior differences between authenticated and unauthenticated requests. + DO NOT use in production applications. + """ + return ADCPClient(TEST_AGENT_A2A_NO_AUTH_CONFIG) + + def _create_creative_agent_client() -> ADCPClient: """Create pre-configured creative agent client. @@ -139,6 +181,50 @@ def _create_test_multi_agent_client() -> ADCPMultiAgentClient: # DO NOT use in production applications. test_agent_a2a: ADCPClient = _create_test_agent_a2a_client() +# Pre-configured test agent client (no auth) using MCP protocol. +# Useful for testing scenarios where authentication is not provided, +# such as testing how agents handle unauthenticated requests or +# comparing behavior between authenticated and unauthenticated calls. +# +# Example: +# ```python +# from adcp.testing import test_agent_no_auth +# +# # Test behavior without authentication +# result = await test_agent_no_auth.get_products( +# GetProductsRequest( +# brief="Coffee subscription service", +# promoted_offering="Premium monthly coffee" +# ) +# ) +# ``` +# +# Note: +# This agent is rate-limited and intended for testing/examples only. +# DO NOT use in production applications. +test_agent_no_auth: ADCPClient = _create_test_agent_no_auth_client() + +# Pre-configured test agent client (no auth) using A2A protocol. +# Identical functionality to test_agent_no_auth but uses A2A instead of MCP. +# +# Example: +# ```python +# from adcp.testing import test_agent_a2a_no_auth +# +# # Test A2A behavior without authentication +# result = await test_agent_a2a_no_auth.get_products( +# GetProductsRequest( +# brief="Sustainable fashion brands", +# promoted_offering="Eco-friendly clothing" +# ) +# ) +# ``` +# +# Note: +# This agent is rate-limited and intended for testing/examples only. +# DO NOT use in production applications. +test_agent_a2a_no_auth: ADCPClient = _create_test_agent_a2a_no_auth_client() + # Pre-configured reference creative agent. # Provides creative preview functionality without authentication. # diff --git a/tests/test_helpers.py b/tests/test_helpers.py index c208d4e..33fc2be 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -6,13 +6,17 @@ from adcp.testing import ( CREATIVE_AGENT_CONFIG, TEST_AGENT_A2A_CONFIG, + TEST_AGENT_A2A_NO_AUTH_CONFIG, TEST_AGENT_MCP_CONFIG, + TEST_AGENT_MCP_NO_AUTH_CONFIG, TEST_AGENT_TOKEN, create_test_agent, creative_agent, test_agent, test_agent_a2a, + test_agent_a2a_no_auth, test_agent_client, + test_agent_no_auth, ) from adcp.types.core import Protocol @@ -22,21 +26,29 @@ def test_exports_from_testing_module(): # These imports should work without errors from adcp.testing import ( TEST_AGENT_A2A_CONFIG, + TEST_AGENT_A2A_NO_AUTH_CONFIG, TEST_AGENT_MCP_CONFIG, + TEST_AGENT_MCP_NO_AUTH_CONFIG, TEST_AGENT_TOKEN, create_test_agent, test_agent, test_agent_a2a, + test_agent_a2a_no_auth, test_agent_client, + test_agent_no_auth, ) assert test_agent is not None assert test_agent_a2a is not None + assert test_agent_no_auth is not None + assert test_agent_a2a_no_auth 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 + assert TEST_AGENT_MCP_NO_AUTH_CONFIG is not None + assert TEST_AGENT_A2A_NO_AUTH_CONFIG is not None def test_test_agent_token(): @@ -177,3 +189,51 @@ 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 + + +def test_mcp_no_auth_config_structure(): + """Test TEST_AGENT_MCP_NO_AUTH_CONFIG has correct structure.""" + assert TEST_AGENT_MCP_NO_AUTH_CONFIG.id == "test-agent-mcp-no-auth" + assert TEST_AGENT_MCP_NO_AUTH_CONFIG.protocol == Protocol.MCP + assert TEST_AGENT_MCP_NO_AUTH_CONFIG.agent_uri == "https://test-agent.adcontextprotocol.org/mcp" + assert TEST_AGENT_MCP_NO_AUTH_CONFIG.auth_token is None + + +def test_a2a_no_auth_config_structure(): + """Test TEST_AGENT_A2A_NO_AUTH_CONFIG has correct structure.""" + assert TEST_AGENT_A2A_NO_AUTH_CONFIG.id == "test-agent-a2a-no-auth" + assert TEST_AGENT_A2A_NO_AUTH_CONFIG.protocol == Protocol.A2A + assert TEST_AGENT_A2A_NO_AUTH_CONFIG.agent_uri == "https://test-agent.adcontextprotocol.org" + assert TEST_AGENT_A2A_NO_AUTH_CONFIG.auth_token is None + + +def test_test_agent_no_auth_is_adcp_client(): + """Test that test_agent_no_auth is an ADCPClient instance.""" + assert isinstance(test_agent_no_auth, ADCPClient) + assert hasattr(test_agent_no_auth, "get_products") + assert hasattr(test_agent_no_auth, "list_creative_formats") + assert callable(test_agent_no_auth.get_products) + assert callable(test_agent_no_auth.list_creative_formats) + + +def test_test_agent_a2a_no_auth_is_adcp_client(): + """Test that test_agent_a2a_no_auth is an ADCPClient instance.""" + assert isinstance(test_agent_a2a_no_auth, ADCPClient) + assert hasattr(test_agent_a2a_no_auth, "get_products") + assert hasattr(test_agent_a2a_no_auth, "list_creative_formats") + assert callable(test_agent_a2a_no_auth.get_products) + assert callable(test_agent_a2a_no_auth.list_creative_formats) + + +def test_test_agent_no_auth_config_match(): + """Test that test_agent_no_auth uses TEST_AGENT_MCP_NO_AUTH_CONFIG.""" + assert test_agent_no_auth.agent_config.id == TEST_AGENT_MCP_NO_AUTH_CONFIG.id + assert test_agent_no_auth.agent_config.protocol == TEST_AGENT_MCP_NO_AUTH_CONFIG.protocol + assert test_agent_no_auth.agent_config.auth_token is None + + +def test_test_agent_a2a_no_auth_config_match(): + """Test that test_agent_a2a_no_auth uses TEST_AGENT_A2A_NO_AUTH_CONFIG.""" + assert test_agent_a2a_no_auth.agent_config.id == TEST_AGENT_A2A_NO_AUTH_CONFIG.id + assert test_agent_a2a_no_auth.agent_config.protocol == TEST_AGENT_A2A_NO_AUTH_CONFIG.protocol + assert test_agent_a2a_no_auth.agent_config.auth_token is None