Skip to content

Commit 1bf68ae

Browse files
authored
test: pin chat listing to a constant number of SQL statements (#46)
1 parent 2a6eea9 commit 1bf68ae

3 files changed

Lines changed: 68 additions & 0 deletions

File tree

‎tests/api/helpers.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import contextlib
12
import typing
23
import uuid
4+
from collections.abc import Iterator
35

6+
import sqlalchemy as sa
47
from httpx import AsyncClient
8+
from sqlalchemy import event
9+
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession
510

611

712
async def register(client: AsyncClient, username: str) -> int:
@@ -33,3 +38,20 @@ async def send(client: AsyncClient, chat_id: int, text: str, key: uuid.UUID | No
3338
json={"idempotency_key": str(key or uuid.uuid4()), "text": text},
3439
)
3540
return response.json()
41+
42+
43+
@contextlib.contextmanager
44+
def count_statements(session: AsyncSession) -> Iterator[list[str]]:
45+
"""Collect every SQL statement sent on the test connection behind ``session`` while the block runs."""
46+
connection: typing.Final = session.bind
47+
assert isinstance(connection, AsyncConnection)
48+
statements: typing.Final[list[str]] = []
49+
50+
def record(_conn: sa.Connection, _cursor: object, statement: str, *_: object) -> None:
51+
statements.append(statement)
52+
53+
event.listen(connection.sync_connection, "before_cursor_execute", record)
54+
try:
55+
yield statements
56+
finally:
57+
event.remove(connection.sync_connection, "before_cursor_execute", record)

‎tests/api/test_chat_listing_api.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import pytest
22
from httpx import AsyncClient
3+
from sqlalchemy.ext.asyncio import AsyncSession
34

5+
from tests.api.helpers import count_statements as _count_statements
46
from tests.api.helpers import create_direct_chat as _create_direct_chat
57
from tests.api.helpers import login as _login
68
from tests.api.helpers import register as _register
@@ -37,6 +39,31 @@ async def test_listing_populates_unread_count_and_last_message(client: AsyncClie
3739
assert item["last_message"]["text"] == "two"
3840

3941

42+
async def test_listing_runs_the_same_number_of_statements_for_one_chat_as_for_many(
43+
client: AsyncClient, db_session: AsyncSession
44+
) -> None:
45+
one_chat_id, _ = await _create_direct_chat(client)
46+
await _send(client, one_chat_id, "hello")
47+
partner_ids = [await _register(client, f"partner{index}") for index in range(5)]
48+
await _register(client, "carol")
49+
for partner_id in partner_ids:
50+
chat = await client.post("/api/chats/", json={"chat_type": "direct", "member_ids": [partner_id]})
51+
await _send(client, chat.json()["id"], "hello")
52+
53+
with _count_statements(db_session) as many_chats_statements:
54+
many_chats_response = await client.get("/api/chats/")
55+
await _login(client, "alice")
56+
with _count_statements(db_session) as one_chat_statements:
57+
one_chat_response = await client.get("/api/chats/")
58+
59+
many_chats_items = many_chats_response.json()["items"]
60+
one_chat_items = one_chat_response.json()["items"]
61+
assert len(many_chats_items) == 5
62+
assert len(one_chat_items) == 1
63+
assert all(item["last_message"] is not None for item in many_chats_items + one_chat_items)
64+
assert len(many_chats_statements) == len(one_chat_statements), many_chats_statements
65+
66+
4067
@pytest.mark.usefixtures("db_session")
4168
async def test_chat_with_no_messages_has_null_last_message_and_zero_unread(client: AsyncClient) -> None:
4269
await _create_direct_chat(client)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import sqlalchemy as sa
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from app.database.resources import create_database_engine
5+
from tests.api.helpers import count_statements as _count_statements
6+
7+
8+
async def test_count_statements_ignores_other_connections(db_session: AsyncSession) -> None:
9+
engine = create_database_engine()
10+
try:
11+
with _count_statements(db_session) as statements:
12+
async with engine.connect() as other_connection:
13+
await other_connection.execute(sa.text("SELECT 1"))
14+
await db_session.execute(sa.text("SELECT 2"))
15+
finally:
16+
await engine.dispose()
17+
18+
assert "SELECT 1" not in statements
19+
assert "SELECT 2" in statements

0 commit comments

Comments
 (0)