chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,306 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import os
from unittest import mock
import google.adk
from google.adk.integrations.bigquery.client import DP_USER_AGENT
from google.adk.integrations.bigquery.client import get_bigquery_client
from google.adk.integrations.bigquery.client import get_dataplex_catalog_client
from google.adk.utils._telemetry_context import _is_visual_builder
from google.api_core.gapic_v1 import client_info as gapic_client_info
import google.auth
from google.auth.exceptions import DefaultCredentialsError
from google.cloud import dataplex_v1
from google.cloud.bigquery import client as bigquery_client
from google.oauth2.credentials import Credentials
def test_bigquery_client_default():
"""Test the default BigQuery client properties."""
# Trigger the BigQuery client creation
client = get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
)
# Verify that the client has the desired project set
assert client.project == "test-gcp-project"
assert client.location is None
def test_bigquery_client_project_set_explicit():
"""Test BigQuery client creation does not invoke default auth."""
# Let's simulate that no environment variables are set, so that any project
# set in there does not interfere with this test
with mock.patch.dict(os.environ, {}, clear=True):
with mock.patch.object(
google.auth, "default", autospec=True
) as mock_default_auth:
# Simulate exception from default auth
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
# Trigger the BigQuery client creation
client = get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
)
# If we are here that already means client creation did not call default
# auth (otherwise we would have run into DefaultCredentialsError set
# above). For the sake of explicitness, trivially assert that the default
# auth was not called, and yet the project was set correctly
mock_default_auth.assert_not_called()
assert client.project == "test-gcp-project"
def test_bigquery_client_project_set_with_default_auth():
"""Test BigQuery client creation invokes default auth to set the project."""
# Let's simulate that no environment variables are set, so that any project
# set in there does not interfere with this test
with mock.patch.dict(os.environ, {}, clear=True):
with mock.patch.object(
google.auth, "default", autospec=True
) as mock_default_auth:
# Simulate credentials
mock_creds = mock.create_autospec(Credentials, instance=True)
# Simulate output of the default auth
mock_default_auth.return_value = (mock_creds, "test-gcp-project")
# Trigger the BigQuery client creation
client = get_bigquery_client(
project=None,
credentials=mock_creds,
)
# Verify that default auth was called once to set the client project
mock_default_auth.assert_called_once()
assert client.project == "test-gcp-project"
def test_bigquery_client_project_set_with_env():
"""Test BigQuery client creation sets the project from environment variable."""
# Let's simulate the project set in environment variables
with mock.patch.dict(
os.environ, {"GOOGLE_CLOUD_PROJECT": "test-gcp-project"}, clear=True
):
with mock.patch.object(
google.auth, "default", autospec=True
) as mock_default_auth:
# Simulate exception from default auth
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
# Trigger the BigQuery client creation
client = get_bigquery_client(
project=None,
credentials=mock.create_autospec(Credentials, instance=True),
)
# If we are here that already means client creation did not call default
# auth (otherwise we would have run into DefaultCredentialsError set
# above). For the sake of explicitness, trivially assert that the default
# auth was not called, and yet the project was set correctly
mock_default_auth.assert_not_called()
assert client.project == "test-gcp-project"
def test_bigquery_client_user_agent_default():
"""Test BigQuery client default user agent."""
with mock.patch.object(
bigquery_client, "Connection", autospec=True
) as mock_connection:
# Trigger the BigQuery client creation
get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
)
# Verify that the tracking user agent was set
client_info_arg = mock_connection.call_args[1].get("client_info")
assert client_info_arg is not None
expected_user_agents = {
"adk-bigquery-tool",
f"google-adk/{google.adk.__version__}",
}
actual_user_agents = set(client_info_arg.user_agent.split())
assert expected_user_agents.issubset(actual_user_agents)
def test_bigquery_client_user_agent_custom():
"""Test BigQuery client custom user agent."""
with mock.patch.object(
bigquery_client, "Connection", autospec=True
) as mock_connection:
# Trigger the BigQuery client creation
get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
user_agent="custom_user_agent",
)
# Verify that the tracking user agent was set
client_info_arg = mock_connection.call_args[1].get("client_info")
assert client_info_arg is not None
expected_user_agents = {
"adk-bigquery-tool",
f"google-adk/{google.adk.__version__}",
"custom_user_agent",
}
actual_user_agents = set(client_info_arg.user_agent.split())
assert expected_user_agents.issubset(actual_user_agents)
def test_bigquery_client_user_agent_custom_list():
"""Test BigQuery client custom user agent."""
with mock.patch.object(
bigquery_client, "Connection", autospec=True
) as mock_connection:
# Trigger the BigQuery client creation
get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
user_agent=["custom_user_agent1", "custom_user_agent2"],
)
# Verify that the tracking user agents were set
client_info_arg = mock_connection.call_args[1].get("client_info")
assert client_info_arg is not None
expected_user_agents = {
"adk-bigquery-tool",
f"google-adk/{google.adk.__version__}",
"custom_user_agent1",
"custom_user_agent2",
}
actual_user_agents = set(client_info_arg.user_agent.split())
assert expected_user_agents.issubset(actual_user_agents)
def test_bigquery_client_user_agent_visual_builder():
"""Test BigQuery client user agent when visual builder flag is set."""
token = _is_visual_builder.set(True)
try:
with mock.patch.object(
bigquery_client, "Connection", autospec=True
) as mock_connection:
# Trigger the BigQuery client creation
get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
)
# Verify that the tracking user agent was set
client_info_arg = mock_connection.call_args[1].get("client_info")
assert client_info_arg is not None
expected_user_agents = {
"adk-bigquery-tool",
f"google-adk/{google.adk.__version__}",
"google-adk-visual-builder",
}
actual_user_agents = set(client_info_arg.user_agent.split())
assert expected_user_agents.issubset(actual_user_agents)
finally:
_is_visual_builder.reset(token)
def test_bigquery_client_location_custom():
"""Test BigQuery client custom location."""
# Trigger the BigQuery client creation
client = get_bigquery_client(
project="test-gcp-project",
credentials=mock.create_autospec(Credentials, instance=True),
location="us-central1",
)
# Verify that the client has the desired project set
assert client.project == "test-gcp-project"
assert client.location == "us-central1"
# Tests for Dataplex Catalog Client
# ------------------------------------------------------------------------------
# Mock the CatalogServiceClient class directly
@mock.patch.object(dataplex_v1, "CatalogServiceClient", autospec=True)
def test_dataplex_client_default(mock_catalog_service_client):
"""Test get_dataplex_catalog_client with default user agent."""
mock_creds = mock.create_autospec(Credentials, instance=True)
client = get_dataplex_catalog_client(credentials=mock_creds)
mock_catalog_service_client.assert_called_once()
_, kwargs = mock_catalog_service_client.call_args
assert kwargs["credentials"] == mock_creds
client_info = kwargs["client_info"]
assert isinstance(client_info, gapic_client_info.ClientInfo)
assert client_info.user_agent == DP_USER_AGENT
# Ensure the function returns the mock instance
assert client == mock_catalog_service_client.return_value
@mock.patch.object(dataplex_v1, "CatalogServiceClient", autospec=True)
def test_dataplex_client_custom_user_agent_str(mock_catalog_service_client):
"""Test get_dataplex_catalog_client with a custom user agent string."""
mock_creds = mock.create_autospec(Credentials, instance=True)
custom_ua = "catalog_ua/1.0"
expected_ua = f"{DP_USER_AGENT} {custom_ua}"
get_dataplex_catalog_client(credentials=mock_creds, user_agent=custom_ua)
mock_catalog_service_client.assert_called_once()
_, kwargs = mock_catalog_service_client.call_args
client_info = kwargs["client_info"]
assert client_info.user_agent == expected_ua
@mock.patch.object(dataplex_v1, "CatalogServiceClient", autospec=True)
def test_dataplex_client_custom_user_agent_list(mock_catalog_service_client):
"""Test get_dataplex_catalog_client with a custom user agent list."""
mock_creds = mock.create_autospec(Credentials, instance=True)
custom_ua_list = ["catalog_ua", "catalog_ua_2.0"]
expected_ua = f"{DP_USER_AGENT} {' '.join(custom_ua_list)}"
get_dataplex_catalog_client(credentials=mock_creds, user_agent=custom_ua_list)
mock_catalog_service_client.assert_called_once()
_, kwargs = mock_catalog_service_client.call_args
client_info = kwargs["client_info"]
assert client_info.user_agent == expected_ua
@mock.patch.object(dataplex_v1, "CatalogServiceClient", autospec=True)
def test_dataplex_client_custom_user_agent_list_with_none(
mock_catalog_service_client,
):
"""Test get_dataplex_catalog_client with a list containing None."""
mock_creds = mock.create_autospec(Credentials, instance=True)
custom_ua_list = ["catalog_ua", None, "catalog_ua_2.0"]
expected_ua = f"{DP_USER_AGENT} catalog_ua catalog_ua_2.0"
get_dataplex_catalog_client(credentials=mock_creds, user_agent=custom_ua_list)
mock_catalog_service_client.assert_called_once()
_, kwargs = mock_catalog_service_client.call_args
client_info = kwargs["client_info"]
assert client_info.user_agent == expected_ua
@@ -0,0 +1,189 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from unittest import mock
from google.adk.integrations.bigquery import BigQueryCredentialsConfig
# Mock the Google OAuth and API dependencies
import google.auth.credentials
import google.oauth2.credentials
import pytest
class TestBigQueryCredentials:
"""Test suite for BigQueryCredentials configuration validation.
This class tests the credential configuration logic that ensures
either existing credentials or client ID/secret pairs are provided.
"""
def test_valid_credentials_object_auth_credentials(self):
"""Test that providing valid Credentials object works correctly with
google.auth.credentials.Credentials.
When a user already has valid OAuth credentials, they should be able
to pass them directly without needing to provide client ID/secret.
"""
# Create a mock auth credentials object
auth_creds = mock.create_autospec(
google.auth.credentials.Credentials, instance=True
)
config = BigQueryCredentialsConfig(credentials=auth_creds)
# Verify that the credentials are properly stored and attributes are extracted
assert config.credentials == auth_creds
assert config.client_secret is None
assert config.scopes == [
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/dataplex.read-write",
]
def test_valid_credentials_object_oauth2_credentials(self):
"""Test that providing valid Credentials object works correctly with
google.oauth2.credentials.Credentials.
When a user already has valid OAuth credentials, they should be able
to pass them directly without needing to provide client ID/secret.
"""
# Create a mock oauth2 credentials object
oauth2_creds = google.oauth2.credentials.Credentials(
"test_token",
client_id="test_client_id",
client_secret="test_client_secret",
scopes=["https://www.googleapis.com/auth/calendar"],
)
config = BigQueryCredentialsConfig(credentials=oauth2_creds)
# Verify that the credentials are properly stored and attributes are extracted
assert config.credentials == oauth2_creds
assert config.client_id == "test_client_id"
assert config.client_secret == "test_client_secret"
assert config.scopes == ["https://www.googleapis.com/auth/calendar"]
def test_valid_client_id_secret_pair_default_scope(self):
"""Test that providing client ID and secret with default scope works.
This tests the scenario where users want to create new OAuth credentials
from scratch using their application's client ID and secret and does not
specify the scopes explicitly.
"""
config = BigQueryCredentialsConfig(
client_id="test_client_id",
client_secret="test_client_secret",
)
assert config.credentials is None
assert config.client_id == "test_client_id"
assert config.client_secret == "test_client_secret"
assert config.scopes == [
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/dataplex.read-write",
]
def test_valid_client_id_secret_pair_w_scope(self):
"""Test that providing client ID and secret with explicit scopes works.
This tests the scenario where users want to create new OAuth credentials
from scratch using their application's client ID and secret and does specify
the scopes explicitly.
"""
config = BigQueryCredentialsConfig(
client_id="test_client_id",
client_secret="test_client_secret",
scopes=[
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/drive",
],
)
assert config.credentials is None
assert config.client_id == "test_client_id"
assert config.client_secret == "test_client_secret"
assert config.scopes == [
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/drive",
]
def test_valid_client_id_secret_pair_w_empty_scope(self):
"""Test that providing client ID and secret with empty scope works.
This tests the corner case scenario where users want to create new OAuth
credentials from scratch using their application's client ID and secret but
specifies empty scope, in which case the default BQ scope is used.
"""
config = BigQueryCredentialsConfig(
client_id="test_client_id",
client_secret="test_client_secret",
scopes=[],
)
assert config.credentials is None
assert config.client_id == "test_client_id"
assert config.client_secret == "test_client_secret"
assert config.scopes == [
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/dataplex.read-write",
]
def test_missing_client_secret_raises_error(self):
"""Test that missing client secret raises appropriate validation error.
This ensures that incomplete OAuth configuration is caught early
rather than failing during runtime.
"""
with pytest.raises(
ValueError,
match=(
"Must provide one of credentials, external_access_token_key, or"
" client_id and client_secret pair"
),
):
BigQueryCredentialsConfig(client_id="test_client_id")
def test_missing_client_id_raises_error(self):
"""Test that missing client ID raises appropriate validation error."""
with pytest.raises(
ValueError,
match=(
"Must provide one of credentials, external_access_token_key, or"
" client_id and client_secret pair"
),
):
BigQueryCredentialsConfig(client_secret="test_client_secret")
def test_empty_configuration_raises_error(self):
"""Test that completely empty configuration is rejected.
Users must provide either existing credentials or the components
needed to create new ones.
"""
with pytest.raises(
ValueError,
match=(
"Must provide one of credentials, external_access_token_key, or"
" client_id and client_secret pair"
),
):
BigQueryCredentialsConfig()
def test_invalid_property_raises_error(self):
"""Test BigQueryCredentialsConfig raises exception when setting invalid property."""
with pytest.raises(ValueError):
BigQueryCredentialsConfig(
client_id="test_client_id",
client_secret="test_client_secret",
non_existent_field="some value",
)
@@ -0,0 +1,185 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pathlib
from unittest import mock
from google.adk.integrations.bigquery import data_insights_tool
import pytest
import yaml
@pytest.mark.parametrize(
"case_file_path",
[
pytest.param("test_data/ask_data_insights_penguins_highest_mass.yaml"),
],
)
@mock.patch.object(
data_insights_tool._gda_stream_util, "get_gda_session", autospec=True
)
def test_ask_data_insights_pipeline_from_file(mock_get_session, case_file_path):
"""Runs a full integration test for the ask_data_insights pipeline using data from a specific file."""
# 1. Construct the full, absolute path to the data file
full_path = pathlib.Path(__file__).parent / case_file_path
# 2. Load the test case data from the specified YAML file
with open(full_path, "r", encoding="utf-8") as f:
case_data = yaml.safe_load(f)
# 3. Prepare the mock stream and expected output from the loaded data
mock_stream_str = case_data["mock_api_stream"]
fake_stream_lines = [
line.encode("utf-8") for line in mock_stream_str.splitlines()
]
# Load the expected output as a list of dictionaries, not a single string
expected_final_list = case_data["expected_output"]
# 4. Configure the mock for requests.post
mock_session = mock.MagicMock()
mock_response = mock.Mock()
mock_response.iter_lines.return_value = fake_stream_lines
# Add raise_for_status mock which is called in the updated code
mock_response.raise_for_status.return_value = None
mock_session.post.return_value.__enter__.return_value = mock_response
mock_get_session.return_value = (
mock_session,
"https://geminidataanalytics.mtls.googleapis.com",
)
# 5. Call the function under test
mock_creds = mock.Mock()
mock_settings = mock.Mock()
mock_settings.max_query_result_rows = 50
result = data_insights_tool.ask_data_insights(
project_id="test-project",
user_query_with_context=case_data["user_question"],
table_references=[],
credentials=mock_creds,
settings=mock_settings,
)
# 6. Assert that the final list of dicts matches the expected output
assert result["status"] == "SUCCESS"
assert result["response"] == expected_final_list
mock_get_session.assert_called_once_with(mock_creds)
mock_session.post.assert_called_once_with(
"https://geminidataanalytics.mtls.googleapis.com/v1beta/projects/test-project/locations/global:chat",
json={
"project": "projects/test-project",
"messages": [{"userMessage": {"text": case_data["user_question"]}}],
"inlineContext": {
"datasourceReferences": {"bq": {"tableReferences": []}},
"systemInstruction": mock.ANY,
"options": {"chart": {"image": {"noImage": {}}}},
},
"clientIdEnum": "GOOGLE_ADK",
},
headers={
"Content-Type": "application/json",
"X-Goog-API-Client": "GOOGLE_ADK",
},
stream=True,
)
@mock.patch.object(data_insights_tool._gda_stream_util, "get_stream")
@mock.patch.object(
data_insights_tool._gda_stream_util, "get_gda_session", autospec=True
)
def test_ask_data_insights_success(mock_get_session, mock_get_stream):
"""Tests the success path of ask_data_insights using decorators."""
# 1. Configure the behavior of the mocked functions
mock_stream = [
{"text": {"parts": ["response1"], "textType": "THOUGHT"}},
{"text": {"parts": ["response2"], "textType": "FINAL_RESPONSE"}},
]
mock_get_stream.return_value = mock_stream
mock_session = mock.MagicMock()
mock_get_session.return_value = (
mock_session,
"https://geminidataanalytics.mtls.googleapis.com",
)
# 2. Create mock inputs for the function call
mock_creds = mock.Mock()
mock_settings = mock.Mock()
mock_settings.max_query_result_rows = 100
# 3. Call the function under test
result = data_insights_tool.ask_data_insights(
project_id="test-project",
user_query_with_context="test query",
table_references=[],
credentials=mock_creds,
settings=mock_settings,
)
# 4. Assert the results are as expected
assert result["status"] == "SUCCESS"
assert result["response"] == mock_stream
mock_get_session.assert_called_once_with(mock_creds)
mock_get_stream.assert_called_once_with(
mock_session,
"https://geminidataanalytics.mtls.googleapis.com/v1beta/projects/test-project/locations/global:chat",
{
"project": "projects/test-project",
"messages": [{"userMessage": {"text": "test query"}}],
"inlineContext": {
"datasourceReferences": {"bq": {"tableReferences": []}},
"systemInstruction": mock.ANY,
"options": {"chart": {"image": {"noImage": {}}}},
},
"clientIdEnum": "GOOGLE_ADK",
},
{
"Content-Type": "application/json",
"X-Goog-API-Client": "GOOGLE_ADK",
},
100,
)
@mock.patch.object(data_insights_tool._gda_stream_util, "get_stream")
@mock.patch.object(
data_insights_tool._gda_stream_util, "get_gda_session", autospec=True
)
def test_ask_data_insights_handles_exception(mock_get_session, mock_get_stream):
"""Tests the exception path of ask_data_insights using decorators."""
# 1. Configure one of the mocks to raise an error
mock_get_stream.side_effect = Exception("API call failed!")
mock_session = mock.MagicMock()
mock_get_session.return_value = (
mock_session,
"https://geminidataanalytics.mtls.googleapis.com",
)
# 2. Create mock inputs
mock_creds = mock.Mock()
mock_settings = mock.Mock()
# 3. Call the function
result = data_insights_tool.ask_data_insights(
project_id="test-project",
user_query_with_context="test query",
table_references=[],
credentials=mock_creds,
settings=mock_settings,
)
# 4. Assert that the error was caught and formatted correctly
assert result["status"] == "ERROR"
assert "API call failed!" in result["error_details"]
mock_get_session.assert_called_once_with(mock_creds)
mock_get_stream.assert_called_once()
@@ -0,0 +1,286 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import os
from unittest import mock
from google.adk.integrations.bigquery import client as bq_client_lib
from google.adk.integrations.bigquery import metadata_tool
from google.adk.integrations.bigquery.config import BigQueryToolConfig
import google.auth
from google.auth.exceptions import DefaultCredentialsError
from google.cloud import bigquery
from google.oauth2.credentials import Credentials
@mock.patch.dict(os.environ, {}, clear=True)
@mock.patch.object(bigquery.Client, "list_datasets", autospec=True)
@mock.patch.object(google.auth, "default", autospec=True)
def test_list_dataset_ids_no_default_auth(
mock_default_auth, mock_list_datasets
):
"""Test list_dataset_ids tool invocation involves no default auth."""
project = "my_project_id"
mock_credentials = mock.create_autospec(Credentials, instance=True)
tool_settings = BigQueryToolConfig()
# Simulate the behavior of default auth - on purpose throw exception when
# the default auth is called
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
mock_list_datasets.return_value = [
bigquery.DatasetReference(project, "dataset1"),
bigquery.DatasetReference(project, "dataset2"),
]
result = metadata_tool.list_dataset_ids(
project, mock_credentials, tool_settings
)
assert result == ["dataset1", "dataset2"]
mock_default_auth.assert_not_called()
@mock.patch.dict(os.environ, {}, clear=True)
@mock.patch.object(bigquery.Client, "get_dataset", autospec=True)
@mock.patch.object(google.auth, "default", autospec=True)
def test_get_dataset_info_no_default_auth(mock_default_auth, mock_get_dataset):
"""Test get_dataset_info tool invocation involves no default auth."""
mock_credentials = mock.create_autospec(Credentials, instance=True)
tool_settings = BigQueryToolConfig()
# Simulate the behavior of default auth - on purpose throw exception when
# the default auth is called
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
mock_get_dataset.return_value = mock.create_autospec(
Credentials, instance=True
)
result = metadata_tool.get_dataset_info(
"my_project_id", "my_dataset_id", mock_credentials, tool_settings
)
assert result != {
"status": "ERROR",
"error_details": "Your default credentials were not found",
}
mock_default_auth.assert_not_called()
@mock.patch.dict(os.environ, {}, clear=True)
@mock.patch.object(bigquery.Client, "list_tables", autospec=True)
@mock.patch.object(google.auth, "default", autospec=True)
def test_list_table_ids_no_default_auth(mock_default_auth, mock_list_tables):
"""Test list_table_ids tool invocation involves no default auth."""
project = "my_project_id"
dataset = "my_dataset_id"
dataset_ref = bigquery.DatasetReference(project, dataset)
mock_credentials = mock.create_autospec(Credentials, instance=True)
tool_settings = BigQueryToolConfig()
# Simulate the behavior of default auth - on purpose throw exception when
# the default auth is called
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
mock_list_tables.return_value = [
bigquery.TableReference(dataset_ref, "table1"),
bigquery.TableReference(dataset_ref, "table2"),
]
result = metadata_tool.list_table_ids(
project, dataset, mock_credentials, tool_settings
)
assert result == ["table1", "table2"]
mock_default_auth.assert_not_called()
@mock.patch.dict(os.environ, {}, clear=True)
@mock.patch.object(bigquery.Client, "get_table", autospec=True)
@mock.patch.object(google.auth, "default", autospec=True)
def test_get_table_info_no_default_auth(mock_default_auth, mock_get_table):
"""Test get_table_info tool invocation involves no default auth."""
mock_credentials = mock.create_autospec(Credentials, instance=True)
tool_settings = BigQueryToolConfig()
# Simulate the behavior of default auth - on purpose throw exception when
# the default auth is called
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
mock_get_table.return_value = mock.create_autospec(Credentials, instance=True)
result = metadata_tool.get_table_info(
"my_project_id",
"my_dataset_id",
"my_table_id",
mock_credentials,
tool_settings,
)
assert result != {
"status": "ERROR",
"error_details": "Your default credentials were not found",
}
mock_default_auth.assert_not_called()
@mock.patch.dict(os.environ, {}, clear=True)
@mock.patch.object(bigquery.Client, "get_job", autospec=True)
@mock.patch.object(google.auth, "default", autospec=True)
def test_get_job_info_no_default_auth(mock_default_auth, mock_get_job):
"""Test get_job_info tool invocation involves no default auth."""
mock_credentials = mock.create_autospec(Credentials, instance=True)
tool_settings = BigQueryToolConfig()
# Simulate the behavior of default auth - on purpose throw exception when
# the default auth is called
mock_default_auth.side_effect = DefaultCredentialsError(
"Your default credentials were not found"
)
mock_get_job.return_value = mock.create_autospec(
bigquery.QueryJob, instance=True
)
result = metadata_tool.get_job_info(
"my_project_id",
"my_job_id",
mock_credentials,
tool_settings,
)
assert result != {
"status": "ERROR",
"error_details": "Your default credentials were not found",
}
mock_default_auth.assert_not_called()
@mock.patch.object(bq_client_lib, "get_bigquery_client", autospec=True)
def test_list_dataset_ids_bq_client_creation(mock_get_bigquery_client):
"""Test BigQuery client creation params during list_dataset_ids tool invocation."""
bq_project = "my_project_id"
bq_credentials = mock.create_autospec(Credentials, instance=True)
application_name = "my-agent"
tool_settings = BigQueryToolConfig(application_name=application_name)
metadata_tool.list_dataset_ids(bq_project, bq_credentials, tool_settings)
mock_get_bigquery_client.assert_called_once()
assert len(mock_get_bigquery_client.call_args.kwargs) == 4
assert mock_get_bigquery_client.call_args.kwargs["project"] == bq_project
assert (
mock_get_bigquery_client.call_args.kwargs["credentials"] == bq_credentials
)
assert mock_get_bigquery_client.call_args.kwargs["user_agent"] == [
application_name,
"list_dataset_ids",
]
@mock.patch.object(bq_client_lib, "get_bigquery_client", autospec=True)
def test_get_dataset_info_bq_client_creation(mock_get_bigquery_client):
"""Test BigQuery client creation params during get_dataset_info tool invocation."""
bq_project = "my_project_id"
bq_dataset = "my_dataset_id"
bq_credentials = mock.create_autospec(Credentials, instance=True)
application_name = "my-agent"
tool_settings = BigQueryToolConfig(application_name=application_name)
metadata_tool.get_dataset_info(
bq_project, bq_dataset, bq_credentials, tool_settings
)
mock_get_bigquery_client.assert_called_once()
assert len(mock_get_bigquery_client.call_args.kwargs) == 4
assert mock_get_bigquery_client.call_args.kwargs["project"] == bq_project
assert (
mock_get_bigquery_client.call_args.kwargs["credentials"] == bq_credentials
)
assert mock_get_bigquery_client.call_args.kwargs["user_agent"] == [
application_name,
"get_dataset_info",
]
@mock.patch.object(bq_client_lib, "get_bigquery_client", autospec=True)
def test_list_table_ids_bq_client_creation(mock_get_bigquery_client):
"""Test BigQuery client creation params during list_table_ids tool invocation."""
bq_project = "my_project_id"
bq_dataset = "my_dataset_id"
bq_credentials = mock.create_autospec(Credentials, instance=True)
application_name = "my-agent"
tool_settings = BigQueryToolConfig(application_name=application_name)
metadata_tool.list_table_ids(
bq_project, bq_dataset, bq_credentials, tool_settings
)
mock_get_bigquery_client.assert_called_once()
assert len(mock_get_bigquery_client.call_args.kwargs) == 4
assert mock_get_bigquery_client.call_args.kwargs["project"] == bq_project
assert (
mock_get_bigquery_client.call_args.kwargs["credentials"] == bq_credentials
)
assert mock_get_bigquery_client.call_args.kwargs["user_agent"] == [
application_name,
"list_table_ids",
]
@mock.patch.object(bq_client_lib, "get_bigquery_client", autospec=True)
def test_get_table_info_bq_client_creation(mock_get_bigquery_client):
"""Test BigQuery client creation params during get_table_info tool invocation."""
bq_project = "my_project_id"
bq_dataset = "my_dataset_id"
bq_table = "my_table_id"
bq_credentials = mock.create_autospec(Credentials, instance=True)
application_name = "my-agent"
tool_settings = BigQueryToolConfig(application_name=application_name)
metadata_tool.get_table_info(
bq_project, bq_dataset, bq_table, bq_credentials, tool_settings
)
mock_get_bigquery_client.assert_called_once()
assert len(mock_get_bigquery_client.call_args.kwargs) == 4
assert mock_get_bigquery_client.call_args.kwargs["project"] == bq_project
assert (
mock_get_bigquery_client.call_args.kwargs["credentials"] == bq_credentials
)
assert mock_get_bigquery_client.call_args.kwargs["user_agent"] == [
application_name,
"get_table_info",
]
@mock.patch.object(bq_client_lib, "get_bigquery_client", autospec=True)
def test_get_job_info_bq_client_creation(mock_get_bigquery_client):
"""Test BigQuery client creation params during get_table_info tool invocation."""
bq_project = "my_project_id"
bq_job_id = "my_job_id"
bq_credentials = mock.create_autospec(Credentials, instance=True)
application_name = "my-agent"
tool_settings = BigQueryToolConfig(application_name=application_name)
metadata_tool.get_job_info(
bq_project, bq_job_id, bq_credentials, tool_settings
)
mock_get_bigquery_client.assert_called_once()
assert len(mock_get_bigquery_client.call_args.kwargs) == 4
assert mock_get_bigquery_client.call_args.kwargs["project"] == bq_project
assert (
mock_get_bigquery_client.call_args.kwargs["credentials"] == bq_credentials
)
assert mock_get_bigquery_client.call_args.kwargs["user_agent"] == [
application_name,
"get_job_info",
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,448 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import sys
from typing import Any
import unittest
from unittest import mock
from absl.testing import parameterized
# Mock google.genai and pydantic if not available, before importing google.adk modules
try:
import google.genai
except ImportError:
m = mock.MagicMock()
m.__path__ = []
sys.modules["google.genai"] = m
sys.modules["google.genai.types"] = mock.MagicMock()
sys.modules["google.genai.errors"] = mock.MagicMock()
try:
import pydantic
except ImportError:
m_pydantic = mock.MagicMock()
class MockBaseModel:
pass
m_pydantic.BaseModel = MockBaseModel
sys.modules["pydantic"] = m_pydantic
try:
import fastapi
import fastapi.openapi.models
except ImportError:
m_fastapi = mock.MagicMock()
m_fastapi.openapi.models = mock.MagicMock()
sys.modules["fastapi"] = m_fastapi
sys.modules["fastapi.openapi"] = mock.MagicMock()
sys.modules["fastapi.openapi.models"] = mock.MagicMock()
from google.adk.integrations.bigquery import search_tool
from google.adk.integrations.bigquery.config import BigQueryToolConfig
from google.api_core import exceptions as api_exceptions
from google.auth.credentials import Credentials
from google.cloud import dataplex_v1
def _mock_creds():
return mock.create_autospec(Credentials, instance=True)
def _mock_settings(app_name: str | None = "test-app"):
return BigQueryToolConfig(application_name=app_name)
def _mock_search_entries_response(results: list[dict[str, Any]]):
mock_response = mock.MagicMock(spec=dataplex_v1.SearchEntriesResponse)
mock_results = []
for r in results:
mock_result = mock.create_autospec(
dataplex_v1.SearchEntriesResult, instance=True
)
# Manually attach dataplex_entry since it's not visible in dir() of the proto class
mock_entry = mock.create_autospec(dataplex_v1.Entry, instance=True)
mock_result.dataplex_entry = mock_entry
mock_entry.name = r.get("name")
mock_entry.entry_type = r.get("entry_type")
mock_entry.update_time = r.get("update_time", "2026-01-14T05:00:00Z")
# Manually attach entry_source since it's not visible in dir() of the proto class
mock_source = mock.create_autospec(dataplex_v1.EntrySource, instance=True)
mock_entry.entry_source = mock_source
mock_source.display_name = r.get("display_name")
mock_source.resource = r.get("linked_resource")
mock_source.description = r.get("description")
mock_source.location = r.get("location")
mock_results.append(mock_result)
mock_response.results = mock_results
return mock_response
class TestSearchCatalog(parameterized.TestCase):
def setUp(self):
super().setUp()
self.mock_dataplex_client = mock.create_autospec(
dataplex_v1.CatalogServiceClient, instance=True
)
# Patch get_dataplex_catalog_client
self.mock_get_dataplex_client = self.enter_context(
mock.patch(
"google.adk.integrations.bigquery.client.get_dataplex_catalog_client",
autospec=True,
)
)
self.mock_get_dataplex_client.return_value = self.mock_dataplex_client
self.mock_dataplex_client.__enter__.return_value = self.mock_dataplex_client
# Patch SearchEntriesRequest
self.mock_search_request = self.enter_context(
mock.patch(
"google.cloud.dataplex_v1.SearchEntriesRequest", autospec=True
)
)
def test_search_catalog_success(self):
"""Test the successful path of search_catalog."""
creds = _mock_creds()
settings = _mock_settings()
prompt = "customer data"
project_id = "test-project"
location = "us"
mock_api_results = [{
"name": "entry1",
"entry_type": "TABLE",
"display_name": "Cust Table",
"linked_resource": (
"//bigquery.googleapis.com/projects/p/datasets/d/tables/t1"
),
"description": "Table 1",
"location": "us",
}]
self.mock_dataplex_client.search_entries.return_value = (
_mock_search_entries_response(mock_api_results)
)
result = search_tool.search_catalog(
prompt=prompt,
project_id=project_id,
credentials=creds,
settings=settings,
location=location,
)
with self.subTest("Test result content"):
self.assertEqual(result["status"], "SUCCESS")
self.assertLen(result["results"], 1)
self.assertEqual(result["results"][0]["name"], "entry1")
self.assertEqual(result["results"][0]["display_name"], "Cust Table")
with self.subTest("Test mock calls"):
self.mock_get_dataplex_client.assert_called_once_with(
credentials=creds, user_agent=["test-app", "search_catalog"]
)
expected_query = (
'(customer data) AND projectid="test-project" AND system=BIGQUERY'
)
self.mock_search_request.assert_called_once_with(
name=f"projects/{project_id}/locations/us",
query=expected_query,
page_size=10,
semantic_search=True,
)
self.mock_dataplex_client.search_entries.assert_called_once_with(
request=self.mock_search_request.return_value
)
def test_search_catalog_no_project_id(self):
"""Test search_catalog with missing project_id."""
result = search_tool.search_catalog(
prompt="test",
project_id="",
credentials=_mock_creds(),
settings=_mock_settings(),
location="us",
)
self.assertEqual(result["status"], "ERROR")
self.assertIn("project_id must be provided", result["error_details"])
self.mock_get_dataplex_client.assert_not_called()
def test_search_catalog_api_error(self):
"""Test search_catalog handling API exceptions."""
self.mock_dataplex_client.search_entries.side_effect = (
api_exceptions.BadRequest("Invalid query")
)
result = search_tool.search_catalog(
prompt="test",
project_id="test-project",
credentials=_mock_creds(),
settings=_mock_settings(),
location="us",
)
self.assertEqual(result["status"], "ERROR")
self.assertIn(
"Dataplex API Error: 400 Invalid query", result["error_details"]
)
def test_search_catalog_other_exception(self):
"""Test search_catalog handling unexpected exceptions."""
self.mock_get_dataplex_client.side_effect = Exception(
"Something went wrong"
)
result = search_tool.search_catalog(
prompt="test",
project_id="test-project",
credentials=_mock_creds(),
settings=_mock_settings(),
location="us",
)
self.assertEqual(result["status"], "ERROR")
self.assertIn("Something went wrong", result["error_details"])
@parameterized.named_parameters(
("project_filter", "p", ["proj1"], None, None, 'projectid="proj1"'),
(
"multi_project_filter",
"p",
["p1", "p2"],
None,
None,
'(projectid="p1" OR projectid="p2")',
),
("type_filter", "p", None, None, ["TABLE"], 'type="TABLE"'),
(
"multi_type_filter",
"p",
None,
None,
["TABLE", "DATASET"],
'(type="TABLE" OR type="DATASET")',
),
(
"project_and_dataset_filters",
"inventory",
["proj1", "proj2"],
["dsetA"],
None,
(
'(projectid="proj1" OR projectid="proj2") AND'
' (linked_resource:"//bigquery.googleapis.com/projects/proj1/datasets/dsetA/*"'
' OR linked_resource:"//bigquery.googleapis.com/projects/proj2/datasets/dsetA/*")'
),
),
)
def test_search_catalog_query_construction(
self, prompt, project_ids, dataset_ids, types, expected_query_part
):
"""Test different query constructions based on filters."""
search_tool.search_catalog(
prompt=prompt,
project_id="test-project",
credentials=_mock_creds(),
settings=_mock_settings(),
location="us",
project_ids_filter=project_ids,
dataset_ids_filter=dataset_ids,
types_filter=types,
)
self.mock_search_request.assert_called_once()
_, kwargs = self.mock_search_request.call_args
query = kwargs["query"]
if prompt:
assert f"({prompt})" in query
assert "system=BIGQUERY" in query
assert expected_query_part in query
def test_search_catalog_no_app_name(self):
"""Test search_catalog when settings.application_name is None."""
creds = _mock_creds()
settings = _mock_settings(app_name=None)
search_tool.search_catalog(
prompt="test",
project_id="test-project",
credentials=creds,
settings=settings,
location="us",
)
self.mock_get_dataplex_client.assert_called_once_with(
credentials=creds, user_agent=[None, "search_catalog"]
)
def test_search_catalog_multi_project_filter_semantic(self):
"""Test semantic search with a multi-project filter."""
creds = _mock_creds()
settings = _mock_settings()
prompt = "What datasets store user profiles?"
project_id = "main-project"
project_filters = ["user-data-proj", "shared-infra-proj"]
location = "global"
self.mock_dataplex_client.search_entries.return_value = (
_mock_search_entries_response([])
)
search_tool.search_catalog(
prompt=prompt,
project_id=project_id,
credentials=creds,
settings=settings,
location=location,
project_ids_filter=project_filters,
types_filter=["DATASET"],
)
expected_query = (
f"({prompt}) AND "
'(projectid="user-data-proj" OR projectid="shared-infra-proj") AND '
'type="DATASET" AND system=BIGQUERY'
)
self.mock_search_request.assert_called_once_with(
name=f"projects/{project_id}/locations/{location}",
query=expected_query,
page_size=10,
semantic_search=True,
)
self.mock_dataplex_client.search_entries.assert_called_once()
def test_search_catalog_natural_language_semantic(self):
"""Test natural language prompts with semantic search enabled and check output."""
creds = _mock_creds()
settings = _mock_settings()
prompt = "Find tables about football matches"
project_id = "sports-analytics"
location = "europe-west1"
# Mock the results that the API would return for this semantic query
mock_api_results = [
{
"name": (
"projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb1"
),
"display_name": "uk_football_premiership",
"entry_type": (
"projects/655216118709/locations/global/entryTypes/bigquery-table"
),
"linked_resource": (
"//bigquery.googleapis.com/projects/sports-analytics/datasets/uk/tables/premiership"
),
"description": "Stats for UK Premier League matches.",
"location": "europe-west1",
},
{
"name": (
"projects/sports-analytics/locations/europe-west1/entryGroups/@bigquery/entries/fb2"
),
"display_name": "serie_a_matches",
"entry_type": (
"projects/655216118709/locations/global/entryTypes/bigquery-table"
),
"linked_resource": (
"//bigquery.googleapis.com/projects/sports-analytics/datasets/italy/tables/serie_a"
),
"description": "Italian Serie A football results.",
"location": "europe-west1",
},
]
self.mock_dataplex_client.search_entries.return_value = (
_mock_search_entries_response(mock_api_results)
)
result = search_tool.search_catalog(
prompt=prompt,
project_id=project_id,
credentials=creds,
settings=settings,
location=location,
)
with self.subTest("Query Construction"):
# Assert the request was made as expected
expected_query = (
f'({prompt}) AND projectid="{project_id}" AND system=BIGQUERY'
)
self.mock_search_request.assert_called_once_with(
name=f"projects/{project_id}/locations/{location}",
query=expected_query,
page_size=10,
semantic_search=True,
)
self.mock_dataplex_client.search_entries.assert_called_once()
with self.subTest("Response Processing"):
# Assert the output is processed correctly
self.assertEqual(result["status"], "SUCCESS")
self.assertLen(result["results"], 2)
self.assertEqual(
result["results"][0]["display_name"], "uk_football_premiership"
)
self.assertEqual(result["results"][1]["display_name"], "serie_a_matches")
self.assertIn("UK Premier League", result["results"][0]["description"])
def test_search_catalog_default_location(self):
"""Test search_catalog fallback to global location when None is provided."""
creds = _mock_creds()
settings = _mock_settings()
# settings.location is None by default
self.mock_dataplex_client.search_entries.return_value = (
_mock_search_entries_response([])
)
search_tool.search_catalog(
prompt="test",
project_id="test-project",
credentials=creds,
settings=settings,
)
self.mock_search_request.assert_called_once()
_, kwargs = self.mock_search_request.call_args
name_arg = kwargs["name"]
self.assertIn("locations/global", name_arg)
def test_search_catalog_settings_location(self):
"""Test search_catalog uses settings.location when provided."""
creds = _mock_creds()
settings = BigQueryToolConfig(location="eu")
self.mock_dataplex_client.search_entries.return_value = (
_mock_search_entries_response([])
)
search_tool.search_catalog(
prompt="test",
project_id="test-project",
credentials=creds,
settings=settings,
)
self.mock_search_request.assert_called_once()
_, kwargs = self.mock_search_request.call_args
name_arg = kwargs["name"]
self.assertIn("locations/eu", name_arg)
@@ -0,0 +1,143 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import warnings
from google.adk.features._feature_registry import _WARNED_FEATURES
from google.adk.integrations.bigquery.config import BigQueryToolConfig
import pytest
@pytest.fixture(autouse=True)
def reset_warned_features():
"""Reset warned features before each test."""
_WARNED_FEATURES.clear()
def test_bigquery_tool_config_invalid_property():
"""Test BigQueryToolConfig raises exception when setting invalid property."""
with pytest.raises(
ValueError,
):
BigQueryToolConfig(non_existent_field="some value")
def test_bigquery_tool_config_invalid_application_name():
"""Test BigQueryToolConfig raises exception with invalid application name."""
with pytest.raises(
ValueError,
match="Application name should not contain spaces.",
):
BigQueryToolConfig(application_name="my agent")
def test_bigquery_tool_config_max_query_result_rows_default():
"""Test BigQueryToolConfig max_query_result_rows default value."""
config = BigQueryToolConfig()
assert config.max_query_result_rows == 50
def test_bigquery_tool_config_max_query_result_rows_custom():
"""Test BigQueryToolConfig max_query_result_rows custom value."""
config = BigQueryToolConfig(max_query_result_rows=100)
assert config.max_query_result_rows == 100
def test_bigquery_tool_config_valid_maximum_bytes_billed():
"""Test BigQueryToolConfig raises exception with valid max bytes billed."""
config = BigQueryToolConfig(maximum_bytes_billed=10_485_760)
assert config.maximum_bytes_billed == 10_485_760
def test_bigquery_tool_config_invalid_maximum_bytes_billed():
"""Test BigQueryToolConfig raises exception with invalid max bytes billed."""
with pytest.raises(
ValueError,
match=(
"In BigQuery on-demand pricing, charges are rounded up to the nearest"
" MB, with a minimum 10 MB data processed per table referenced by the"
" query, and with a minimum 10 MB data processed per query. So"
" max_bytes_billed must be set >=10485760."
),
):
BigQueryToolConfig(maximum_bytes_billed=10_485_759)
@pytest.mark.parametrize(
"labels",
[
pytest.param(
{"environment": "test", "team": "data"},
id="valid-labels",
),
pytest.param(
{},
id="empty-labels",
),
pytest.param(
None,
id="none-labels",
),
],
)
def test_bigquery_tool_config_valid_labels(labels):
"""Test BigQueryToolConfig accepts valid labels."""
config = BigQueryToolConfig(job_labels=labels)
assert config.job_labels == labels
@pytest.mark.parametrize(
("labels", "message"),
[
pytest.param(
"invalid",
"Input should be a valid dictionary",
id="invalid-type",
),
pytest.param(
{123: "value"},
"Input should be a valid string",
id="non-str-key",
),
pytest.param(
{"key": 123},
"Input should be a valid string",
id="non-str-value",
),
pytest.param(
{"": "value"},
"Label keys cannot be empty",
id="empty-label-key",
),
pytest.param(
{"adk-bigquery-test": "value"},
'Label key cannot start with "adk-bigquery-"',
id="internal-label-key",
),
pytest.param(
{f"key_{i}": "value" for i in range(21)},
"Only up to 20 job labels can be provided",
id="too-many-labels",
),
],
)
def test_bigquery_tool_config_invalid_labels(labels, message):
"""Test BigQueryToolConfig raises an exception with invalid labels."""
with pytest.raises(
ValueError,
match=message,
):
BigQueryToolConfig(job_labels=labels)
@@ -0,0 +1,134 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from google.adk.integrations.bigquery import BigQueryCredentialsConfig
from google.adk.integrations.bigquery import BigQueryToolset
from google.adk.integrations.bigquery.config import BigQueryToolConfig
from google.adk.tools.google_tool import GoogleTool
import pytest
@pytest.mark.asyncio
async def test_bigquery_toolset_tools_default():
"""Test default BigQuery toolset.
This test verifies the behavior of the BigQuery toolset when no filter is
specified.
"""
credentials_config = BigQueryCredentialsConfig(
client_id="abc", client_secret="def"
)
toolset = BigQueryToolset(
credentials_config=credentials_config, bigquery_tool_config=None
)
# Verify that the tool config is initialized to default values.
assert isinstance(toolset._tool_settings, BigQueryToolConfig) # pylint: disable=protected-access
assert toolset._tool_settings.__dict__ == BigQueryToolConfig().__dict__ # pylint: disable=protected-access
tools = await toolset.get_tools()
assert tools is not None
assert len(tools) == 11
assert all([isinstance(tool, GoogleTool) for tool in tools])
expected_tool_names = set([
"list_dataset_ids",
"get_dataset_info",
"list_table_ids",
"get_table_info",
"get_job_info",
"execute_sql",
"ask_data_insights",
"forecast",
"analyze_contribution",
"detect_anomalies",
"search_catalog",
])
actual_tool_names = set([tool.name for tool in tools])
assert actual_tool_names == expected_tool_names
@pytest.mark.parametrize(
"selected_tools",
[
pytest.param([], id="None"),
pytest.param(
["list_dataset_ids", "get_dataset_info"], id="dataset-metadata"
),
pytest.param(["list_table_ids", "get_table_info"], id="table-metadata"),
pytest.param(["execute_sql"], id="query"),
],
)
@pytest.mark.asyncio
async def test_bigquery_toolset_tools_selective(selected_tools):
"""Test BigQuery toolset with filter.
This test verifies the behavior of the BigQuery toolset when filter is
specified. A use case for this would be when the agent builder wants to
use only a subset of the tools provided by the toolset.
"""
credentials_config = BigQueryCredentialsConfig(
client_id="abc", client_secret="def"
)
toolset = BigQueryToolset(
credentials_config=credentials_config, tool_filter=selected_tools
)
tools = await toolset.get_tools()
assert tools is not None
assert len(tools) == len(selected_tools)
assert all([isinstance(tool, GoogleTool) for tool in tools])
expected_tool_names = set(selected_tools)
actual_tool_names = set([tool.name for tool in tools])
assert actual_tool_names == expected_tool_names
@pytest.mark.parametrize(
("selected_tools", "returned_tools"),
[
pytest.param(["unknown"], [], id="all-unknown"),
pytest.param(
["unknown", "execute_sql"],
["execute_sql"],
id="mixed-known-unknown",
),
],
)
@pytest.mark.asyncio
async def test_bigquery_toolset_unknown_tool(selected_tools, returned_tools):
"""Test BigQuery toolset with filter.
This test verifies the behavior of the BigQuery toolset when filter is
specified with an unknown tool.
"""
credentials_config = BigQueryCredentialsConfig(
client_id="abc", client_secret="def"
)
toolset = BigQueryToolset(
credentials_config=credentials_config, tool_filter=selected_tools
)
tools = await toolset.get_tools()
assert tools is not None
assert len(tools) == len(returned_tools)
assert all([isinstance(tool, GoogleTool) for tool in tools])
expected_tool_names = set(returned_tools)
actual_tool_names = set([tool.name for tool in tools])
assert actual_tool_names == expected_tool_names
@@ -0,0 +1,115 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
description: "Tests a full, realistic stream about finding the penguin island with the highest body mass."
user_question: "Penguins on which island have the highest average body mass?"
mock_api_stream: |
[{
"timestamp": "2025-07-17T17:25:28.231Z",
"systemMessage": {
"text": {
"parts": [
"Penguins on which island have the highest average body mass?"
],
"textType": "THOUGHT"
}
}
}
,
{
"timestamp": "2025-07-17T17:25:31.171Z",
"systemMessage": {
"data": {
"generatedSql": "SELECT island, AVG(body_mass_g) AS average_body_mass\nFROM `bigframes-dev-perf`.`bigframes_testing_eu`.`penguins`\nGROUP BY island;"
}
}
}
,
{
"timestamp": "2025-07-17T17:25:32.664Z",
"systemMessage": {
"data": {
"result": {
"data": [
{
"island": "Biscoe",
"average_body_mass": "4716.017964071853"
},
{
"island": "Dream",
"average_body_mass": "3712.9032258064512"
},
{
"island": "Torgersen",
"average_body_mass": "3706.3725490196075"
}
],
"name": "average_body_mass_by_island",
"schema": {
"fields": [
{
"name": "island",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "average_body_mass",
"type": "FLOAT",
"mode": "NULLABLE"
}
]
}
}
}
}
}
,
{
"timestamp": "2025-07-17T17:25:40.018Z",
"systemMessage": {
"text": {
"parts": [
"Penguins on Biscoe island have the highest average body mass, with an average of 4716.02g."
],
"textType": "FINAL_RESPONSE"
}
}
}
]
expected_output:
- text:
parts:
- 'Penguins on which island have the highest average body mass?'
textType: THOUGHT
- data:
generatedSql: "SELECT island, AVG(body_mass_g) AS average_body_mass\nFROM `bigframes-dev-perf`.`bigframes_testing_eu`.`penguins`\nGROUP BY island;"
- Data Retrieved:
headers:
- island
- average_body_mass
rows:
- - Biscoe
- '4716.017964071853'
- - Dream
- '3712.9032258064512'
- - Torgersen
- '3706.3725490196075'
summary: Showing all 3 rows.
- text:
parts:
- "Penguins on Biscoe island have the highest average body mass, with an average of 4716.02g."
textType: FINAL_RESPONSE