Skip to content

Commit 327aef1

Browse files
ambvclaude
andcommitted
Add pytest config and test fixtures for the backend
In-memory SQLite database, httpx AsyncClient, and factory fixtures for all core models (commits, binaries, environments, runs, benchmark results, auth tokens, admin sessions). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 663a0da commit 327aef1

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

backend/pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
testpaths = tests
3+
asyncio_mode = auto

backend/tests/__init__.py

Whitespace-only changes.

backend/tests/conftest.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
"""Shared fixtures for backend tests."""
2+
3+
import hashlib
4+
import secrets
5+
from datetime import datetime, timedelta
6+
7+
import pytest
8+
import pytest_asyncio
9+
from httpx import ASGITransport, AsyncClient
10+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
11+
12+
from app.database import get_database
13+
from app.factory import create_app
14+
from app.config import Settings
15+
from app.models import Base, AuthToken, AdminUser, AdminSession
16+
17+
18+
@pytest.fixture(scope="session")
19+
def test_settings():
20+
return Settings(
21+
database_url="sqlite+aiosqlite://",
22+
cors_origins="http://localhost:9002",
23+
admin_initial_username="test_admin",
24+
enable_health_check_db=False,
25+
log_level="WARNING",
26+
)
27+
28+
29+
@pytest_asyncio.fixture
30+
async def db_engine():
31+
engine = create_async_engine("sqlite+aiosqlite://", echo=False)
32+
async with engine.begin() as conn:
33+
await conn.run_sync(Base.metadata.create_all)
34+
yield engine
35+
async with engine.begin() as conn:
36+
await conn.run_sync(Base.metadata.drop_all)
37+
await engine.dispose()
38+
39+
40+
@pytest_asyncio.fixture
41+
async def db_session(db_engine):
42+
session_factory = async_sessionmaker(
43+
db_engine, class_=AsyncSession, expire_on_commit=False
44+
)
45+
async with session_factory() as session:
46+
yield session
47+
48+
49+
@pytest_asyncio.fixture
50+
async def app(db_engine, test_settings):
51+
session_factory = async_sessionmaker(
52+
db_engine, class_=AsyncSession, expire_on_commit=False
53+
)
54+
55+
async def _override_get_database():
56+
async with session_factory() as session:
57+
yield session
58+
59+
application = create_app(settings=test_settings)
60+
application.dependency_overrides[get_database] = _override_get_database
61+
yield application
62+
application.dependency_overrides.clear()
63+
64+
65+
@pytest_asyncio.fixture
66+
async def client(app):
67+
transport = ASGITransport(app=app)
68+
async with AsyncClient(transport=transport, base_url="http://test") as c:
69+
yield c
70+
71+
72+
@pytest_asyncio.fixture
73+
async def auth_token(db_session):
74+
"""Create an active auth token and return (token_string, token_model)."""
75+
raw_token = secrets.token_hex(32)
76+
token = AuthToken(
77+
token=raw_token,
78+
name="test-worker",
79+
description="Token for testing",
80+
)
81+
db_session.add(token)
82+
await db_session.commit()
83+
await db_session.refresh(token)
84+
return raw_token, token
85+
86+
87+
@pytest_asyncio.fixture
88+
async def auth_headers(auth_token):
89+
"""Authorization headers for authenticated requests."""
90+
raw_token, _ = auth_token
91+
return {"Authorization": f"Bearer {raw_token}"}
92+
93+
94+
@pytest_asyncio.fixture
95+
async def admin_user(db_session):
96+
user = AdminUser(
97+
github_username="test_admin",
98+
added_by="system",
99+
notes="Test admin",
100+
)
101+
db_session.add(user)
102+
await db_session.commit()
103+
await db_session.refresh(user)
104+
return user
105+
106+
107+
@pytest_asyncio.fixture
108+
async def admin_session(db_session, admin_user):
109+
"""Create an admin session and return (session_token, session_model)."""
110+
raw_token = secrets.token_hex(32)
111+
session = AdminSession(
112+
session_token=raw_token,
113+
github_user_id=12345,
114+
github_username=admin_user.github_username,
115+
github_name="Test Admin",
116+
github_email="admin@test.com",
117+
github_avatar_url="https://example.com/avatar.png",
118+
expires_at=datetime.now() + timedelta(hours=24),
119+
)
120+
db_session.add(session)
121+
await db_session.commit()
122+
await db_session.refresh(session)
123+
return raw_token, session
124+
125+
126+
@pytest_asyncio.fixture
127+
async def admin_cookies(admin_session):
128+
"""Cookies dict for admin-authenticated requests."""
129+
raw_token, _ = admin_session
130+
return {"admin_session": raw_token}
131+
132+
133+
@pytest_asyncio.fixture
134+
async def sample_binary(db_session):
135+
from app.models import Binary
136+
binary = Binary(
137+
id="default",
138+
name="Default",
139+
flags=["--enable-optimizations"],
140+
description="Standard build",
141+
color="#8b5cf6",
142+
icon="server",
143+
display_order=0,
144+
)
145+
db_session.add(binary)
146+
await db_session.commit()
147+
await db_session.refresh(binary)
148+
return binary
149+
150+
151+
@pytest_asyncio.fixture
152+
async def sample_environment(db_session):
153+
from app.models import Environment
154+
env = Environment(
155+
id="linux-x86_64",
156+
name="Linux x86_64",
157+
description="Standard Linux build environment",
158+
)
159+
db_session.add(env)
160+
await db_session.commit()
161+
await db_session.refresh(env)
162+
return env
163+
164+
165+
@pytest_asyncio.fixture
166+
async def sample_commit(db_session):
167+
from app.models import Commit
168+
commit = Commit(
169+
sha="a" * 40,
170+
timestamp=datetime(2025, 6, 15, 12, 0, 0),
171+
message="Test commit",
172+
author="Test Author",
173+
python_major=3,
174+
python_minor=14,
175+
python_patch=0,
176+
)
177+
db_session.add(commit)
178+
await db_session.commit()
179+
await db_session.refresh(commit)
180+
return commit
181+
182+
183+
@pytest_asyncio.fixture
184+
async def sample_run(db_session, sample_commit, sample_binary, sample_environment):
185+
from app.models import Run
186+
run = Run(
187+
run_id="run_test_001",
188+
commit_sha=sample_commit.sha,
189+
binary_id=sample_binary.id,
190+
environment_id=sample_environment.id,
191+
python_major=3,
192+
python_minor=14,
193+
python_patch=0,
194+
timestamp=datetime(2025, 6, 15, 12, 30, 0),
195+
)
196+
db_session.add(run)
197+
await db_session.commit()
198+
await db_session.refresh(run)
199+
return run
200+
201+
202+
@pytest_asyncio.fixture
203+
async def sample_benchmark_result(db_session, sample_run):
204+
from app.models import BenchmarkResult
205+
result = BenchmarkResult(
206+
id=f"{sample_run.run_id}_json-dumps",
207+
run_id=sample_run.run_id,
208+
benchmark_name="json_dumps",
209+
high_watermark_bytes=1_000_000,
210+
allocation_histogram=[[64, 500], [128, 300], [256, 100]],
211+
total_allocated_bytes=5_000_000,
212+
top_allocating_functions=[
213+
{"function": "json.dumps", "count": 100, "total_size": 500_000}
214+
],
215+
flamegraph_html="<html>flamegraph</html>",
216+
)
217+
db_session.add(result)
218+
await db_session.commit()
219+
await db_session.refresh(result)
220+
return result

0 commit comments

Comments
 (0)