mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 08:00:13 +01:00
feat: Add include/exclude card lists feature with web UI, validation, fuzzy matching, and JSON persistence (ALLOW_MUST_HAVES=1)
This commit is contained in:
parent
7ef45252f7
commit
0516260304
39 changed files with 3672 additions and 626 deletions
144
code/tests/test_exclude_cards_performance.py
Normal file
144
code/tests/test_exclude_cards_performance.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
Exclude Cards Performance Tests
|
||||
|
||||
Ensures that exclude filtering doesn't create significant performance
|
||||
regressions and meets the specified benchmarks for parsing, filtering,
|
||||
and validation operations.
|
||||
"""
|
||||
import time
|
||||
import pytest
|
||||
from deck_builder.include_exclude_utils import parse_card_list_input
|
||||
|
||||
|
||||
def test_card_parsing_speed():
|
||||
"""Test that exclude card parsing is fast."""
|
||||
# Create a list of 15 cards (max excludes)
|
||||
exclude_cards_text = "\n".join([
|
||||
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
||||
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
||||
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor",
|
||||
"Mana Crypt", "Chrome Mox", "Mox Diamond", "Mox Opal", "Lotus Petal"
|
||||
])
|
||||
|
||||
# Time the parsing operation
|
||||
start_time = time.time()
|
||||
for _ in range(100): # Run 100 times to get a meaningful measurement
|
||||
result = parse_card_list_input(exclude_cards_text)
|
||||
end_time = time.time()
|
||||
|
||||
# Should complete 100 parses in well under 1 second
|
||||
total_time = end_time - start_time
|
||||
avg_time_per_parse = total_time / 100
|
||||
|
||||
assert len(result) == 15
|
||||
assert avg_time_per_parse < 0.01 # Less than 10ms per parse (very generous)
|
||||
print(f"Average parse time: {avg_time_per_parse*1000:.2f}ms")
|
||||
|
||||
|
||||
def test_large_cardpool_filtering_speed():
|
||||
"""Simulate exclude filtering performance on a large card pool."""
|
||||
# Create a mock dataframe-like structure to simulate filtering
|
||||
mock_card_pool_size = 20000 # Typical large card pool
|
||||
exclude_list = [
|
||||
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
||||
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
||||
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor",
|
||||
"Mana Crypt", "Chrome Mox", "Mox Diamond", "Mox Opal", "Lotus Petal"
|
||||
]
|
||||
|
||||
# Simulate the filtering operation (set-based lookup)
|
||||
exclude_set = set(exclude_list)
|
||||
|
||||
# Create mock card names
|
||||
mock_cards = [f"Card {i}" for i in range(mock_card_pool_size)]
|
||||
# Add a few cards that will be excluded
|
||||
mock_cards.extend(exclude_list)
|
||||
|
||||
# Time the filtering operation
|
||||
start_time = time.time()
|
||||
filtered_cards = [card for card in mock_cards if card not in exclude_set]
|
||||
end_time = time.time()
|
||||
|
||||
filter_time = end_time - start_time
|
||||
|
||||
# Should complete filtering in well under 50ms (our target)
|
||||
assert filter_time < 0.050 # 50ms
|
||||
print(f"Filtering {len(mock_cards)} cards took {filter_time*1000:.2f}ms")
|
||||
|
||||
# Verify filtering worked
|
||||
for excluded_card in exclude_list:
|
||||
assert excluded_card not in filtered_cards
|
||||
|
||||
|
||||
def test_validation_api_response_time():
|
||||
"""Test validation endpoint response time."""
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
# 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)
|
||||
|
||||
# Enable feature flag
|
||||
original_value = os.environ.get('ALLOW_MUST_HAVES')
|
||||
os.environ['ALLOW_MUST_HAVES'] = '1'
|
||||
|
||||
try:
|
||||
# Fresh import
|
||||
try:
|
||||
del importlib.sys.modules['code.web.app']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
app_module = importlib.import_module('code.web.app')
|
||||
client = TestClient(app_module.app)
|
||||
|
||||
# Test data
|
||||
exclude_text = "\n".join([
|
||||
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
||||
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
||||
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor"
|
||||
])
|
||||
|
||||
# Time the validation request
|
||||
start_time = time.time()
|
||||
response = client.post('/build/validate/exclude_cards',
|
||||
data={'exclude_cards': exclude_text})
|
||||
end_time = time.time()
|
||||
|
||||
response_time = end_time - start_time
|
||||
|
||||
# Should respond in under 100ms (our target)
|
||||
assert response_time < 0.100 # 100ms
|
||||
assert response.status_code == 200
|
||||
|
||||
print(f"Validation endpoint response time: {response_time*1000:.2f}ms")
|
||||
|
||||
finally:
|
||||
# Restore environment
|
||||
if original_value is not None:
|
||||
os.environ['ALLOW_MUST_HAVES'] = original_value
|
||||
else:
|
||||
os.environ.pop('ALLOW_MUST_HAVES', None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_count", [0, 5, 10, 15])
|
||||
def test_parsing_scales_with_list_size(exclude_count):
|
||||
"""Test that performance scales reasonably with number of excludes."""
|
||||
exclude_cards = [f"Exclude Card {i}" for i in range(exclude_count)]
|
||||
exclude_text = "\n".join(exclude_cards)
|
||||
|
||||
start_time = time.time()
|
||||
result = parse_card_list_input(exclude_text)
|
||||
end_time = time.time()
|
||||
|
||||
parse_time = end_time - start_time
|
||||
|
||||
# Even with maximum excludes, should be very fast
|
||||
assert parse_time < 0.005 # 5ms
|
||||
assert len(result) == exclude_count
|
||||
|
||||
print(f"Parse time for {exclude_count} excludes: {parse_time*1000:.2f}ms")
|
||||
Loading…
Add table
Add a link
Reference in a new issue