Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/database/resources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import typing

from db_retry import Transaction
from sqlalchemy.ext import asyncio as sa

from app.settings import settings
Expand Down Expand Up @@ -34,3 +35,10 @@ def create_session(engine: sa.AsyncEngine | sa.AsyncConnection) -> sa.AsyncSessi
async def close_session(session: sa.AsyncSession) -> None:
task: typing.Final = asyncio.create_task(session.close())
await asyncio.shield(task)


def create_autocommit_transaction(session: sa.AsyncSession) -> Transaction:
# Isolation level can only be set before the session holds a connection; test sessions are bound to one.
if isinstance(session.bind, sa.AsyncEngine) and not session.in_transaction():
return Transaction(session=session, isolation_level="AUTOCOMMIT")
return Transaction(session=session)
10 changes: 9 additions & 1 deletion app/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class Database(Group):
creator=Transaction,
kwargs={"session": database_session},
)
autocommit_transaction = providers.Factory(
scope=Scope.REQUEST,
creator=database_resources.create_autocommit_transaction,
bound_type=None,
kwargs={"session": database_session},
)


class Repositories(Group, scope=Scope.REQUEST):
Expand Down Expand Up @@ -77,7 +83,9 @@ class UseCases(Group, scope=Scope.REQUEST):
fetch_user_use_case = providers.Factory(creator=FetchUserUseCase)
mark_read_use_case = providers.Factory(creator=MarkReadUseCase)
revoke_token_use_case = providers.Factory(creator=RevokeTokenUseCase)
check_token_revoked_use_case = providers.Factory(creator=CheckTokenRevokedUseCase)
check_token_revoked_use_case = providers.Factory(
creator=CheckTokenRevokedUseCase, kwargs={"transaction": Database.autocommit_transaction}
)


ALL_GROUPS: typing.Final[list[type[Group]]] = [Database, Repositories, UseCases]
30 changes: 30 additions & 0 deletions tests/use_cases/test_check_token_revoked.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import asyncio
import typing

import asyncpg
import modern_di

from app import ioc
from app.database.resources import create_database_engine


async def test_revocation_check_sends_no_transaction_statements(di_container: modern_di.Container) -> None:
engine: typing.Final = create_database_engine()
di_container.override(ioc.Database.database_engine, engine)
queries: typing.Final[list[asyncpg.connection.LoggedQuery]] = []

try:
async with engine.connect() as connection:
driver_connection = (await connection.get_raw_connection()).driver_connection
assert isinstance(driver_connection, asyncpg.Connection)
driver_connection.add_query_logger(queries.append)

async with di_container.build_child_container(scope=modern_di.Scope.REQUEST) as container:
check_token_revoked = container.resolve_provider(ioc.UseCases.check_token_revoked_use_case)
assert await check_token_revoked(jti="unknown") is False
await asyncio.sleep(0)
finally:
di_container.reset_override(ioc.Database.database_engine)
await engine.dispose()

assert not {"BEGIN;", "COMMIT;", "ROLLBACK;"} & {logged.query for logged in queries}, queries
Loading