mtg_python_deckbuilder/code/tests/test_exclude_cards_compatibility.py

173 lines
5.8 KiB
Python

"""
Exclude Cards Compatibility Tests
Ensures that existing deck configurations build identically when the
include/exclude feature is not used, and that JSON import/export preserves
exclude_cards when the feature is enabled.
"""
import base64
import json
import pytest
from starlette.testclient import TestClient
@pytest.fixture
def client():
"""Test client with ALLOW_MUST_HAVES enabled."""
import importlib
import os
import sys
# Ensure project root is in sys.path for reliable imports
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
# Ensure feature flag is enabled for tests
original_value = os.environ.get('ALLOW_MUST_HAVES')
os.environ['ALLOW_MUST_HAVES'] = '1'
# Force fresh import to pick up environment change
try:
del importlib.sys.modules['code.web.app']
except KeyError:
pass
app_module = importlib.import_module('code.web.app')
client = TestClient(app_module.app)
yield client
# Restore original environment
if original_value is not None:
os.environ['ALLOW_MUST_HAVES'] = original_value
else:
os.environ.pop('ALLOW_MUST_HAVES', None)
def test_legacy_configs_build_unchanged(client):
"""Ensure existing deck configs (without exclude_cards) build identically."""
# Legacy payload without exclude_cards
legacy_payload = {
"commander": "Inti, Seneschal of the Sun",
"tags": ["discard"],
"bracket": 3,
"ideals": {
"ramp": 10, "lands": 36, "basic_lands": 18,
"creatures": 28, "removal": 10, "wipes": 3,
"card_advantage": 8, "protection": 4
},
"tag_mode": "AND",
"flags": {"owned_only": False, "prefer_owned": False},
"locks": [],
}
# Convert to permalink token
raw = json.dumps(legacy_payload, separators=(",", ":")).encode('utf-8')
token = base64.urlsafe_b64encode(raw).decode('ascii').rstrip('=')
# Import the legacy config
response = client.get(f'/build/from?state={token}')
assert response.status_code == 200
# Should work without errors and not include exclude_cards in session
# (This test verifies that the absence of exclude_cards doesn't break anything)
def test_exclude_cards_json_roundtrip(client):
"""Test that exclude_cards are preserved in JSON export/import."""
# Start a session
r = client.get('/build')
assert r.status_code == 200
# Create a config with exclude_cards via form submission
form_data = {
"name": "Test Deck",
"commander": "Inti, Seneschal of the Sun",
"primary_tag": "discard",
"bracket": 3,
"ramp": 10,
"lands": 36,
"basic_lands": 18,
"creatures": 28,
"removal": 10,
"wipes": 3,
"card_advantage": 8,
"protection": 4,
"exclude_cards": "Sol Ring\nRhystic Study\nSmothering Tithe"
}
# Submit the form to create the config
r2 = client.post('/build/new', data=form_data)
assert r2.status_code == 200
# Get the session cookie for the next request
session_cookie = r2.cookies.get('sid')
assert session_cookie is not None, "Session cookie not found"
# Export permalink with exclude_cards
if session_cookie:
client.cookies.set('sid', session_cookie)
r3 = client.get('/build/permalink')
assert r3.status_code == 200
permalink_data = r3.json()
assert permalink_data["ok"] is True
assert "exclude_cards" in permalink_data["state"]
exported_excludes = permalink_data["state"]["exclude_cards"]
assert "Sol Ring" in exported_excludes
assert "Rhystic Study" in exported_excludes
assert "Smothering Tithe" in exported_excludes
# Test round-trip: import the exported config
token = permalink_data["permalink"].split("state=")[1]
r4 = client.get(f'/build/from?state={token}')
assert r4.status_code == 200
# Get new permalink to verify the exclude_cards were preserved
# (We need to get the session cookie from the import response)
import_cookie = r4.cookies.get('sid')
assert import_cookie is not None, "Import session cookie not found"
if import_cookie:
client.cookies.set('sid', import_cookie)
r5 = client.get('/build/permalink')
assert r5.status_code == 200
reimported_data = r5.json()
assert reimported_data["ok"] is True
assert "exclude_cards" in reimported_data["state"]
# Should be identical to the original export
reimported_excludes = reimported_data["state"]["exclude_cards"]
assert reimported_excludes == exported_excludes
def test_validation_endpoint_functionality(client):
"""Test the exclude cards validation endpoint."""
# Test empty input
r1 = client.post('/build/validate/exclude_cards', data={'exclude_cards': ''})
assert r1.status_code == 200
data1 = r1.json()
assert data1["count"] == 0
# Test valid input
exclude_text = "Sol Ring\nRhystic Study\nSmothering Tithe"
r2 = client.post('/build/validate/exclude_cards', data={'exclude_cards': exclude_text})
assert r2.status_code == 200
data2 = r2.json()
assert data2["count"] == 3
assert data2["limit"] == 15
assert data2["over_limit"] is False
assert len(data2["cards"]) == 3
# Test over-limit input (16 cards when limit is 15)
many_cards = "\n".join([f"Card {i}" for i in range(16)])
r3 = client.post('/build/validate/exclude_cards', data={'exclude_cards': many_cards})
assert r3.status_code == 200
data3 = r3.json()
assert data3["count"] == 16
assert data3["over_limit"] is True
assert len(data3["warnings"]) > 0
assert "Too many excludes" in data3["warnings"][0]