web: DRY Step 5 and alternatives (partial+macro), centralize start_ctx/owned_set, adopt builder_*

This commit is contained in:
mwisnowski 2025-09-02 11:39:14 -07:00
parent fe9aabbce9
commit 014bcc37b7
24 changed files with 1200 additions and 766 deletions

View file

@ -0,0 +1,60 @@
from __future__ import annotations
from code.web.services.build_utils import start_ctx_from_session, owned_set, owned_names
def _fake_session(**kw):
# Provide minimal session keys used by start_ctx_from_session
base = {
"commander": "Cmdr",
"tags": ["Aggro", "Spells"],
"bracket": 3,
"ideals": {"creatures": 25},
"tag_mode": "AND",
"use_owned_only": False,
"prefer_owned": False,
"locks": [],
"custom_export_base": "TestDeck",
"multi_copy": None,
"prefer_combos": False,
"combo_target_count": 2,
"combo_balance": "mix",
}
base.update(kw)
return base
def test_owned_helpers_do_not_crash():
# These reflect over the owned store; they should be resilient
s = owned_set()
assert isinstance(s, set)
n = owned_names()
assert isinstance(n, list)
def test_start_ctx_from_session_minimal(monkeypatch):
# Avoid integration dependency by faking orchestrator.start_build_ctx
calls = {}
def _fake_start_build_ctx(**kwargs):
calls.update(kwargs)
return {"builder": object(), "stages": [], "idx": 0, "last_visible_idx": 0}
import code.web.services.build_utils as bu
monkeypatch.setattr(bu.orch, "start_build_ctx", _fake_start_build_ctx)
sess = _fake_session()
ctx = start_ctx_from_session(sess, set_on_session=False)
assert isinstance(ctx, dict)
assert "builder" in ctx
assert "stages" in ctx
assert "idx" in ctx
def test_start_ctx_from_session_sets_on_session(monkeypatch):
def _fake_start_build_ctx(**kwargs):
return {"builder": object(), "stages": [], "idx": 0}
import code.web.services.build_utils as bu
monkeypatch.setattr(bu.orch, "start_build_ctx", _fake_start_build_ctx)
sess = _fake_session()
ctx = start_ctx_from_session(sess, set_on_session=True)
assert sess.get("build_ctx") == ctx

View file

@ -0,0 +1,19 @@
from __future__ import annotations
from code.web.services.orchestrator import is_setup_ready, is_setup_stale
def test_is_setup_ready_false_when_missing():
# On a clean checkout without csv_files, this should be False
assert is_setup_ready() in (False, True) # Function exists and returns a bool
def test_is_setup_stale_never_when_disabled_env(monkeypatch):
monkeypatch.setenv("WEB_AUTO_REFRESH_DAYS", "0")
assert is_setup_stale() is False
def test_is_setup_stale_is_bool():
# We don't assert specific timing behavior in unit tests; just type/robustness
res = is_setup_stale()
assert res in (False, True)

View file

@ -0,0 +1,76 @@
from __future__ import annotations
from types import SimpleNamespace
from code.web.services.build_utils import step5_error_ctx
class _Req(SimpleNamespace):
# minimal object to satisfy template context needs
pass
def test_step5_error_ctx_shape():
req = _Req()
sess = {
"commander": "Atraxa, Praetors' Voice",
"tags": ["+1/+1 Counters"],
"bracket": 3,
"ideals": {"lands": 36},
"use_owned_only": False,
"prefer_owned": False,
"replace_mode": True,
"locks": ["sol ring"],
}
ctx = step5_error_ctx(req, sess, "Boom")
# Ensure required keys for _step5.html are present with safe defaults
for k in (
"request",
"commander",
"tags",
"bracket",
"values",
"owned_only",
"prefer_owned",
"owned_set",
"game_changers",
"replace_mode",
"prefer_combos",
"combo_target_count",
"combo_balance",
"status",
"stage_label",
"log",
"added_cards",
"i",
"n",
"csv_path",
"txt_path",
"summary",
"show_skipped",
"total_cards",
"added_total",
"skipped",
):
assert k in ctx
assert ctx["status"] == "Error"
assert isinstance(ctx["added_cards"], list)
assert ctx["show_skipped"] is False
def test_step5_error_ctx_respects_flags():
req = _Req()
sess = {
"use_owned_only": True,
"prefer_owned": True,
"combo_target_count": 3,
"combo_balance": "early",
}
ctx = step5_error_ctx(req, sess, "Oops", include_name=False, include_locks=False)
assert "name" not in ctx
assert "locks" not in ctx
# Flags should flow through
assert ctx["owned_only"] is True
assert ctx["prefer_owned"] is True
assert ctx["combo_target_count"] == 3
assert ctx["combo_balance"] == "early"

View file

@ -0,0 +1,31 @@
from __future__ import annotations
from code.web.services.summary_utils import summary_ctx
def test_summary_ctx_empty_summary():
ctx = summary_ctx(summary=None, commander="Test Commander", tags=["Aggro"])
assert isinstance(ctx, dict)
assert ctx.get("owned_set") is not None
assert isinstance(ctx.get("combos"), list)
assert isinstance(ctx.get("synergies"), list)
assert ctx.get("versions") == {}
assert ctx.get("commander") == "Test Commander"
assert ctx.get("tags") == ["Aggro"]
def test_summary_ctx_with_summary_basic():
# Minimal fake summary structure sufficient for detect_for_summary to accept
summary = {
"type_breakdown": {"counts": {}, "order": [], "cards": {}, "total": 0},
"pip_distribution": {"counts": {}, "weights": {}},
"mana_generation": {},
"mana_curve": {"total_spells": 0},
"colors": [],
}
ctx = summary_ctx(summary=summary, commander="Cmdr", tags=["Spells"])
assert "owned_set" in ctx and isinstance(ctx["owned_set"], set)
assert "game_changers" in ctx
assert "combos" in ctx and isinstance(ctx["combos"], list)
assert "synergies" in ctx and isinstance(ctx["synergies"], list)
assert "versions" in ctx and isinstance(ctx["versions"], dict)