Skip to content

Commit d76e104

Browse files
StuMasonclaude
andcommitted
test: Add comprehensive tests for per-user API keys
Adds 24 new tests covering: - API key generation (format, uniqueness, hashing) - Key creation (user-scoped, service-level, custom rate limits) - Key validation (valid, invalid, inactive keys) - Rate limiting (decrement, exceeded, reset) - Temp code exchange (valid, invalid, used, expired, wrong client) - Key management (regenerate, revoke) - User scoping properties Also adds: - tests/conftest.py with async database fixtures using SQLite - aiosqlite dev dependency for in-memory test database Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 67dd8f9 commit d76e104

File tree

4 files changed

+482
-0
lines changed

4 files changed

+482
-0
lines changed

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ asyncio_mode = "auto"
108108
testpaths = ["tests"]
109109
addopts = "-v --cov=polar_flow_server --cov-report=term-missing"
110110

111+
[dependency-groups]
112+
dev = [
113+
"aiosqlite>=0.22.1",
114+
]
115+
111116
# NOTE: For local development with unreleased SDK changes, uncomment:
112117
# [tool.uv.sources]
113118
# polar-flow-api = { path = "../polar-flow" }

tests/conftest.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Shared test fixtures."""
2+
3+
import asyncio
4+
from collections.abc import AsyncIterator, Iterator
5+
from datetime import UTC, datetime, timedelta
6+
7+
import pytest
8+
from sqlalchemy import event
9+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
10+
from sqlalchemy.pool import StaticPool
11+
12+
from polar_flow_server.models.base import Base
13+
14+
15+
@pytest.fixture(scope="session")
16+
def event_loop() -> Iterator[asyncio.AbstractEventLoop]:
17+
"""Create event loop for async tests."""
18+
loop = asyncio.new_event_loop()
19+
yield loop
20+
loop.close()
21+
22+
23+
@pytest.fixture
24+
async def async_engine():
25+
"""Create async SQLite engine for testing."""
26+
engine = create_async_engine(
27+
"sqlite+aiosqlite:///:memory:",
28+
connect_args={"check_same_thread": False},
29+
poolclass=StaticPool,
30+
)
31+
32+
# Enable foreign keys for SQLite
33+
@event.listens_for(engine.sync_engine, "connect")
34+
def set_sqlite_pragma(dbapi_connection, connection_record):
35+
cursor = dbapi_connection.cursor()
36+
cursor.execute("PRAGMA foreign_keys=ON")
37+
cursor.close()
38+
39+
async with engine.begin() as conn:
40+
await conn.run_sync(Base.metadata.create_all)
41+
42+
yield engine
43+
44+
async with engine.begin() as conn:
45+
await conn.run_sync(Base.metadata.drop_all)
46+
47+
await engine.dispose()
48+
49+
50+
@pytest.fixture
51+
async def async_session(async_engine) -> AsyncIterator[AsyncSession]:
52+
"""Create async session for testing."""
53+
async_session_maker = async_sessionmaker(
54+
async_engine,
55+
class_=AsyncSession,
56+
expire_on_commit=False,
57+
)
58+
59+
async with async_session_maker() as session:
60+
yield session
61+
62+
63+
@pytest.fixture
64+
async def test_user(async_session: AsyncSession):
65+
"""Create a test user."""
66+
from polar_flow_server.models.user import User
67+
68+
user = User(
69+
id="test-user-uuid-1",
70+
polar_user_id="polar_user_001",
71+
access_token_encrypted="encrypted_token_placeholder",
72+
token_expires_at=datetime.now(UTC) + timedelta(days=365),
73+
is_active=True,
74+
)
75+
async_session.add(user)
76+
await async_session.commit()
77+
await async_session.refresh(user)
78+
return user
79+
80+
81+
@pytest.fixture
82+
async def test_user_2(async_session: AsyncSession):
83+
"""Create a second test user."""
84+
from polar_flow_server.models.user import User
85+
86+
user = User(
87+
id="test-user-uuid-2",
88+
polar_user_id="polar_user_002",
89+
access_token_encrypted="encrypted_token_placeholder",
90+
token_expires_at=datetime.now(UTC) + timedelta(days=365),
91+
is_active=True,
92+
)
93+
async_session.add(user)
94+
await async_session.commit()
95+
await async_session.refresh(user)
96+
return user
97+
98+
99+
@pytest.fixture
100+
async def user_api_key(async_session: AsyncSession, test_user):
101+
"""Create a user-scoped API key."""
102+
from polar_flow_server.core.api_keys import create_api_key_for_user
103+
104+
api_key, raw_key = await create_api_key_for_user(
105+
user_id=test_user.polar_user_id,
106+
name="Test User Key",
107+
session=async_session,
108+
)
109+
await async_session.commit()
110+
return api_key, raw_key
111+
112+
113+
@pytest.fixture
114+
async def service_api_key(async_session: AsyncSession):
115+
"""Create a service-level API key."""
116+
from polar_flow_server.core.api_keys import create_service_key
117+
118+
api_key, raw_key = await create_service_key(
119+
name="Test Service Key",
120+
session=async_session,
121+
)
122+
await async_session.commit()
123+
return api_key, raw_key
124+
125+
126+
@pytest.fixture
127+
async def temp_auth_code(async_session: AsyncSession, test_user):
128+
"""Create a temporary auth code."""
129+
from polar_flow_server.core.api_keys import create_temp_auth_code
130+
131+
temp_code, raw_code = await create_temp_auth_code(
132+
user_id=test_user.polar_user_id,
133+
session=async_session,
134+
client_id="test_client",
135+
)
136+
await async_session.commit()
137+
return temp_code, raw_code

0 commit comments

Comments
 (0)