Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev
This commit is contained in:
@@ -16,7 +16,6 @@ def test_base_metadata_collects_declared_tables() -> None:
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
def test_shared_mixins_define_expected_columns() -> None:
|
||||
assert "id" in UUIDPrimaryKeyMixin.__dict__
|
||||
assert "created_at" in TimestampMixin.__dict__
|
||||
@@ -24,20 +23,26 @@ def test_shared_mixins_define_expected_columns() -> None:
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
def test_expected_tables_are_registered() -> None:
|
||||
assert set(Base.metadata.tables) == {
|
||||
"refresh_tokens",
|
||||
"config_profile_includes",
|
||||
"config_profiles",
|
||||
"git_repositories",
|
||||
"health_checks",
|
||||
"instance_events",
|
||||
"notifications",
|
||||
"projects",
|
||||
"ssh_keys",
|
||||
"terminal_sessions",
|
||||
"tool_definition_manifests",
|
||||
"tool_instances",
|
||||
"tool_types",
|
||||
"user_configs",
|
||||
"users",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
def test_user_table_has_required_columns() -> None:
|
||||
columns = User.__table__.columns
|
||||
|
||||
@@ -56,7 +61,6 @@ def test_user_table_has_required_columns() -> None:
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
def test_project_relationships_point_to_owner_and_default_ssh_key() -> None:
|
||||
owner_fk = next(iter(Project.__table__.c.owner_id.foreign_keys))
|
||||
ssh_fk = next(iter(Project.__table__.c.default_ssh_key_id.foreign_keys))
|
||||
@@ -68,7 +72,6 @@ def test_project_relationships_point_to_owner_and_default_ssh_key() -> None:
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
def test_repository_and_user_config_relationships_are_registered() -> None:
|
||||
project_fk = next(iter(GitRepository.__table__.c.project_id.foreign_keys))
|
||||
owner_fk = next(iter(GitRepository.__table__.c.owner_id.foreign_keys))
|
||||
@@ -84,9 +87,13 @@ def test_repository_and_user_config_relationships_are_registered() -> None:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
|
||||
async def test_async_session_can_insert_and_load_user(db_session: AsyncSession) -> None:
|
||||
user = User(email="dev@headquarter.local", name="Dev User", authentik_id="dev-user", avatar_url=None)
|
||||
user = User(
|
||||
email="dev@headquarter.local",
|
||||
name="Dev User",
|
||||
authentik_id="dev-user",
|
||||
avatar_url=None,
|
||||
)
|
||||
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
"""Integration tests for notifications API."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src.models.user import User
|
||||
from src.models.user_config import UserConfig
|
||||
from src.services.notification_service import NotificationService
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def notification_service() -> NotificationService:
|
||||
return NotificationService()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def user_a(db_session: AsyncSession) -> User:
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="user-a@headquarter.local",
|
||||
name="User A",
|
||||
authentik_id=f"authentik-{uuid.uuid4()}",
|
||||
avatar_url=None,
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def user_b(db_session: AsyncSession) -> User:
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="user-b@headquarter.local",
|
||||
name="User B",
|
||||
authentik_id=f"authentik-{uuid.uuid4()}",
|
||||
avatar_url=None,
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
|
||||
|
||||
def _mint_cookie_for_user(test_client: TestClient, user_id: uuid.UUID) -> None:
|
||||
from src.auth.session import create_session_cookie
|
||||
from src.config import Settings
|
||||
|
||||
settings = Settings()
|
||||
cookie = create_session_cookie(
|
||||
settings=settings,
|
||||
user_id=str(user_id),
|
||||
)
|
||||
test_client.cookies.set("session", cookie)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_list_requires_auth(test_client: TestClient) -> None:
|
||||
response = test_client.get("/notifications")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_list_returns_only_own_notifications(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
async def create_notifications() -> None:
|
||||
await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="A"
|
||||
)
|
||||
await notification_service.create_notification(
|
||||
db_session, user_b.id, category="instance", severity="info", title="B"
|
||||
)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.run(create_notifications())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.get("/notifications")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["items"]) == 1
|
||||
assert data["items"][0]["title"] == "A"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_list_pagination(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def create_many() -> None:
|
||||
for i in range(25):
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title=f"Notification {i}",
|
||||
)
|
||||
n.created_at = datetime.now(timezone.utc) - timedelta(seconds=i)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(n)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.run(create_many())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.get("/notifications?limit=10&offset=10")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["items"]) == 10
|
||||
assert data["total"] == 25
|
||||
assert data["limit"] == 10
|
||||
assert data["offset"] == 10
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_unread_count_endpoint(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def create_unread() -> None:
|
||||
for _ in range(3):
|
||||
await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Unread",
|
||||
)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.run(create_unread())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.get("/notifications/unread")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["count"] == 3
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_mark_read_endpoint(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def create_and_get() -> uuid.UUID:
|
||||
n = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="To read"
|
||||
)
|
||||
return n.id
|
||||
|
||||
import asyncio
|
||||
|
||||
nid = asyncio.run(create_and_get())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.patch(f"/notifications/{nid}/read")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["read_at"] is not None
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_mark_read_404_for_other_user(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
async def create_and_get() -> uuid.UUID:
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Owned by A",
|
||||
)
|
||||
return n.id
|
||||
|
||||
import asyncio
|
||||
|
||||
nid = asyncio.run(create_and_get())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_b.id)
|
||||
response = authenticated_client.patch(f"/notifications/{nid}/read")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_mark_all_read_endpoint(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def create_unread() -> None:
|
||||
for _ in range(4):
|
||||
await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Unread",
|
||||
)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.run(create_unread())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.post("/notifications/mark-all-read")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["marked_count"] == 4
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_dismiss_endpoint(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def create_and_get() -> uuid.UUID:
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="To dismiss",
|
||||
)
|
||||
return n.id
|
||||
|
||||
import asyncio
|
||||
|
||||
nid = asyncio.run(create_and_get())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.delete(f"/notifications/{nid}")
|
||||
assert response.status_code == 204
|
||||
|
||||
response = authenticated_client.get("/notifications")
|
||||
data = response.json()
|
||||
assert len(data["items"]) == 0
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_dismiss_404_for_other_user(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
async def create_and_get() -> uuid.UUID:
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Owned by A",
|
||||
)
|
||||
return n.id
|
||||
|
||||
import asyncio
|
||||
|
||||
nid = asyncio.run(create_and_get())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_b.id)
|
||||
response = authenticated_client.delete(f"/notifications/{nid}")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_mute_categories_filter_in_list(
|
||||
authenticated_client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
async def setup() -> None:
|
||||
config = UserConfig(
|
||||
user_id=user_a.id, config={"notification_mute_categories": ["instance"]}
|
||||
)
|
||||
db_session.add(config)
|
||||
await db_session.commit()
|
||||
|
||||
await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Instance",
|
||||
)
|
||||
await notification_service.create_notification(
|
||||
db_session, user_a.id, category="system", severity="info", title="System"
|
||||
)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.run(setup())
|
||||
|
||||
_mint_cookie_for_user(authenticated_client, user_a.id)
|
||||
response = authenticated_client.get("/notifications")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["items"]) == 1
|
||||
assert data["items"][0]["title"] == "System"
|
||||
@@ -0,0 +1,334 @@
|
||||
"""Unit tests for NotificationService."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src.models.notification import Notification
|
||||
from src.models.user import User
|
||||
from src.services.notification_service import NotificationService
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def notification_service() -> NotificationService:
|
||||
return NotificationService()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def user_a(db_session: AsyncSession) -> User:
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="user-a@headquarter.local",
|
||||
name="User A",
|
||||
authentik_id=f"authentik-{uuid.uuid4()}",
|
||||
avatar_url=None,
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def user_b(db_session: AsyncSession) -> User:
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="user-b@headquarter.local",
|
||||
name="User B",
|
||||
authentik_id=f"authentik-{uuid.uuid4()}",
|
||||
avatar_url=None,
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_notification(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
notification = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Container started",
|
||||
message="Instance is running",
|
||||
source_type="tool_instances",
|
||||
source_id=uuid.uuid4(),
|
||||
metadata={"key": "value"},
|
||||
)
|
||||
|
||||
assert notification.user_id == user_a.id
|
||||
assert notification.category == "instance"
|
||||
assert notification.severity == "info"
|
||||
assert notification.title == "Container started"
|
||||
assert notification.message == "Instance is running"
|
||||
assert notification.source_type == "tool_instances"
|
||||
assert notification.notification_metadata == {"key": "value"}
|
||||
assert notification.read_at is None
|
||||
assert notification.dismissed_at is None
|
||||
assert notification.created_at is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notifications_orders_by_created_at_desc(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n1 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="First"
|
||||
)
|
||||
n1.created_at = datetime.now(timezone.utc) - timedelta(seconds=2)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(n1)
|
||||
|
||||
n2 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Second"
|
||||
)
|
||||
n2.created_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(n2)
|
||||
|
||||
n3 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Third"
|
||||
)
|
||||
|
||||
items, total = await notification_service.list_notifications(db_session, user_a.id)
|
||||
|
||||
assert total == 3
|
||||
assert [item.id for item in items] == [n3.id, n2.id, n1.id]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notifications_excludes_dismissed(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n1 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Visible"
|
||||
)
|
||||
n2 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Dismissed"
|
||||
)
|
||||
await notification_service.dismiss(db_session, n2.id, user_a.id)
|
||||
|
||||
items, total = await notification_service.list_notifications(db_session, user_a.id)
|
||||
|
||||
assert total == 1
|
||||
assert items[0].id == n1.id
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notifications_unread_only(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n1 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Unread"
|
||||
)
|
||||
n2 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Read"
|
||||
)
|
||||
await notification_service.mark_read(db_session, n2.id, user_a.id)
|
||||
|
||||
items, total = await notification_service.list_notifications(
|
||||
db_session, user_a.id, unread_only=True
|
||||
)
|
||||
|
||||
assert total == 1
|
||||
assert items[0].id == n1.id
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unread_count(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
for i in range(5):
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title=f"Notification {i}",
|
||||
)
|
||||
if i >= 3:
|
||||
await notification_service.mark_read(db_session, n.id, user_a.id)
|
||||
|
||||
count = await notification_service.get_unread_count(db_session, user_a.id)
|
||||
|
||||
assert count == 3
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_read_sets_read_at(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Unread"
|
||||
)
|
||||
|
||||
updated = await notification_service.mark_read(db_session, n.id, user_a.id)
|
||||
|
||||
assert updated.read_at is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_all_read_affects_all_unread(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
for i in range(4):
|
||||
await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title=f"Notification {i}",
|
||||
)
|
||||
|
||||
marked = await notification_service.mark_all_read(db_session, user_a.id)
|
||||
|
||||
assert marked == 4
|
||||
count = await notification_service.get_unread_count(db_session, user_a.id)
|
||||
assert count == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_dismiss_sets_dismissed_at(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="To dismiss"
|
||||
)
|
||||
|
||||
await notification_service.dismiss(db_session, n.id, user_a.id)
|
||||
|
||||
result = await db_session.execute(
|
||||
select(Notification).where(Notification.id == n.id)
|
||||
)
|
||||
row = result.scalar_one()
|
||||
assert row.dismissed_at is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_read_wrong_owner_raises(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
n = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Owned by A"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Notification not found"):
|
||||
await notification_service.mark_read(db_session, n.id, user_b.id)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_dismiss_wrong_owner_raises(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
n = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Owned by A"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Notification not found"):
|
||||
await notification_service.dismiss(db_session, n.id, user_b.id)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notifications_mute_categories(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title="Instance"
|
||||
)
|
||||
n2 = await notification_service.create_notification(
|
||||
db_session, user_a.id, category="system", severity="info", title="System"
|
||||
)
|
||||
|
||||
items, total = await notification_service.list_notifications(
|
||||
db_session, user_a.id, mute_categories=["instance"]
|
||||
)
|
||||
|
||||
assert total == 1
|
||||
assert items[0].id == n2.id
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unread_count_excludes_dismissed(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
) -> None:
|
||||
n = await notification_service.create_notification(
|
||||
db_session,
|
||||
user_a.id,
|
||||
category="instance",
|
||||
severity="info",
|
||||
title="Unread dismissed",
|
||||
)
|
||||
await notification_service.dismiss(db_session, n.id, user_a.id)
|
||||
|
||||
count = await notification_service.get_unread_count(db_session, user_a.id)
|
||||
|
||||
assert count == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_all_read_affects_only_caller(
|
||||
db_session: AsyncSession,
|
||||
notification_service: NotificationService,
|
||||
user_a: User,
|
||||
user_b: User,
|
||||
) -> None:
|
||||
for i in range(3):
|
||||
await notification_service.create_notification(
|
||||
db_session, user_a.id, category="instance", severity="info", title=f"A-{i}"
|
||||
)
|
||||
for i in range(2):
|
||||
await notification_service.create_notification(
|
||||
db_session, user_b.id, category="instance", severity="info", title=f"B-{i}"
|
||||
)
|
||||
|
||||
marked = await notification_service.mark_all_read(db_session, user_a.id)
|
||||
|
||||
assert marked == 3
|
||||
count_a = await notification_service.get_unread_count(db_session, user_a.id)
|
||||
count_b = await notification_service.get_unread_count(db_session, user_b.id)
|
||||
assert count_a == 0
|
||||
assert count_b == 2
|
||||
Reference in New Issue
Block a user