ec2b666284
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
533 lines
19 KiB
Python
533 lines
19 KiB
Python
# 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 unittest.mock import MagicMock
|
|
|
|
from google.adk.tools.spanner import client
|
|
from google.adk.tools.spanner import search_tool
|
|
from google.adk.tools.spanner import utils
|
|
from google.cloud.spanner_admin_database_v1.types import DatabaseDialect
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_credentials():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_spanner_ids():
|
|
return {
|
|
"project_id": "test-project",
|
|
"instance_id": "test-instance",
|
|
"database_id": "test-database",
|
|
"table_name": "test-table",
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("embedding_option_key", "embedding_option_value", "expected_embedding"),
|
|
[
|
|
pytest.param(
|
|
"spanner_googlesql_embedding_model_name",
|
|
"EmbeddingsModel",
|
|
[0.1, 0.2, 0.3],
|
|
id="spanner_googlesql_embedding_model",
|
|
),
|
|
pytest.param(
|
|
"vertex_ai_embedding_model_name",
|
|
"text-embedding-005",
|
|
[0.4, 0.5, 0.6],
|
|
id="vertex_ai_embedding_model",
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(utils, "embed_contents_async", autospec=True)
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_knn_success(
|
|
mock_get_spanner_client,
|
|
mock_embed_contents_async,
|
|
mock_spanner_ids,
|
|
mock_credentials,
|
|
embedding_option_key,
|
|
embedding_option_value,
|
|
expected_embedding,
|
|
):
|
|
"""Test similarity_search function with kNN success."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_snapshot = MagicMock()
|
|
mock_database.snapshot.return_value.__enter__.return_value = mock_snapshot
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
if embedding_option_key == "vertex_ai_embedding_model_name":
|
|
mock_embed_contents_async.return_value = [expected_embedding]
|
|
# execute_sql is called once for the kNN search
|
|
mock_snapshot.execute_sql.return_value = iter([("result1",), ("result2",)])
|
|
else:
|
|
mock_embedding_result = MagicMock()
|
|
mock_embedding_result.one.return_value = (expected_embedding,)
|
|
# First call to execute_sql is for getting the embedding,
|
|
# second call is for the kNN search
|
|
mock_snapshot.execute_sql.side_effect = [
|
|
mock_embedding_result,
|
|
iter([("result1",), ("result2",)]),
|
|
]
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={embedding_option_key: embedding_option_value},
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "SUCCESS", result
|
|
assert result["rows"] == [("result1",), ("result2",)]
|
|
|
|
# Check the generated SQL for kNN search
|
|
call_args = mock_snapshot.execute_sql.call_args
|
|
sql = call_args.args[0]
|
|
assert "COSINE_DISTANCE" in sql
|
|
assert "@embedding" in sql
|
|
assert call_args.kwargs == {"params": {"embedding": expected_embedding}}
|
|
if embedding_option_key == "vertex_ai_embedding_model_name":
|
|
mock_embed_contents_async.assert_called_once_with(
|
|
embedding_option_value, ["test query"], None
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_ann_success(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search function with ANN success."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_snapshot = MagicMock()
|
|
mock_embedding_result = MagicMock()
|
|
mock_embedding_result.one.return_value = ([0.1, 0.2, 0.3],)
|
|
# First call to execute_sql is for getting the embedding
|
|
# Second call is for the ANN search
|
|
mock_snapshot.execute_sql.side_effect = [
|
|
mock_embedding_result,
|
|
iter([("ann_result1",), ("ann_result2",)]),
|
|
]
|
|
mock_database.snapshot.return_value.__enter__.return_value = mock_snapshot
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_googlesql_embedding_model_name": "test_model"
|
|
},
|
|
credentials=mock_credentials,
|
|
search_options={
|
|
"nearest_neighbors_algorithm": "APPROXIMATE_NEAREST_NEIGHBORS"
|
|
},
|
|
)
|
|
assert result["status"] == "SUCCESS", result
|
|
assert result["rows"] == [("ann_result1",), ("ann_result2",)]
|
|
call_args = mock_snapshot.execute_sql.call_args
|
|
sql = call_args.args[0]
|
|
assert "APPROX_COSINE_DISTANCE" in sql
|
|
assert "@embedding" in sql
|
|
assert call_args.kwargs == {"params": {"embedding": [0.1, 0.2, 0.3]}}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_error(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search function with a generic error."""
|
|
mock_get_spanner_client.side_effect = Exception("Test Exception")
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
embedding_options={
|
|
"spanner_googlesql_embedding_model_name": "test_model"
|
|
},
|
|
columns=["col1"],
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert "Test Exception" in result["error_details"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(utils, "embed_contents_async")
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_circular_row_fallback_to_string(
|
|
mock_get_spanner_client,
|
|
mock_embed_contents_async,
|
|
mock_spanner_ids,
|
|
mock_credentials,
|
|
):
|
|
"""Test similarity_search stringifies rows with circular references."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_snapshot = MagicMock()
|
|
circular_row = []
|
|
circular_row.append(circular_row)
|
|
mock_embed_contents_async.return_value = [[0.1, 0.2, 0.3]]
|
|
mock_snapshot.execute_sql.return_value = iter([circular_row])
|
|
mock_database.snapshot.return_value.__enter__.return_value = mock_snapshot
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"vertex_ai_embedding_model_name": "text-embedding-005"
|
|
},
|
|
credentials=mock_credentials,
|
|
)
|
|
|
|
assert result["status"] == "SUCCESS", result
|
|
assert result["rows"] == [str(circular_row)]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_postgresql_knn_success(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with PostgreSQL dialect for kNN."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_snapshot = MagicMock()
|
|
mock_embedding_result = MagicMock()
|
|
mock_embedding_result.one.return_value = ([0.1, 0.2, 0.3],)
|
|
mock_snapshot.execute_sql.side_effect = [
|
|
mock_embedding_result,
|
|
iter([("pg_result",)]),
|
|
]
|
|
mock_database.snapshot.return_value.__enter__.return_value = mock_snapshot
|
|
mock_database.database_dialect = DatabaseDialect.POSTGRESQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test_endpoint"
|
|
)
|
|
},
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "SUCCESS", result
|
|
assert result["rows"] == [("pg_result",)]
|
|
call_args = mock_snapshot.execute_sql.call_args
|
|
sql = call_args.args[0]
|
|
assert "spanner.cosine_distance" in sql
|
|
assert "$1" in sql
|
|
assert call_args.kwargs == {"params": {"p1": [0.1, 0.2, 0.3]}}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_postgresql_ann_unsupported(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with unsupported ANN for PostgreSQL dialect."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.POSTGRESQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test_endpoint"
|
|
)
|
|
},
|
|
credentials=mock_credentials,
|
|
search_options={
|
|
"nearest_neighbors_algorithm": "APPROXIMATE_NEAREST_NEIGHBORS"
|
|
},
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert (
|
|
"APPROXIMATE_NEAREST_NEIGHBORS is not supported for PostgreSQL dialect."
|
|
in result["error_details"]
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_gsql_missing_embedding_model_error(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with missing embedding_options for GoogleSQL dialect."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test_endpoint"
|
|
)
|
|
},
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert (
|
|
"embedding_options['vertex_ai_embedding_model_name'] or"
|
|
" embedding_options['spanner_googlesql_embedding_model_name'] must be"
|
|
" specified for GoogleSQL dialect Spanner database."
|
|
in result["error_details"]
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_pg_missing_embedding_model_error(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with missing embedding_options for PostgreSQL dialect."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.POSTGRESQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_googlesql_embedding_model_name": "EmbeddingsModel"
|
|
},
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert (
|
|
"embedding_options['vertex_ai_embedding_model_name'] or"
|
|
" embedding_options['spanner_postgresql_vertex_ai_embedding_model_endpoint']"
|
|
" must be specified for PostgreSQL dialect Spanner database."
|
|
in result["error_details"]
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"embedding_options",
|
|
[
|
|
pytest.param(
|
|
{
|
|
"vertex_ai_embedding_model_name": "test-model",
|
|
"spanner_googlesql_embedding_model_name": "test-model-2",
|
|
},
|
|
id="vertex_ai_and_googlesql",
|
|
),
|
|
pytest.param(
|
|
{
|
|
"vertex_ai_embedding_model_name": "test-model",
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test-endpoint"
|
|
),
|
|
},
|
|
id="vertex_ai_and_postgresql",
|
|
),
|
|
pytest.param(
|
|
{
|
|
"spanner_googlesql_embedding_model_name": "test-model",
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test-endpoint"
|
|
),
|
|
},
|
|
id="googlesql_and_postgresql",
|
|
),
|
|
pytest.param(
|
|
{
|
|
"vertex_ai_embedding_model_name": "test-model",
|
|
"spanner_googlesql_embedding_model_name": "test-model-2",
|
|
"spanner_postgresql_vertex_ai_embedding_model_endpoint": (
|
|
"test-endpoint"
|
|
),
|
|
},
|
|
id="all_three_models",
|
|
),
|
|
pytest.param(
|
|
{},
|
|
id="no_models",
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_multiple_embedding_options_error(
|
|
mock_get_spanner_client,
|
|
mock_spanner_ids,
|
|
mock_credentials,
|
|
embedding_options,
|
|
):
|
|
"""Test similarity_search with multiple embedding models."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options=embedding_options,
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert (
|
|
"Exactly one embedding model option must be specified."
|
|
in result["error_details"]
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_output_dimensionality_gsql_error(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with output_dimensionality and spanner_googlesql_embedding_model_name."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={
|
|
"spanner_googlesql_embedding_model_name": "EmbeddingsModel",
|
|
"output_dimensionality": 128,
|
|
},
|
|
credentials=mock_credentials,
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert "is not supported when" in result["error_details"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@mock.patch.object(client, "get_spanner_client")
|
|
async def test_similarity_search_unsupported_algorithm_error(
|
|
mock_get_spanner_client, mock_spanner_ids, mock_credentials
|
|
):
|
|
"""Test similarity_search with an unsupported nearest neighbors algorithm."""
|
|
mock_spanner_client = MagicMock()
|
|
mock_instance = MagicMock()
|
|
mock_database = MagicMock()
|
|
mock_database.database_dialect = DatabaseDialect.GOOGLE_STANDARD_SQL
|
|
mock_instance.database.return_value = mock_database
|
|
mock_spanner_client.instance.return_value = mock_instance
|
|
mock_get_spanner_client.return_value = mock_spanner_client
|
|
|
|
result = await search_tool.similarity_search(
|
|
project_id=mock_spanner_ids["project_id"],
|
|
instance_id=mock_spanner_ids["instance_id"],
|
|
database_id=mock_spanner_ids["database_id"],
|
|
table_name=mock_spanner_ids["table_name"],
|
|
query="test query",
|
|
embedding_column_to_search="embedding_col",
|
|
columns=["col1"],
|
|
embedding_options={"vertex_ai_embedding_model_name": "test-model"},
|
|
credentials=mock_credentials,
|
|
search_options={"nearest_neighbors_algorithm": "INVALID_ALGORITHM"},
|
|
)
|
|
assert result["status"] == "ERROR"
|
|
assert "Unsupported search_options" in result["error_details"]
|