Files
LangBot/tests/unit_tests/utils/test_funcschema.py
huanghuoguoguo d89356af65 Merge remote-tracking branch 'origin/fix/utils-funcschema-missing-doc' into validation/test-build-with-fixes
# Conflicts:
#	tests/unit_tests/utils/test_funcschema.py
2026-05-16 10:43:22 +08:00

210 lines
6.5 KiB
Python

"""Unit tests for utils funcschema.
Tests cover:
- get_func_schema() function
- Docstring parsing
- Parameter type extraction
- Required parameter detection
Note: Do NOT use 'from __future__ import annotations' because
funcschema.py expects actual type objects, not string annotations.
"""
import pytest
from importlib import import_module
def get_funcschema_module():
"""Lazy import to avoid circular import issues."""
return import_module('langbot.pkg.utils.funcschema')
class TestGetFuncSchema:
"""Tests for get_func_schema function."""
def test_simple_function_schema(self):
"""Test schema generation for simple function."""
funcschema = get_funcschema_module()
def simple_func(name: str, count: int):
"""Simple function description.
Args:
name: The name parameter.
count: The count parameter.
"""
pass
result = funcschema.get_func_schema(simple_func)
assert result['description'] == 'Simple function description.'
assert result['parameters']['type'] == 'object'
assert 'name' in result['parameters']['properties']
assert 'count' in result['parameters']['properties']
assert result['parameters']['properties']['name']['type'] == 'string'
assert result['parameters']['properties']['count']['type'] == 'integer'
def test_parameter_type_mapping(self):
"""Test that Python types are mapped to JSON schema types."""
funcschema = get_funcschema_module()
def typed_func(a: str, b: int, c: float, d: bool, e: list, f: dict):
"""Typed function.
Args:
a: String param.
b: Int param.
c: Float param.
d: Bool param.
e: List param.
f: Dict param.
"""
pass
result = funcschema.get_func_schema(typed_func)
props = result['parameters']['properties']
assert props['a']['type'] == 'string'
assert props['b']['type'] == 'integer'
assert props['c']['type'] == 'number'
assert props['d']['type'] == 'boolean'
assert props['e']['type'] == 'array'
assert props['f']['type'] == 'object'
def test_required_parameters_detection(self):
"""Test that required parameters are detected correctly."""
funcschema = get_funcschema_module()
def func_with_defaults(name: str, optional: str = 'default'):
"""Function with default.
Args:
name: Required param.
optional: Optional param.
"""
pass
result = funcschema.get_func_schema(func_with_defaults)
assert 'name' in result['parameters']['required']
assert 'optional' not in result['parameters']['required']
def test_self_and_query_excluded(self):
"""Test that self and query parameters are excluded."""
funcschema = get_funcschema_module()
def method_func(self, query, other: str):
"""Method function.
Args:
self: Self parameter.
query: Query parameter.
other: Other parameter.
"""
pass
result = funcschema.get_func_schema(method_func)
props = result['parameters']['properties']
assert 'self' not in props
assert 'query' not in props
assert 'other' in props
def test_array_type_extraction(self):
"""Test that list[T] types extract element type."""
funcschema = get_funcschema_module()
def list_func(items: list[str], numbers: list[int]):
"""List function.
Args:
items: List of strings.
numbers: List of integers.
"""
pass
result = funcschema.get_func_schema(list_func)
props = result['parameters']['properties']
assert props['items']['type'] == 'array'
assert props['items']['items']['type'] == 'string'
assert props['numbers']['type'] == 'array'
assert props['numbers']['items']['type'] == 'integer'
def test_function_without_docstring_raises(self):
"""Test that function without docstring raises exception."""
funcschema = get_funcschema_module()
def no_doc_func(a: str):
pass
with pytest.raises(Exception) as exc_info:
funcschema.get_func_schema(no_doc_func)
assert 'has no docstring' in str(exc_info.value)
def test_description_extraction(self):
"""Test that description is extracted from first paragraph."""
funcschema = get_funcschema_module()
def desc_func(a: str):
"""This is the description.
Args:
a: Param a.
"""
pass
result = funcschema.get_func_schema(desc_func)
assert result['description'] == 'This is the description.'
def test_function_reference_stored(self):
"""Test that function reference is stored in schema."""
funcschema = get_funcschema_module()
def stored_func(a: str):
"""Stored function.
Args:
a: Param a.
"""
pass
result = funcschema.get_func_schema(stored_func)
assert result['function'] is stored_func
def test_description_from_args_doc(self):
"""Test that arg description is extracted from docstring."""
funcschema = get_funcschema_module()
def doc_func(param_name: str):
"""Function with documented param.
Args:
param_name: This is the param description.
"""
pass
result = funcschema.get_func_schema(doc_func)
assert result['parameters']['properties']['param_name']['description'] == 'This is the param description.'
def test_missing_parameter_doc_uses_empty_description(self):
"""Test that undocumented parameters do not crash schema generation."""
funcschema = get_funcschema_module()
def partially_documented_func(documented: str, undocumented: int):
"""Function with one undocumented param.
Args:
documented: Documented parameter.
"""
pass
result = funcschema.get_func_schema(partially_documented_func)
props = result['parameters']['properties']
assert props['documented']['description'] == 'Documented parameter.'
assert props['undocumented']['description'] == ''