feat: auth-aware UI and role-based access control (#67)
## Summary - Add `is_admin` column to users table with Alembic migration and a `require_admin` FastAPI dependency that protects all admin-facing write endpoints (games, pokemon, evolutions, bosses, routes CRUD) - Expose admin status to frontend via user API and update AuthContext to fetch/store `isAdmin` after login - Make navigation menu auth-aware (different links for logged-out, logged-in, and admin users) and protect frontend routes with `ProtectedRoute` and `AdminRoute` components, preserving deep-linking through redirects - Fix test reliability: `drop_all` before `create_all` to clear stale PostgreSQL enums from interrupted test runs - Fix test auth: add `admin_client` fixture and use valid UUID for mock user so tests pass with new admin-protected endpoints ## Test plan - [x] All 252 backend tests pass - [ ] Verify non-admin users cannot access admin write endpoints (games, pokemon, evolutions, bosses CRUD) - [ ] Verify admin users can access admin endpoints normally - [ ] Verify navigation shows correct links for logged-out, logged-in, and admin states - [ ] Verify `/admin/*` routes redirect non-admin users with a toast - [ ] Verify `/runs/new` and `/genlockes/new` redirect unauthenticated users to login, then back after auth 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #67 Co-authored-by: Julian Tabel <juliantabel.jt@gmail.com> Co-committed-by: Julian Tabel <juliantabel.jt@gmail.com>
This commit was merged in pull request #67.
This commit is contained in:
@@ -7,7 +7,7 @@ from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
import app.models # noqa: F401 — ensures all models register with Base.metadata
|
||||
from app.core.auth import AuthUser, get_current_user
|
||||
from app.core.auth import AuthUser, get_current_user, require_admin
|
||||
from app.core.database import Base, get_session
|
||||
from app.main import app
|
||||
|
||||
@@ -24,6 +24,7 @@ async def engine():
|
||||
"""Create the test engine and schema once for the entire session."""
|
||||
eng = create_async_engine(TEST_DATABASE_URL, echo=False)
|
||||
async with eng.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield eng
|
||||
async with eng.begin() as conn:
|
||||
@@ -69,7 +70,11 @@ async def client(db_session):
|
||||
@pytest.fixture
|
||||
def mock_auth_user():
|
||||
"""Return a mock authenticated user for tests."""
|
||||
return AuthUser(id="test-user-123", email="test@example.com", role="authenticated")
|
||||
return AuthUser(
|
||||
id="00000000-0000-4000-a000-000000000001",
|
||||
email="test@example.com",
|
||||
role="authenticated",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -93,11 +98,34 @@ async def auth_client(db_session, auth_override):
|
||||
yield ac
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def admin_override(mock_auth_user):
|
||||
"""Override require_admin and get_current_user to return a mock user."""
|
||||
|
||||
def _override():
|
||||
return mock_auth_user
|
||||
|
||||
app.dependency_overrides[require_admin] = _override
|
||||
app.dependency_overrides[get_current_user] = _override
|
||||
yield
|
||||
app.dependency_overrides.pop(require_admin, None)
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def admin_client(db_session, admin_override):
|
||||
"""Async HTTP client with mocked admin authentication."""
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app), base_url="http://test"
|
||||
) as ac:
|
||||
yield ac
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_token():
|
||||
"""Generate a valid JWT token for testing."""
|
||||
payload = {
|
||||
"sub": "test-user-123",
|
||||
"sub": "00000000-0000-4000-a000-000000000001",
|
||||
"email": "test@example.com",
|
||||
"role": "authenticated",
|
||||
"aud": "authenticated",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import time
|
||||
from uuid import UUID
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from app.core.auth import AuthUser, get_current_user, require_auth
|
||||
from app.core.auth import AuthUser, get_current_user, require_admin, require_auth
|
||||
from app.core.config import settings
|
||||
from app.main import app
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -177,3 +179,140 @@ async def test_read_endpoint_without_token(db_session):
|
||||
) as ac:
|
||||
response = await ac.get("/runs")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
async def test_require_admin_valid_admin_user(db_session):
|
||||
"""Test require_admin passes through for admin user."""
|
||||
user_id = "11111111-1111-1111-1111-111111111111"
|
||||
admin_user = User(
|
||||
id=UUID(user_id),
|
||||
email="admin@example.com",
|
||||
is_admin=True,
|
||||
)
|
||||
db_session.add(admin_user)
|
||||
await db_session.commit()
|
||||
|
||||
auth_user = AuthUser(id=user_id, email="admin@example.com")
|
||||
result = await require_admin(user=auth_user, session=db_session)
|
||||
assert result is auth_user
|
||||
|
||||
|
||||
async def test_require_admin_non_admin_user(db_session):
|
||||
"""Test require_admin raises 403 for non-admin user."""
|
||||
from fastapi import HTTPException
|
||||
|
||||
user_id = "22222222-2222-2222-2222-222222222222"
|
||||
regular_user = User(
|
||||
id=UUID(user_id),
|
||||
email="user@example.com",
|
||||
is_admin=False,
|
||||
)
|
||||
db_session.add(regular_user)
|
||||
await db_session.commit()
|
||||
|
||||
auth_user = AuthUser(id=user_id, email="user@example.com")
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await require_admin(user=auth_user, session=db_session)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "Admin access required"
|
||||
|
||||
|
||||
async def test_require_admin_user_not_in_db(db_session):
|
||||
"""Test require_admin raises 403 for user not in database."""
|
||||
from fastapi import HTTPException
|
||||
|
||||
auth_user = AuthUser(
|
||||
id="33333333-3333-3333-3333-333333333333", email="ghost@example.com"
|
||||
)
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await require_admin(user=auth_user, session=db_session)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "Admin access required"
|
||||
|
||||
|
||||
async def test_admin_endpoint_returns_403_for_non_admin(
|
||||
db_session, jwt_secret, monkeypatch
|
||||
):
|
||||
"""Test that admin endpoint returns 403 for authenticated non-admin user."""
|
||||
user_id = "44444444-4444-4444-4444-444444444444"
|
||||
regular_user = User(
|
||||
id=UUID(user_id),
|
||||
email="nonadmin@example.com",
|
||||
is_admin=False,
|
||||
)
|
||||
db_session.add(regular_user)
|
||||
await db_session.commit()
|
||||
|
||||
monkeypatch.setattr(settings, "supabase_jwt_secret", jwt_secret)
|
||||
token = jwt.encode(
|
||||
{
|
||||
"sub": user_id,
|
||||
"email": "nonadmin@example.com",
|
||||
"role": "authenticated",
|
||||
"aud": "authenticated",
|
||||
"exp": int(time.time()) + 3600,
|
||||
},
|
||||
jwt_secret,
|
||||
algorithm="HS256",
|
||||
)
|
||||
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
) as ac:
|
||||
response = await ac.post(
|
||||
"/games",
|
||||
json={
|
||||
"name": "Test Game",
|
||||
"slug": "test-game",
|
||||
"generation": 1,
|
||||
"region": "Kanto",
|
||||
"category": "core",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json()["detail"] == "Admin access required"
|
||||
|
||||
|
||||
async def test_admin_endpoint_succeeds_for_admin(db_session, jwt_secret, monkeypatch):
|
||||
"""Test that admin endpoint succeeds for authenticated admin user."""
|
||||
user_id = "55555555-5555-5555-5555-555555555555"
|
||||
admin_user = User(
|
||||
id=UUID(user_id),
|
||||
email="admin@example.com",
|
||||
is_admin=True,
|
||||
)
|
||||
db_session.add(admin_user)
|
||||
await db_session.commit()
|
||||
|
||||
monkeypatch.setattr(settings, "supabase_jwt_secret", jwt_secret)
|
||||
token = jwt.encode(
|
||||
{
|
||||
"sub": user_id,
|
||||
"email": "admin@example.com",
|
||||
"role": "authenticated",
|
||||
"aud": "authenticated",
|
||||
"exp": int(time.time()) + 3600,
|
||||
},
|
||||
jwt_secret,
|
||||
algorithm="HS256",
|
||||
)
|
||||
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
) as ac:
|
||||
response = await ac.post(
|
||||
"/games",
|
||||
json={
|
||||
"name": "Test Game",
|
||||
"slug": "test-game",
|
||||
"generation": 1,
|
||||
"region": "Kanto",
|
||||
"category": "core",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.json()["name"] == "Test Game"
|
||||
|
||||
@@ -17,9 +17,9 @@ GAME_PAYLOAD = {
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def game(auth_client: AsyncClient) -> dict:
|
||||
async def game(admin_client: AsyncClient) -> dict:
|
||||
"""A game created via the API (no version_group_id)."""
|
||||
response = await auth_client.post(BASE, json=GAME_PAYLOAD)
|
||||
response = await admin_client.post(BASE, json=GAME_PAYLOAD)
|
||||
assert response.status_code == 201
|
||||
return response.json()
|
||||
|
||||
@@ -68,8 +68,8 @@ class TestListGames:
|
||||
|
||||
|
||||
class TestCreateGame:
|
||||
async def test_creates_and_returns_game(self, auth_client: AsyncClient):
|
||||
response = await auth_client.post(BASE, json=GAME_PAYLOAD)
|
||||
async def test_creates_and_returns_game(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(BASE, json=GAME_PAYLOAD)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["name"] == "Pokemon Red"
|
||||
@@ -77,15 +77,15 @@ class TestCreateGame:
|
||||
assert isinstance(data["id"], int)
|
||||
|
||||
async def test_duplicate_slug_returns_409(
|
||||
self, auth_client: AsyncClient, game: dict
|
||||
self, admin_client: AsyncClient, game: dict
|
||||
):
|
||||
response = await auth_client.post(
|
||||
response = await admin_client.post(
|
||||
BASE, json={**GAME_PAYLOAD, "name": "Pokemon Red v2"}
|
||||
)
|
||||
assert response.status_code == 409
|
||||
|
||||
async def test_missing_required_field_returns_422(self, auth_client: AsyncClient):
|
||||
response = await auth_client.post(BASE, json={"name": "Pokemon Red"})
|
||||
async def test_missing_required_field_returns_422(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(BASE, json={"name": "Pokemon Red"})
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@@ -115,35 +115,35 @@ class TestGetGame:
|
||||
|
||||
|
||||
class TestUpdateGame:
|
||||
async def test_updates_name(self, auth_client: AsyncClient, game: dict):
|
||||
response = await auth_client.put(
|
||||
async def test_updates_name(self, admin_client: AsyncClient, game: dict):
|
||||
response = await admin_client.put(
|
||||
f"{BASE}/{game['id']}", json={"name": "Pokemon Blue"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Pokemon Blue"
|
||||
|
||||
async def test_slug_unchanged_on_partial_update(
|
||||
self, auth_client: AsyncClient, game: dict
|
||||
self, admin_client: AsyncClient, game: dict
|
||||
):
|
||||
response = await auth_client.put(
|
||||
response = await admin_client.put(
|
||||
f"{BASE}/{game['id']}", json={"name": "New Name"}
|
||||
)
|
||||
assert response.json()["slug"] == "red"
|
||||
|
||||
async def test_not_found_returns_404(self, auth_client: AsyncClient):
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (
|
||||
await auth_client.put(f"{BASE}/9999", json={"name": "x"})
|
||||
await admin_client.put(f"{BASE}/9999", json={"name": "x"})
|
||||
).status_code == 404
|
||||
|
||||
async def test_duplicate_slug_returns_409(self, auth_client: AsyncClient):
|
||||
await auth_client.post(
|
||||
async def test_duplicate_slug_returns_409(self, admin_client: AsyncClient):
|
||||
await admin_client.post(
|
||||
BASE, json={**GAME_PAYLOAD, "slug": "blue", "name": "Blue"}
|
||||
)
|
||||
r1 = await auth_client.post(
|
||||
r1 = await admin_client.post(
|
||||
BASE, json={**GAME_PAYLOAD, "slug": "red", "name": "Red"}
|
||||
)
|
||||
game_id = r1.json()["id"]
|
||||
response = await auth_client.put(f"{BASE}/{game_id}", json={"slug": "blue"})
|
||||
response = await admin_client.put(f"{BASE}/{game_id}", json={"slug": "blue"})
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
@@ -153,13 +153,13 @@ class TestUpdateGame:
|
||||
|
||||
|
||||
class TestDeleteGame:
|
||||
async def test_deletes_game(self, auth_client: AsyncClient, game: dict):
|
||||
response = await auth_client.delete(f"{BASE}/{game['id']}")
|
||||
async def test_deletes_game(self, admin_client: AsyncClient, game: dict):
|
||||
response = await admin_client.delete(f"{BASE}/{game['id']}")
|
||||
assert response.status_code == 204
|
||||
assert (await auth_client.get(f"{BASE}/{game['id']}")).status_code == 404
|
||||
assert (await admin_client.get(f"{BASE}/{game['id']}")).status_code == 404
|
||||
|
||||
async def test_not_found_returns_404(self, auth_client: AsyncClient):
|
||||
assert (await auth_client.delete(f"{BASE}/9999")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.delete(f"{BASE}/9999")).status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -195,9 +195,9 @@ class TestListByRegion:
|
||||
|
||||
|
||||
class TestCreateRoute:
|
||||
async def test_creates_route(self, auth_client: AsyncClient, game_with_vg: tuple):
|
||||
async def test_creates_route(self, admin_client: AsyncClient, game_with_vg: tuple):
|
||||
game_id, _ = game_with_vg
|
||||
response = await auth_client.post(
|
||||
response = await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes",
|
||||
json={"name": "Pallet Town", "order": 1},
|
||||
)
|
||||
@@ -208,35 +208,35 @@ class TestCreateRoute:
|
||||
assert isinstance(data["id"], int)
|
||||
|
||||
async def test_game_detail_includes_route(
|
||||
self, auth_client: AsyncClient, game_with_vg: tuple
|
||||
self, admin_client: AsyncClient, game_with_vg: tuple
|
||||
):
|
||||
game_id, _ = game_with_vg
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "Route 1", "order": 1}
|
||||
)
|
||||
response = await auth_client.get(f"{BASE}/{game_id}")
|
||||
response = await admin_client.get(f"{BASE}/{game_id}")
|
||||
routes = response.json()["routes"]
|
||||
assert len(routes) == 1
|
||||
assert routes[0]["name"] == "Route 1"
|
||||
|
||||
async def test_game_without_version_group_returns_400(
|
||||
self, auth_client: AsyncClient, game: dict
|
||||
self, admin_client: AsyncClient, game: dict
|
||||
):
|
||||
response = await auth_client.post(
|
||||
response = await admin_client.post(
|
||||
f"{BASE}/{game['id']}/routes",
|
||||
json={"name": "Route 1", "order": 1},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
async def test_list_routes_excludes_routes_without_encounters(
|
||||
self, auth_client: AsyncClient, game_with_vg: tuple
|
||||
self, admin_client: AsyncClient, game_with_vg: tuple
|
||||
):
|
||||
"""list_game_routes only returns routes that have Pokemon encounters."""
|
||||
game_id, _ = game_with_vg
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "Route 1", "order": 1}
|
||||
)
|
||||
response = await auth_client.get(f"{BASE}/{game_id}/routes?flat=true")
|
||||
response = await admin_client.get(f"{BASE}/{game_id}/routes?flat=true")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
@@ -248,15 +248,15 @@ class TestCreateRoute:
|
||||
|
||||
class TestUpdateRoute:
|
||||
async def test_updates_route_name(
|
||||
self, auth_client: AsyncClient, game_with_vg: tuple
|
||||
self, admin_client: AsyncClient, game_with_vg: tuple
|
||||
):
|
||||
game_id, _ = game_with_vg
|
||||
r = (
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "Old Name", "order": 1}
|
||||
)
|
||||
).json()
|
||||
response = await auth_client.put(
|
||||
response = await admin_client.put(
|
||||
f"{BASE}/{game_id}/routes/{r['id']}",
|
||||
json={"name": "New Name"},
|
||||
)
|
||||
@@ -264,11 +264,11 @@ class TestUpdateRoute:
|
||||
assert response.json()["name"] == "New Name"
|
||||
|
||||
async def test_route_not_found_returns_404(
|
||||
self, auth_client: AsyncClient, game_with_vg: tuple
|
||||
self, admin_client: AsyncClient, game_with_vg: tuple
|
||||
):
|
||||
game_id, _ = game_with_vg
|
||||
assert (
|
||||
await auth_client.put(f"{BASE}/{game_id}/routes/9999", json={"name": "x"})
|
||||
await admin_client.put(f"{BASE}/{game_id}/routes/9999", json={"name": "x"})
|
||||
).status_code == 404
|
||||
|
||||
|
||||
@@ -278,26 +278,26 @@ class TestUpdateRoute:
|
||||
|
||||
|
||||
class TestDeleteRoute:
|
||||
async def test_deletes_route(self, auth_client: AsyncClient, game_with_vg: tuple):
|
||||
async def test_deletes_route(self, admin_client: AsyncClient, game_with_vg: tuple):
|
||||
game_id, _ = game_with_vg
|
||||
r = (
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "Route 1", "order": 1}
|
||||
)
|
||||
).json()
|
||||
assert (
|
||||
await auth_client.delete(f"{BASE}/{game_id}/routes/{r['id']}")
|
||||
await admin_client.delete(f"{BASE}/{game_id}/routes/{r['id']}")
|
||||
).status_code == 204
|
||||
# No longer in game detail
|
||||
detail = (await auth_client.get(f"{BASE}/{game_id}")).json()
|
||||
detail = (await admin_client.get(f"{BASE}/{game_id}")).json()
|
||||
assert all(route["id"] != r["id"] for route in detail["routes"])
|
||||
|
||||
async def test_route_not_found_returns_404(
|
||||
self, auth_client: AsyncClient, game_with_vg: tuple
|
||||
self, admin_client: AsyncClient, game_with_vg: tuple
|
||||
):
|
||||
game_id, _ = game_with_vg
|
||||
assert (
|
||||
await auth_client.delete(f"{BASE}/{game_id}/routes/9999")
|
||||
await admin_client.delete(f"{BASE}/{game_id}/routes/9999")
|
||||
).status_code == 404
|
||||
|
||||
|
||||
@@ -307,20 +307,20 @@ class TestDeleteRoute:
|
||||
|
||||
|
||||
class TestReorderRoutes:
|
||||
async def test_reorders_routes(self, auth_client: AsyncClient, game_with_vg: tuple):
|
||||
async def test_reorders_routes(self, admin_client: AsyncClient, game_with_vg: tuple):
|
||||
game_id, _ = game_with_vg
|
||||
r1 = (
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "A", "order": 1}
|
||||
)
|
||||
).json()
|
||||
r2 = (
|
||||
await auth_client.post(
|
||||
await admin_client.post(
|
||||
f"{BASE}/{game_id}/routes", json={"name": "B", "order": 2}
|
||||
)
|
||||
).json()
|
||||
|
||||
response = await auth_client.put(
|
||||
response = await admin_client.put(
|
||||
f"{BASE}/{game_id}/routes/reorder",
|
||||
json={
|
||||
"routes": [{"id": r1["id"], "order": 2}, {"id": r2["id"], "order": 1}]
|
||||
|
||||
@@ -55,7 +55,7 @@ async def games_ctx(db_session: AsyncSession) -> dict:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def ctx(db_session: AsyncSession, client: AsyncClient, games_ctx: dict) -> dict:
|
||||
async def ctx(db_session: AsyncSession, admin_client: AsyncClient, games_ctx: dict) -> dict:
|
||||
"""Full context: routes + pokemon + genlocke + encounter for advance/transfer tests."""
|
||||
route1 = Route(name="GT Route 1", version_group_id=games_ctx["vg1_id"], order=1)
|
||||
route2 = Route(name="GT Route 2", version_group_id=games_ctx["vg2_id"], order=1)
|
||||
@@ -67,7 +67,7 @@ async def ctx(db_session: AsyncSession, client: AsyncClient, games_ctx: dict) ->
|
||||
db_session.add(pikachu)
|
||||
await db_session.commit()
|
||||
|
||||
r = await client.post(
|
||||
r = await admin_client.post(
|
||||
GENLOCKES_BASE,
|
||||
json={
|
||||
"name": "Test Genlocke",
|
||||
@@ -80,7 +80,7 @@ async def ctx(db_session: AsyncSession, client: AsyncClient, games_ctx: dict) ->
|
||||
leg1 = next(leg for leg in genlocke["legs"] if leg["legOrder"] == 1)
|
||||
run_id = leg1["runId"]
|
||||
|
||||
enc_r = await client.post(
|
||||
enc_r = await admin_client.post(
|
||||
f"{RUNS_BASE}/{run_id}/encounters",
|
||||
json={"routeId": route1.id, "pokemonId": pikachu.id, "status": "caught"},
|
||||
)
|
||||
@@ -104,13 +104,13 @@ async def ctx(db_session: AsyncSession, client: AsyncClient, games_ctx: dict) ->
|
||||
|
||||
|
||||
class TestListGenlockes:
|
||||
async def test_empty_returns_empty_list(self, client: AsyncClient):
|
||||
response = await client.get(GENLOCKES_BASE)
|
||||
async def test_empty_returns_empty_list(self, admin_client: AsyncClient):
|
||||
response = await admin_client.get(GENLOCKES_BASE)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
async def test_returns_created_genlocke(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.get(GENLOCKES_BASE)
|
||||
async def test_returns_created_genlocke(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.get(GENLOCKES_BASE)
|
||||
assert response.status_code == 200
|
||||
names = [g["name"] for g in response.json()]
|
||||
assert "Test Genlocke" in names
|
||||
@@ -123,9 +123,9 @@ class TestListGenlockes:
|
||||
|
||||
class TestCreateGenlocke:
|
||||
async def test_creates_with_legs_and_first_run(
|
||||
self, client: AsyncClient, games_ctx: dict
|
||||
self, admin_client: AsyncClient, games_ctx: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
GENLOCKES_BASE,
|
||||
json={
|
||||
"name": "My Genlocke",
|
||||
@@ -144,14 +144,14 @@ class TestCreateGenlocke:
|
||||
leg2 = next(leg for leg in data["legs"] if leg["legOrder"] == 2)
|
||||
assert leg2["runId"] is None
|
||||
|
||||
async def test_empty_game_ids_returns_400(self, client: AsyncClient):
|
||||
response = await client.post(
|
||||
async def test_empty_game_ids_returns_400(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(
|
||||
GENLOCKES_BASE, json={"name": "Bad", "gameIds": []}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
async def test_invalid_game_id_returns_404(self, client: AsyncClient):
|
||||
response = await client.post(
|
||||
async def test_invalid_game_id_returns_404(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(
|
||||
GENLOCKES_BASE, json={"name": "Bad", "gameIds": [9999]}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
@@ -164,9 +164,9 @@ class TestCreateGenlocke:
|
||||
|
||||
class TestGetGenlocke:
|
||||
async def test_returns_genlocke_with_legs_and_stats(
|
||||
self, client: AsyncClient, ctx: dict
|
||||
self, admin_client: AsyncClient, ctx: dict
|
||||
):
|
||||
response = await client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
response = await admin_client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == ctx["genlocke_id"]
|
||||
@@ -174,8 +174,8 @@ class TestGetGenlocke:
|
||||
assert "stats" in data
|
||||
assert data["stats"]["totalLegs"] == 2
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{GENLOCKES_BASE}/9999")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{GENLOCKES_BASE}/9999")).status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -184,30 +184,30 @@ class TestGetGenlocke:
|
||||
|
||||
|
||||
class TestUpdateGenlocke:
|
||||
async def test_updates_name(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.patch(
|
||||
async def test_updates_name(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.patch(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}", json={"name": "Renamed"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Renamed"
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (
|
||||
await client.patch(f"{GENLOCKES_BASE}/9999", json={"name": "x"})
|
||||
await admin_client.patch(f"{GENLOCKES_BASE}/9999", json={"name": "x"})
|
||||
).status_code == 404
|
||||
|
||||
|
||||
class TestDeleteGenlocke:
|
||||
async def test_deletes_genlocke(self, client: AsyncClient, ctx: dict):
|
||||
async def test_deletes_genlocke(self, admin_client: AsyncClient, ctx: dict):
|
||||
assert (
|
||||
await client.delete(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
await admin_client.delete(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
).status_code == 204
|
||||
assert (
|
||||
await client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
await admin_client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}")
|
||||
).status_code == 404
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.delete(f"{GENLOCKES_BASE}/9999")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.delete(f"{GENLOCKES_BASE}/9999")).status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -216,8 +216,8 @@ class TestDeleteGenlocke:
|
||||
|
||||
|
||||
class TestGenlockeLegs:
|
||||
async def test_adds_leg(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_adds_leg(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs",
|
||||
json={"gameId": ctx["game1_id"]},
|
||||
)
|
||||
@@ -225,28 +225,28 @@ class TestGenlockeLegs:
|
||||
legs = response.json()["legs"]
|
||||
assert len(legs) == 3 # was 2, now 3
|
||||
|
||||
async def test_remove_leg_without_run(self, client: AsyncClient, ctx: dict):
|
||||
async def test_remove_leg_without_run(self, admin_client: AsyncClient, ctx: dict):
|
||||
# Leg 2 has no run yet — can be removed
|
||||
leg2 = next(leg for leg in ctx["genlocke"]["legs"] if leg["legOrder"] == 2)
|
||||
response = await client.delete(
|
||||
response = await admin_client.delete(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/{leg2['id']}"
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
async def test_remove_leg_with_run_returns_400(
|
||||
self, client: AsyncClient, ctx: dict
|
||||
self, admin_client: AsyncClient, ctx: dict
|
||||
):
|
||||
# Leg 1 has a run — cannot remove
|
||||
leg1 = next(leg for leg in ctx["genlocke"]["legs"] if leg["legOrder"] == 1)
|
||||
response = await client.delete(
|
||||
response = await admin_client.delete(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/{leg1['id']}"
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
async def test_add_leg_invalid_game_returns_404(
|
||||
self, client: AsyncClient, ctx: dict
|
||||
self, admin_client: AsyncClient, ctx: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs",
|
||||
json={"gameId": 9999},
|
||||
)
|
||||
@@ -259,33 +259,33 @@ class TestGenlockeLegs:
|
||||
|
||||
|
||||
class TestAdvanceLeg:
|
||||
async def test_uncompleted_run_returns_400(self, client: AsyncClient, ctx: dict):
|
||||
async def test_uncompleted_run_returns_400(self, admin_client: AsyncClient, ctx: dict):
|
||||
"""Cannot advance when leg 1's run is still active."""
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/1/advance"
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
async def test_no_next_leg_returns_400(self, client: AsyncClient, games_ctx: dict):
|
||||
async def test_no_next_leg_returns_400(self, admin_client: AsyncClient, games_ctx: dict):
|
||||
"""A single-leg genlocke cannot be advanced."""
|
||||
r = await client.post(
|
||||
r = await admin_client.post(
|
||||
GENLOCKES_BASE,
|
||||
json={"name": "Single Leg", "gameIds": [games_ctx["game1_id"]]},
|
||||
)
|
||||
genlocke = r.json()
|
||||
run_id = genlocke["legs"][0]["runId"]
|
||||
await client.patch(f"{RUNS_BASE}/{run_id}", json={"status": "completed"})
|
||||
await admin_client.patch(f"{RUNS_BASE}/{run_id}", json={"status": "completed"})
|
||||
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{genlocke['id']}/legs/1/advance"
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
async def test_advances_to_next_leg(self, client: AsyncClient, ctx: dict):
|
||||
async def test_advances_to_next_leg(self, admin_client: AsyncClient, ctx: dict):
|
||||
"""Completing the current run allows advancing to the next leg."""
|
||||
await client.patch(f"{RUNS_BASE}/{ctx['run_id']}", json={"status": "completed"})
|
||||
await admin_client.patch(f"{RUNS_BASE}/{ctx['run_id']}", json={"status": "completed"})
|
||||
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/1/advance"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@@ -293,11 +293,11 @@ class TestAdvanceLeg:
|
||||
leg2 = next(leg for leg in legs if leg["legOrder"] == 2)
|
||||
assert leg2["runId"] is not None
|
||||
|
||||
async def test_advances_with_transfers(self, client: AsyncClient, ctx: dict):
|
||||
async def test_advances_with_transfers(self, admin_client: AsyncClient, ctx: dict):
|
||||
"""Advancing with transfer_encounter_ids creates egg encounters in the next leg."""
|
||||
await client.patch(f"{RUNS_BASE}/{ctx['run_id']}", json={"status": "completed"})
|
||||
await admin_client.patch(f"{RUNS_BASE}/{ctx['run_id']}", json={"status": "completed"})
|
||||
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/1/advance",
|
||||
json={"transferEncounterIds": [ctx["encounter_id"]]},
|
||||
)
|
||||
@@ -308,7 +308,7 @@ class TestAdvanceLeg:
|
||||
assert new_run_id is not None
|
||||
|
||||
# The new run should contain the transferred (egg) encounter
|
||||
run_detail = (await client.get(f"{RUNS_BASE}/{new_run_id}")).json()
|
||||
run_detail = (await admin_client.get(f"{RUNS_BASE}/{new_run_id}")).json()
|
||||
assert len(run_detail["encounters"]) == 1
|
||||
|
||||
|
||||
@@ -318,56 +318,56 @@ class TestAdvanceLeg:
|
||||
|
||||
|
||||
class TestGenlockeGraveyard:
|
||||
async def test_returns_empty_graveyard(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/graveyard")
|
||||
async def test_returns_empty_graveyard(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/graveyard")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["entries"] == []
|
||||
assert data["totalDeaths"] == 0
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{GENLOCKES_BASE}/9999/graveyard")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{GENLOCKES_BASE}/9999/graveyard")).status_code == 404
|
||||
|
||||
|
||||
class TestGenlockeLineages:
|
||||
async def test_returns_empty_lineages(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/lineages")
|
||||
async def test_returns_empty_lineages(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/lineages")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["lineages"] == []
|
||||
assert data["totalLineages"] == 0
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{GENLOCKES_BASE}/9999/lineages")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{GENLOCKES_BASE}/9999/lineages")).status_code == 404
|
||||
|
||||
|
||||
class TestGenlockeRetiredFamilies:
|
||||
async def test_returns_empty_retired_families(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.get(
|
||||
async def test_returns_empty_retired_families(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.get(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/retired-families"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["retired_pokemon_ids"] == []
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (
|
||||
await client.get(f"{GENLOCKES_BASE}/9999/retired-families")
|
||||
await admin_client.get(f"{GENLOCKES_BASE}/9999/retired-families")
|
||||
).status_code == 404
|
||||
|
||||
|
||||
class TestLegSurvivors:
|
||||
async def test_returns_survivors(self, client: AsyncClient, ctx: dict):
|
||||
async def test_returns_survivors(self, admin_client: AsyncClient, ctx: dict):
|
||||
"""The one caught encounter in leg 1 shows up as a survivor."""
|
||||
response = await client.get(
|
||||
response = await admin_client.get(
|
||||
f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/1/survivors"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()) == 1
|
||||
|
||||
async def test_leg_not_found_returns_404(self, client: AsyncClient, ctx: dict):
|
||||
async def test_leg_not_found_returns_404(self, admin_client: AsyncClient, ctx: dict):
|
||||
assert (
|
||||
await client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/99/survivors")
|
||||
await admin_client.get(f"{GENLOCKES_BASE}/{ctx['genlocke_id']}/legs/99/survivors")
|
||||
).status_code == 404
|
||||
|
||||
|
||||
@@ -385,13 +385,13 @@ BOSS_PAYLOAD = {
|
||||
|
||||
|
||||
class TestBossCRUD:
|
||||
async def test_empty_list(self, client: AsyncClient, games_ctx: dict):
|
||||
response = await client.get(f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses")
|
||||
async def test_empty_list(self, admin_client: AsyncClient, games_ctx: dict):
|
||||
response = await admin_client.get(f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
async def test_creates_boss(self, client: AsyncClient, games_ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_creates_boss(self, admin_client: AsyncClient, games_ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses", json=BOSS_PAYLOAD
|
||||
)
|
||||
assert response.status_code == 201
|
||||
@@ -400,50 +400,50 @@ class TestBossCRUD:
|
||||
assert data["levelCap"] == 14
|
||||
assert data["pokemon"] == []
|
||||
|
||||
async def test_updates_boss(self, client: AsyncClient, games_ctx: dict):
|
||||
async def test_updates_boss(self, admin_client: AsyncClient, games_ctx: dict):
|
||||
boss = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses", json=BOSS_PAYLOAD
|
||||
)
|
||||
).json()
|
||||
response = await client.put(
|
||||
response = await admin_client.put(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses/{boss['id']}",
|
||||
json={"levelCap": 20},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["levelCap"] == 20
|
||||
|
||||
async def test_deletes_boss(self, client: AsyncClient, games_ctx: dict):
|
||||
async def test_deletes_boss(self, admin_client: AsyncClient, games_ctx: dict):
|
||||
boss = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses", json=BOSS_PAYLOAD
|
||||
)
|
||||
).json()
|
||||
assert (
|
||||
await client.delete(
|
||||
await admin_client.delete(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses/{boss['id']}"
|
||||
)
|
||||
).status_code == 204
|
||||
assert (
|
||||
await client.get(f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses")
|
||||
await admin_client.get(f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses")
|
||||
).json() == []
|
||||
|
||||
async def test_boss_not_found_returns_404(
|
||||
self, client: AsyncClient, games_ctx: dict
|
||||
self, admin_client: AsyncClient, games_ctx: dict
|
||||
):
|
||||
assert (
|
||||
await client.put(
|
||||
await admin_client.put(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses/9999",
|
||||
json={"levelCap": 10},
|
||||
)
|
||||
).status_code == 404
|
||||
|
||||
async def test_invalid_game_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{GAMES_BASE}/9999/bosses")).status_code == 404
|
||||
async def test_invalid_game_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{GAMES_BASE}/9999/bosses")).status_code == 404
|
||||
|
||||
async def test_game_without_version_group_returns_400(self, client: AsyncClient):
|
||||
async def test_game_without_version_group_returns_400(self, admin_client: AsyncClient):
|
||||
game = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
GAMES_BASE,
|
||||
json={
|
||||
"name": "No VG",
|
||||
@@ -454,7 +454,7 @@ class TestBossCRUD:
|
||||
)
|
||||
).json()
|
||||
assert (
|
||||
await client.get(f"{GAMES_BASE}/{game['id']}/bosses")
|
||||
await admin_client.get(f"{GAMES_BASE}/{game['id']}/bosses")
|
||||
).status_code == 400
|
||||
|
||||
|
||||
@@ -465,27 +465,27 @@ class TestBossCRUD:
|
||||
|
||||
class TestBossResults:
|
||||
@pytest.fixture
|
||||
async def boss_ctx(self, client: AsyncClient, games_ctx: dict) -> dict:
|
||||
async def boss_ctx(self, admin_client: AsyncClient, games_ctx: dict) -> dict:
|
||||
"""A boss battle and a run for boss-result tests."""
|
||||
boss = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
f"{GAMES_BASE}/{games_ctx['game1_id']}/bosses", json=BOSS_PAYLOAD
|
||||
)
|
||||
).json()
|
||||
run = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
RUNS_BASE, json={"gameId": games_ctx["game1_id"], "name": "Boss Run"}
|
||||
)
|
||||
).json()
|
||||
return {"boss_id": boss["id"], "run_id": run["id"]}
|
||||
|
||||
async def test_empty_list(self, client: AsyncClient, boss_ctx: dict):
|
||||
response = await client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
async def test_empty_list(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
response = await admin_client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
async def test_creates_boss_result(self, client: AsyncClient, boss_ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_creates_boss_result(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results",
|
||||
json={"bossBattleId": boss_ctx["boss_id"], "result": "won", "attempts": 1},
|
||||
)
|
||||
@@ -495,13 +495,13 @@ class TestBossResults:
|
||||
assert data["attempts"] == 1
|
||||
assert data["completedAt"] is not None
|
||||
|
||||
async def test_upserts_existing_result(self, client: AsyncClient, boss_ctx: dict):
|
||||
async def test_upserts_existing_result(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
"""POSTing the same boss twice updates the result (upsert)."""
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results",
|
||||
json={"bossBattleId": boss_ctx["boss_id"], "result": "won", "attempts": 1},
|
||||
)
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results",
|
||||
json={"bossBattleId": boss_ctx["boss_id"], "result": "lost", "attempts": 3},
|
||||
)
|
||||
@@ -510,31 +510,31 @@ class TestBossResults:
|
||||
assert response.json()["attempts"] == 3
|
||||
# Still only one record
|
||||
all_results = (
|
||||
await client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
await admin_client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
).json()
|
||||
assert len(all_results) == 1
|
||||
|
||||
async def test_deletes_boss_result(self, client: AsyncClient, boss_ctx: dict):
|
||||
async def test_deletes_boss_result(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
result = (
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results",
|
||||
json={"bossBattleId": boss_ctx["boss_id"], "result": "won"},
|
||||
)
|
||||
).json()
|
||||
assert (
|
||||
await client.delete(
|
||||
await admin_client.delete(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results/{result['id']}"
|
||||
)
|
||||
).status_code == 204
|
||||
assert (
|
||||
await client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
await admin_client.get(f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results")
|
||||
).json() == []
|
||||
|
||||
async def test_invalid_run_returns_404(self, client: AsyncClient, boss_ctx: dict):
|
||||
assert (await client.get(f"{RUNS_BASE}/9999/boss-results")).status_code == 404
|
||||
async def test_invalid_run_returns_404(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
assert (await admin_client.get(f"{RUNS_BASE}/9999/boss-results")).status_code == 404
|
||||
|
||||
async def test_invalid_boss_returns_404(self, client: AsyncClient, boss_ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_invalid_boss_returns_404(self, admin_client: AsyncClient, boss_ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{RUNS_BASE}/{boss_ctx['run_id']}/boss-results",
|
||||
json={"bossBattleId": 9999, "result": "won"},
|
||||
)
|
||||
@@ -547,8 +547,8 @@ class TestBossResults:
|
||||
|
||||
|
||||
class TestStats:
|
||||
async def test_returns_stats_structure(self, client: AsyncClient):
|
||||
response = await client.get(STATS_BASE)
|
||||
async def test_returns_stats_structure(self, admin_client: AsyncClient):
|
||||
response = await admin_client.get(STATS_BASE)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["totalRuns"] == 0
|
||||
@@ -556,9 +556,9 @@ class TestStats:
|
||||
assert data["topCaughtPokemon"] == []
|
||||
assert data["typeDistribution"] == []
|
||||
|
||||
async def test_reflects_created_data(self, client: AsyncClient, ctx: dict):
|
||||
async def test_reflects_created_data(self, admin_client: AsyncClient, ctx: dict):
|
||||
"""Stats should reflect the run and encounter created in ctx."""
|
||||
response = await client.get(STATS_BASE)
|
||||
response = await admin_client.get(STATS_BASE)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["totalRuns"] >= 1
|
||||
@@ -572,23 +572,23 @@ class TestStats:
|
||||
|
||||
|
||||
class TestExport:
|
||||
async def test_export_games_returns_list(self, client: AsyncClient):
|
||||
response = await client.get(f"{EXPORT_BASE}/games")
|
||||
async def test_export_games_returns_list(self, admin_client: AsyncClient):
|
||||
response = await admin_client.get(f"{EXPORT_BASE}/games")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
|
||||
async def test_export_pokemon_returns_list(self, client: AsyncClient):
|
||||
response = await client.get(f"{EXPORT_BASE}/pokemon")
|
||||
async def test_export_pokemon_returns_list(self, admin_client: AsyncClient):
|
||||
response = await admin_client.get(f"{EXPORT_BASE}/pokemon")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
|
||||
async def test_export_evolutions_returns_list(self, client: AsyncClient):
|
||||
response = await client.get(f"{EXPORT_BASE}/evolutions")
|
||||
async def test_export_evolutions_returns_list(self, admin_client: AsyncClient):
|
||||
response = await admin_client.get(f"{EXPORT_BASE}/evolutions")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
|
||||
async def test_export_game_routes_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{EXPORT_BASE}/games/9999/routes")).status_code == 404
|
||||
async def test_export_game_routes_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{EXPORT_BASE}/games/9999/routes")).status_code == 404
|
||||
|
||||
async def test_export_game_bosses_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.get(f"{EXPORT_BASE}/games/9999/bosses")).status_code == 404
|
||||
async def test_export_game_bosses_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.get(f"{EXPORT_BASE}/games/9999/bosses")).status_code == 404
|
||||
|
||||
@@ -29,21 +29,21 @@ CHARMANDER_DATA = {
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def pikachu(client: AsyncClient) -> dict:
|
||||
response = await client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
async def pikachu(admin_client: AsyncClient) -> dict:
|
||||
response = await admin_client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
assert response.status_code == 201
|
||||
return response.json()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def charmander(client: AsyncClient) -> dict:
|
||||
response = await client.post(POKEMON_BASE, json=CHARMANDER_DATA)
|
||||
async def charmander(admin_client: AsyncClient) -> dict:
|
||||
response = await admin_client.post(POKEMON_BASE, json=CHARMANDER_DATA)
|
||||
assert response.status_code == 201
|
||||
return response.json()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def ctx(db_session: AsyncSession, client: AsyncClient) -> dict:
|
||||
async def ctx(db_session: AsyncSession, admin_client: AsyncClient) -> dict:
|
||||
"""Full context: game + route + two pokemon + nuzlocke encounter on pikachu."""
|
||||
vg = VersionGroup(name="Poke Test VG", slug="poke-test-vg")
|
||||
db_session.add(vg)
|
||||
@@ -63,11 +63,11 @@ async def ctx(db_session: AsyncSession, client: AsyncClient) -> dict:
|
||||
db_session.add(route)
|
||||
await db_session.flush()
|
||||
|
||||
r1 = await client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
r1 = await admin_client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
assert r1.status_code == 201
|
||||
pikachu = r1.json()
|
||||
|
||||
r2 = await client.post(POKEMON_BASE, json=CHARMANDER_DATA)
|
||||
r2 = await admin_client.post(POKEMON_BASE, json=CHARMANDER_DATA)
|
||||
assert r2.status_code == 201
|
||||
charmander = r2.json()
|
||||
|
||||
@@ -146,8 +146,8 @@ class TestListPokemon:
|
||||
|
||||
|
||||
class TestCreatePokemon:
|
||||
async def test_creates_pokemon(self, client: AsyncClient):
|
||||
response = await client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
async def test_creates_pokemon(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(POKEMON_BASE, json=PIKACHU_DATA)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["name"] == "pikachu"
|
||||
@@ -156,16 +156,16 @@ class TestCreatePokemon:
|
||||
assert isinstance(data["id"], int)
|
||||
|
||||
async def test_duplicate_pokeapi_id_returns_409(
|
||||
self, client: AsyncClient, pikachu: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
POKEMON_BASE,
|
||||
json={**PIKACHU_DATA, "name": "pikachu-copy"},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
|
||||
async def test_missing_required_returns_422(self, client: AsyncClient):
|
||||
response = await client.post(POKEMON_BASE, json={"name": "pikachu"})
|
||||
async def test_missing_required_returns_422(self, admin_client: AsyncClient):
|
||||
response = await admin_client.post(POKEMON_BASE, json={"name": "pikachu"})
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@@ -190,25 +190,25 @@ class TestGetPokemon:
|
||||
|
||||
|
||||
class TestUpdatePokemon:
|
||||
async def test_updates_name(self, client: AsyncClient, pikachu: dict):
|
||||
response = await client.put(
|
||||
async def test_updates_name(self, admin_client: AsyncClient, pikachu: dict):
|
||||
response = await admin_client.put(
|
||||
f"{POKEMON_BASE}/{pikachu['id']}", json={"name": "Pikachu"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Pikachu"
|
||||
|
||||
async def test_duplicate_pokeapi_id_returns_409(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
response = await client.put(
|
||||
response = await admin_client.put(
|
||||
f"{POKEMON_BASE}/{pikachu['id']}",
|
||||
json={"pokeapiId": charmander["pokeapiId"]},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (
|
||||
await client.put(f"{POKEMON_BASE}/9999", json={"name": "x"})
|
||||
await admin_client.put(f"{POKEMON_BASE}/9999", json={"name": "x"})
|
||||
).status_code == 404
|
||||
|
||||
|
||||
@@ -218,22 +218,22 @@ class TestUpdatePokemon:
|
||||
|
||||
|
||||
class TestDeletePokemon:
|
||||
async def test_deletes_pokemon(self, client: AsyncClient, charmander: dict):
|
||||
async def test_deletes_pokemon(self, admin_client: AsyncClient, charmander: dict):
|
||||
assert (
|
||||
await client.delete(f"{POKEMON_BASE}/{charmander['id']}")
|
||||
await admin_client.delete(f"{POKEMON_BASE}/{charmander['id']}")
|
||||
).status_code == 204
|
||||
assert (
|
||||
await client.get(f"{POKEMON_BASE}/{charmander['id']}")
|
||||
await admin_client.get(f"{POKEMON_BASE}/{charmander['id']}")
|
||||
).status_code == 404
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.delete(f"{POKEMON_BASE}/9999")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.delete(f"{POKEMON_BASE}/9999")).status_code == 404
|
||||
|
||||
async def test_pokemon_with_encounters_returns_409(
|
||||
self, client: AsyncClient, ctx: dict
|
||||
self, admin_client: AsyncClient, ctx: dict
|
||||
):
|
||||
"""Pokemon referenced by a nuzlocke encounter cannot be deleted."""
|
||||
response = await client.delete(f"{POKEMON_BASE}/{ctx['pikachu_id']}")
|
||||
response = await admin_client.delete(f"{POKEMON_BASE}/{ctx['pikachu_id']}")
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
@@ -249,9 +249,9 @@ class TestPokemonFamilies:
|
||||
assert response.json()["families"] == []
|
||||
|
||||
async def test_returns_family_grouping(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -259,7 +259,7 @@ class TestPokemonFamilies:
|
||||
"trigger": "level-up",
|
||||
},
|
||||
)
|
||||
response = await client.get(f"{POKEMON_BASE}/families")
|
||||
response = await admin_client.get(f"{POKEMON_BASE}/families")
|
||||
assert response.status_code == 200
|
||||
families = response.json()["families"]
|
||||
assert len(families) == 1
|
||||
@@ -280,9 +280,9 @@ class TestPokemonEvolutionChain:
|
||||
assert response.json() == []
|
||||
|
||||
async def test_returns_chain_for_multi_stage(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -290,7 +290,7 @@ class TestPokemonEvolutionChain:
|
||||
"trigger": "level-up",
|
||||
},
|
||||
)
|
||||
response = await client.get(f"{POKEMON_BASE}/{pikachu['id']}/evolution-chain")
|
||||
response = await admin_client.get(f"{POKEMON_BASE}/{pikachu['id']}/evolution-chain")
|
||||
assert response.status_code == 200
|
||||
chain = response.json()
|
||||
assert len(chain) == 1
|
||||
@@ -317,9 +317,9 @@ class TestListEvolutions:
|
||||
assert data["total"] == 0
|
||||
|
||||
async def test_returns_created_evolution(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -327,14 +327,14 @@ class TestListEvolutions:
|
||||
"trigger": "level-up",
|
||||
},
|
||||
)
|
||||
response = await client.get(EVO_BASE)
|
||||
response = await admin_client.get(EVO_BASE)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["total"] == 1
|
||||
|
||||
async def test_filter_by_trigger(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
await client.post(
|
||||
await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -342,9 +342,9 @@ class TestListEvolutions:
|
||||
"trigger": "use-item",
|
||||
},
|
||||
)
|
||||
hit = await client.get(EVO_BASE, params={"trigger": "use-item"})
|
||||
hit = await admin_client.get(EVO_BASE, params={"trigger": "use-item"})
|
||||
assert hit.json()["total"] == 1
|
||||
miss = await client.get(EVO_BASE, params={"trigger": "level-up"})
|
||||
miss = await admin_client.get(EVO_BASE, params={"trigger": "level-up"})
|
||||
assert miss.json()["total"] == 0
|
||||
|
||||
|
||||
@@ -355,9 +355,9 @@ class TestListEvolutions:
|
||||
|
||||
class TestCreateEvolution:
|
||||
async def test_creates_evolution(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -374,9 +374,9 @@ class TestCreateEvolution:
|
||||
assert data["toPokemon"]["name"] == "charmander"
|
||||
|
||||
async def test_invalid_from_pokemon_returns_404(
|
||||
self, client: AsyncClient, charmander: dict
|
||||
self, admin_client: AsyncClient, charmander: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": 9999,
|
||||
@@ -387,9 +387,9 @@ class TestCreateEvolution:
|
||||
assert response.status_code == 404
|
||||
|
||||
async def test_invalid_to_pokemon_returns_404(
|
||||
self, client: AsyncClient, pikachu: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict
|
||||
):
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -408,9 +408,9 @@ class TestCreateEvolution:
|
||||
class TestUpdateEvolution:
|
||||
@pytest.fixture
|
||||
async def evolution(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
) -> dict:
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -420,16 +420,16 @@ class TestUpdateEvolution:
|
||||
)
|
||||
return response.json()
|
||||
|
||||
async def test_updates_trigger(self, client: AsyncClient, evolution: dict):
|
||||
response = await client.put(
|
||||
async def test_updates_trigger(self, admin_client: AsyncClient, evolution: dict):
|
||||
response = await admin_client.put(
|
||||
f"{EVO_BASE}/{evolution['id']}", json={"trigger": "use-item"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["trigger"] == "use-item"
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (
|
||||
await client.put(f"{EVO_BASE}/9999", json={"trigger": "level-up"})
|
||||
await admin_client.put(f"{EVO_BASE}/9999", json={"trigger": "level-up"})
|
||||
).status_code == 404
|
||||
|
||||
|
||||
@@ -441,9 +441,9 @@ class TestUpdateEvolution:
|
||||
class TestDeleteEvolution:
|
||||
@pytest.fixture
|
||||
async def evolution(
|
||||
self, client: AsyncClient, pikachu: dict, charmander: dict
|
||||
self, admin_client: AsyncClient, pikachu: dict, charmander: dict
|
||||
) -> dict:
|
||||
response = await client.post(
|
||||
response = await admin_client.post(
|
||||
EVO_BASE,
|
||||
json={
|
||||
"fromPokemonId": pikachu["id"],
|
||||
@@ -453,12 +453,12 @@ class TestDeleteEvolution:
|
||||
)
|
||||
return response.json()
|
||||
|
||||
async def test_deletes_evolution(self, client: AsyncClient, evolution: dict):
|
||||
assert (await client.delete(f"{EVO_BASE}/{evolution['id']}")).status_code == 204
|
||||
assert (await client.get(EVO_BASE)).json()["total"] == 0
|
||||
async def test_deletes_evolution(self, admin_client: AsyncClient, evolution: dict):
|
||||
assert (await admin_client.delete(f"{EVO_BASE}/{evolution['id']}")).status_code == 204
|
||||
assert (await admin_client.get(EVO_BASE)).json()["total"] == 0
|
||||
|
||||
async def test_not_found_returns_404(self, client: AsyncClient):
|
||||
assert (await client.delete(f"{EVO_BASE}/9999")).status_code == 404
|
||||
async def test_not_found_returns_404(self, admin_client: AsyncClient):
|
||||
assert (await admin_client.delete(f"{EVO_BASE}/9999")).status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -467,13 +467,13 @@ class TestDeleteEvolution:
|
||||
|
||||
|
||||
class TestRouteEncounters:
|
||||
async def test_empty_list_for_route(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.get(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon")
|
||||
async def test_empty_list_for_route(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.get(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
async def test_creates_route_encounter(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_creates_route_encounter(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon",
|
||||
json={
|
||||
"pokemonId": ctx["charmander_id"],
|
||||
@@ -490,8 +490,8 @@ class TestRouteEncounters:
|
||||
assert data["encounterRate"] == 10
|
||||
assert data["pokemon"]["name"] == "charmander"
|
||||
|
||||
async def test_invalid_route_returns_404(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_invalid_route_returns_404(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{ROUTE_BASE}/9999/pokemon",
|
||||
json={
|
||||
"pokemonId": ctx["charmander_id"],
|
||||
@@ -504,8 +504,8 @@ class TestRouteEncounters:
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
async def test_invalid_pokemon_returns_404(self, client: AsyncClient, ctx: dict):
|
||||
response = await client.post(
|
||||
async def test_invalid_pokemon_returns_404(self, admin_client: AsyncClient, ctx: dict):
|
||||
response = await admin_client.post(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon",
|
||||
json={
|
||||
"pokemonId": 9999,
|
||||
@@ -518,8 +518,8 @@ class TestRouteEncounters:
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
async def test_updates_route_encounter(self, client: AsyncClient, ctx: dict):
|
||||
r = await client.post(
|
||||
async def test_updates_route_encounter(self, admin_client: AsyncClient, ctx: dict):
|
||||
r = await admin_client.post(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon",
|
||||
json={
|
||||
"pokemonId": ctx["charmander_id"],
|
||||
@@ -531,23 +531,23 @@ class TestRouteEncounters:
|
||||
},
|
||||
)
|
||||
enc = r.json()
|
||||
response = await client.put(
|
||||
response = await admin_client.put(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/{enc['id']}",
|
||||
json={"encounterRate": 25},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["encounterRate"] == 25
|
||||
|
||||
async def test_update_not_found_returns_404(self, client: AsyncClient, ctx: dict):
|
||||
async def test_update_not_found_returns_404(self, admin_client: AsyncClient, ctx: dict):
|
||||
assert (
|
||||
await client.put(
|
||||
await admin_client.put(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/9999",
|
||||
json={"encounterRate": 5},
|
||||
)
|
||||
).status_code == 404
|
||||
|
||||
async def test_deletes_route_encounter(self, client: AsyncClient, ctx: dict):
|
||||
r = await client.post(
|
||||
async def test_deletes_route_encounter(self, admin_client: AsyncClient, ctx: dict):
|
||||
r = await admin_client.post(
|
||||
f"{ROUTE_BASE}/{ctx['route_id']}/pokemon",
|
||||
json={
|
||||
"pokemonId": ctx["charmander_id"],
|
||||
@@ -560,13 +560,13 @@ class TestRouteEncounters:
|
||||
)
|
||||
enc = r.json()
|
||||
assert (
|
||||
await client.delete(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/{enc['id']}")
|
||||
await admin_client.delete(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/{enc['id']}")
|
||||
).status_code == 204
|
||||
assert (
|
||||
await client.get(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon")
|
||||
await admin_client.get(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon")
|
||||
).json() == []
|
||||
|
||||
async def test_delete_not_found_returns_404(self, client: AsyncClient, ctx: dict):
|
||||
async def test_delete_not_found_returns_404(self, admin_client: AsyncClient, ctx: dict):
|
||||
assert (
|
||||
await client.delete(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/9999")
|
||||
await admin_client.delete(f"{ROUTE_BASE}/{ctx['route_id']}/pokemon/9999")
|
||||
).status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user