Skip to content

Commit c8a1122

Browse files
authored
Merge pull request #16 from python-discord/feat/team-by-name
Implement endpoint for getting team by name
2 parents 3b37ac0 + 1d2aea1 commit c8a1122

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""add unique team name and jam constraint
2+
3+
Revision ID: 3bb5cc4b5d48
4+
Revises: a2c25c740f2a
5+
Create Date: 2022-07-05 20:42:53.033311
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '3bb5cc4b5d48'
14+
down_revision = 'a2c25c740f2a'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.create_index('team_name_jam_unique', 'teams', [sa.text('lower(name)'), 'jam_id'], unique=True)
21+
22+
23+
def downgrade():
24+
op.drop_index('team_name_jam_unique', 'teams')

api/database.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from sqlalchemy import BigInteger, Boolean, Column, Enum, ForeignKey, Integer, PrimaryKeyConstraint, Text
1+
from sqlalchemy import BigInteger, Boolean, Column, Enum, ForeignKey, Index, Integer, PrimaryKeyConstraint, Text, text
22
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
33
from sqlalchemy.ext.declarative import declarative_base
44
from sqlalchemy.orm import relationship, sessionmaker
@@ -65,6 +65,10 @@ class Team(Base):
6565
jam = relationship("Jam", back_populates="teams", lazy="joined")
6666
users = relationship("TeamUser", back_populates="team", lazy="joined")
6767

68+
__table_args__ = (
69+
Index('team_name_jam_unique', text("lower(name)"), "jam_id", unique=True),
70+
)
71+
6872

6973
class Winner(Base):
7074
"""A user who has won a code jam."""

api/routers/teams.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from typing import Optional
2+
13
from fastapi import APIRouter, Depends, HTTPException, Response
4+
from sqlalchemy import func
25
from sqlalchemy.ext.asyncio import AsyncSession
36
from sqlalchemy.future import select
47

@@ -43,6 +46,36 @@ async def get_teams(current_jam: bool = False, session: AsyncSession = Depends(g
4346
return teams.scalars().all()
4447

4548

49+
@router.get(
50+
"/find",
51+
response_model=TeamResponse,
52+
responses={
53+
404: {
54+
"description": "Team could not be found."
55+
}
56+
}
57+
)
58+
async def find_team_by_name(
59+
name: str, jam_id: Optional[int] = None, session: AsyncSession = Depends(get_db_session)
60+
) -> Team:
61+
"""Get a specific code jam team by name."""
62+
if jam_id is None:
63+
teams = await session.execute(
64+
select(Team).join(Team.jam).where(func.lower(Team.name) == func.lower(name) and Jam.ongoing == True)
65+
)
66+
else:
67+
teams = await session.execute(
68+
select(Team).where((func.lower(Team.name) == func.lower(name)) & (Team.jam_id == jam_id))
69+
)
70+
71+
teams.unique()
72+
73+
if not (team := teams.scalars().one_or_none()):
74+
raise HTTPException(status_code=404, detail="Team with specified name could not be found.")
75+
76+
return team
77+
78+
4679
@router.get(
4780
"/{team_id}",
4881
response_model=TeamResponse,

tests/routers/test_teams.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,65 @@ async def test_remove_nonexisting_team_user(
147147
)
148148

149149
assert response.status_code == 400
150+
151+
152+
async def test_find_nonexisting_team(
153+
client: AsyncClient,
154+
app: FastAPI
155+
) -> None:
156+
"""Getting a non-existing team by name should return 404."""
157+
response = await client.get(
158+
app.url_path_for("find_team_by_name"),
159+
params={"name": "team that should never exist"}
160+
)
161+
162+
assert response.status_code == 404
163+
164+
165+
async def test_find_existing_team_case_insensitive_current_jam(
166+
client: AsyncClient,
167+
app: FastAPI,
168+
created_codejam: models.CodeJamResponse
169+
) -> None:
170+
"""Getting an existing team (current jam) should return 200 and be case-insensitive."""
171+
team = created_codejam.teams[0]
172+
173+
response = await client.get(
174+
app.url_path_for("find_team_by_name"),
175+
params={"name": team.name.upper()}
176+
)
177+
assert response.status_code == 200
178+
179+
parsed = models.TeamResponse(**response.json())
180+
assert parsed == team
181+
182+
183+
async def test_find_team_by_name_wrong_jam(
184+
client: AsyncClient,
185+
app: FastAPI,
186+
created_codejam: models.CodeJamResponse
187+
) -> None:
188+
"""Getting an existing team from wrong code jam should return 404."""
189+
response = await client.get(
190+
app.url_path_for("find_team_by_name"),
191+
params={"name": created_codejam.teams[0].name.upper(), "jam_id": 9999}
192+
)
193+
assert response.status_code == 404
194+
195+
196+
async def test_find_team_by_name_using_specific_jam(
197+
client: AsyncClient,
198+
app: FastAPI,
199+
created_codejam: models.CodeJamResponse
200+
) -> None:
201+
"""Getting an existing team using right jam ID should return 200 and be case-insensitive."""
202+
team = created_codejam.teams[0]
203+
204+
response = await client.get(
205+
app.url_path_for("find_team_by_name"),
206+
params={"name": team.name.upper(), "jam_id": created_codejam.id}
207+
)
208+
assert response.status_code == 200
209+
210+
parsed = models.TeamResponse(**response.json())
211+
assert parsed == team

0 commit comments

Comments
 (0)