| """Tests for api/models/responses.py Pydantic response models.""" |
|
|
| from api.models.anthropic import ( |
| ContentBlockText, |
| ContentBlockThinking, |
| ContentBlockToolUse, |
| ) |
| from api.models.responses import MessagesResponse, TokenCountResponse, Usage |
|
|
|
|
| class TestUsage: |
| """Tests for Usage model.""" |
|
|
| def test_required_fields(self): |
| usage = Usage(input_tokens=10, output_tokens=20) |
| assert usage.input_tokens == 10 |
| assert usage.output_tokens == 20 |
|
|
| def test_cache_defaults_zero(self): |
| usage = Usage(input_tokens=1, output_tokens=2) |
| assert usage.cache_creation_input_tokens == 0 |
| assert usage.cache_read_input_tokens == 0 |
|
|
| def test_cache_fields_set(self): |
| usage = Usage( |
| input_tokens=10, |
| output_tokens=20, |
| cache_creation_input_tokens=5, |
| cache_read_input_tokens=3, |
| ) |
| assert usage.cache_creation_input_tokens == 5 |
| assert usage.cache_read_input_tokens == 3 |
|
|
| def test_serialization(self): |
| usage = Usage(input_tokens=10, output_tokens=20) |
| data = usage.model_dump() |
| assert data == { |
| "input_tokens": 10, |
| "output_tokens": 20, |
| "cache_creation_input_tokens": 0, |
| "cache_read_input_tokens": 0, |
| } |
|
|
|
|
| class TestTokenCountResponse: |
| """Tests for TokenCountResponse model.""" |
|
|
| def test_basic(self): |
| resp = TokenCountResponse(input_tokens=42) |
| assert resp.input_tokens == 42 |
|
|
| def test_serialization(self): |
| resp = TokenCountResponse(input_tokens=100) |
| data = resp.model_dump() |
| assert data == {"input_tokens": 100} |
|
|
|
|
| class TestMessagesResponse: |
| """Tests for MessagesResponse model.""" |
|
|
| def test_minimum_fields(self): |
| resp = MessagesResponse( |
| id="msg_001", |
| model="test-model", |
| content=[ContentBlockText(type="text", text="Hello")], |
| usage=Usage(input_tokens=10, output_tokens=5), |
| ) |
| assert resp.id == "msg_001" |
| assert resp.model == "test-model" |
| assert resp.role == "assistant" |
| assert resp.type == "message" |
| assert resp.stop_reason is None |
| assert resp.stop_sequence is None |
|
|
| def test_with_text_content(self): |
| resp = MessagesResponse( |
| id="msg_002", |
| model="model", |
| content=[ContentBlockText(type="text", text="response")], |
| usage=Usage(input_tokens=1, output_tokens=1), |
| ) |
| assert len(resp.content) == 1 |
| block = resp.content[0] |
| assert isinstance(block, ContentBlockText) |
| assert block.type == "text" |
| assert block.text == "response" |
|
|
| def test_with_tool_use_content(self): |
| resp = MessagesResponse( |
| id="msg_003", |
| model="model", |
| content=[ |
| ContentBlockToolUse( |
| type="tool_use", |
| id="tool_1", |
| name="Read", |
| input={"path": "test.py"}, |
| ) |
| ], |
| usage=Usage(input_tokens=1, output_tokens=1), |
| stop_reason="tool_use", |
| ) |
| block = resp.content[0] |
| assert isinstance(block, ContentBlockToolUse) |
| assert block.type == "tool_use" |
| assert block.name == "Read" |
| assert resp.stop_reason == "tool_use" |
|
|
| def test_with_thinking_content(self): |
| resp = MessagesResponse( |
| id="msg_004", |
| model="model", |
| content=[ |
| ContentBlockThinking(type="thinking", thinking="Let me reason..."), |
| ContentBlockText(type="text", text="Answer"), |
| ], |
| usage=Usage(input_tokens=5, output_tokens=10), |
| ) |
| assert len(resp.content) == 2 |
| block0 = resp.content[0] |
| assert isinstance(block0, ContentBlockThinking) |
| assert block0.type == "thinking" |
| assert block0.thinking == "Let me reason..." |
| block1 = resp.content[1] |
| assert isinstance(block1, ContentBlockText) |
| assert block1.type == "text" |
|
|
| def test_with_all_content_types(self): |
| resp = MessagesResponse( |
| id="msg_005", |
| model="model", |
| content=[ |
| ContentBlockThinking(type="thinking", thinking="hmm"), |
| ContentBlockText(type="text", text="result"), |
| ContentBlockToolUse( |
| type="tool_use", id="t1", name="Bash", input={"command": "ls"} |
| ), |
| ], |
| usage=Usage(input_tokens=10, output_tokens=20), |
| stop_reason="tool_use", |
| ) |
| assert len(resp.content) == 3 |
|
|
| def test_with_dict_content(self): |
| """Dict content (unknown block type) should be accepted.""" |
| resp = MessagesResponse( |
| id="msg_006", |
| model="model", |
| content=[{"type": "custom", "data": "value"}], |
| usage=Usage(input_tokens=1, output_tokens=1), |
| ) |
| block = resp.content[0] |
| assert isinstance(block, dict) |
| assert block["type"] == "custom" |
|
|
| def test_stop_reason_values(self): |
| """All valid stop_reason values should be accepted.""" |
| from typing import Literal |
|
|
| reasons: list[ |
| Literal["end_turn", "max_tokens", "stop_sequence", "tool_use"] |
| ] = [ |
| "end_turn", |
| "max_tokens", |
| "stop_sequence", |
| "tool_use", |
| ] |
| for reason in reasons: |
| resp = MessagesResponse( |
| id="msg", |
| model="model", |
| content=[ContentBlockText(type="text", text="x")], |
| usage=Usage(input_tokens=1, output_tokens=1), |
| stop_reason=reason, |
| ) |
| assert resp.stop_reason == reason |
|
|
| def test_serialization_round_trip(self): |
| resp = MessagesResponse( |
| id="msg_rt", |
| model="model-v1", |
| content=[ContentBlockText(type="text", text="hello")], |
| usage=Usage(input_tokens=10, output_tokens=5), |
| stop_reason="end_turn", |
| ) |
| data = resp.model_dump() |
| restored = MessagesResponse(**data) |
| assert restored.id == resp.id |
| assert restored.model == resp.model |
| assert restored.stop_reason == resp.stop_reason |
|
|
| def test_empty_content_list(self): |
| resp = MessagesResponse( |
| id="msg_empty", |
| model="model", |
| content=[], |
| usage=Usage(input_tokens=0, output_tokens=0), |
| ) |
| assert resp.content == [] |
|
|