Files
Commander-Deck-App-backup/backend/tests/test_claude_client.py
T
2026-06-22 22:14:05 -06:00

183 lines
6.2 KiB
Python

"""
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 == []