- migrations 012 + 013: cauldron_meal_plan_slots + cauldron_pick_points - db: list_plan_slots, save_plan_slots, delete_plan_slots, mark_plan_generated, clear_plan_generated, award_pick_points, enrich_plan_with_slots; scoreboard extended with points (sum from pick_points) and weeks_locked alias - forge.generate_plan: sonnet prompt builds 7-day plan respecting picks, validates slot count + day uniqueness + slug-in-pool, fills picker_subs from ground-truth picks (model output is advisory) - POST /api/plan/generate: race-safe (existing slots → 409 with plan), lock-aware (locked → 409), idempotent - POST /api/plan/regenerate: re-roll for the original generator, gated by ownership + lock; wipes slots + pick_points then re-runs generate - plan.html: generate CTA + 7 day cards with picker chips + AI reason + re-roll button (generator-only, pre-lock); scoreboard now shows points + wins - /list: pulls plan slots, queries Mealie for ingredients, runs aggregator, renders 48px-tall checkbox shopping list with localStorage state per plan_id - tests: 13 new tests across forge.generate_plan + /api/plan/generate routes + /list view + scoreboard SQL inspection. conftest+_testenv stub pymysql/oidc/foods at import time so tests run against module-level app without a live DB. Both pytest and `unittest discover` paths green (27/27). Defers: bulk sterilizer admin (A1), foods dedupe (A2), Mealie shopping-list- export (button rendered but disabled). 7-slot count is fixed at the endpoint (no UI for slot-count selection yet). Spec: memory/spec-cauldron-v0.3.md
85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
"""Test environment bootstrap.
|
|
|
|
Cauldron's server module instantiates a DB + OAuth client + Mealie client at
|
|
import time, so tests must stub those before importing cauldron.server.
|
|
|
|
This module performs that stubbing exactly once (idempotent — safe to import
|
|
multiple times). Both pytest's conftest.py and direct `unittest discover`
|
|
runs route through here.
|
|
"""
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from cryptography.fernet import Fernet
|
|
|
|
|
|
_BOOTSTRAPPED = False
|
|
|
|
|
|
def _stub_env() -> None:
|
|
os.environ.setdefault("SECRET_KEY", "test-secret")
|
|
os.environ.setdefault("MEALIE_BASE_URL", "http://mealie.test")
|
|
os.environ.setdefault("MEALIE_API_TOKEN", "mealie-test-token")
|
|
os.environ.setdefault("CLAWDFORGE_URL", "http://forge.test")
|
|
os.environ.setdefault("CLAWDFORGE_TOKEN", "forge-test-token")
|
|
os.environ.setdefault("ADMIN_BEARER", "admin-test-bearer")
|
|
os.environ.setdefault("OIDC_ISSUER", "http://authentik.test/application/o/cauldron")
|
|
os.environ.setdefault("OIDC_CLIENT_ID", "test-client")
|
|
os.environ.setdefault("OIDC_CLIENT_SECRET", "test-secret-client")
|
|
os.environ.setdefault("OIDC_REDIRECT_URI", "http://localhost/auth/callback")
|
|
os.environ.setdefault("DB_HOST", "localhost")
|
|
os.environ.setdefault("DB_NAME", "cauldron_test")
|
|
os.environ.setdefault("DB_USER", "cauldron")
|
|
os.environ.setdefault("DB_PASSWORD", "test")
|
|
os.environ.setdefault("CAULDRON_FERNET_KEY", Fernet.generate_key().decode())
|
|
|
|
|
|
class _FakeCursor:
|
|
def __init__(self):
|
|
self.lastrowid = 0
|
|
self.rowcount = 0
|
|
def execute(self, *a, **kw): pass
|
|
def fetchone(self): return None
|
|
def fetchall(self): return []
|
|
def __enter__(self): return self
|
|
def __exit__(self, *a): return False
|
|
|
|
|
|
class _FakeConn:
|
|
def __init__(self, *a, **kw): pass
|
|
def cursor(self): return _FakeCursor()
|
|
def commit(self): pass
|
|
def rollback(self): pass
|
|
def close(self): pass
|
|
|
|
|
|
def bootstrap() -> None:
|
|
"""Apply env + import-time monkey patches. Idempotent."""
|
|
global _BOOTSTRAPPED
|
|
if _BOOTSTRAPPED:
|
|
return
|
|
_stub_env()
|
|
|
|
# pymysql.connect → no-op fake
|
|
patch("pymysql.connect", _FakeConn).start()
|
|
|
|
# Pre-import dotted modules so the next patches resolve
|
|
import cauldron.db # noqa: F401
|
|
import cauldron.oidc # noqa: F401
|
|
import cauldron.foods # noqa: F401
|
|
|
|
# Skip migrations
|
|
patch("cauldron.db.DB.migrate", lambda self: []).start()
|
|
|
|
# Skip OIDC metadata fetch
|
|
oauth_obj = MagicMock()
|
|
oauth_obj.cauldron = MagicMock()
|
|
patch("cauldron.oidc.init_oauth", lambda *a, **kw: oauth_obj).start()
|
|
|
|
# Skip foods seed loader
|
|
patch("cauldron.foods.load_seed_if_empty", lambda db: 0).start()
|
|
|
|
_BOOTSTRAPPED = True
|
|
|
|
|
|
bootstrap()
|