mtg_python_deckbuilder/code/tests/test_partner_selection.py

324 lines
No EOL
11 KiB
Python

from __future__ import annotations
import logging
from types import SimpleNamespace
import pandas as pd
import pytest
from deck_builder.combined_commander import PartnerMode
from deck_builder.partner_selection import apply_partner_inputs
from exceptions import CommanderPartnerError
class _StubBuilder:
def __init__(self, dataframe: pd.DataFrame) -> None:
self._df = dataframe
def load_commander_data(self) -> pd.DataFrame:
return self._df.copy(deep=True)
@pytest.fixture()
def builder() -> _StubBuilder:
data = [
{
"name": "Halana, Kessig Ranger",
"faceName": "Halana, Kessig Ranger",
"colorIdentity": ["G"],
"themeTags": ["Aggro"],
"text": "Reach\nPartner (You can have two commanders if both have partner.)",
"type": "Legendary Creature — Human Archer",
},
{
"name": "Alena, Kessig Trapper",
"faceName": "Alena, Kessig Trapper",
"colorIdentity": ["R"],
"themeTags": ["Aggro"],
"text": "First strike\nPartner",
"type": "Legendary Creature — Human Scout",
},
{
"name": "Lae'zel, Vlaakith's Champion",
"faceName": "Lae'zel, Vlaakith's Champion",
"colorIdentity": ["W"],
"themeTags": ["Counters"],
"text": "If you would put one or more counters on a creature... Choose a Background (You can have a Background as a second commander.)",
"type": "Legendary Creature — Gith Warrior",
},
{
"name": "Commander A",
"faceName": "Commander A",
"colorIdentity": ["W"],
"themeTags": ["Value"],
"text": "Partner with Commander B (When this creature enters the battlefield, target player may put Commander B into their hand from their library, then shuffle.)",
"type": "Legendary Creature — Advisor",
},
{
"name": "Commander B",
"faceName": "Commander B",
"colorIdentity": ["B"],
"themeTags": ["Graveyard"],
"text": "Partner with Commander A",
"type": "Legendary Creature — Advisor",
},
{
"name": "The Tenth Doctor",
"faceName": "The Tenth Doctor",
"colorIdentity": ["U", "R"],
"themeTags": ["Time", "Doctor"],
"text": "Whenever you cast a spell with cascade, put a time counter on target permanent",
"type": "Legendary Creature — Time Lord Doctor",
},
{
"name": "Donna Noble",
"faceName": "Donna Noble",
"colorIdentity": ["W"],
"themeTags": ["Support"],
"text": "Vigilance\nDoctor's companion (You can have two commanders if the other is a Doctor.)",
"type": "Legendary Creature — Human Advisor",
},
{
"name": "Amy Pond",
"faceName": "Amy Pond",
"colorIdentity": ["R"],
"themeTags": ["Aggro", "Doctor's Companion", "Partner With"],
"text": (
"Partner with Rory Williams\\nWhenever Amy Pond deals combat damage to a player, "
"choose a suspended card you own and remove that many time counters from it.\\n"
"Doctor's companion (You can have two commanders if the other is the Doctor.)"
),
"type": "Legendary Creature — Human",
},
{
"name": "Rory Williams",
"faceName": "Rory Williams",
"colorIdentity": ["W", "U"],
"themeTags": ["Human", "Doctor's Companion", "Partner With"],
"text": (
"Partner with Amy Pond\\nFirst strike, lifelink\\n"
"Doctor's companion (You can have two commanders if the other is a Doctor.)"
),
"type": "Legendary Creature — Human Soldier",
},
]
df = pd.DataFrame(data)
return _StubBuilder(df)
def _background_catalog() -> SimpleNamespace:
card = SimpleNamespace(
name="Scion of Halaster",
display_name="Scion of Halaster",
color_identity=("B",),
themes=("Backgrounds Matter",),
theme_tags=("Backgrounds Matter",),
oracle_text="Commander creatures you own have menace.",
type_line="Legendary Enchantment — Background",
is_background=True,
)
class _Catalog:
def __init__(self, entry: SimpleNamespace) -> None:
self._entry = entry
self.entries = (entry,)
def get(self, name: str) -> SimpleNamespace | None:
lowered = name.strip().casefold()
if lowered in {
self._entry.name.casefold(),
self._entry.display_name.casefold(),
}:
return self._entry
return None
return _Catalog(card)
def test_feature_disabled_returns_none(builder: _StubBuilder) -> None:
result = apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
secondary_name="Alena, Kessig Trapper",
feature_enabled=False,
background_catalog=_background_catalog(),
)
assert result is None
def test_conflicting_inputs_raise_error(builder: _StubBuilder) -> None:
with pytest.raises(CommanderPartnerError):
apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
secondary_name="Alena, Kessig Trapper",
background_name="Scion of Halaster",
feature_enabled=True,
background_catalog=_background_catalog(),
)
def test_background_requires_primary_support(builder: _StubBuilder) -> None:
with pytest.raises(CommanderPartnerError):
apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
background_name="Scion of Halaster",
feature_enabled=True,
background_catalog=_background_catalog(),
)
def test_background_success(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Lae'zel, Vlaakith's Champion",
background_name="Scion of Halaster",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.BACKGROUND
assert combined.secondary_name == "Scion of Halaster"
assert combined.color_identity == ("W", "B")
def test_partner_with_detection(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Commander A",
secondary_name="Commander B",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.PARTNER_WITH
assert combined.color_identity == ("W", "B")
def test_partner_detection(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
secondary_name="Alena, Kessig Trapper",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.PARTNER
assert combined.color_identity == ("R", "G")
def test_doctor_companion_pairing(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="The Tenth Doctor",
secondary_name="Donna Noble",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.DOCTOR_COMPANION
assert combined.secondary_name == "Donna Noble"
assert combined.color_identity == ("W", "U", "R")
def test_doctor_requires_companion(builder: _StubBuilder) -> None:
with pytest.raises(CommanderPartnerError):
apply_partner_inputs(
builder,
primary_name="The Tenth Doctor",
secondary_name="Halana, Kessig Ranger",
feature_enabled=True,
background_catalog=_background_catalog(),
)
def test_companion_requires_doctor(builder: _StubBuilder) -> None:
with pytest.raises(CommanderPartnerError):
apply_partner_inputs(
builder,
primary_name="Donna Noble",
secondary_name="Commander A",
feature_enabled=True,
background_catalog=_background_catalog(),
)
def test_amy_prefers_partner_with_when_rory_selected(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Amy Pond",
secondary_name="Rory Williams",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.PARTNER_WITH
def test_amy_can_pair_with_the_doctor(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Amy Pond",
secondary_name="The Tenth Doctor",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.DOCTOR_COMPANION
def test_rory_can_partner_with_amy(builder: _StubBuilder) -> None:
combined = apply_partner_inputs(
builder,
primary_name="Rory Williams",
secondary_name="Amy Pond",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
assert combined.partner_mode is PartnerMode.PARTNER_WITH
def test_logging_emits_partner_mode_selected(caplog: pytest.LogCaptureFixture, builder: _StubBuilder) -> None:
with caplog.at_level(logging.INFO):
combined = apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
secondary_name="Alena, Kessig Trapper",
feature_enabled=True,
background_catalog=_background_catalog(),
)
assert combined is not None
records = [record for record in caplog.records if getattr(record, "event", "") == "partner_mode_selected"]
assert records, "Expected partner_mode_selected log event"
payload = getattr(records[-1], "payload", {})
assert payload.get("mode") == PartnerMode.PARTNER.value
assert payload.get("commanders", {}).get("primary") == "Halana, Kessig Ranger"
assert payload.get("commanders", {}).get("secondary") == "Alena, Kessig Trapper"
assert payload.get("colors_before") == ["G"]
assert payload.get("colors_after") == ["R", "G"]
assert payload.get("color_delta", {}).get("added") == ["R"]
def test_logging_includes_selection_source(caplog: pytest.LogCaptureFixture, builder: _StubBuilder) -> None:
with caplog.at_level(logging.INFO):
combined = apply_partner_inputs(
builder,
primary_name="Halana, Kessig Ranger",
secondary_name="Alena, Kessig Trapper",
feature_enabled=True,
background_catalog=_background_catalog(),
selection_source="suggestion",
)
assert combined is not None
records = [record for record in caplog.records if getattr(record, "event", "") == "partner_mode_selected"]
assert records, "Expected partner_mode_selected log event"
payload = getattr(records[-1], "payload", {})
assert payload.get("selection_source") == "suggestion"