|
13 | 13 | # limitations under the License.
|
14 | 14 |
|
15 | 15 | import asyncio
|
| 16 | +import json |
16 | 17 | import logging
|
| 18 | +import os |
| 19 | +from pathlib import Path |
| 20 | +import sys |
| 21 | +import tempfile |
17 | 22 | import time
|
18 | 23 | from typing import Any
|
19 |
| -from typing import Optional |
20 | 24 | from unittest.mock import MagicMock
|
21 | 25 | from unittest.mock import patch
|
22 | 26 |
|
@@ -465,6 +469,9 @@ def test_app(
|
465 | 469 | artifact_service_uri="",
|
466 | 470 | memory_service_uri="",
|
467 | 471 | allow_origins=["*"],
|
| 472 | + a2a=False, # Disable A2A for most tests |
| 473 | + host="127.0.0.1", |
| 474 | + port=8000, |
468 | 475 | )
|
469 | 476 |
|
470 | 477 | # Create a TestClient that doesn't start a real server
|
@@ -520,6 +527,134 @@ async def create_test_eval_set(
|
520 | 527 | return test_session_info
|
521 | 528 |
|
522 | 529 |
|
| 530 | +@pytest.fixture |
| 531 | +@pytest.mark.skipif( |
| 532 | + sys.version_info < (3, 10), reason="A2A requires Python 3.10+" |
| 533 | +) |
| 534 | +def temp_agents_dir_with_a2a(): |
| 535 | + """Create a temporary agents directory with A2A agent configurations for testing.""" |
| 536 | + with tempfile.TemporaryDirectory() as temp_dir: |
| 537 | + # Create test agent directory |
| 538 | + agent_dir = Path(temp_dir) / "test_a2a_agent" |
| 539 | + agent_dir.mkdir() |
| 540 | + |
| 541 | + # Create agent.json file |
| 542 | + agent_card = { |
| 543 | + "name": "test_a2a_agent", |
| 544 | + "description": "Test A2A agent", |
| 545 | + "version": "1.0.0", |
| 546 | + "author": "test", |
| 547 | + "capabilities": ["text"], |
| 548 | + } |
| 549 | + |
| 550 | + with open(agent_dir / "agent.json", "w") as f: |
| 551 | + json.dump(agent_card, f) |
| 552 | + |
| 553 | + # Create a simple agent.py file |
| 554 | + agent_py_content = """ |
| 555 | +from google.adk.agents.base_agent import BaseAgent |
| 556 | +
|
| 557 | +class TestA2AAgent(BaseAgent): |
| 558 | + def __init__(self): |
| 559 | + super().__init__(name="test_a2a_agent") |
| 560 | +""" |
| 561 | + |
| 562 | + with open(agent_dir / "agent.py", "w") as f: |
| 563 | + f.write(agent_py_content) |
| 564 | + |
| 565 | + yield temp_dir |
| 566 | + |
| 567 | + |
| 568 | +@pytest.fixture |
| 569 | +@pytest.mark.skipif( |
| 570 | + sys.version_info < (3, 10), reason="A2A requires Python 3.10+" |
| 571 | +) |
| 572 | +def test_app_with_a2a( |
| 573 | + mock_session_service, |
| 574 | + mock_artifact_service, |
| 575 | + mock_memory_service, |
| 576 | + mock_agent_loader, |
| 577 | + mock_eval_sets_manager, |
| 578 | + mock_eval_set_results_manager, |
| 579 | + temp_agents_dir_with_a2a, |
| 580 | +): |
| 581 | + """Create a TestClient for the FastAPI app with A2A enabled.""" |
| 582 | + |
| 583 | + # Mock A2A related classes |
| 584 | + with ( |
| 585 | + patch("signal.signal", return_value=None), |
| 586 | + patch( |
| 587 | + "google.adk.cli.fast_api.InMemorySessionService", |
| 588 | + return_value=mock_session_service, |
| 589 | + ), |
| 590 | + patch( |
| 591 | + "google.adk.cli.fast_api.InMemoryArtifactService", |
| 592 | + return_value=mock_artifact_service, |
| 593 | + ), |
| 594 | + patch( |
| 595 | + "google.adk.cli.fast_api.InMemoryMemoryService", |
| 596 | + return_value=mock_memory_service, |
| 597 | + ), |
| 598 | + patch( |
| 599 | + "google.adk.cli.fast_api.AgentLoader", |
| 600 | + return_value=mock_agent_loader, |
| 601 | + ), |
| 602 | + patch( |
| 603 | + "google.adk.cli.fast_api.LocalEvalSetsManager", |
| 604 | + return_value=mock_eval_sets_manager, |
| 605 | + ), |
| 606 | + patch( |
| 607 | + "google.adk.cli.fast_api.LocalEvalSetResultsManager", |
| 608 | + return_value=mock_eval_set_results_manager, |
| 609 | + ), |
| 610 | + patch( |
| 611 | + "google.adk.cli.cli_eval.run_evals", |
| 612 | + new=mock_run_evals_for_fast_api, |
| 613 | + ), |
| 614 | + patch("a2a.server.tasks.InMemoryTaskStore") as mock_task_store, |
| 615 | + patch( |
| 616 | + "google.adk.a2a.executor.a2a_agent_executor.A2aAgentExecutor" |
| 617 | + ) as mock_executor, |
| 618 | + patch( |
| 619 | + "a2a.server.request_handlers.DefaultRequestHandler" |
| 620 | + ) as mock_handler, |
| 621 | + patch("a2a.server.apps.A2AStarletteApplication") as mock_a2a_app, |
| 622 | + ): |
| 623 | + # Configure mocks |
| 624 | + mock_task_store.return_value = MagicMock() |
| 625 | + mock_executor.return_value = MagicMock() |
| 626 | + mock_handler.return_value = MagicMock() |
| 627 | + |
| 628 | + # Mock A2AStarletteApplication |
| 629 | + mock_app_instance = MagicMock() |
| 630 | + mock_app_instance.routes.return_value = ( |
| 631 | + [] |
| 632 | + ) # Return empty routes for testing |
| 633 | + mock_a2a_app.return_value = mock_app_instance |
| 634 | + |
| 635 | + # Change to temp directory |
| 636 | + original_cwd = os.getcwd() |
| 637 | + os.chdir(temp_agents_dir_with_a2a) |
| 638 | + |
| 639 | + try: |
| 640 | + app = get_fast_api_app( |
| 641 | + agents_dir=".", |
| 642 | + web=True, |
| 643 | + session_service_uri="", |
| 644 | + artifact_service_uri="", |
| 645 | + memory_service_uri="", |
| 646 | + allow_origins=["*"], |
| 647 | + a2a=True, |
| 648 | + host="127.0.0.1", |
| 649 | + port=8000, |
| 650 | + ) |
| 651 | + |
| 652 | + client = TestClient(app) |
| 653 | + yield client |
| 654 | + finally: |
| 655 | + os.chdir(original_cwd) |
| 656 | + |
| 657 | + |
523 | 658 | #################################################
|
524 | 659 | # Test Cases
|
525 | 660 | #################################################
|
@@ -760,5 +895,28 @@ def test_debug_trace(test_app):
|
760 | 895 | logger.info("Debug trace test completed successfully")
|
761 | 896 |
|
762 | 897 |
|
| 898 | +@pytest.mark.skipif( |
| 899 | + sys.version_info < (3, 10), reason="A2A requires Python 3.10+" |
| 900 | +) |
| 901 | +def test_a2a_agent_discovery(test_app_with_a2a): |
| 902 | + """Test that A2A agents are properly discovered and configured.""" |
| 903 | + # This test mainly verifies that the A2A setup doesn't break the app |
| 904 | + response = test_app_with_a2a.get("/list-apps") |
| 905 | + assert response.status_code == 200 |
| 906 | + logger.info("A2A agent discovery test passed") |
| 907 | + |
| 908 | + |
| 909 | +@pytest.mark.skipif( |
| 910 | + sys.version_info < (3, 10), reason="A2A requires Python 3.10+" |
| 911 | +) |
| 912 | +def test_a2a_disabled_by_default(test_app): |
| 913 | + """Test that A2A functionality is disabled by default.""" |
| 914 | + # The regular test_app fixture has a2a=False |
| 915 | + # This test ensures no A2A routes are added |
| 916 | + response = test_app.get("/list-apps") |
| 917 | + assert response.status_code == 200 |
| 918 | + logger.info("A2A disabled by default test passed") |
| 919 | + |
| 920 | + |
763 | 921 | if __name__ == "__main__":
|
764 | 922 | pytest.main(["-xvs", __file__])
|
0 commit comments