import pytest from random import Random from draft import backbone_deck, draft_anchor_indexes, draft_deck, draft_deck_from_packs, draft_order, validated_pack_choice class DraftClient: # Initialize call tracking for draft generation. def __init__(self) -> None: self.calls = 0 self.deck_sizes: list[int] = [] # Return one legal earth card and record deck-aware call order. def create_card(self, payload: dict[str, object]) -> dict[str, object]: self.calls += 1 self.deck_sizes.append(len(payload["current_deck"])) # type: ignore[arg-type] return {"name": f"Ward {self.calls}", "effects": [{"primitive_id": "ward"}]} class DraftPackClient: # Initialize call tracking for pack generation. def __init__(self) -> None: self.calls = 0 self.deck_sizes: list[int] = [] self.anchor_sizes: list[int] = [] # Return one legal earth pack and record deck-aware call order. def create_pack(self, payload: dict[str, object]) -> dict[str, object]: self.calls += 1 self.deck_sizes.append(len(payload["current_deck"])) # type: ignore[arg-type] self.anchor_sizes.append(len(payload["draft_anchors"])) # type: ignore[arg-type] return { "cards": [ {"name": f"Ward {self.calls}A", "effects": [{"primitive_id": "ward"}]}, {"name": f"Ward {self.calls}B", "effects": [{"primitive_id": "block"}]}, {"name": f"Ward {self.calls}C", "effects": [{"primitive_id": "conditional"}]}, ] } # Verify backbone decks are six fixed text cards. def test_backbone_deck_has_standard_cards() -> None: deck = backbone_deck("ice", "cyberpunk") assert len(deck) == 6 assert deck[0].name == "Attack (weak)" assert deck[0].school == "ice" assert deck[5].rules_text() == "Draw 1 card." # Verify synergy drafting makes nine deck-aware model calls. def test_draft_deck_adds_nine_synergy_cards() -> None: client = DraftClient() deck = draft_deck(client, "earth", "dark fantasy", (1, 2, 3, 4, 5, 1, 2, 3, 4)) assert len(deck) == 15 assert client.deck_sizes == [6, 7, 8, 9, 10, 11, 12, 13, 14] assert deck[-1].name == "Ward 9" # Verify pack drafting adds the selected candidate from each pack. def test_draft_deck_from_packs_adds_selected_cards() -> None: client = DraftPackClient() choices: list[int] = [] # Always choose the second card in the pack. def choose_index(current_deck: tuple[object, ...], pack: tuple[object, ...]) -> int: assert len(pack) == 3 choices.append(len(current_deck)) return 1 deck = draft_deck_from_packs(client, "earth", "dark fantasy", (1, 2, 3, 4, 5, 1, 2, 3, 4), choose_index, rng=Random(0)) assert len(deck) == 15 assert client.deck_sizes == [6, 7, 8, 9, 10, 11, 12, 13, 14] assert client.anchor_sizes[:2] == [0, 1] assert client.anchor_sizes[2:] == [2, 2, 2, 2, 2, 2, 2] assert choices == [6, 7, 8, 9, 10, 11, 12, 13, 14] assert deck[-1].name == "Ward 9B" # Verify anchor indexes are two random synergy positions. def test_draft_anchor_indexes() -> None: anchors = draft_anchor_indexes(9, Random(0)) assert len(anchors) == 2 assert anchors <= set(range(9)) with pytest.raises(ValueError, match="at least two"): draft_anchor_indexes(1, Random(0)) # Verify draft order drafts anchors before remaining cards. def test_draft_order_starts_with_anchors() -> None: assert draft_order(5, frozenset((3, 1))) == (1, 3, 0, 2, 4) # Verify draft rejects incomplete synergy cost schedules. def test_draft_deck_requires_nine_costs() -> None: with pytest.raises(ValueError, match="exactly nine"): draft_deck(DraftClient(), "earth", "dark fantasy", (1,)) with pytest.raises(ValueError, match="exactly nine"): draft_deck_from_packs(DraftPackClient(), "earth", "dark fantasy", (1,), lambda _, __: 0) # Verify draft pack choices must point into the pack. def test_validated_pack_choice_rejects_bad_index() -> None: pack = backbone_deck("earth", "dark fantasy")[:3] assert validated_pack_choice(2, pack) == 2 with pytest.raises(ValueError, match="outside"): validated_pack_choice(3, pack) with pytest.raises(ValueError, match="outside"): validated_pack_choice(-1, pack)