Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,66 @@ jobs:
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"

test-upgrade:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for git clone of tags

- name: Fetch tags
run: git fetch --tags

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
prune-cache: false

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"

- name: Cache HuggingFace models
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-

- name: Install hindsight-dev dependencies
working-directory: ./hindsight-dev
run: uv sync --frozen --extra test --index-strategy unsafe-best-match

- name: Install current hindsight-api
working-directory: ./hindsight-api
run: uv sync --frozen --index-strategy unsafe-best-match

- name: Pre-download models
working-directory: ./hindsight-api
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"

- name: Run upgrade tests
working-directory: ./hindsight-dev
run: uv run pytest upgrade_tests/ -v --tb=short

verify-generated-files:
runs-on: ubuntu-latest
env:
Expand Down
9 changes: 8 additions & 1 deletion hindsight-dev/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ dependencies = [
"pydantic>=2.0.0",
]

[project.optional-dependencies]
test = [
"pytest>=8.0.0",
"httpx>=0.27.0",
"python-dotenv>=1.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["hindsight_dev", "benchmarks"]
packages = ["hindsight_dev", "benchmarks", "upgrade_tests"]

[tool.uv.sources]
hindsight-api = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions hindsight-dev/upgrade_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Upgrade and backwards compatibility tests
110 changes: 110 additions & 0 deletions hindsight-dev/upgrade_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
Pytest configuration and fixtures for upgrade tests.
"""

import asyncio
import logging
import os
from pathlib import Path

import pytest
from dotenv import load_dotenv

# Configure logging for tests
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

# Reduce noise from httpx
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)


def pytest_configure(config):
"""Load environment variables before running tests."""
# Look for .env in the workspace root
env_file = Path(__file__).parent.parent.parent / ".env"
if env_file.exists():
load_dotenv(env_file)


_pg0_instance = None
_pg0_url = None


def _get_or_create_pg0():
"""Get or create the shared pg0 instance for upgrade tests."""
global _pg0_instance, _pg0_url
from hindsight_api.pg0 import EmbeddedPostgres

if _pg0_instance is None:
_pg0_instance = EmbeddedPostgres(name="hindsight-upgrade-test", port=5560)

loop = asyncio.new_event_loop()
try:
_pg0_url = loop.run_until_complete(_pg0_instance.ensure_running())
finally:
loop.close()

return _pg0_url


def _clean_database(db_url: str):
"""Drop all tables in the database to reset state for next test."""
from sqlalchemy import create_engine, text

engine = create_engine(db_url)
with engine.connect() as conn:
# Drop all tables in public schema (cascade to handle foreign keys)
tables = conn.execute(
text("""
SELECT tablename FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT LIKE 'pg_%'
""")
).fetchall()
for table in tables:
conn.execute(text(f'DROP TABLE IF EXISTS public."{table[0]}" CASCADE'))
conn.commit()
engine.dispose()


@pytest.fixture(scope="function")
def db_url():
"""
Provide a PostgreSQL connection URL for upgrade tests.

Uses pg0 (embedded PostgreSQL) for a clean, isolated test database.
The database is cleaned between tests to ensure fresh state for migrations.
"""
url = _get_or_create_pg0()

# Clean database before each test
_clean_database(url)

yield url

# No cleanup after - database is cleaned at start of next test


@pytest.fixture(scope="module")
def llm_config():
"""
Provide LLM configuration from environment.

Returns a dict with provider, api_key, and model.
"""
return {
"provider": os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"),
"api_key": os.getenv("HINDSIGHT_API_LLM_API_KEY") or os.getenv("GROQ_API_KEY"),
"model": os.getenv("HINDSIGHT_API_LLM_MODEL", "llama-3.3-70b-versatile"),
}


@pytest.fixture
def unique_bank_id():
"""Generate a unique bank ID for each test."""
import uuid

return f"upgrade_test_{uuid.uuid4().hex[:8]}"
Loading
Loading