Release 2026-06-15/16: B2 billing-orphan fix + demo conversion (B1/B3/B4/B5) + conversation history (72h) + per-tenant teach-the-bot + onboarding videos
4778987 verified | """Shared test fixtures. | |
| Unit tests run against an in-memory sqlite DB (shared across sessions via | |
| StaticPool) so the suite needs no external services. The Postgres/pgvector | |
| path is exercised by guarded integration tests (``TEST_DATABASE_URL``). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import pytest | |
| import pytest_asyncio | |
| from sqlalchemy.pool import StaticPool | |
| # Ensure env is sane before any app import reads Settings. | |
| os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite://") | |
| os.environ.setdefault("TESTING", "1") | |
| os.environ.setdefault("ADMIN_TOKEN", "test-admin-token") | |
| os.environ.setdefault("SHOPIFY_APP_PROXY_SECRET", "test-proxy-secret") | |
| from app import db # noqa: E402 | |
| async def db_session(): | |
| """A fresh in-memory sqlite DB with all tables created, per test.""" | |
| db.init_engine( | |
| "sqlite+aiosqlite://", | |
| connect_args={"check_same_thread": False}, | |
| poolclass=StaticPool, | |
| ) | |
| # Import models so they register on Base.metadata before create_all. | |
| try: | |
| import app.models # noqa: F401 | |
| except ModuleNotFoundError: | |
| pass | |
| await db.create_all() | |
| async with db.get_sessionmaker()() as session: | |
| yield session | |
| await db.get_engine().dispose() | |
| def is_postgres() -> bool: | |
| return db.is_postgres(os.environ.get("DATABASE_URL", "")) | |
| async def tenant(db_session): | |
| """A committed tenant for route tests (slug 't1').""" | |
| from app.models import Tenant | |
| t = Tenant(slug="t1", name="Tienda 1", brand_name="Asistente T1") | |
| db_session.add(t) | |
| await db_session.commit() | |
| return t | |
| async def default_tenant(db_session): | |
| """A committed 'default' tenant (used by the App Proxy fallback).""" | |
| from app.models import Tenant | |
| t = Tenant(slug="default", name="Default", brand_name="Asistente") | |
| db_session.add(t) | |
| await db_session.commit() | |
| return t | |
| def _reset_order_limiter(): | |
| """Isolate the in-process rate limiters between tests.""" | |
| from app.ratelimit import get_order_limiter | |
| from app.routes import chat | |
| get_order_limiter().clear() | |
| chat._chat_limiter.clear() | |
| yield | |
| get_order_limiter().clear() | |
| chat._chat_limiter.clear() | |
| async def app_client(db_session): | |
| """ASGI test client whose DB dependency is bound to the test session.""" | |
| import httpx | |
| from app.db import get_session | |
| from app.main import create_app | |
| app = create_app() | |
| async def _override_session(): | |
| yield db_session | |
| app.dependency_overrides[get_session] = _override_session | |
| transport = httpx.ASGITransport(app=app) | |
| async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: | |
| yield app, client | |
| app.dependency_overrides.clear() | |