test: add claude_client tests
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Tests for claude_client.py — JSON parsing, payload building, slot parsing,
|
||||
UNOWNED marker stripping, and CardEntry construction.
|
||||
"""
|
||||
import pytest
|
||||
from app.services.ai.claude_client import (
|
||||
_parse_json,
|
||||
_parse_slot,
|
||||
_build_payload,
|
||||
CardEntry,
|
||||
CutEntry,
|
||||
DeckPayload,
|
||||
UNOWNED_RE,
|
||||
)
|
||||
from app.models.deck import CardSlot
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _parse_json
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestParseJson:
|
||||
def test_valid_json(self):
|
||||
text = '{"deck_name": "Test", "cards": []}'
|
||||
result = _parse_json(text)
|
||||
assert result["deck_name"] == "Test"
|
||||
|
||||
def test_strips_markdown_fences(self):
|
||||
text = '''```json
|
||||
{"deck_name": "Test", "cards": []}
|
||||
```'''
|
||||
result = _parse_json(text)
|
||||
assert result["deck_name"] == "Test"
|
||||
|
||||
def test_strips_plain_fences(self):
|
||||
text = '''```
|
||||
{"deck_name": "Test", "cards": []}
|
||||
```'''
|
||||
result = _parse_json(text)
|
||||
assert result["deck_name"] == "Test"
|
||||
|
||||
def test_raises_on_invalid_json(self):
|
||||
with pytest.raises(ValueError, match="not valid JSON"):
|
||||
_parse_json('{"broken: json}')
|
||||
|
||||
def test_raises_on_empty_string(self):
|
||||
with pytest.raises(ValueError):
|
||||
_parse_json("")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _parse_slot
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestParseSlot:
|
||||
def test_valid_slots(self):
|
||||
assert _parse_slot("creature") == CardSlot.CREATURE
|
||||
assert _parse_slot("instant") == CardSlot.INSTANT
|
||||
assert _parse_slot("sorcery") == CardSlot.SORCERY
|
||||
assert _parse_slot("enchantment") == CardSlot.ENCHANTMENT
|
||||
assert _parse_slot("artifact") == CardSlot.ARTIFACT
|
||||
assert _parse_slot("planeswalker") == CardSlot.PLANESWALKER
|
||||
assert _parse_slot("land") == CardSlot.LAND
|
||||
assert _parse_slot("battle") == CardSlot.BATTLE
|
||||
|
||||
def test_plural_aliases(self):
|
||||
assert _parse_slot("creatures") == CardSlot.CREATURE
|
||||
assert _parse_slot("instants") == CardSlot.INSTANT
|
||||
assert _parse_slot("sorceries") == CardSlot.SORCERY
|
||||
assert _parse_slot("lands") == CardSlot.LAND
|
||||
|
||||
def test_case_insensitive(self):
|
||||
assert _parse_slot("CREATURE") == CardSlot.CREATURE
|
||||
assert _parse_slot("Land") == CardSlot.LAND
|
||||
|
||||
def test_unknown_defaults_to_creature(self):
|
||||
assert _parse_slot("unknown_type") == CardSlot.CREATURE
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CardEntry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCardEntry:
|
||||
def test_basic_card(self):
|
||||
entry = CardEntry({"name": "Sol Ring", "slot": "artifact", "quantity": 1})
|
||||
assert entry.card_name == "Sol Ring"
|
||||
assert entry.slot == CardSlot.ARTIFACT
|
||||
assert entry.quantity == 1
|
||||
assert entry.is_owned is True
|
||||
|
||||
def test_unowned_marker_stripped(self):
|
||||
entry = CardEntry({"name": "Sol Ring [UNOWNED]", "slot": "artifact", "quantity": 1})
|
||||
assert entry.card_name == "Sol Ring"
|
||||
assert entry.is_owned is False
|
||||
|
||||
def test_unowned_marker_case_insensitive(self):
|
||||
entry = CardEntry({"name": "Sol Ring [unowned]", "slot": "artifact", "quantity": 1})
|
||||
assert entry.card_name == "Sol Ring"
|
||||
assert entry.is_owned is False
|
||||
|
||||
def test_quantity_defaults_to_1(self):
|
||||
entry = CardEntry({"name": "Forest", "slot": "land"})
|
||||
assert entry.quantity == 1
|
||||
|
||||
def test_quantity_minimum_1(self):
|
||||
entry = CardEntry({"name": "Forest", "slot": "land", "quantity": 0})
|
||||
assert entry.quantity == 1
|
||||
|
||||
def test_reasoning_stored(self):
|
||||
entry = CardEntry({"name": "Sol Ring", "slot": "artifact", "reasoning": "Best ramp"})
|
||||
assert entry.reasoning == "Best ramp"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _build_payload
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildPayload:
|
||||
def _card(self, name="Sol Ring", slot="artifact"):
|
||||
return {"name": name, "slot": slot, "quantity": 1, "reasoning": "Good card"}
|
||||
|
||||
def test_standard_cards_key(self):
|
||||
data = {
|
||||
"deck_name": "Test Deck",
|
||||
"strategy_summary": "A test deck",
|
||||
"cards": [self._card()]
|
||||
}
|
||||
payload = _build_payload(data)
|
||||
assert payload.deck_name == "Test Deck"
|
||||
assert payload.strategy_summary == "A test deck"
|
||||
assert len(payload.cards) == 1
|
||||
assert payload.cards[0].card_name == "Sol Ring"
|
||||
|
||||
def test_alternative_decklist_key(self):
|
||||
data = {
|
||||
"deck_name": "Test Deck",
|
||||
"decklist": [self._card()]
|
||||
}
|
||||
payload = _build_payload(data)
|
||||
assert len(payload.cards) == 1
|
||||
|
||||
def test_alternative_deck_key(self):
|
||||
data = {
|
||||
"deck_name": "Test Deck",
|
||||
"deck": [self._card()]
|
||||
}
|
||||
payload = _build_payload(data)
|
||||
assert len(payload.cards) == 1
|
||||
|
||||
def test_cards_key_takes_priority_over_deck(self):
|
||||
data = {
|
||||
"cards": [self._card("Sol Ring")],
|
||||
"deck": [self._card("Black Lotus")],
|
||||
}
|
||||
payload = _build_payload(data)
|
||||
assert payload.cards[0].card_name == "Sol Ring"
|
||||
|
||||
def test_default_deck_name(self):
|
||||
payload = _build_payload({})
|
||||
assert payload.deck_name == "Untitled Deck"
|
||||
|
||||
def test_skips_cards_without_name(self):
|
||||
data = {"cards": [{"slot": "artifact"}, self._card()]}
|
||||
payload = _build_payload(data)
|
||||
assert len(payload.cards) == 1
|
||||
|
||||
def test_cuts_parsed(self):
|
||||
data = {
|
||||
"cards": [],
|
||||
"cuts": [{"name": "Swamp", "reasoning": "Too slow"}]
|
||||
}
|
||||
payload = _build_payload(data)
|
||||
assert len(payload.cuts) == 1
|
||||
assert payload.cuts[0].card_name == "Swamp"
|
||||
|
||||
def test_empty_data(self):
|
||||
payload = _build_payload({})
|
||||
assert payload.deck_name == "Untitled Deck"
|
||||
assert payload.strategy_summary == ""
|
||||
assert payload.cards == []
|
||||
assert payload.cuts == []
|
||||
Reference in New Issue
Block a user