Add more integration tests (#41)

This commit is contained in:
Erik Vroon
2022-12-27 17:27:08 +01:00
committed by GitHub
parent cca3aedb7f
commit e77eecb09c
11 changed files with 218 additions and 18 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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]:

View File

@@ -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'}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
)

View File

@@ -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)

View 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',
}
],
}

View File

@@ -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]