141 lines
5.1 KiB
Python
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()
|