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