Skip to content

Commit

Permalink
Fix db initialisation (#427)
Browse files Browse the repository at this point in the history
fixes #426
  • Loading branch information
evroon committed Feb 4, 2024
1 parent 7dffea5 commit 31818c3
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 101 deletions.
18 changes: 5 additions & 13 deletions backend/bracket/cronjobs/scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
from heliclockter import timedelta

from bracket.models.db.account import UserAccountType
from bracket.sql.clubs import get_clubs_for_user_id, sql_delete_club
from bracket.sql.tournaments import sql_delete_tournament_completely, sql_get_tournaments
from bracket.sql.users import delete_user, get_expired_demo_users
from bracket.sql.users import delete_user_and_owned_clubs, get_expired_demo_users
from bracket.utils.asyncio import AsyncioTasksManager
from bracket.utils.logging import logger
from bracket.utils.types import assert_some
Expand All @@ -16,22 +14,16 @@

async def delete_demo_accounts() -> None:
demo_users = await get_expired_demo_users()
if len(demo_users) < 1:
return

logger.info(f"Deleting {len(demo_users)} expired demo accounts")

for demo_user in demo_users:
assert demo_user.account_type is UserAccountType.DEMO
user_id = assert_some(demo_user.id)

for club in await get_clubs_for_user_id(user_id):
club_id = assert_some(club.id)

for tournament in await sql_get_tournaments((club_id,), None):
tournament_id = assert_some(tournament.id)
await sql_delete_tournament_completely(tournament_id)

await sql_delete_club(club_id)

await delete_user(user_id)
await delete_user_and_owned_clubs(user_id)


async def run_cronjob(cronjob_entrypoint: CronjobT, delta_time: timedelta) -> None:
Expand Down
15 changes: 15 additions & 0 deletions backend/bracket/sql/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from bracket.database import database
from bracket.models.db.user import User, UserInDB, UserPublic, UserToUpdate
from bracket.schema import users
from bracket.sql.clubs import get_clubs_for_user_id, sql_delete_club
from bracket.sql.tournaments import sql_delete_tournament_completely, sql_get_tournaments
from bracket.utils.db import fetch_one_parsed
from bracket.utils.types import assert_some

Expand Down Expand Up @@ -112,3 +114,16 @@ async def check_whether_email_is_in_use(email: str) -> bool:

async def get_user(email: str) -> UserInDB | None:
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))


async def delete_user_and_owned_clubs(user_id: int) -> None:
for club in await get_clubs_for_user_id(user_id):
club_id = assert_some(club.id)

for tournament in await sql_get_tournaments((club_id,), None):
tournament_id = assert_some(tournament.id)
await sql_delete_tournament_completely(tournament_id)

await sql_delete_club(club_id)

await delete_user(user_id)
28 changes: 15 additions & 13 deletions backend/bracket/utils/db_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
users_x_clubs,
)
from bracket.sql.stage_items import sql_create_stage_item
from bracket.sql.users import get_user
from bracket.sql.users import create_user, get_user
from bracket.utils.db import insert_generic
from bracket.utils.dummy_records import (
DUMMY_CLUB,
Expand Down Expand Up @@ -68,7 +68,7 @@
)
from bracket.utils.logging import logger
from bracket.utils.security import pwd_context
from bracket.utils.types import BaseModelT
from bracket.utils.types import BaseModelT, assert_some

if TYPE_CHECKING:
from sqlalchemy import Table
Expand All @@ -78,16 +78,16 @@ async def create_admin_user() -> int:
assert config.admin_email
assert config.admin_password

admin = User(
name="Admin",
email=config.admin_email,
password_hash=pwd_context.hash(config.admin_password),
created=datetime_utc.now(),
account_type=UserAccountType.REGULAR,
user = await create_user(
User(
name="Admin",
email=config.admin_email,
password_hash=pwd_context.hash(config.admin_password),
created=datetime_utc.now(),
account_type=UserAccountType.REGULAR,
)
)

user: int = await database.execute(query=users.insert(), values=admin.dict())
return user
return assert_some(user.id)


async def init_db_when_empty() -> int | None:
Expand All @@ -107,10 +107,10 @@ async def init_db_when_empty() -> int | None:
return None


async def sql_create_dev_db() -> None:
async def sql_create_dev_db() -> int:
# TODO: refactor into smaller functions
# pylint: disable=too-many-statements
assert environment is Environment.DEVELOPMENT
assert environment is not Environment.PRODUCTION

logger.warning("Initializing database with dummy records")
await database.connect()
Expand Down Expand Up @@ -321,3 +321,5 @@ async def insert_dummy(obj_to_insert: BaseModelT, update_data: dict[str, Any] =

for tournament in await database.fetch_all(tournaments.select()):
await recalculate_ranking_for_tournament_id(tournament.id) # type: ignore[attr-defined]

return user_id_1
72 changes: 0 additions & 72 deletions backend/tests/integration_tests/api/conftest.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
# pylint: disable=redefined-outer-name
import asyncio
import os
from asyncio import AbstractEventLoop
from collections.abc import AsyncIterator
from time import sleep

import pytest
from databases import Database

from bracket.database import database, engine
from bracket.schema import metadata
from tests.integration_tests.api.shared import UvicornTestServer
from tests.integration_tests.models import AuthContext
from tests.integration_tests.sql import inserted_auth_context


@pytest.fixture(scope="module")
Expand All @@ -26,66 +17,3 @@ async def startup_and_shutdown_uvicorn_server() -> AsyncIterator[None]:
yield
finally:
await server.down()


@pytest.fixture(scope="session")
def event_loop() -> AsyncIterator[AbstractEventLoop]: # type: ignore[misc]
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()

yield loop
loop.close()


@pytest.fixture(scope="session", autouse=True)
async def reinit_database(event_loop: AbstractEventLoop, worker_id: str) -> AsyncIterator[Database]:
"""
Creates the test database on the first test run in the session.
When running in parallel, the first test runner (gw0) creates a "lock" file and initializes the
database. The other runners poll this file and wait until it has been removed by gw0.
When running tests sequentially, the master worker just creates the test database and that's it.
"""
await database.connect()

if worker_id == "master":
metadata.drop_all(engine)
metadata.create_all(engine)

try:
yield database
finally:
await database.disconnect()

return

lock_path = "/tmp/tm_test_lock"

if worker_id == "gw0":
try:
with open(lock_path, mode="w") as file:
file.write("")

metadata.drop_all(engine)
metadata.create_all(engine)
finally:
os.remove(lock_path)
else:
for _ in range(50):
sleep(0.1)
if not os.path.exists(lock_path):
break

try:
yield database
finally:
await database.disconnect()


@pytest.fixture(scope="session")
async def auth_context(reinit_database: Database) -> AsyncIterator[AuthContext]:
async with reinit_database:
async with inserted_auth_context() as auth_context:
yield auth_context
91 changes: 91 additions & 0 deletions backend/tests/integration_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# pylint: disable=redefined-outer-name
import asyncio
import os
from asyncio import AbstractEventLoop
from collections.abc import AsyncIterator
from time import sleep

import pytest
from databases import Database

from bracket.database import database, engine
from bracket.schema import metadata
from tests.integration_tests.api.shared import UvicornTestServer
from tests.integration_tests.models import AuthContext
from tests.integration_tests.sql import inserted_auth_context


@pytest.fixture(scope="module")
async def startup_and_shutdown_uvicorn_server() -> AsyncIterator[None]:
"""
Start server as test fixture and tear down after test
"""
server = UvicornTestServer()
try:
await server.up()
yield
finally:
await server.down()


@pytest.fixture(scope="session")
def event_loop() -> AsyncIterator[AbstractEventLoop]: # type: ignore[misc]
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()

yield loop
loop.close()


@pytest.fixture(scope="session", autouse=True)
async def reinit_database(event_loop: AbstractEventLoop, worker_id: str) -> AsyncIterator[Database]:
"""
Creates the test database on the first test run in the session.
When running in parallel, the first test runner (gw0) creates a "lock" file and initializes the
database. The other runners poll this file and wait until it has been removed by gw0.
When running tests sequentially, the master worker just creates the test database and that's it.
"""
await database.connect()

if worker_id == "master":
metadata.drop_all(engine)
metadata.create_all(engine)

try:
yield database
finally:
await database.disconnect()

return

lock_path = "/tmp/tm_test_lock"

if worker_id == "gw0":
try:
with open(lock_path, mode="w") as file:
file.write("")

metadata.drop_all(engine)
metadata.create_all(engine)
finally:
os.remove(lock_path)
else:
for _ in range(50):
sleep(0.1)
if not os.path.exists(lock_path):
break

try:
yield database
finally:
await database.disconnect()


@pytest.fixture(scope="session")
async def auth_context(reinit_database: Database) -> AsyncIterator[AuthContext]:
async with reinit_database:
async with inserted_auth_context() as auth_context:
yield auth_context
7 changes: 7 additions & 0 deletions backend/tests/integration_tests/db_init_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from bracket.sql.users import delete_user_and_owned_clubs
from bracket.utils.db_init import sql_create_dev_db


async def test_db_init() -> None:
user_id = await sql_create_dev_db()
await delete_user_and_owned_clubs(user_id)
3 changes: 0 additions & 3 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,3 @@ coverage:
patch:
default:
informational: true

ignore:
- "backend/bracket/utils/db_init.py"

0 comments on commit 31818c3

Please sign in to comment.