Files
govoplan-notifications/tests/test_notification_list_efficiency.py
T

141 lines
5.1 KiB
Python

from __future__ import annotations
import unittest
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session
from govoplan_core.db.base import Base
from govoplan_notifications.backend.db.models import (
NotificationDeliveryAttempt,
NotificationMessage,
)
from govoplan_notifications.backend.schemas import NotificationCreateRequest
from govoplan_notifications.backend.service import (
create_notification,
list_notifications,
notification_response,
)
class NotificationListEfficiencyTests(unittest.TestCase):
def setUp(self) -> None:
self.engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(
self.engine,
tables=[
NotificationMessage.__table__,
NotificationDeliveryAttempt.__table__,
],
)
def tearDown(self) -> None:
self.engine.dispose()
def seed(self, count: int) -> str:
with Session(self.engine) as session:
first_id = ""
for index in range(count + 2):
row = create_notification(
session,
tenant_id="tenant-other" if index == count else "tenant-one",
payload=NotificationCreateRequest(
source_module="test",
source_resource_type="record",
event_kind="changed",
enqueue_delivery=False,
recipient_id="other-recipient"
if index == count + 1
else "reader",
subject=f"Fixture {index}",
),
)
if not first_id:
first_id = row.id
session.add(
NotificationDeliveryAttempt(
notification_id=row.id,
tenant_id=row.tenant_id,
attempt_no=1,
channel="inbox",
status="failed",
)
)
session.commit()
return first_id
def test_list_and_full_attempt_projection_use_two_queries_independent_of_page_size(
self,
) -> None:
for count in (1, 40):
with self.subTest(count=count):
first_id = self.seed(count)
statements: list[str] = []
def count_selects(
_connection, _cursor, statement, _parameters, _context, _many
):
if statement.lstrip().upper().startswith("SELECT"):
statements.append(statement)
event.listen(self.engine, "before_cursor_execute", count_selects)
try:
with Session(self.engine) as session:
rows = list_notifications(
session,
tenant_id="tenant-one",
recipient_ids=("reader",),
limit=count,
)
payloads = [notification_response(row) for row in rows]
self.assertEqual(count, len(payloads))
self.assertTrue(
all(len(item["attempts"]) == 1 for item in payloads)
)
self.assertTrue(
all(
item["tenant_id"] == "tenant-one"
and item["recipient_id"] == "reader"
for item in payloads
)
)
self.assertEqual(
2,
len(statements),
"The list and attempt projection must not add a query per message.",
)
finally:
event.remove(self.engine, "before_cursor_execute", count_selects)
self.assertTrue(first_id)
def test_attempts_with_inconsistent_tenant_are_never_projected_even_from_a_loaded_relationship(
self,
) -> None:
first_id = self.seed(1)
with Session(self.engine) as session:
session.add(
NotificationDeliveryAttempt(
id="foreign-attempt",
notification_id=first_id,
tenant_id="tenant-other",
attempt_no=2,
channel="mail",
status="failed",
error="Foreign tenant evidence",
)
)
session.commit()
with Session(self.engine) as session:
row = session.get(NotificationMessage, first_id)
self.assertEqual(2, len(row.attempts))
self.assertEqual(1, len(notification_response(row)["attempts"]))
with Session(self.engine) as session:
rows = list_notifications(
session, tenant_id="tenant-one", recipient_ids=("reader",)
)
self.assertEqual([1], [len(row.attempts) for row in rows])
if __name__ == "__main__":
unittest.main()