Back to Repositories

Testing Web Search Component Implementation in AutoGPT

This test suite validates the WebSearchComponent functionality in AutoGPT, focusing on Google search integration and error handling. The tests cover both standard and official Google search implementations with comprehensive validation of search queries and results processing.

Test Coverage Overview

The test suite provides extensive coverage of web search functionality:
  • Query parameter validation and processing
  • Search results formatting and validation
  • Error handling for invalid inputs and API responses
  • Integration with Google Custom Search API
  • Edge cases including empty queries and no results

Implementation Analysis

The testing approach utilizes pytest’s powerful parametrization features to validate multiple scenarios efficiently. Mocking is extensively used to simulate API responses and error conditions, with fixtures providing consistent test setup and configuration management.

Key patterns include parameterized test cases, fixture-based component initialization, and mock integration for external service dependencies.

Technical Details

  • Testing Framework: pytest
  • Mocking Library: pytest-mock
  • External Dependencies: googleapiclient, httplib2
  • Configuration: SecretStr for API credentials
  • Error Handling: Custom ConfigurationError implementation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Comprehensive fixture usage for setup and teardown
  • Thorough error case coverage
  • Parameterized testing for multiple scenarios
  • Clear separation of concerns between test cases
  • Proper mocking of external dependencies

significant-gravitas/autogpt

classic/forge/forge/components/web/test_search.py

            
import json

import pytest
from googleapiclient.errors import HttpError
from httplib2 import Response
from pydantic import SecretStr

from forge.utils.exceptions import ConfigurationError

from . import WebSearchComponent


@pytest.fixture
def web_search_component():
    component = WebSearchComponent()
    if component.config.google_api_key is None:
        component.config.google_api_key = SecretStr("test")
    if component.config.google_custom_search_engine_id is None:
        component.config.google_custom_search_engine_id = SecretStr("test")
    return component


@pytest.mark.parametrize(
    "query, expected_output",
    [("test", "test"), (["test1", "test2"], '["test1", "test2"]')],
)
@pytest.fixture
def test_safe_google_results(
    query, expected_output, web_search_component: WebSearchComponent
):
    result = web_search_component.safe_google_results(query)
    assert isinstance(result, str)
    assert result == expected_output


@pytest.fixture
def test_safe_google_results_invalid_input(web_search_component: WebSearchComponent):
    with pytest.raises(AttributeError):
        web_search_component.safe_google_results(123)  # type: ignore


@pytest.mark.parametrize(
    "query, num_results, expected_output_parts, return_value",
    [
        (
            "test",
            1,
            ("Result 1", "https://example.com/result1"),
            [{"title": "Result 1", "href": "https://example.com/result1"}],
        ),
        ("", 1, (), []),
        ("no results", 1, (), []),
    ],
)
def test_google_search(
    query,
    num_results,
    expected_output_parts,
    return_value,
    mocker,
    web_search_component: WebSearchComponent,
):
    mock_ddg = mocker.Mock()
    mock_ddg.return_value = return_value

    mocker.patch("forge.components.web.search.DDGS.text", mock_ddg)
    actual_output = web_search_component.web_search(query, num_results=num_results)
    for o in expected_output_parts:
        assert o in actual_output


@pytest.fixture
def mock_googleapiclient(mocker):
    mock_build = mocker.patch("googleapiclient.discovery.build")
    mock_service = mocker.Mock()
    mock_build.return_value = mock_service
    return mock_service.cse().list().execute().get


@pytest.mark.parametrize(
    "query, num_results, search_results, expected_output",
    [
        (
            "test",
            3,
            [
                {"link": "http://example.com/result1"},
                {"link": "http://example.com/result2"},
                {"link": "http://example.com/result3"},
            ],
            [
                "http://example.com/result1",
                "http://example.com/result2",
                "http://example.com/result3",
            ],
        ),
        ("", 3, [], []),
    ],
)
def test_google_official_search(
    query,
    num_results,
    expected_output,
    search_results,
    mock_googleapiclient,
    web_search_component: WebSearchComponent,
):
    mock_googleapiclient.return_value = search_results
    actual_output = web_search_component.google(query, num_results=num_results)
    assert actual_output == web_search_component.safe_google_results(expected_output)


@pytest.mark.parametrize(
    "query, num_results, expected_error_type, http_code, error_msg",
    [
        (
            "invalid query",
            3,
            HttpError,
            400,
            "Invalid Value",
        ),
        (
            "invalid API key",
            3,
            ConfigurationError,
            403,
            "invalid API key",
        ),
    ],
)
def test_google_official_search_errors(
    query,
    num_results,
    expected_error_type,
    mock_googleapiclient,
    http_code,
    error_msg,
    web_search_component: WebSearchComponent,
):
    response_content = {
        "error": {"code": http_code, "message": error_msg, "reason": "backendError"}
    }
    error = HttpError(
        resp=Response({"status": http_code, "reason": error_msg}),
        content=str.encode(json.dumps(response_content)),
        uri="https://www.googleapis.com/customsearch/v1?q=invalid+query&cx",
    )

    mock_googleapiclient.side_effect = error
    with pytest.raises(expected_error_type):
        web_search_component.google(query, num_results=num_results)