Add comprehensive tests for openai_compat helper functions
Tests cover _join_url, _normalize_content, _parse_tool_arguments, _parse_usage, _build_response_format, and _optional_int with 31 test cases including edge cases and error paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: abdoelsayed2016 <27821589+abdoelsayed2016@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d0cd08947a
commit
42abcde18c
@@ -0,0 +1,167 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from src.openai_compat import (
|
||||
OpenAICompatError,
|
||||
_build_response_format,
|
||||
_join_url,
|
||||
_normalize_content,
|
||||
_optional_int,
|
||||
_parse_tool_arguments,
|
||||
_parse_usage,
|
||||
)
|
||||
from src.agent_types import OutputSchemaConfig, UsageStats
|
||||
|
||||
|
||||
class TestJoinUrl(unittest.TestCase):
|
||||
def test_base_with_trailing_slash(self):
|
||||
self.assertEqual(_join_url('http://localhost:8000/', 'v1/chat'), 'http://localhost:8000/v1/chat')
|
||||
|
||||
def test_base_without_trailing_slash(self):
|
||||
self.assertEqual(_join_url('http://localhost:8000', 'v1/chat'), 'http://localhost:8000/v1/chat')
|
||||
|
||||
def test_suffix_with_leading_slash(self):
|
||||
self.assertEqual(_join_url('http://localhost:8000', '/v1/chat'), 'http://localhost:8000/v1/chat')
|
||||
|
||||
|
||||
class TestNormalizeContent(unittest.TestCase):
|
||||
def test_string_passthrough(self):
|
||||
self.assertEqual(_normalize_content('hello'), 'hello')
|
||||
|
||||
def test_none_returns_empty(self):
|
||||
self.assertEqual(_normalize_content(None), '')
|
||||
|
||||
def test_list_of_strings_joined(self):
|
||||
self.assertEqual(_normalize_content(['hello', ' ', 'world']), 'hello world')
|
||||
|
||||
def test_list_of_text_dicts(self):
|
||||
items = [{'type': 'text', 'text': 'hello'}, {'type': 'text', 'text': ' world'}]
|
||||
self.assertEqual(_normalize_content(items), 'hello world')
|
||||
|
||||
def test_list_of_mixed_items(self):
|
||||
items = ['start ', {'type': 'text', 'text': 'middle'}, ' end']
|
||||
self.assertEqual(_normalize_content(items), 'start middle end')
|
||||
|
||||
def test_non_string_non_list_returns_str(self):
|
||||
self.assertEqual(_normalize_content(42), '42')
|
||||
|
||||
|
||||
class TestParseToolArguments(unittest.TestCase):
|
||||
def test_dict_passthrough(self):
|
||||
d = {'key': 'value'}
|
||||
self.assertIs(_parse_tool_arguments(d), d)
|
||||
|
||||
def test_valid_json_string(self):
|
||||
self.assertEqual(_parse_tool_arguments('{"a": 1}'), {'a': 1})
|
||||
|
||||
def test_empty_string_returns_empty_dict(self):
|
||||
self.assertEqual(_parse_tool_arguments(''), {})
|
||||
|
||||
def test_none_returns_empty_dict(self):
|
||||
self.assertEqual(_parse_tool_arguments(None), {})
|
||||
|
||||
def test_invalid_json_raises(self):
|
||||
with self.assertRaises(OpenAICompatError):
|
||||
_parse_tool_arguments('{bad json}')
|
||||
|
||||
def test_json_non_dict_raises(self):
|
||||
with self.assertRaises(OpenAICompatError):
|
||||
_parse_tool_arguments('[1, 2, 3]')
|
||||
|
||||
def test_unsupported_type_raises(self):
|
||||
with self.assertRaises(OpenAICompatError):
|
||||
_parse_tool_arguments(12345)
|
||||
|
||||
|
||||
class TestParseUsage(unittest.TestCase):
|
||||
def test_standard_fields(self):
|
||||
usage = _parse_usage({'input_tokens': 10, 'output_tokens': 20})
|
||||
self.assertEqual(usage.input_tokens, 10)
|
||||
self.assertEqual(usage.output_tokens, 20)
|
||||
|
||||
def test_prompt_completion_aliases(self):
|
||||
usage = _parse_usage({'prompt_tokens': 15, 'completion_tokens': 25})
|
||||
self.assertEqual(usage.input_tokens, 15)
|
||||
self.assertEqual(usage.output_tokens, 25)
|
||||
|
||||
def test_ollama_aliases(self):
|
||||
usage = _parse_usage({'prompt_eval_count': 12, 'eval_count': 18})
|
||||
self.assertEqual(usage.input_tokens, 12)
|
||||
self.assertEqual(usage.output_tokens, 18)
|
||||
|
||||
def test_cache_tokens(self):
|
||||
usage = _parse_usage({
|
||||
'input_tokens': 1,
|
||||
'output_tokens': 1,
|
||||
'cache_creation_input_tokens': 100,
|
||||
'cache_read_input_tokens': 200,
|
||||
})
|
||||
self.assertEqual(usage.cache_creation_input_tokens, 100)
|
||||
self.assertEqual(usage.cache_read_input_tokens, 200)
|
||||
|
||||
def test_reasoning_tokens_top_level_and_details(self):
|
||||
usage_top = _parse_usage({'input_tokens': 1, 'output_tokens': 1, 'reasoning_tokens': 50})
|
||||
self.assertEqual(usage_top.reasoning_tokens, 50)
|
||||
|
||||
usage_details = _parse_usage({
|
||||
'input_tokens': 1,
|
||||
'output_tokens': 1,
|
||||
'completion_tokens_details': {'reasoning_tokens': 75},
|
||||
})
|
||||
self.assertEqual(usage_details.reasoning_tokens, 75)
|
||||
|
||||
def test_non_dict_returns_empty(self):
|
||||
usage = _parse_usage('not a dict')
|
||||
self.assertEqual(usage, UsageStats())
|
||||
|
||||
def test_string_number_coercion(self):
|
||||
usage = _parse_usage({'input_tokens': '10', 'output_tokens': '20'})
|
||||
self.assertEqual(usage.input_tokens, 10)
|
||||
self.assertEqual(usage.output_tokens, 20)
|
||||
|
||||
|
||||
class TestBuildResponseFormat(unittest.TestCase):
|
||||
def test_none_returns_none(self):
|
||||
self.assertIsNone(_build_response_format(None))
|
||||
|
||||
def test_valid_schema(self):
|
||||
schema = OutputSchemaConfig(
|
||||
name='test_schema',
|
||||
schema={'type': 'object', 'properties': {'x': {'type': 'integer'}}},
|
||||
strict=True,
|
||||
)
|
||||
result = _build_response_format(schema)
|
||||
self.assertEqual(result, {
|
||||
'type': 'json_schema',
|
||||
'json_schema': {
|
||||
'name': 'test_schema',
|
||||
'schema': {'type': 'object', 'properties': {'x': {'type': 'integer'}}},
|
||||
'strict': True,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class TestOptionalInt(unittest.TestCase):
|
||||
def test_int_passthrough(self):
|
||||
self.assertEqual(_optional_int(42), 42)
|
||||
|
||||
def test_float_truncated(self):
|
||||
self.assertEqual(_optional_int(3.9), 3)
|
||||
|
||||
def test_string_parsed(self):
|
||||
self.assertEqual(_optional_int('7'), 7)
|
||||
|
||||
def test_bool_returns_zero(self):
|
||||
self.assertEqual(_optional_int(True), 0)
|
||||
self.assertEqual(_optional_int(False), 0)
|
||||
|
||||
def test_none_returns_zero(self):
|
||||
self.assertEqual(_optional_int(None), 0)
|
||||
|
||||
def test_invalid_string_returns_zero(self):
|
||||
self.assertEqual(_optional_int('abc'), 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user