Files
cf4e2981e6 fix: resolve 78 test failures via shared stub cleanup and dependent fixes (#85) (#88)
Centralize and enhance test stubs to implement the full CommanderGameState
interface (battlefield(), living_players(), stack_is_empty(), sim_state,
active_player(), priority_player(), to_dict()). Fix all dependent test
failures across 12 files:

- conftest.py: Enhanced StubCard/StubPlayer/StubGameState with full API
  compatibility; added basic_creature fixture; fixed fresh_app singleton leak
- test_phase3.py: Added battlefield(), active_player(), prompt_builder compat
- test_integration.py: Fixed ThreatAssessor import (class→function), added
  threat_fn dict wrapper, sim_state delegate
- test_phase6.py/test_phase8.py: Replaced deprecated get_event_loop() with
  asyncio.run()
- test_benchmark.py: Same async fix + ThreatAssessor functional API
- sim/test_engine.py: Removed nonexistent seed= param, use run() method
- sim/test_models.py: basic_creature fixture now in conftest
- deck_builder/test_models.py: Updated ValidationError regex match
- state_logger.py: Added monotonic counter for unique snapshot filenames
- ai_opponent.py: Moved _CANNED dict to module-level constant (dataclass fix)
- web/app.py: Mount lobby router (fixes 6 LobbyAPI test failures)
- test_phase5.py: SessionStore + api._store reset fixture

Result: 76 failures + 2 errors → 0 failures, 0 errors (331 passed, 30 skipped)

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 17:32:30 -04:00

163 lines
5.7 KiB
Python

"""Tests for the Pydantic models used by the Ollama deck builder."""
import pytest
from pydantic import ValidationError
from src.commander_ai_lab.deck_builder.core.models import (
BuildRequest,
BuildResult,
CardEntry,
Color,
CommanderDeck,
DeckRatios,
WUBRG,
)
# ── Color enum ───────────────────────────────────────────────────
class TestColor:
def test_wubrg_set_has_five_colors(self):
assert WUBRG == {"W", "U", "B", "R", "G"}
def test_enum_values(self):
assert Color.WHITE.value == "W"
assert Color.BLUE.value == "U"
assert Color.GREEN.value == "G"
# ── CardEntry ────────────────────────────────────────────────────
class TestCardEntry:
def test_minimal_card(self):
card = CardEntry(name="Sol Ring")
assert card.name == "Sol Ring"
assert card.quantity == 1
assert card.category == "uncategorized"
assert card.cmc == 0.0
def test_color_identity_from_string(self):
card = CardEntry(name="Counterspell", color_identity="UB")
assert card.color_identity == {"U", "B"}
def test_color_identity_from_list(self):
card = CardEntry(name="Swords to Plowshares", color_identity=["W"])
assert card.color_identity == {"W"}
def test_invalid_colors_stripped(self):
card = CardEntry(name="Test", color_identity=["W", "X", "Z"])
assert card.color_identity == {"W"}
def test_full_card(self):
card = CardEntry(
name="Rhystic Study",
quantity=1,
category="card_draw",
color_identity=["U"],
mana_cost="{2}{U}",
cmc=3.0,
type_line="Enchantment",
scryfall_id="abc-123",
edhrec_rank=5,
source="edhrec",
)
assert card.source == "edhrec"
assert card.edhrec_rank == 5
# ── DeckRatios ───────────────────────────────────────────────────
class TestDeckRatios:
def test_default_sums_to_99(self):
ratios = DeckRatios()
total = (
ratios.lands + ratios.ramp + ratios.card_draw
+ ratios.removal + ratios.protection + ratios.synergy
+ ratios.wincon + ratios.uncategorized
)
assert total == 99
def test_bad_total_raises(self):
with pytest.raises(ValidationError, match="less_than_equal|must sum to 99"):
DeckRatios(lands=50)
def test_out_of_range_raises(self):
with pytest.raises(ValidationError):
DeckRatios(lands=5) # ge=30
# ── CommanderDeck ────────────────────────────────────────────────
def _make_99_cards(ci=None):
"""Helper: build a valid 99-card list of basic lands."""
ci = ci or set()
cards = []
basics = ["Plains", "Island", "Swamp", "Mountain", "Forest"]
for i in range(99):
cards.append(CardEntry(
name=basics[i % 5],
quantity=1,
category="lands",
color_identity=ci,
))
return cards
class TestCommanderDeck:
def test_valid_deck(self):
cmdr = CardEntry(name="Atraxa", color_identity=["W", "U", "B", "G"])
deck = CommanderDeck(
commander=cmdr,
cards=_make_99_cards({"W", "U", "B", "G"}),
)
assert len(deck.cards) == 99
def test_wrong_card_count_raises(self):
cmdr = CardEntry(name="Atraxa", color_identity=["W", "U", "B", "G"])
with pytest.raises(ValidationError, match="exactly 99 cards"):
CommanderDeck(commander=cmdr, cards=_make_99_cards()[:50])
def test_singleton_violation_raises(self):
cmdr = CardEntry(name="Atraxa", color_identity=list(WUBRG))
cards = _make_99_cards(WUBRG)[:97]
cards.append(CardEntry(name="Sol Ring", color_identity=set()))
cards.append(CardEntry(name="Sol Ring", color_identity=set()))
with pytest.raises(ValidationError, match="Singleton violation"):
CommanderDeck(commander=cmdr, cards=cards)
def test_color_identity_violation_raises(self):
cmdr = CardEntry(name="Mono White", color_identity=["W"])
cards = _make_99_cards({"W"})[:98]
cards.append(CardEntry(name="Counterspell", color_identity=["U"]))
with pytest.raises(ValidationError, match="outside commander identity"):
CommanderDeck(commander=cmdr, cards=cards)
# ── BuildRequest / BuildResult ───────────────────────────────────
class TestBuildRequest:
def test_minimal(self):
req = BuildRequest(commander_name="Atraxa")
assert req.commander_name == "Atraxa"
assert req.collection_only is False
assert req.budget_limit is None
def test_with_strategy(self):
req = BuildRequest(
commander_name="Atraxa",
strategy_notes="superfriends planeswalker tribal",
)
assert "superfriends" in req.strategy_notes
class TestBuildResult:
def test_minimal(self):
cmdr = CardEntry(name="Atraxa", color_identity=["W", "U", "B", "G"])
deck = CommanderDeck(
commander=cmdr,
cards=_make_99_cards({"W", "U", "B", "G"}),
)
result = BuildResult(deck=deck)
assert result.build_time_seconds == 0.0
assert result.warnings == []
assert result.sources_consulted == []