| |
|
|
| """ |
| Unit tests for OpenAI Pydantic models. |
| |
| Comprehensive tests for all OpenAI-compatible API models: |
| - Model listing (OpenAIModel, ModelList) |
| - Chat messages (ChatMessage) |
| - Tools (ToolFunction, Tool) |
| - Requests (ChatCompletionRequest) |
| - Responses (ChatCompletionChoice, ChatCompletionUsage, ChatCompletionResponse) |
| - Streaming (ChatCompletionChunk, ChatCompletionChunkChoice, ChatCompletionChunkDelta) |
| """ |
|
|
| import pytest |
| from pydantic import ValidationError |
|
|
| from kiro.models_openai import ( |
| |
| OpenAIModel, |
| ModelList, |
| |
| ChatMessage, |
| |
| ToolFunction, |
| Tool, |
| |
| ChatCompletionRequest, |
| |
| ChatCompletionChoice, |
| ChatCompletionUsage, |
| ChatCompletionResponse, |
| |
| ChatCompletionChunkDelta, |
| ChatCompletionChunkChoice, |
| ChatCompletionChunk, |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestOpenAIModel: |
| """Tests for OpenAIModel Pydantic model.""" |
| |
| def test_valid_model(self): |
| """ |
| What it does: Verifies creation of valid OpenAIModel. |
| Purpose: Ensure model accepts valid data. |
| """ |
| print("Setup: Creating OpenAIModel with valid data...") |
| model = OpenAIModel( |
| id="claude-sonnet-4-5", |
| description="Claude Sonnet 4.5 model" |
| ) |
| |
| print(f"Result: {model}") |
| print(f"Comparing id: Expected 'claude-sonnet-4-5', Got '{model.id}'") |
| assert model.id == "claude-sonnet-4-5" |
| |
| print(f"Comparing object: Expected 'model', Got '{model.object}'") |
| assert model.object == "model" |
| |
| print(f"Comparing owned_by: Expected 'anthropic', Got '{model.owned_by}'") |
| assert model.owned_by == "anthropic" |
| |
| print(f"Comparing description: Got '{model.description}'") |
| assert model.description == "Claude Sonnet 4.5 model" |
| |
| def test_requires_id(self): |
| """ |
| What it does: Verifies that id is required. |
| Purpose: Ensure validation fails without id. |
| """ |
| print("Setup: Attempting to create OpenAIModel without id...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| OpenAIModel() |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "id" in str(exc_info.value) |
| |
| def test_object_defaults_to_model(self): |
| """ |
| What it does: Verifies that object defaults to "model". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating OpenAIModel without explicit object...") |
| model = OpenAIModel(id="test-model") |
| |
| print(f"Comparing object: Expected 'model', Got '{model.object}'") |
| assert model.object == "model" |
| |
| def test_owned_by_defaults_to_anthropic(self): |
| """ |
| What it does: Verifies that owned_by defaults to "anthropic". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating OpenAIModel without explicit owned_by...") |
| model = OpenAIModel(id="test-model") |
| |
| print(f"Comparing owned_by: Expected 'anthropic', Got '{model.owned_by}'") |
| assert model.owned_by == "anthropic" |
| |
| def test_created_is_auto_generated(self): |
| """ |
| What it does: Verifies that created timestamp is auto-generated. |
| Purpose: Ensure timestamp is set automatically. |
| """ |
| print("Setup: Creating OpenAIModel without explicit created...") |
| model = OpenAIModel(id="test-model") |
| |
| print(f"Comparing created: Got {model.created}") |
| assert model.created > 0 |
| assert isinstance(model.created, int) |
| |
| def test_description_is_optional(self): |
| """ |
| What it does: Verifies that description is optional. |
| Purpose: Ensure models without description work. |
| """ |
| print("Setup: Creating OpenAIModel without description...") |
| model = OpenAIModel(id="test-model") |
| |
| print(f"Comparing description: Expected None, Got {model.description}") |
| assert model.description is None |
|
|
|
|
| |
| |
| |
|
|
| class TestModelList: |
| """Tests for ModelList Pydantic model.""" |
| |
| def test_valid_model_list(self): |
| """ |
| What it does: Verifies creation of valid ModelList. |
| Purpose: Ensure model list accepts valid data. |
| """ |
| print("Setup: Creating ModelList with valid data...") |
| model_list = ModelList( |
| data=[ |
| OpenAIModel(id="claude-sonnet-4-5"), |
| OpenAIModel(id="claude-opus-4") |
| ] |
| ) |
| |
| print(f"Result: {model_list}") |
| print(f"Comparing object: Expected 'list', Got '{model_list.object}'") |
| assert model_list.object == "list" |
| |
| print(f"Comparing data length: Expected 2, Got {len(model_list.data)}") |
| assert len(model_list.data) == 2 |
| |
| def test_object_defaults_to_list(self): |
| """ |
| What it does: Verifies that object defaults to "list". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ModelList without explicit object...") |
| model_list = ModelList(data=[]) |
| |
| print(f"Comparing object: Expected 'list', Got '{model_list.object}'") |
| assert model_list.object == "list" |
| |
| def test_requires_data(self): |
| """ |
| What it does: Verifies that data is required. |
| Purpose: Ensure validation fails without data. |
| """ |
| print("Setup: Attempting to create ModelList without data...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ModelList() |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "data" in str(exc_info.value) |
| |
| def test_accepts_empty_list(self): |
| """ |
| What it does: Verifies that empty list is accepted. |
| Purpose: Ensure empty model list works. |
| """ |
| print("Setup: Creating ModelList with empty data...") |
| model_list = ModelList(data=[]) |
| |
| print(f"Comparing data: Expected [], Got {model_list.data}") |
| assert model_list.data == [] |
|
|
|
|
| |
| |
| |
|
|
| class TestChatMessage: |
| """Tests for ChatMessage Pydantic model.""" |
| |
| def test_valid_user_message(self): |
| """ |
| What it does: Verifies creation of valid user message. |
| Purpose: Ensure model accepts valid user message. |
| """ |
| print("Setup: Creating ChatMessage with user role...") |
| message = ChatMessage(role="user", content="Hello!") |
| |
| print(f"Result: {message}") |
| print(f"Comparing role: Expected 'user', Got '{message.role}'") |
| assert message.role == "user" |
| |
| print(f"Comparing content: Expected 'Hello!', Got '{message.content}'") |
| assert message.content == "Hello!" |
| |
| def test_valid_assistant_message(self): |
| """ |
| What it does: Verifies creation of valid assistant message. |
| Purpose: Ensure model accepts valid assistant message. |
| """ |
| print("Setup: Creating ChatMessage with assistant role...") |
| message = ChatMessage(role="assistant", content="Hi there!") |
| |
| print(f"Result: {message}") |
| print(f"Comparing role: Expected 'assistant', Got '{message.role}'") |
| assert message.role == "assistant" |
| |
| def test_valid_system_message(self): |
| """ |
| What it does: Verifies creation of valid system message. |
| Purpose: Ensure model accepts valid system message. |
| """ |
| print("Setup: Creating ChatMessage with system role...") |
| message = ChatMessage(role="system", content="You are helpful.") |
| |
| print(f"Result: {message}") |
| print(f"Comparing role: Expected 'system', Got '{message.role}'") |
| assert message.role == "system" |
| |
| def test_valid_tool_message(self): |
| """ |
| What it does: Verifies creation of valid tool message. |
| Purpose: Ensure model accepts valid tool message. |
| """ |
| print("Setup: Creating ChatMessage with tool role...") |
| message = ChatMessage( |
| role="tool", |
| content="Tool result", |
| tool_call_id="call_123" |
| ) |
| |
| print(f"Result: {message}") |
| print(f"Comparing role: Expected 'tool', Got '{message.role}'") |
| assert message.role == "tool" |
| |
| print(f"Comparing tool_call_id: Expected 'call_123', Got '{message.tool_call_id}'") |
| assert message.tool_call_id == "call_123" |
| |
| def test_requires_role(self): |
| """ |
| What it does: Verifies that role is required. |
| Purpose: Ensure validation fails without role. |
| """ |
| print("Setup: Attempting to create ChatMessage without role...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatMessage(content="Hello") |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "role" in str(exc_info.value) |
| |
| def test_content_is_optional(self): |
| """ |
| What it does: Verifies that content is optional. |
| Purpose: Ensure messages without content work (e.g., tool calls). |
| """ |
| print("Setup: Creating ChatMessage without content...") |
| message = ChatMessage(role="assistant") |
| |
| print(f"Comparing content: Expected None, Got {message.content}") |
| assert message.content is None |
| |
| def test_accepts_list_content(self): |
| """ |
| What it does: Verifies that list content is accepted. |
| Purpose: Ensure multimodal content works. |
| """ |
| print("Setup: Creating ChatMessage with list content...") |
| message = ChatMessage( |
| role="user", |
| content=[ |
| {"type": "text", "text": "What's in this image?"}, |
| {"type": "image_url", "image_url": {"url": "https://example.com/img.jpg"}} |
| ] |
| ) |
| |
| print(f"Result: {message}") |
| print(f"Comparing content type: Expected list, Got {type(message.content)}") |
| assert isinstance(message.content, list) |
| assert len(message.content) == 2 |
| |
| def test_accepts_tool_calls(self): |
| """ |
| What it does: Verifies that tool_calls is accepted. |
| Purpose: Ensure assistant messages with tool calls work. |
| """ |
| print("Setup: Creating ChatMessage with tool_calls...") |
| message = ChatMessage( |
| role="assistant", |
| content="I'll call a tool", |
| tool_calls=[{ |
| "id": "call_123", |
| "type": "function", |
| "function": {"name": "get_weather", "arguments": '{"location": "Moscow"}'} |
| }] |
| ) |
| |
| print(f"Result: {message}") |
| print(f"Comparing tool_calls: Got {message.tool_calls}") |
| assert message.tool_calls is not None |
| assert len(message.tool_calls) == 1 |
| |
| def test_name_is_optional(self): |
| """ |
| What it does: Verifies that name is optional. |
| Purpose: Ensure messages without name work. |
| """ |
| print("Setup: Creating ChatMessage without name...") |
| message = ChatMessage(role="user", content="Hello") |
| |
| print(f"Comparing name: Expected None, Got {message.name}") |
| assert message.name is None |
| |
| def test_accepts_name(self): |
| """ |
| What it does: Verifies that name is accepted. |
| Purpose: Ensure named messages work. |
| """ |
| print("Setup: Creating ChatMessage with name...") |
| message = ChatMessage(role="user", content="Hello", name="John") |
| |
| print(f"Comparing name: Expected 'John', Got '{message.name}'") |
| assert message.name == "John" |
| |
| def test_extra_fields_allowed(self): |
| """ |
| What it does: Verifies that extra fields are allowed. |
| Purpose: Ensure model_config extra="allow" works. |
| """ |
| print("Setup: Creating ChatMessage with extra field...") |
| message = ChatMessage(role="user", content="Hello", custom_field="value") |
| |
| print(f"Comparing custom_field: Got '{message.custom_field}'") |
| assert message.custom_field == "value" |
|
|
|
|
| |
| |
| |
|
|
| class TestToolFunction: |
| """Tests for ToolFunction Pydantic model.""" |
| |
| def test_valid_tool_function(self): |
| """ |
| What it does: Verifies creation of valid ToolFunction. |
| Purpose: Ensure model accepts valid tool function. |
| """ |
| print("Setup: Creating ToolFunction with valid data...") |
| func = ToolFunction( |
| name="get_weather", |
| description="Get weather for a location", |
| parameters={ |
| "type": "object", |
| "properties": {"location": {"type": "string"}} |
| } |
| ) |
| |
| print(f"Result: {func}") |
| print(f"Comparing name: Expected 'get_weather', Got '{func.name}'") |
| assert func.name == "get_weather" |
| |
| print(f"Comparing description: Got '{func.description}'") |
| assert func.description == "Get weather for a location" |
| |
| print(f"Comparing parameters: Got {func.parameters}") |
| assert "properties" in func.parameters |
| |
| def test_requires_name(self): |
| """ |
| What it does: Verifies that name is required. |
| Purpose: Ensure validation fails without name. |
| """ |
| print("Setup: Attempting to create ToolFunction without name...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ToolFunction(description="Test") |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "name" in str(exc_info.value) |
| |
| def test_description_is_optional(self): |
| """ |
| What it does: Verifies that description is optional. |
| Purpose: Ensure functions without description work. |
| """ |
| print("Setup: Creating ToolFunction without description...") |
| func = ToolFunction(name="test_func") |
| |
| print(f"Comparing description: Expected None, Got {func.description}") |
| assert func.description is None |
| |
| def test_parameters_is_optional(self): |
| """ |
| What it does: Verifies that parameters is optional. |
| Purpose: Ensure functions without parameters work. |
| """ |
| print("Setup: Creating ToolFunction without parameters...") |
| func = ToolFunction(name="no_params_func") |
| |
| print(f"Comparing parameters: Expected None, Got {func.parameters}") |
| assert func.parameters is None |
|
|
|
|
| |
| |
| |
|
|
| class TestTool: |
| """Tests for Tool Pydantic model.""" |
| |
| def test_valid_tool(self): |
| """ |
| What it does: Verifies creation of valid Tool. |
| Purpose: Ensure model accepts valid tool. |
| """ |
| print("Setup: Creating Tool with valid data...") |
| tool = Tool( |
| type="function", |
| function=ToolFunction( |
| name="get_weather", |
| description="Get weather", |
| parameters={} |
| ) |
| ) |
| |
| print(f"Result: {tool}") |
| print(f"Comparing type: Expected 'function', Got '{tool.type}'") |
| assert tool.type == "function" |
| |
| print(f"Comparing function.name: Expected 'get_weather', Got '{tool.function.name}'") |
| assert tool.function.name == "get_weather" |
| |
| def test_type_defaults_to_function(self): |
| """ |
| What it does: Verifies that type defaults to "function". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating Tool without explicit type...") |
| tool = Tool(function=ToolFunction(name="test")) |
| |
| print(f"Comparing type: Expected 'function', Got '{tool.type}'") |
| assert tool.type == "function" |
| |
| def test_function_is_optional_for_flat_format(self): |
| """ |
| What it does: Verifies that function is optional (for flat format compatibility). |
| Purpose: Ensure flat format (Cursor-style) is supported without function field. |
| """ |
| print("Setup: Creating Tool with flat format (name, description, input_schema)...") |
| tool = Tool( |
| type="function", |
| name="test_tool", |
| description="A test tool", |
| input_schema={"type": "object", "properties": {}} |
| ) |
| |
| print(f"Result: {tool}") |
| print(f"Comparing name: Expected 'test_tool', Got '{tool.name}'") |
| assert tool.name == "test_tool" |
| |
| print(f"Comparing function: Expected None, Got {tool.function}") |
| assert tool.function is None |
| |
| print(f"Comparing description: Expected 'A test tool', Got '{tool.description}'") |
| assert tool.description == "A test tool" |
| |
| def test_standard_format_still_works(self): |
| """ |
| What it does: Verifies that standard OpenAI format still works. |
| Purpose: Ensure backward compatibility with standard format. |
| """ |
| print("Setup: Creating Tool with standard OpenAI format (function field)...") |
| tool = Tool( |
| type="function", |
| function=ToolFunction(name="standard_tool", description="Standard") |
| ) |
| |
| print(f"Result: {tool}") |
| print(f"Comparing function.name: Expected 'standard_tool', Got '{tool.function.name}'") |
| assert tool.function.name == "standard_tool" |
| |
| print(f"Comparing name: Expected None, Got {tool.name}") |
| assert tool.name is None |
|
|
|
|
| |
| |
| |
|
|
| class TestChatCompletionRequest: |
| """Tests for ChatCompletionRequest Pydantic model.""" |
| |
| def test_valid_request(self): |
| """ |
| What it does: Verifies creation of valid ChatCompletionRequest. |
| Purpose: Ensure model accepts valid request. |
| """ |
| print("Setup: Creating ChatCompletionRequest with valid data...") |
| request = ChatCompletionRequest( |
| model="claude-sonnet-4-5", |
| messages=[ChatMessage(role="user", content="Hello")] |
| ) |
| |
| print(f"Result: {request}") |
| print(f"Comparing model: Expected 'claude-sonnet-4-5', Got '{request.model}'") |
| assert request.model == "claude-sonnet-4-5" |
| |
| print(f"Comparing messages length: Expected 1, Got {len(request.messages)}") |
| assert len(request.messages) == 1 |
| |
| print(f"Comparing stream: Expected False, Got {request.stream}") |
| assert request.stream is False |
| |
| def test_requires_model(self): |
| """ |
| What it does: Verifies that model is required. |
| Purpose: Ensure validation fails without model. |
| """ |
| print("Setup: Attempting to create ChatCompletionRequest without model...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatCompletionRequest(messages=[ChatMessage(role="user", content="Hi")]) |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "model" in str(exc_info.value) |
| |
| def test_requires_messages(self): |
| """ |
| What it does: Verifies that messages is required. |
| Purpose: Ensure validation fails without messages. |
| """ |
| print("Setup: Attempting to create ChatCompletionRequest without messages...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatCompletionRequest(model="claude-sonnet-4-5") |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "messages" in str(exc_info.value) |
| |
| def test_requires_at_least_one_message(self): |
| """ |
| What it does: Verifies that at least one message is required. |
| Purpose: Ensure validation fails with empty messages. |
| """ |
| print("Setup: Attempting to create ChatCompletionRequest with empty messages...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatCompletionRequest(model="claude-sonnet-4-5", messages=[]) |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| |
| def test_stream_defaults_to_false(self): |
| """ |
| What it does: Verifies that stream defaults to False. |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ChatCompletionRequest without explicit stream...") |
| request = ChatCompletionRequest( |
| model="test", |
| messages=[ChatMessage(role="user", content="Hi")] |
| ) |
| |
| print(f"Comparing stream: Expected False, Got {request.stream}") |
| assert request.stream is False |
| |
| def test_accepts_stream_true(self): |
| """ |
| What it does: Verifies that stream=True is accepted. |
| Purpose: Ensure streaming requests work. |
| """ |
| print("Setup: Creating ChatCompletionRequest with stream=True...") |
| request = ChatCompletionRequest( |
| model="test", |
| messages=[ChatMessage(role="user", content="Hi")], |
| stream=True |
| ) |
| |
| print(f"Comparing stream: Expected True, Got {request.stream}") |
| assert request.stream is True |
| |
| def test_accepts_tools(self): |
| """ |
| What it does: Verifies that tools are accepted. |
| Purpose: Ensure function calling works. |
| """ |
| print("Setup: Creating ChatCompletionRequest with tools...") |
| request = ChatCompletionRequest( |
| model="test", |
| messages=[ChatMessage(role="user", content="Hi")], |
| tools=[Tool(function=ToolFunction(name="test_tool"))] |
| ) |
| |
| print(f"Comparing tools: Got {request.tools}") |
| assert request.tools is not None |
| assert len(request.tools) == 1 |
| |
| def test_accepts_generation_parameters(self): |
| """ |
| What it does: Verifies that generation parameters are accepted. |
| Purpose: Ensure temperature, top_p, max_tokens work. |
| """ |
| print("Setup: Creating ChatCompletionRequest with generation params...") |
| request = ChatCompletionRequest( |
| model="test", |
| messages=[ChatMessage(role="user", content="Hi")], |
| temperature=0.7, |
| top_p=0.9, |
| max_tokens=1000 |
| ) |
| |
| print(f"Comparing temperature: Expected 0.7, Got {request.temperature}") |
| assert request.temperature == 0.7 |
| |
| print(f"Comparing top_p: Expected 0.9, Got {request.top_p}") |
| assert request.top_p == 0.9 |
| |
| print(f"Comparing max_tokens: Expected 1000, Got {request.max_tokens}") |
| assert request.max_tokens == 1000 |
|
|
|
|
| |
| |
| |
|
|
| class TestChatCompletionUsage: |
| """Tests for ChatCompletionUsage Pydantic model.""" |
| |
| def test_valid_usage(self): |
| """ |
| What it does: Verifies creation of valid ChatCompletionUsage. |
| Purpose: Ensure model accepts valid usage data. |
| """ |
| print("Setup: Creating ChatCompletionUsage with valid data...") |
| usage = ChatCompletionUsage( |
| prompt_tokens=100, |
| completion_tokens=50, |
| total_tokens=150 |
| ) |
| |
| print(f"Result: {usage}") |
| print(f"Comparing prompt_tokens: Expected 100, Got {usage.prompt_tokens}") |
| assert usage.prompt_tokens == 100 |
| |
| print(f"Comparing completion_tokens: Expected 50, Got {usage.completion_tokens}") |
| assert usage.completion_tokens == 50 |
| |
| print(f"Comparing total_tokens: Expected 150, Got {usage.total_tokens}") |
| assert usage.total_tokens == 150 |
| |
| def test_defaults_to_zero(self): |
| """ |
| What it does: Verifies that all fields default to 0. |
| Purpose: Ensure default values are set correctly. |
| """ |
| print("Setup: Creating ChatCompletionUsage without explicit values...") |
| usage = ChatCompletionUsage() |
| |
| print(f"Comparing prompt_tokens: Expected 0, Got {usage.prompt_tokens}") |
| assert usage.prompt_tokens == 0 |
| |
| print(f"Comparing completion_tokens: Expected 0, Got {usage.completion_tokens}") |
| assert usage.completion_tokens == 0 |
| |
| print(f"Comparing total_tokens: Expected 0, Got {usage.total_tokens}") |
| assert usage.total_tokens == 0 |
| |
| def test_credits_used_is_optional(self): |
| """ |
| What it does: Verifies that credits_used is optional. |
| Purpose: Ensure Kiro-specific field is optional. |
| """ |
| print("Setup: Creating ChatCompletionUsage without credits_used...") |
| usage = ChatCompletionUsage() |
| |
| print(f"Comparing credits_used: Expected None, Got {usage.credits_used}") |
| assert usage.credits_used is None |
|
|
|
|
| |
| |
| |
|
|
| class TestChatCompletionChoice: |
| """Tests for ChatCompletionChoice Pydantic model.""" |
| |
| def test_valid_choice(self): |
| """ |
| What it does: Verifies creation of valid ChatCompletionChoice. |
| Purpose: Ensure model accepts valid choice data. |
| """ |
| print("Setup: Creating ChatCompletionChoice with valid data...") |
| choice = ChatCompletionChoice( |
| index=0, |
| message={"role": "assistant", "content": "Hello!"}, |
| finish_reason="stop" |
| ) |
| |
| print(f"Result: {choice}") |
| print(f"Comparing index: Expected 0, Got {choice.index}") |
| assert choice.index == 0 |
| |
| print(f"Comparing message: Got {choice.message}") |
| assert choice.message["role"] == "assistant" |
| |
| print(f"Comparing finish_reason: Expected 'stop', Got '{choice.finish_reason}'") |
| assert choice.finish_reason == "stop" |
| |
| def test_index_defaults_to_zero(self): |
| """ |
| What it does: Verifies that index defaults to 0. |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ChatCompletionChoice without explicit index...") |
| choice = ChatCompletionChoice(message={"role": "assistant", "content": "Hi"}) |
| |
| print(f"Comparing index: Expected 0, Got {choice.index}") |
| assert choice.index == 0 |
| |
| def test_requires_message(self): |
| """ |
| What it does: Verifies that message is required. |
| Purpose: Ensure validation fails without message. |
| """ |
| print("Setup: Attempting to create ChatCompletionChoice without message...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatCompletionChoice(index=0, finish_reason="stop") |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "message" in str(exc_info.value) |
| |
| def test_finish_reason_is_optional(self): |
| """ |
| What it does: Verifies that finish_reason is optional. |
| Purpose: Ensure choices without finish_reason work. |
| """ |
| print("Setup: Creating ChatCompletionChoice without finish_reason...") |
| choice = ChatCompletionChoice(message={"role": "assistant", "content": "Hi"}) |
| |
| print(f"Comparing finish_reason: Expected None, Got {choice.finish_reason}") |
| assert choice.finish_reason is None |
|
|
|
|
| |
| |
| |
|
|
| class TestChatCompletionResponse: |
| """Tests for ChatCompletionResponse Pydantic model.""" |
| |
| def test_valid_response(self): |
| """ |
| What it does: Verifies creation of valid ChatCompletionResponse. |
| Purpose: Ensure model accepts valid response data. |
| """ |
| print("Setup: Creating ChatCompletionResponse with valid data...") |
| response = ChatCompletionResponse( |
| id="chatcmpl-123", |
| model="claude-sonnet-4-5", |
| choices=[ChatCompletionChoice( |
| message={"role": "assistant", "content": "Hello!"}, |
| finish_reason="stop" |
| )], |
| usage=ChatCompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15) |
| ) |
| |
| print(f"Result: {response}") |
| print(f"Comparing id: Expected 'chatcmpl-123', Got '{response.id}'") |
| assert response.id == "chatcmpl-123" |
| |
| print(f"Comparing object: Expected 'chat.completion', Got '{response.object}'") |
| assert response.object == "chat.completion" |
| |
| print(f"Comparing model: Expected 'claude-sonnet-4-5', Got '{response.model}'") |
| assert response.model == "claude-sonnet-4-5" |
| |
| print(f"Comparing choices length: Expected 1, Got {len(response.choices)}") |
| assert len(response.choices) == 1 |
| |
| def test_object_defaults_to_chat_completion(self): |
| """ |
| What it does: Verifies that object defaults to "chat.completion". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ChatCompletionResponse without explicit object...") |
| response = ChatCompletionResponse( |
| id="test", |
| model="test", |
| choices=[ChatCompletionChoice(message={"role": "assistant", "content": "Hi"})], |
| usage=ChatCompletionUsage() |
| ) |
| |
| print(f"Comparing object: Expected 'chat.completion', Got '{response.object}'") |
| assert response.object == "chat.completion" |
| |
| def test_created_is_auto_generated(self): |
| """ |
| What it does: Verifies that created timestamp is auto-generated. |
| Purpose: Ensure timestamp is set automatically. |
| """ |
| print("Setup: Creating ChatCompletionResponse without explicit created...") |
| response = ChatCompletionResponse( |
| id="test", |
| model="test", |
| choices=[ChatCompletionChoice(message={"role": "assistant", "content": "Hi"})], |
| usage=ChatCompletionUsage() |
| ) |
| |
| print(f"Comparing created: Got {response.created}") |
| assert response.created > 0 |
| assert isinstance(response.created, int) |
| |
| def test_requires_id(self): |
| """ |
| What it does: Verifies that id is required. |
| Purpose: Ensure validation fails without id. |
| """ |
| print("Setup: Attempting to create ChatCompletionResponse without id...") |
| |
| print("Action: Creating model (should raise ValidationError)...") |
| with pytest.raises(ValidationError) as exc_info: |
| ChatCompletionResponse( |
| model="test", |
| choices=[ChatCompletionChoice(message={"role": "assistant", "content": "Hi"})], |
| usage=ChatCompletionUsage() |
| ) |
| |
| print(f"ValidationError raised: {exc_info.value}") |
| assert "id" in str(exc_info.value) |
|
|
|
|
| |
| |
| |
|
|
| class TestChatCompletionChunkDelta: |
| """Tests for ChatCompletionChunkDelta Pydantic model.""" |
| |
| def test_valid_delta_with_content(self): |
| """ |
| What it does: Verifies creation of valid delta with content. |
| Purpose: Ensure model accepts content delta. |
| """ |
| print("Setup: Creating ChatCompletionChunkDelta with content...") |
| delta = ChatCompletionChunkDelta(content="Hello") |
| |
| print(f"Result: {delta}") |
| print(f"Comparing content: Expected 'Hello', Got '{delta.content}'") |
| assert delta.content == "Hello" |
| |
| def test_valid_delta_with_role(self): |
| """ |
| What it does: Verifies creation of valid delta with role. |
| Purpose: Ensure model accepts role delta (first chunk). |
| """ |
| print("Setup: Creating ChatCompletionChunkDelta with role...") |
| delta = ChatCompletionChunkDelta(role="assistant") |
| |
| print(f"Result: {delta}") |
| print(f"Comparing role: Expected 'assistant', Got '{delta.role}'") |
| assert delta.role == "assistant" |
| |
| def test_all_fields_optional(self): |
| """ |
| What it does: Verifies that all fields are optional. |
| Purpose: Ensure empty delta works. |
| """ |
| print("Setup: Creating empty ChatCompletionChunkDelta...") |
| delta = ChatCompletionChunkDelta() |
| |
| print(f"Comparing role: Expected None, Got {delta.role}") |
| assert delta.role is None |
| |
| print(f"Comparing content: Expected None, Got {delta.content}") |
| assert delta.content is None |
| |
| print(f"Comparing tool_calls: Expected None, Got {delta.tool_calls}") |
| assert delta.tool_calls is None |
| |
| def test_accepts_tool_calls(self): |
| """ |
| What it does: Verifies that tool_calls is accepted. |
| Purpose: Ensure streaming tool calls work. |
| """ |
| print("Setup: Creating ChatCompletionChunkDelta with tool_calls...") |
| delta = ChatCompletionChunkDelta( |
| tool_calls=[{"index": 0, "id": "call_1", "function": {"name": "test"}}] |
| ) |
| |
| print(f"Comparing tool_calls: Got {delta.tool_calls}") |
| assert delta.tool_calls is not None |
| assert len(delta.tool_calls) == 1 |
|
|
|
|
| class TestChatCompletionChunkChoice: |
| """Tests for ChatCompletionChunkChoice Pydantic model.""" |
| |
| def test_valid_chunk_choice(self): |
| """ |
| What it does: Verifies creation of valid chunk choice. |
| Purpose: Ensure model accepts valid chunk choice. |
| """ |
| print("Setup: Creating ChatCompletionChunkChoice with valid data...") |
| choice = ChatCompletionChunkChoice( |
| index=0, |
| delta=ChatCompletionChunkDelta(content="Hello") |
| ) |
| |
| print(f"Result: {choice}") |
| print(f"Comparing index: Expected 0, Got {choice.index}") |
| assert choice.index == 0 |
| |
| print(f"Comparing delta.content: Expected 'Hello', Got '{choice.delta.content}'") |
| assert choice.delta.content == "Hello" |
| |
| def test_index_defaults_to_zero(self): |
| """ |
| What it does: Verifies that index defaults to 0. |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ChatCompletionChunkChoice without explicit index...") |
| choice = ChatCompletionChunkChoice(delta=ChatCompletionChunkDelta()) |
| |
| print(f"Comparing index: Expected 0, Got {choice.index}") |
| assert choice.index == 0 |
| |
| def test_finish_reason_is_optional(self): |
| """ |
| What it does: Verifies that finish_reason is optional. |
| Purpose: Ensure intermediate chunks work. |
| """ |
| print("Setup: Creating ChatCompletionChunkChoice without finish_reason...") |
| choice = ChatCompletionChunkChoice(delta=ChatCompletionChunkDelta(content="Hi")) |
| |
| print(f"Comparing finish_reason: Expected None, Got {choice.finish_reason}") |
| assert choice.finish_reason is None |
| |
| def test_accepts_finish_reason(self): |
| """ |
| What it does: Verifies that finish_reason is accepted. |
| Purpose: Ensure final chunk works. |
| """ |
| print("Setup: Creating ChatCompletionChunkChoice with finish_reason...") |
| choice = ChatCompletionChunkChoice( |
| delta=ChatCompletionChunkDelta(), |
| finish_reason="stop" |
| ) |
| |
| print(f"Comparing finish_reason: Expected 'stop', Got '{choice.finish_reason}'") |
| assert choice.finish_reason == "stop" |
|
|
|
|
| class TestChatCompletionChunk: |
| """Tests for ChatCompletionChunk Pydantic model.""" |
| |
| def test_valid_chunk(self): |
| """ |
| What it does: Verifies creation of valid chunk. |
| Purpose: Ensure model accepts valid chunk data. |
| """ |
| print("Setup: Creating ChatCompletionChunk with valid data...") |
| chunk = ChatCompletionChunk( |
| id="chatcmpl-123", |
| model="claude-sonnet-4-5", |
| choices=[ChatCompletionChunkChoice( |
| delta=ChatCompletionChunkDelta(content="Hello") |
| )] |
| ) |
| |
| print(f"Result: {chunk}") |
| print(f"Comparing id: Expected 'chatcmpl-123', Got '{chunk.id}'") |
| assert chunk.id == "chatcmpl-123" |
| |
| print(f"Comparing object: Expected 'chat.completion.chunk', Got '{chunk.object}'") |
| assert chunk.object == "chat.completion.chunk" |
| |
| print(f"Comparing model: Expected 'claude-sonnet-4-5', Got '{chunk.model}'") |
| assert chunk.model == "claude-sonnet-4-5" |
| |
| def test_object_defaults_to_chunk(self): |
| """ |
| What it does: Verifies that object defaults to "chat.completion.chunk". |
| Purpose: Ensure default value is set correctly. |
| """ |
| print("Setup: Creating ChatCompletionChunk without explicit object...") |
| chunk = ChatCompletionChunk( |
| id="test", |
| model="test", |
| choices=[ChatCompletionChunkChoice(delta=ChatCompletionChunkDelta())] |
| ) |
| |
| print(f"Comparing object: Expected 'chat.completion.chunk', Got '{chunk.object}'") |
| assert chunk.object == "chat.completion.chunk" |
| |
| def test_usage_is_optional(self): |
| """ |
| What it does: Verifies that usage is optional. |
| Purpose: Ensure intermediate chunks work without usage. |
| """ |
| print("Setup: Creating ChatCompletionChunk without usage...") |
| chunk = ChatCompletionChunk( |
| id="test", |
| model="test", |
| choices=[ChatCompletionChunkChoice(delta=ChatCompletionChunkDelta())] |
| ) |
| |
| print(f"Comparing usage: Expected None, Got {chunk.usage}") |
| assert chunk.usage is None |
| |
| def test_accepts_usage(self): |
| """ |
| What it does: Verifies that usage is accepted. |
| Purpose: Ensure final chunk with usage works. |
| """ |
| print("Setup: Creating ChatCompletionChunk with usage...") |
| chunk = ChatCompletionChunk( |
| id="test", |
| model="test", |
| choices=[ChatCompletionChunkChoice( |
| delta=ChatCompletionChunkDelta(), |
| finish_reason="stop" |
| )], |
| usage=ChatCompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15) |
| ) |
| |
| print(f"Comparing usage: Got {chunk.usage}") |
| assert chunk.usage is not None |
| assert chunk.usage.total_tokens == 15 |
|
|