84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""Tests for the notification store's per-user read-state contract.
|
|||
|
|
|
||
|
|
These pin the core property that makes broadcast-to-everyone work without a
|
||
|
|
subscription model: one notification row is shared by all users, and each
|
||
|
|
user's read state is tracked independently against it.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from app.infrastructure.perception.mock_notification_store import MockNotificationStore
|
||
|
|
|
||
|
|
|
||
|
|
def _store_with_one_notification() -> MockNotificationStore:
|
||
|
|
store = MockNotificationStore()
|
||
|
|
store.create(
|
||
|
|
event_id="evt-001",
|
||
|
|
kind="new",
|
||
|
|
title="《电动汽车安全要求》国家标准第三版正式发布",
|
||
|
|
impact_level="high",
|
||
|
|
summary=None,
|
||
|
|
)
|
||
|
|
return store
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_new_notification_is_unread_for_everyone():
|
||
|
|
"""Nobody has read it yet, so unread_count is 1 for any user."""
|
||
|
|
store = _store_with_one_notification()
|
||
|
|
assert store.unread_count("user-a") == 1
|
||
|
|
assert store.unread_count("user-b") == 1
|
||
|
|
|
||
|
|
|
||
|
|
def test_mark_all_read_zeroes_the_count_for_that_user():
|
||
|
|
"""Reading clears the count for the user who read it."""
|
||
|
|
store = _store_with_one_notification()
|
||
|
|
marked = store.mark_all_read("user-a")
|
||
|
|
assert marked == 1
|
||
|
|
assert store.unread_count("user-a") == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_one_users_read_state_does_not_affect_another():
|
||
|
|
"""The whole point of read receipts over per-user fan-out: independence."""
|
||
|
|
store = _store_with_one_notification()
|
||
|
|
store.mark_all_read("user-a")
|
||
|
|
assert store.unread_count("user-a") == 0
|
||
|
|
assert store.unread_count("user-b") == 1
|
||
|
|
|
||
|
|
|
||
|
|
def test_list_for_user_reports_the_read_flag_correctly():
|
||
|
|
"""The list endpoint must reflect this user's own read state per item."""
|
||
|
|
store = _store_with_one_notification()
|
||
|
|
store.mark_all_read("user-a")
|
||
|
|
|
||
|
|
items_a = store.list_for_user("user-a")
|
||
|
|
items_b = store.list_for_user("user-b")
|
||
|
|
|
||
|
|
assert len(items_a) == 1
|
||
|
|
assert items_a[0]["read"] is True
|
||
|
|
assert items_b[0]["read"] is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_list_for_user_orders_newest_first():
|
||
|
|
"""Notifications appear most-recent-first regardless of creation order."""
|
||
|
|
store = MockNotificationStore()
|
||
|
|
store.create(event_id="evt-1", kind="new", title="first", impact_level="low", summary=None)
|
||
|
|
store.create(event_id="evt-2", kind="new", title="second", impact_level="low", summary=None)
|
||
|
|
|
||
|
|
items = store.list_for_user("user-a")
|
||
|
|
|
||
|
|
assert [item["title"] for item in items] == ["second", "first"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_mark_all_read_is_a_no_op_on_an_empty_feed():
|
||
|
|
"""Reading an empty feed must not raise and reports zero marked."""
|
||
|
|
store = MockNotificationStore()
|
||
|
|
assert store.mark_all_read("user-a") == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_mark_all_read_does_not_recount_already_read_notifications():
|
||
|
|
"""Calling mark_all_read twice must not double-count as newly marked."""
|
||
|
|
store = _store_with_one_notification()
|
||
|
|
first = store.mark_all_read("user-a")
|
||
|
|
second = store.mark_all_read("user-a")
|
||
|
|
assert first == 1
|
||
|
|
assert second == 0
|