martian7777
feat: implement backend core with ORM models, authentication, and AI-driven telemetry diagnostics
e5be436 | """Pytest fixtures: in-memory async SQLite DB, app override, HTTP client. | |
| Tests run fully offline — no PostgreSQL, no OpenRouter — using an in-memory | |
| SQLite database and the built-in mock AI explainer. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from collections.abc import AsyncGenerator | |
| import pytest_asyncio | |
| # Force a deterministic, secret-free test configuration before app import. | |
| os.environ.setdefault("ENVIRONMENT", "testing") | |
| os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-please-ignore") | |
| os.environ.setdefault("OPENROUTER_API_KEY", "") # ensure mock explainer | |
| os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///:memory:") | |
| from httpx import ASGITransport, AsyncClient # noqa: E402 | |
| from sqlalchemy.ext.asyncio import ( # noqa: E402 | |
| AsyncSession, | |
| async_sessionmaker, | |
| create_async_engine, | |
| ) | |
| from sqlalchemy.pool import StaticPool # noqa: E402 | |
| from app.core.database import get_db # noqa: E402 | |
| from app.main import app # noqa: E402 | |
| from app.models import Base # noqa: E402 | |
| async def engine() -> AsyncGenerator: | |
| eng = create_async_engine( | |
| "sqlite+aiosqlite:///:memory:", | |
| connect_args={"check_same_thread": False}, | |
| poolclass=StaticPool, | |
| ) | |
| async with eng.begin() as conn: | |
| await conn.run_sync(Base.metadata.create_all) | |
| yield eng | |
| await eng.dispose() | |
| async def session_factory(engine): | |
| return async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False) | |
| async def client(session_factory) -> AsyncGenerator[AsyncClient, None]: | |
| async def override_get_db() -> AsyncGenerator[AsyncSession, None]: | |
| async with session_factory() as session: | |
| try: | |
| yield session | |
| await session.commit() | |
| except Exception: | |
| await session.rollback() | |
| raise | |
| app.dependency_overrides[get_db] = override_get_db | |
| transport = ASGITransport(app=app) | |
| async with AsyncClient(transport=transport, base_url="http://test") as ac: | |
| yield ac | |
| app.dependency_overrides.clear() | |
| async def auth_client(client: AsyncClient) -> AsyncClient: | |
| """An AsyncClient with a registered+logged-in user's bearer token set.""" | |
| email = "tester@example.com" | |
| password = "supersecret123" | |
| await client.post( | |
| "/api/v1/auth/register", | |
| json={"email": email, "password": password, "full_name": "Tester"}, | |
| ) | |
| resp = await client.post( | |
| "/api/v1/auth/login", | |
| data={"username": email, "password": password}, | |
| ) | |
| token = resp.json()["access_token"] | |
| client.headers.update({"Authorization": f"Bearer {token}"}) | |
| return client | |
| async def machine_id(auth_client: AsyncClient) -> str: | |
| resp = await auth_client.post( | |
| "/api/v1/machines", | |
| json={"name": "Pump-1", "type": "pump", "location": "Plant A"}, | |
| ) | |
| return resp.json()["id"] | |