Spaces:
Sleeping
Sleeping
| """ | |
| 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]}" | |
| ) | |
| 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() | |
| 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}"} | |
| 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 | |
| def test_session_id(): | |
| """Generate a unique session ID for this test run.""" | |
| return str(uuid.uuid4()) | |
| def test_arch_session_id(): | |
| """Generate a unique architecture session ID for this test run.""" | |
| return f"arch_test_{uuid.uuid4().hex[:12]}" | |
| def live_db_url(): | |
| """Return the live database URL.""" | |
| return os.getenv("DATABASE_URL", "") | |
| def pinecone_configured(): | |
| """Return True if Pinecone is configured.""" | |
| return bool(os.getenv("PINECONE_API_KEY") and os.getenv("PINECONE_INDEX")) | |
| 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")) | |