Pause feature work to install a test harness and harden the backend before resuming Lesson 12. This guide replaces “go straight to converter resolver UI” with a 6-phase pass that pays for itself across every subsequent feature.
schema.execute(), typed-error Unions, N+1 guardsauto mode with session-scoped event loopjoin_transaction_mode="create_savepoint" for per-test rollback isolationschema.execute() for ~80% of resolver tests; httpx.AsyncClient + ASGITransport for the wiring layerMaskErrors extension for unexpected onesmtg_test to existing Compose PostgresThese are confirmed in the codebase. Don’t fix them out of order — write the failing test first, then fix.
_price_cents uses round() (banker’s rounding): round(0.5) == 0, so "0.005" → 0 cents instead of 1_card_to_row uses round(cmc): cards with cmc=0.5 (Little Girl) silently become cmc=0on_conflict_do_update set_ dict omits updated_at — re-syncs never refresh the timestamphttpx.AsyncClient has no retry transport, no User-Agent headerCollectionItem has no UNIQUE on (scryfall_id, foil, condition, language) — concurrent adds create duplicatesadd_to_collection has no FK existence check before insert8–14 working days end-to-end, heavily front-loaded on Phase A. The harness is the single most leveraged piece of work — Phases B–F are mostly mechanical once it’s right.