""" Pytest configuration for integration tests. These tests hit REAL services with LIVE API keys, and only run when RUN_LIVE_INTEGRATION=1. Without it the whole directory is skipped. That gate is the point. Previously these ran by default against a stubbed NVIDIA_API_KEY, every call 401'd, and each test caught its own failure with `pytest.skip("LLM may be unavailable")` - so a suite that never once reached a third-party service reported green. A test that cannot fail is not a test. Run them with: RUN_LIVE_INTEGRATION=1 pytest tests/integration -v """ import os import uuid import pytest from fastapi.testclient import TestClient # Ensure project root is on path import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from app.main import app from tests.conftest import LIVE_INTEGRATION _THIS_DIR = Path(__file__).parent def pytest_collection_modifyitems(config, items): """Skip this directory unless live mode is explicitly requested. The hook receives every collected item, not only this package's, so items are filtered by path - marking the whole list skips the unit suite too. """ if LIVE_INTEGRATION: return skip = pytest.mark.skip( reason="live integration; set RUN_LIVE_INTEGRATION=1 to run against real services" ) for item in items: if _THIS_DIR in Path(str(item.fspath)).parents: item.add_marker(skip) def _require_env(*vars: str) -> None: """Fail if a required credential is missing. Fails rather than skips: reaching here means live mode was asked for, so an absent credential is a misconfiguration to fix, not a reason to go quiet. """ missing = [v for v in vars if not os.getenv(v)] if missing: pytest.fail(f"Missing required env vars for live run: {', '.join(missing)}") def require_ok(response, what: str) -> None: """Assert a live call succeeded, surfacing the body when it did not. Replaces the `if failed: pytest.skip(...)` pattern. If a third-party call is broken, a pre-deployment run must say so. """ assert response.status_code == 200, ( f"{what} failed with HTTP {response.status_code}: {response.text[:500]}" ) assert "chat_error" not in response.text, ( f"{what} returned an error event: {response.text[:500]}" ) @pytest.fixture(scope="session") def seed_test_user(): """Seed a test user with id=1 so FK constraints in integration tests work.""" from app.core.database import SessionLocal from app.core.models import User from sqlalchemy import text db = SessionLocal() try: existing = db.query(User).filter_by(id=1).first() if not existing: user = User( id=1, google_id="integration_test_user", email="integration@example.com", full_name="Integration Test User", persona="founder", role="user", ) db.add(user) db.commit() # Inserting an explicit id does NOT advance the Postgres sequence, so # the next natural insert asks for id=1 and collides with this row. On a # freshly migrated database that means the first real Google sign-up # fails with a duplicate key error. Resync so a live-DB run leaves the # sequence usable. if db.bind.dialect.name == "postgresql": db.execute( text( "SELECT setval(pg_get_serial_sequence('users', 'id'), " "COALESCE((SELECT MAX(id) FROM users), 1))" ) ) db.commit() finally: db.close() @pytest.fixture(scope="session") def auth_headers(seed_test_user): """Return auth headers with a valid JWT for the seeded test user.""" from datetime import datetime, timedelta, timezone from jose import jwt from app.core.config import settings from app.core.database import SessionLocal from app.core.models import User # Use the email of whichever user has id=1 (existing or freshly seeded) db = SessionLocal() try: user = db.query(User).filter(User.id == 1).first() user_email = user.email if user else "integration@example.com" finally: db.close() payload = { "sub": user_email, "exp": datetime.now(timezone.utc) + timedelta(hours=1), } token = jwt.encode(payload, settings.secret_key, algorithm=settings.algorithm) return {"Authorization": f"Bearer {token}"} @pytest.fixture(scope="session") def live_client(seed_test_user): """TestClient connected to the live FastAPI app (real DB, real services).""" _require_env("NVIDIA_API_KEY", "DATABASE_URL", "SECRET_KEY") with TestClient(app) as client: yield client @pytest.fixture(scope="session") def test_session_id(): """Generate a unique session ID for this test run.""" return str(uuid.uuid4()) @pytest.fixture(scope="session") def test_arch_session_id(): """Generate a unique architecture session ID for this test run.""" return f"arch_test_{uuid.uuid4().hex[:12]}" @pytest.fixture(scope="session") def live_db_url(): """Return the live database URL.""" return os.getenv("DATABASE_URL", "") @pytest.fixture(scope="session") def pinecone_configured(): """Return True if Pinecone is configured.""" return bool(os.getenv("PINECONE_API_KEY") and os.getenv("PINECONE_INDEX")) @pytest.fixture(scope="session") def redis_configured(): """Return True if Upstash Redis is configured.""" return bool(os.getenv("UPSTASH_REDIS_REST_URL") and os.getenv("UPSTASH_REDIS_REST_TOKEN"))