mtg_python_deckbuilder/code/tests/test_theme_preview_p0_new.py

75 lines
3.4 KiB
Python

import os
import time
import json
from code.web.services.theme_preview import get_theme_preview, preview_metrics, bust_preview_cache
def test_colors_filter_constraint_green_subset():
"""colors=G should only return cards whose color identities are subset of {G} or colorless ('' list)."""
payload = get_theme_preview('Blink', limit=8, colors='G') # pick any theme; data-driven
for card in payload['sample']:
if not card['colors']:
continue
assert set(card['colors']).issubset({'G'}), f"Card {card['name']} had colors {card['colors']} outside filter"
def test_synthetic_placeholder_fill_present_when_short():
# Force scarcity via impossible color filter letter ensuring empty real pool -> synthetic placeholders
payload = get_theme_preview('Blink', limit=50, colors='Z')
# All real cards filtered out; placeholders must appear
synthetic_roles = [c for c in payload['sample'] if 'synthetic' in (c.get('roles') or [])]
assert synthetic_roles, 'Expected at least one synthetic placeholder entry under restrictive color filter'
assert any('synthetic_synergy_placeholder' in (c.get('reasons') or []) for c in synthetic_roles), 'Missing synthetic placeholder reason'
def test_cache_hit_timing_and_log(monkeypatch, capsys):
os.environ['WEB_THEME_PREVIEW_LOG'] = '1'
# Force fresh build
bust_preview_cache()
payload1 = get_theme_preview('Blink', limit=6)
assert payload1['cache_hit'] is False
# Second call should hit cache
payload2 = get_theme_preview('Blink', limit=6)
assert payload2['cache_hit'] is True
captured = capsys.readouterr().out.splitlines()
assert any('theme_preview_build' in line for line in captured), 'Missing build log'
assert any('theme_preview_cache_hit' in line for line in captured), 'Missing cache hit log'
def test_per_theme_percentiles_and_raw_counts():
bust_preview_cache()
for _ in range(5):
get_theme_preview('Blink', limit=6)
metrics = preview_metrics()
per = metrics['per_theme']
assert 'blink' in per, 'Expected theme slug in per_theme metrics'
blink_stats = per['blink']
assert 'p50_ms' in blink_stats and 'p95_ms' in blink_stats, 'Missing percentile metrics'
assert 'curated_total' in blink_stats and 'sampled_total' in blink_stats, 'Missing raw curated/sample per-theme totals'
def test_structured_log_contains_new_fields(capsys):
os.environ['WEB_THEME_PREVIEW_LOG'] = '1'
bust_preview_cache()
get_theme_preview('Blink', limit=5)
out_lines = capsys.readouterr().out.splitlines()
build_lines = [line for line in out_lines if 'theme_preview_build' in line]
assert build_lines, 'No build log lines found'
parsed = [json.loads(line) for line in build_lines]
obj = parsed[-1]
assert 'curated_total' in obj and 'sampled_total' in obj and 'role_counts' in obj, 'Missing expected structured log fields'
def test_warm_index_latency_reduction():
bust_preview_cache()
t0 = time.time()
get_theme_preview('Blink', limit=6)
cold = time.time() - t0
t1 = time.time()
get_theme_preview('Blink', limit=6)
warm = time.time() - t1
# Warm path should generally be faster; allow flakiness with generous factor
# If cold time is extremely small (timer resolution), skip strict assertion
if cold < 0.0005: # <0.5ms treat as indistinguishable; skip to avoid flaky failure
return
assert warm <= cold * 1.2, f"Expected warm path faster or near equal (cold={cold}, warm={warm})"