adk-python/tests/unittests/tools/openapi_tool/common/test_common.py
Jack Sun 05142a07cc
Moves unittests to root folder and adds github action to run unit tests. (#72)
* Move unit tests to root package.

* Adds deps to "test" extra, and mark two broken tests in tests/unittests/auth/test_auth_handler.py

* Adds github workflow

* minor fix in lite_llm.py for python 3.9.

* format pyproject.toml
2025-04-11 08:25:59 -07:00

437 lines
14 KiB
Python

# Copyright 2025 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 typing import Any
from typing import Dict
from typing import List
from fastapi.openapi.models import Response, Schema
from google.adk.tools.openapi_tool.common.common import ApiParameter
from google.adk.tools.openapi_tool.common.common import PydocHelper
from google.adk.tools.openapi_tool.common.common import rename_python_keywords
from google.adk.tools.openapi_tool.common.common import to_snake_case
from google.adk.tools.openapi_tool.common.common import TypeHintHelper
import pytest
def dict_to_responses(input: Dict[str, Any]) -> Dict[str, Response]:
return {k: Response.model_validate(input[k]) for k in input}
class TestToSnakeCase:
@pytest.mark.parametrize(
'input_str, expected_output',
[
('lowerCamelCase', 'lower_camel_case'),
('UpperCamelCase', 'upper_camel_case'),
('space separated', 'space_separated'),
('REST API', 'rest_api'),
('Mixed_CASE with_Spaces', 'mixed_case_with_spaces'),
('__init__', 'init'),
('APIKey', 'api_key'),
('SomeLongURL', 'some_long_url'),
('CONSTANT_CASE', 'constant_case'),
('already_snake_case', 'already_snake_case'),
('single', 'single'),
('', ''),
(' spaced ', 'spaced'),
('with123numbers', 'with123numbers'),
('With_Mixed_123_and_SPACES', 'with_mixed_123_and_spaces'),
('HTMLParser', 'html_parser'),
('HTTPResponseCode', 'http_response_code'),
('a_b_c', 'a_b_c'),
('A_B_C', 'a_b_c'),
('fromAtoB', 'from_ato_b'),
('XMLHTTPRequest', 'xmlhttp_request'),
('_leading', 'leading'),
('trailing_', 'trailing'),
(' leading_and_trailing_ ', 'leading_and_trailing'),
('Multiple___Underscores', 'multiple_underscores'),
(' spaces_and___underscores ', 'spaces_and_underscores'),
(' _mixed_Case ', 'mixed_case'),
('123Start', '123_start'),
('End123', 'end123'),
('Mid123dle', 'mid123dle'),
],
)
def test_to_snake_case(self, input_str, expected_output):
assert to_snake_case(input_str) == expected_output
class TestRenamePythonKeywords:
@pytest.mark.parametrize(
'input_str, expected_output',
[
('in', 'param_in'),
('for', 'param_for'),
('class', 'param_class'),
('normal', 'normal'),
('param_if', 'param_if'),
('', ''),
],
)
def test_rename_python_keywords(self, input_str, expected_output):
assert rename_python_keywords(input_str) == expected_output
class TestApiParameter:
def test_api_parameter_initialization(self):
schema = Schema(type='string', description='A string parameter')
param = ApiParameter(
original_name='testParam',
description='A string description',
param_location='query',
param_schema=schema,
)
assert param.original_name == 'testParam'
assert param.param_location == 'query'
assert param.param_schema.type == 'string'
assert param.param_schema.description == 'A string parameter'
assert param.py_name == 'test_param'
assert param.type_hint == 'str'
assert param.type_value == str
assert param.description == 'A string description'
def test_api_parameter_keyword_rename(self):
schema = Schema(type='string')
param = ApiParameter(
original_name='in',
param_location='query',
param_schema=schema,
)
assert param.py_name == 'param_in'
def test_api_parameter_custom_py_name(self):
schema = Schema(type='integer')
param = ApiParameter(
original_name='testParam',
param_location='query',
param_schema=schema,
py_name='custom_name',
)
assert param.py_name == 'custom_name'
def test_api_parameter_str_representation(self):
schema = Schema(type='number')
param = ApiParameter(
original_name='testParam',
param_location='query',
param_schema=schema,
)
assert str(param) == 'test_param: float'
def test_api_parameter_to_arg_string(self):
schema = Schema(type='boolean')
param = ApiParameter(
original_name='testParam',
param_location='query',
param_schema=schema,
)
assert param.to_arg_string() == 'test_param=test_param'
def test_api_parameter_to_dict_property(self):
schema = Schema(type='string')
param = ApiParameter(
original_name='testParam',
param_location='path',
param_schema=schema,
)
assert param.to_dict_property() == '"test_param": test_param'
def test_api_parameter_model_serializer(self):
schema = Schema(type='string', description='test description')
param = ApiParameter(
original_name='TestParam',
param_location='path',
param_schema=schema,
py_name='test_param_custom',
description='test description',
)
serialized_param = param.model_dump(mode='json', exclude_none=True)
assert serialized_param == {
'original_name': 'TestParam',
'param_location': 'path',
'param_schema': {'type': 'string', 'description': 'test description'},
'description': 'test description',
'py_name': 'test_param_custom',
}
@pytest.mark.parametrize(
'schema, expected_type_value, expected_type_hint',
[
({'type': 'integer'}, int, 'int'),
({'type': 'number'}, float, 'float'),
({'type': 'boolean'}, bool, 'bool'),
({'type': 'string'}, str, 'str'),
(
{'type': 'string', 'format': 'date'},
str,
'str',
),
(
{'type': 'string', 'format': 'date-time'},
str,
'str',
),
(
{'type': 'array', 'items': {'type': 'integer'}},
List[int],
'List[int]',
),
(
{'type': 'array', 'items': {'type': 'string'}},
List[str],
'List[str]',
),
(
{
'type': 'array',
'items': {'type': 'object'},
},
List[Dict[str, Any]],
'List[Dict[str, Any]]',
),
({'type': 'object'}, Dict[str, Any], 'Dict[str, Any]'),
({'type': 'unknown'}, Any, 'Any'),
({}, Any, 'Any'),
],
)
def test_api_parameter_type_hint_helper(
self, schema, expected_type_value, expected_type_hint
):
param = ApiParameter(
original_name='test', param_location='query', param_schema=schema
)
assert param.type_value == expected_type_value
assert param.type_hint == expected_type_hint
assert (
TypeHintHelper.get_type_hint(param.param_schema) == expected_type_hint
)
assert (
TypeHintHelper.get_type_value(param.param_schema) == expected_type_value
)
def test_api_parameter_description(self):
schema = Schema(type='string')
param = ApiParameter(
original_name='param1',
param_location='query',
param_schema=schema,
description='The description',
)
assert param.description == 'The description'
def test_api_parameter_description_use_schema_fallback(self):
schema = Schema(type='string', description='The description')
param = ApiParameter(
original_name='param1',
param_location='query',
param_schema=schema,
)
assert param.description == 'The description'
class TestTypeHintHelper:
@pytest.mark.parametrize(
'schema, expected_type_value, expected_type_hint',
[
({'type': 'integer'}, int, 'int'),
({'type': 'number'}, float, 'float'),
({'type': 'string'}, str, 'str'),
(
{
'type': 'array',
'items': {'type': 'string'},
},
List[str],
'List[str]',
),
],
)
def test_get_type_value_and_hint(
self, schema, expected_type_value, expected_type_hint
):
param = ApiParameter(
original_name='test_param',
param_location='query',
param_schema=schema,
description='Test parameter',
)
assert (
TypeHintHelper.get_type_value(param.param_schema) == expected_type_value
)
assert (
TypeHintHelper.get_type_hint(param.param_schema) == expected_type_hint
)
class TestPydocHelper:
def test_generate_param_doc_simple(self):
schema = Schema(type='string')
param = ApiParameter(
original_name='test_param',
param_location='query',
param_schema=schema,
description='Test description',
)
expected_doc = 'test_param (str): Test description'
assert PydocHelper.generate_param_doc(param) == expected_doc
def test_generate_param_doc_no_description(self):
schema = Schema(type='integer')
param = ApiParameter(
original_name='test_param',
param_location='query',
param_schema=schema,
)
expected_doc = 'test_param (int): '
assert PydocHelper.generate_param_doc(param) == expected_doc
def test_generate_param_doc_object(self):
schema = Schema(
type='object',
properties={
'prop1': {'type': 'string', 'description': 'Prop1 desc'},
'prop2': {'type': 'integer'},
},
)
param = ApiParameter(
original_name='test_param',
param_location='query',
param_schema=schema,
description='Test object parameter',
)
expected_doc = (
'test_param (Dict[str, Any]): Test object parameter Object'
' properties:\n prop1 (str): Prop1 desc\n prop2'
' (int): \n'
)
assert PydocHelper.generate_param_doc(param) == expected_doc
def test_generate_param_doc_object_no_properties(self):
schema = Schema(type='object', description='A test schema')
param = ApiParameter(
original_name='test_param',
param_location='query',
param_schema=schema,
description='The description.',
)
expected_doc = 'test_param (Dict[str, Any]): The description.'
assert PydocHelper.generate_param_doc(param) == expected_doc
def test_generate_return_doc_simple(self):
responses = {
'200': {
'description': 'Successful response',
'content': {'application/json': {'schema': {'type': 'string'}}},
}
}
expected_doc = 'Returns (str): Successful response'
assert (
PydocHelper.generate_return_doc(dict_to_responses(responses))
== expected_doc
)
def test_generate_return_doc_no_content(self):
responses = {'204': {'description': 'No content'}}
assert not PydocHelper.generate_return_doc(dict_to_responses(responses))
def test_generate_return_doc_object(self):
responses = {
'200': {
'description': 'Successful object response',
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'prop1': {
'type': 'string',
'description': 'Prop1 desc',
},
'prop2': {'type': 'integer'},
},
}
}
},
}
}
return_doc = PydocHelper.generate_return_doc(dict_to_responses(responses))
assert 'Returns (Dict[str, Any]): Successful object response' in return_doc
assert 'prop1 (str): Prop1 desc' in return_doc
assert 'prop2 (int):' in return_doc
def test_generate_return_doc_multiple_success(self):
responses = {
'200': {
'description': 'Successful response',
'content': {'application/json': {'schema': {'type': 'string'}}},
},
'400': {'description': 'Bad request'},
}
expected_doc = 'Returns (str): Successful response'
assert (
PydocHelper.generate_return_doc(dict_to_responses(responses))
== expected_doc
)
def test_generate_return_doc_2xx_smallest_status_code_response(self):
responses = {
'201': {
'description': '201 response',
'content': {'application/json': {'schema': {'type': 'integer'}}},
},
'200': {
'description': '200 response',
'content': {'application/json': {'schema': {'type': 'string'}}},
},
'400': {'description': 'Bad request'},
}
expected_doc = 'Returns (str): 200 response'
assert (
PydocHelper.generate_return_doc(dict_to_responses(responses))
== expected_doc
)
def test_generate_return_doc_contentful_response(self):
responses = {
'200': {'description': 'No content response'},
'201': {
'description': '201 response',
'content': {'application/json': {'schema': {'type': 'string'}}},
},
'400': {'description': 'Bad request'},
}
expected_doc = 'Returns (str): 201 response'
assert (
PydocHelper.generate_return_doc(dict_to_responses(responses))
== expected_doc
)
if __name__ == '__main__':
pytest.main([__file__])