Full-stack mobile app for pole dance championship management. Backend: FastAPI + SQLAlchemy 2 (async) + SQLite (dev) / PostgreSQL (prod) - JWT auth with refresh token rotation - Championship CRUD with Instagram Graph API sync (APScheduler) - Registration flow with status management - Participant list publish with Expo push notifications - Alembic migrations, pytest test suite Mobile: React Native + Expo (TypeScript) - Auth gate: pending approval screen for new members - Championships list & detail screens - Registration form with status tracking - React Query + Zustand + React Navigation v6 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
2.6 KiB
Python
90 lines
2.6 KiB
Python
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_register_and_login(client):
|
|
# Register
|
|
res = await client.post(
|
|
"/api/v1/auth/register",
|
|
json={
|
|
"email": "test@example.com",
|
|
"password": "secret123",
|
|
"full_name": "Test User",
|
|
},
|
|
)
|
|
assert res.status_code == 201
|
|
tokens = res.json()
|
|
assert "access_token" in tokens
|
|
assert "refresh_token" in tokens
|
|
|
|
# Duplicate registration should fail
|
|
res2 = await client.post(
|
|
"/api/v1/auth/register",
|
|
json={
|
|
"email": "test@example.com",
|
|
"password": "secret123",
|
|
"full_name": "Test User",
|
|
},
|
|
)
|
|
assert res2.status_code == 409
|
|
|
|
# Login with correct credentials
|
|
res3 = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={"email": "test@example.com", "password": "secret123"},
|
|
)
|
|
assert res3.status_code == 200
|
|
|
|
# Login with wrong password
|
|
res4 = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={"email": "test@example.com", "password": "wrong"},
|
|
)
|
|
assert res4.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_me_requires_auth(client):
|
|
res = await client.get("/api/v1/auth/me")
|
|
assert res.status_code in (401, 403) # missing Authorization header
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_pending_user_cannot_access_championships(client):
|
|
await client.post(
|
|
"/api/v1/auth/register",
|
|
json={"email": "pending@example.com", "password": "pw", "full_name": "Pending"},
|
|
)
|
|
login = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={"email": "pending@example.com", "password": "pw"},
|
|
)
|
|
token = login.json()["access_token"]
|
|
res = await client.get(
|
|
"/api/v1/championships",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert res.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_token_refresh(client):
|
|
await client.post(
|
|
"/api/v1/auth/register",
|
|
json={"email": "refresh@example.com", "password": "pw", "full_name": "Refresh"},
|
|
)
|
|
login = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={"email": "refresh@example.com", "password": "pw"},
|
|
)
|
|
refresh_token = login.json()["refresh_token"]
|
|
|
|
res = await client.post("/api/v1/auth/refresh", json={"refresh_token": refresh_token})
|
|
assert res.status_code == 200
|
|
new_tokens = res.json()
|
|
assert "access_token" in new_tokens
|
|
|
|
# Old refresh token should now be revoked
|
|
res2 = await client.post("/api/v1/auth/refresh", json={"refresh_token": refresh_token})
|
|
assert res2.status_code == 401
|