mirror of
https://github.com/evroon/bracket.git
synced 2026-03-02 05:37:30 -05:00
Add more integration tests (#41)
This commit is contained in:
2
.github/workflows/backend.yml
vendored
2
.github/workflows/backend.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
env:
|
||||
ENVIRONMENT: CI
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
- name: Run mypy
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from heliclockter import datetime_utc
|
||||
|
||||
from bracket.models.db.shared import BaseModelORM
|
||||
@@ -9,14 +11,14 @@ class Player(BaseModelORM):
|
||||
created: datetime_utc
|
||||
team_id: int | None = None
|
||||
tournament_id: int
|
||||
elo_score: float
|
||||
elo_score: Decimal
|
||||
|
||||
|
||||
class PlayerBody(BaseModelORM):
|
||||
name: str
|
||||
team_id: int | None
|
||||
|
||||
|
||||
class PlayerToInsert(PlayerBody):
|
||||
created: datetime_utc
|
||||
tournament_id: int
|
||||
elo_score: Decimal
|
||||
|
||||
@@ -70,7 +70,10 @@ async def create_player(
|
||||
last_record_id = await database.execute(
|
||||
query=players.insert(),
|
||||
values=PlayerToInsert(
|
||||
**player_body.dict(), created=datetime_utc.now(), tournament_id=tournament_id
|
||||
**player_body.dict(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
elo_score=0,
|
||||
).dict(),
|
||||
)
|
||||
return SinglePlayerResponse(
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Type
|
||||
from databases import Database
|
||||
from sqlalchemy.sql import Select
|
||||
|
||||
from bracket.utils.types import BaseModelT
|
||||
from bracket.utils.types import BaseModelT, assert_some
|
||||
|
||||
|
||||
async def fetch_one_parsed(
|
||||
@@ -13,6 +13,12 @@ async def fetch_one_parsed(
|
||||
return model.parse_obj(record._mapping) if record is not None else None
|
||||
|
||||
|
||||
async def fetch_one_parsed_certain(
|
||||
database: Database, model: Type[BaseModelT], query: Select
|
||||
) -> BaseModelT:
|
||||
return assert_some(await fetch_one_parsed(database, model, query))
|
||||
|
||||
|
||||
async def fetch_all_parsed(
|
||||
database: Database, model: Type[BaseModelT], query: Select
|
||||
) -> list[BaseModelT]:
|
||||
|
||||
@@ -50,7 +50,7 @@ async def test_auth_on_protected_endpoint(startup_and_shutdown_uvicorn_server: N
|
||||
headers = {'Authorization': f'Bearer {get_mock_token()}'}
|
||||
|
||||
async with inserted_user(MOCK_USER) as user_inserted:
|
||||
response = JsonDict(await send_request(HTTPMethod.GET, 'users/me', {}, headers))
|
||||
response = JsonDict(await send_request(HTTPMethod.GET, 'users/me', {}, None, headers))
|
||||
|
||||
assert response == {
|
||||
'id': user_inserted.id,
|
||||
@@ -63,5 +63,5 @@ async def test_auth_on_protected_endpoint(startup_and_shutdown_uvicorn_server: N
|
||||
async def test_invalid_token(startup_and_shutdown_uvicorn_server: None) -> None:
|
||||
headers = {'Authorization': 'Bearer some.invalid.token'}
|
||||
|
||||
response = JsonDict(await send_request(HTTPMethod.GET, 'users/me', {}, headers))
|
||||
response = JsonDict(await send_request(HTTPMethod.GET, 'users/me', {}, None, headers))
|
||||
assert response == {'detail': 'Could not validate credentials'}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from bracket.database import database
|
||||
from bracket.models.db.player import Player
|
||||
from bracket.schema import players
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_PLAYER1, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
from tests.integration_tests.api.shared import send_tournament_request
|
||||
from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request
|
||||
from tests.integration_tests.models import AuthContext
|
||||
from tests.integration_tests.sql import inserted_player, inserted_team
|
||||
from tests.integration_tests.sql import assert_row_count_and_clear, inserted_player, inserted_team
|
||||
|
||||
|
||||
async def test_players_endpoint(
|
||||
@@ -22,3 +26,48 @@ async def test_players_endpoint(
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_create_player(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'name': 'Some new name'}
|
||||
response = await send_tournament_request(HTTPMethod.POST, 'players', auth_context, json=body)
|
||||
assert response['data']['name'] == body['name'] # type: ignore[call-overload]
|
||||
await assert_row_count_and_clear(players, 1)
|
||||
|
||||
|
||||
async def test_delete_player(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
async with inserted_team(DUMMY_TEAM1) as team_inserted:
|
||||
async with inserted_player(
|
||||
DUMMY_PLAYER1.copy(update={'team_id': team_inserted.id})
|
||||
) as player_inserted:
|
||||
assert (
|
||||
await send_tournament_request(
|
||||
HTTPMethod.DELETE, f'players/{player_inserted.id}', auth_context
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(players, 0)
|
||||
|
||||
|
||||
async def test_update_player(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'name': 'Some new name'}
|
||||
async with inserted_team(DUMMY_TEAM1) as team_inserted:
|
||||
async with inserted_player(
|
||||
DUMMY_PLAYER1.copy(update={'team_id': team_inserted.id})
|
||||
) as player_inserted:
|
||||
response = await send_tournament_request(
|
||||
HTTPMethod.PATCH, f'players/{player_inserted.id}', auth_context, json=body
|
||||
)
|
||||
patched_player = await fetch_one_parsed_certain(
|
||||
database, Player, query=players.select().where(players.c.id == player_inserted.id)
|
||||
)
|
||||
assert patched_player.name == body['name']
|
||||
assert response['data']['name'] == body['name'] # type: ignore[call-overload]
|
||||
|
||||
await assert_row_count_and_clear(players, 1)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from bracket.database import database
|
||||
from bracket.models.db.round import Round
|
||||
from bracket.schema import rounds
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_ROUND1, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
from tests.integration_tests.api.shared import send_tournament_request
|
||||
from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request
|
||||
from tests.integration_tests.models import AuthContext
|
||||
from tests.integration_tests.sql import inserted_round, inserted_team
|
||||
from tests.integration_tests.sql import assert_row_count_and_clear, inserted_round, inserted_team
|
||||
|
||||
|
||||
async def test_rounds_endpoint(
|
||||
@@ -23,3 +27,50 @@ async def test_rounds_endpoint(
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_create_round(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
async with inserted_team(DUMMY_TEAM1):
|
||||
assert (
|
||||
await send_tournament_request(HTTPMethod.POST, 'rounds', auth_context, {})
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
|
||||
|
||||
async def test_delete_round(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
async with inserted_team(DUMMY_TEAM1):
|
||||
async with inserted_round(DUMMY_ROUND1) as round_inserted:
|
||||
assert (
|
||||
await send_tournament_request(
|
||||
HTTPMethod.DELETE, f'rounds/{round_inserted.id}', auth_context, {}
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(rounds, 0)
|
||||
|
||||
|
||||
async def test_update_round(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'name': 'Some new name', 'is_draft': True, 'is_active': False}
|
||||
async with inserted_team(DUMMY_TEAM1):
|
||||
async with inserted_round(DUMMY_ROUND1) as round_inserted:
|
||||
assert (
|
||||
await send_tournament_request(
|
||||
HTTPMethod.PATCH, f'rounds/{round_inserted.id}', auth_context, None, body
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
patched_round = await fetch_one_parsed_certain(
|
||||
database, Round, query=rounds.select().where(rounds.c.id == round_inserted.id)
|
||||
)
|
||||
assert patched_round.name == body['name']
|
||||
assert patched_round.is_draft == body['is_draft']
|
||||
assert patched_round.is_active == body['is_active']
|
||||
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
|
||||
@@ -8,10 +8,13 @@ import uvicorn
|
||||
from fastapi import FastAPI
|
||||
|
||||
from bracket.app import app
|
||||
from bracket.routes.models import SuccessResponse
|
||||
from bracket.utils.http import HTTPMethod
|
||||
from bracket.utils.types import JsonDict, JsonObject
|
||||
from tests.integration_tests.models import AuthContext
|
||||
|
||||
SUCCESS_RESPONSE = SuccessResponse().dict()
|
||||
|
||||
|
||||
def find_free_port() -> int:
|
||||
"""
|
||||
@@ -64,13 +67,18 @@ class UvicornTestServer(uvicorn.Server):
|
||||
|
||||
|
||||
async def send_request(
|
||||
method: HTTPMethod, endpoint: str, body: JsonDict = {}, headers: JsonDict = {}
|
||||
method: HTTPMethod,
|
||||
endpoint: str,
|
||||
body: JsonDict | None = None,
|
||||
json: JsonDict | None = None,
|
||||
headers: JsonDict = {},
|
||||
) -> JsonObject:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request(
|
||||
method=method.value,
|
||||
url=get_root_uvicorn_url() + endpoint,
|
||||
data=body,
|
||||
json=json,
|
||||
headers=headers,
|
||||
) as resp:
|
||||
response: JsonObject = await resp.json()
|
||||
@@ -78,14 +86,28 @@ async def send_request(
|
||||
|
||||
|
||||
async def send_auth_request(
|
||||
method: HTTPMethod, endpoint: str, auth_context: AuthContext, body: JsonDict = {}
|
||||
method: HTTPMethod,
|
||||
endpoint: str,
|
||||
auth_context: AuthContext,
|
||||
body: JsonDict | None = None,
|
||||
json: JsonDict | None = None,
|
||||
) -> JsonObject:
|
||||
return await send_request(method, endpoint, body, auth_context.headers)
|
||||
return await send_request(
|
||||
method=method, endpoint=endpoint, body=body, json=json, headers=auth_context.headers
|
||||
)
|
||||
|
||||
|
||||
async def send_tournament_request(
|
||||
method: HTTPMethod, endpoint: str, auth_context: AuthContext, body: JsonDict = {}
|
||||
method: HTTPMethod,
|
||||
endpoint: str,
|
||||
auth_context: AuthContext,
|
||||
body: JsonDict | None = None,
|
||||
json: JsonDict | None = None,
|
||||
) -> JsonObject:
|
||||
return await send_request(
|
||||
method, f'tournaments/{auth_context.tournament.id}/{endpoint}', body, auth_context.headers
|
||||
method=method,
|
||||
endpoint=f'tournaments/{auth_context.tournament.id}/{endpoint}',
|
||||
body=body,
|
||||
json=json,
|
||||
headers=auth_context.headers,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from bracket.database import database
|
||||
from bracket.models.db.team import Team
|
||||
from bracket.schema import teams
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
from tests.integration_tests.api.shared import send_tournament_request
|
||||
from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request
|
||||
from tests.integration_tests.models import AuthContext
|
||||
from tests.integration_tests.sql import inserted_team
|
||||
from tests.integration_tests.sql import assert_row_count_and_clear, inserted_team
|
||||
|
||||
|
||||
async def test_teams_endpoint(
|
||||
@@ -21,3 +25,42 @@ async def test_teams_endpoint(
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_create_team(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'name': 'Some new name', 'active': True, 'player_ids': []}
|
||||
response = await send_tournament_request(HTTPMethod.POST, 'teams', auth_context, None, body)
|
||||
assert response['data']['name'] == body['name'] # type: ignore[call-overload]
|
||||
await assert_row_count_and_clear(teams, 1)
|
||||
|
||||
|
||||
async def test_delete_team(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
async with inserted_team(DUMMY_TEAM1) as team_inserted:
|
||||
assert (
|
||||
await send_tournament_request(
|
||||
HTTPMethod.DELETE, f'teams/{team_inserted.id}', auth_context, {}
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(teams, 0)
|
||||
|
||||
|
||||
async def test_update_team(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'name': 'Some new name', 'active': True, 'player_ids': []}
|
||||
async with inserted_team(DUMMY_TEAM1) as team_inserted:
|
||||
response = await send_tournament_request(
|
||||
HTTPMethod.PATCH, f'teams/{team_inserted.id}', auth_context, None, body
|
||||
)
|
||||
patched_round = await fetch_one_parsed_certain(
|
||||
database, Team, query=teams.select().where(teams.c.id == team_inserted.id)
|
||||
)
|
||||
assert patched_round.name == body['name']
|
||||
assert response['data']['name'] == body['name'] # type: ignore[call-overload]
|
||||
|
||||
await assert_row_count_and_clear(teams, 1)
|
||||
|
||||
19
backend/tests/integration_tests/api/tournaments_test.py
Normal file
19
backend/tests/integration_tests/api/tournaments_test.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME
|
||||
from bracket.utils.http import HTTPMethod
|
||||
from tests.integration_tests.api.shared import send_auth_request
|
||||
from tests.integration_tests.models import AuthContext
|
||||
|
||||
|
||||
async def test_tournaments_endpoint(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
assert await send_auth_request(HTTPMethod.GET, 'tournaments', auth_context, {}) == {
|
||||
'data': [
|
||||
{
|
||||
'id': auth_context.tournament.id,
|
||||
'club_id': auth_context.club.id,
|
||||
'created': DUMMY_MOCK_TIME.isoformat(),
|
||||
'name': 'Some Cool Tournament',
|
||||
}
|
||||
],
|
||||
}
|
||||
@@ -19,6 +19,11 @@ from tests.integration_tests.mocks import MOCK_USER, get_mock_token
|
||||
from tests.integration_tests.models import AuthContext
|
||||
|
||||
|
||||
async def assert_row_count_and_clear(table: Table, expected_rows: int) -> None:
|
||||
assert len(await database.fetch_all(query=table.select())) == expected_rows
|
||||
await database.execute(query=table.delete())
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_generic(
|
||||
data_model: BaseModelT, table: Table, return_type: Type[BaseModelT]
|
||||
|
||||
Reference in New Issue
Block a user