feat: 重构 Reminder Notification 系统并更新应用包名
This commit is contained in:
@@ -4,7 +4,7 @@ import json
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
@@ -27,6 +27,7 @@ class _FakeService:
|
||||
created_request: Any = None
|
||||
created_id: str = field(default_factory=lambda: str(uuid4()))
|
||||
list_calls: list[dict[str, Any]] = field(default_factory=list)
|
||||
range_calls: list[dict[str, Any]] = field(default_factory=list)
|
||||
deleted_ids: list[str] = field(default_factory=list)
|
||||
|
||||
async def list_paginated(
|
||||
@@ -47,6 +48,29 @@ class _FakeService:
|
||||
)
|
||||
return [item], 1
|
||||
|
||||
async def list_by_date_range(self, request: Any):
|
||||
self.range_calls.append(
|
||||
{
|
||||
"start_at": request.start_at,
|
||||
"end_at": request.end_at,
|
||||
}
|
||||
)
|
||||
return [
|
||||
SimpleNamespace(
|
||||
id=UUID(self.created_id),
|
||||
owner_id=uuid4(),
|
||||
title="会议",
|
||||
description="今天下午五点的会议",
|
||||
start_at=datetime(2026, 3, 17, 9, 0, tzinfo=timezone.utc),
|
||||
end_at=datetime(2026, 3, 17, 9, 30, tzinfo=timezone.utc),
|
||||
timezone="Asia/Shanghai",
|
||||
status="active",
|
||||
source_type="manual",
|
||||
metadata=None,
|
||||
subscribers=[],
|
||||
)
|
||||
]
|
||||
|
||||
async def create_agent_generated(self, request):
|
||||
self.created_request = request
|
||||
return SimpleNamespace(
|
||||
@@ -235,22 +259,48 @@ async def test_calendar_read_returns_structured_result_with_ids(
|
||||
)
|
||||
|
||||
result = await calendar_module.calendar_read(
|
||||
query="会议",
|
||||
page=1,
|
||||
page_size=20,
|
||||
start_at="2026-03-17T00:00:00+08:00",
|
||||
end_at="2026-03-18T00:00:00+08:00",
|
||||
session=SimpleNamespace(),
|
||||
owner_id=uuid4(),
|
||||
)
|
||||
payload = _decode_tool_response(result)
|
||||
result_data = json.loads(payload["result"])
|
||||
|
||||
assert payload["status"] == "success"
|
||||
assert result_data["total"] == 1
|
||||
assert result_data["items"][0]["id"] == fake_service.created_id
|
||||
assert result_data["items"][0]["timezone"] == "Asia/Shanghai"
|
||||
assert result_data["items"][0]["description"] == "今天下午五点的会议"
|
||||
assert result_data["items"][0]["status"] == "active"
|
||||
assert fake_service.range_calls == [
|
||||
{
|
||||
"start_at": datetime(2026, 3, 16, 16, 0, tzinfo=timezone.utc),
|
||||
"end_at": datetime(2026, 3, 17, 16, 0, tzinfo=timezone.utc),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_calendar_read_rejects_naive_datetime_string(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
fake_service = _FakeService()
|
||||
monkeypatch.setattr(
|
||||
calendar_module, "create_schedule_service", lambda *_: fake_service
|
||||
)
|
||||
|
||||
result = await calendar_module.calendar_read(
|
||||
start_at="2026-03-17T00:00:00",
|
||||
end_at="2026-03-18T00:00:00+08:00",
|
||||
session=SimpleNamespace(),
|
||||
owner_id=uuid4(),
|
||||
)
|
||||
payload = _decode_tool_response(result)
|
||||
|
||||
assert payload["status"] == "success"
|
||||
assert payload["result"].startswith("status=success")
|
||||
assert "total=1" in payload["result"]
|
||||
assert "timezone=Asia/Shanghai" in payload["result"]
|
||||
assert "description=今天下午五点的会议" in payload["result"]
|
||||
assert "status=active" in payload["result"]
|
||||
assert fake_service.created_id in payload["result"]
|
||||
assert fake_service.list_calls == [{"page": 1, "page_size": 20, "query": "会议"}]
|
||||
assert payload["status"] == "failure"
|
||||
assert payload["error"]["code"] == "INVALID_ARGUMENT"
|
||||
assert "时区" in payload["error"]["message"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -312,3 +362,39 @@ async def test_calendar_share_rejects_invalid_phone(
|
||||
|
||||
assert payload["status"] == "failure"
|
||||
assert payload["error"]["code"] == "INVALID_ARGUMENT"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_calendar_share_accepts_json_invitee_payload(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
fake_service = _FakeService()
|
||||
monkeypatch.setattr(
|
||||
calendar_module, "create_schedule_service", lambda *_: fake_service
|
||||
)
|
||||
event_id = str(uuid4())
|
||||
|
||||
result = await calendar_module.calendar_share(
|
||||
event_id=event_id,
|
||||
invitees=cast(
|
||||
Any,
|
||||
[
|
||||
{
|
||||
"phone": "8613900001234",
|
||||
"permissionView": True,
|
||||
"permissionEdit": False,
|
||||
"permissionInvite": False,
|
||||
}
|
||||
],
|
||||
),
|
||||
session=SimpleNamespace(),
|
||||
owner_id=uuid4(),
|
||||
)
|
||||
payload = _decode_tool_response(result)
|
||||
|
||||
assert payload["status"] == "success"
|
||||
assert payload["result"].startswith("status=success success=1 failed=0")
|
||||
assert len(fake_service.share_calls) == 1
|
||||
share_call = fake_service.share_calls[0]
|
||||
assert share_call["item_id"] == event_id
|
||||
assert share_call["request"].phone == "+8613900001234"
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from core.runtime import cli
|
||||
|
||||
|
||||
class _FakeScheduler:
|
||||
def __init__(self) -> None:
|
||||
self.started = False
|
||||
self.shutdown_called = False
|
||||
self.jobs: list[dict[str, Any]] = []
|
||||
|
||||
def add_job(self, func: Any, **kwargs: Any) -> None:
|
||||
self.jobs.append({"func": func, **kwargs})
|
||||
|
||||
def start(self) -> None:
|
||||
self.started = True
|
||||
|
||||
def shutdown(self, *, wait: bool) -> None:
|
||||
self.shutdown_called = True
|
||||
self.shutdown_wait = wait
|
||||
|
||||
|
||||
class _StopEvent:
|
||||
async def wait(self) -> None:
|
||||
raise asyncio.CancelledError
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_automation_scheduler_forever_uses_async_scheduler(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
fake_scheduler = _FakeScheduler()
|
||||
dispatch_limits: list[int] = []
|
||||
|
||||
async def _fake_scan(*, limit: int) -> None:
|
||||
dispatch_limits.append(limit)
|
||||
|
||||
monkeypatch.setattr(cli, "AsyncIOScheduler", lambda: fake_scheduler)
|
||||
monkeypatch.setattr(cli, "run_automation_scheduler_scan", _fake_scan)
|
||||
monkeypatch.setattr(cli.asyncio, "Event", lambda: _StopEvent())
|
||||
|
||||
settings = cli.config.automation_scheduler
|
||||
old_enabled = settings.enabled
|
||||
old_interval = settings.interval_seconds
|
||||
old_limit = settings.batch_limit
|
||||
settings.enabled = True
|
||||
settings.interval_seconds = 9
|
||||
settings.batch_limit = 7
|
||||
|
||||
try:
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await cli.run_automation_scheduler_forever()
|
||||
finally:
|
||||
settings.enabled = old_enabled
|
||||
settings.interval_seconds = old_interval
|
||||
settings.batch_limit = old_limit
|
||||
|
||||
assert fake_scheduler.started is True
|
||||
assert fake_scheduler.shutdown_called is True
|
||||
assert len(fake_scheduler.jobs) == 1
|
||||
assert fake_scheduler.jobs[0]["max_instances"] == 1
|
||||
assert fake_scheduler.jobs[0]["coalesce"] is True
|
||||
|
||||
scan_job = fake_scheduler.jobs[0]["func"]
|
||||
await scan_job()
|
||||
assert dispatch_limits == [7]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_automation_scheduler_forever_disabled_noop(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
settings = cli.config.automation_scheduler
|
||||
old_enabled = settings.enabled
|
||||
settings.enabled = False
|
||||
|
||||
called = False
|
||||
|
||||
def _unexpected_scheduler() -> _FakeScheduler:
|
||||
nonlocal called
|
||||
called = True
|
||||
return _FakeScheduler()
|
||||
|
||||
monkeypatch.setattr(cli, "AsyncIOScheduler", _unexpected_scheduler)
|
||||
|
||||
try:
|
||||
await cli.run_automation_scheduler_forever()
|
||||
finally:
|
||||
settings.enabled = old_enabled
|
||||
|
||||
assert called is False
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from schemas.agent.runtime_models import RouterAgentOutput
|
||||
from schemas.agent.runtime_models import RouterAgentOutput, WorkerAgentOutputRich
|
||||
|
||||
|
||||
def test_router_agent_output_coerces_key_entity_value_to_string() -> None:
|
||||
@@ -32,3 +32,59 @@ def test_router_agent_output_coerces_key_entity_value_to_string() -> None:
|
||||
model = RouterAgentOutput.model_validate(payload)
|
||||
|
||||
assert model.key_entities[0].value == "8"
|
||||
|
||||
|
||||
def test_router_agent_output_coerces_constraint_value_to_string() -> None:
|
||||
payload = {
|
||||
"normalized_task_input": {
|
||||
"user_text": "test",
|
||||
"multimodal_summary": [],
|
||||
"context_summary": "",
|
||||
},
|
||||
"key_entities": [],
|
||||
"constraints": [
|
||||
{
|
||||
"key": "strict_mode",
|
||||
"value": True,
|
||||
"required": True,
|
||||
}
|
||||
],
|
||||
"task_typing": {
|
||||
"primary": "planning",
|
||||
"secondary": [],
|
||||
},
|
||||
"execution_mode": "onestep",
|
||||
"result_typing": {
|
||||
"primary": "summary",
|
||||
"secondary": [],
|
||||
},
|
||||
}
|
||||
|
||||
model = RouterAgentOutput.model_validate(payload)
|
||||
|
||||
assert model.constraints[0].value == "True"
|
||||
|
||||
|
||||
def test_worker_agent_output_rich_accepts_list_item_status_object() -> None:
|
||||
payload = {
|
||||
"status": "success",
|
||||
"answer": "done",
|
||||
"result_type": "summary",
|
||||
"ui_hints": {
|
||||
"intent": "status",
|
||||
"status": "info",
|
||||
"title": "状态",
|
||||
"listItems": [
|
||||
{
|
||||
"title": "任务A",
|
||||
"status": {"type": "info", "value": "已归档"},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
model = WorkerAgentOutputRich.model_validate(payload)
|
||||
|
||||
assert model.ui_hints is not None
|
||||
assert model.ui_hints.list_items[0].status is not None
|
||||
assert model.ui_hints.list_items[0].status.value == "info"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import cast
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import UUID, uuid4
|
||||
@@ -64,10 +64,14 @@ class FakeFriendshipRepo:
|
||||
inbox.id = uuid4()
|
||||
inbox.recipient_id = recipient_id
|
||||
inbox.sender_id = initiator_id
|
||||
inbox.schedule_item_id = None
|
||||
inbox.status = InboxMessageStatus.PENDING
|
||||
inbox.message_type = InboxMessageType.FRIEND_REQUEST
|
||||
inbox.friendship_id = friendship.id
|
||||
inbox.content = {"type": "request", "message": content}
|
||||
inbox.is_read = False
|
||||
inbox.created_at = datetime.now(timezone.utc)
|
||||
inbox.updated_at = datetime.now(timezone.utc)
|
||||
self._inbox_messages.append(inbox)
|
||||
|
||||
return friendship, inbox
|
||||
@@ -91,10 +95,14 @@ class FakeFriendshipRepo:
|
||||
inbox.id = uuid4()
|
||||
inbox.recipient_id = recipient_id
|
||||
inbox.sender_id = initiator_id
|
||||
inbox.schedule_item_id = None
|
||||
inbox.status = InboxMessageStatus.PENDING
|
||||
inbox.message_type = InboxMessageType.FRIEND_REQUEST
|
||||
inbox.friendship_id = friendship.id
|
||||
inbox.content = {"type": "request", "message": content}
|
||||
inbox.is_read = False
|
||||
inbox.created_at = datetime.now(timezone.utc)
|
||||
inbox.updated_at = datetime.now(timezone.utc)
|
||||
self._inbox_messages.append(inbox)
|
||||
|
||||
return friendship, inbox
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from models.inbox_messages import InboxMessage
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
from v1.inbox_messages import realtime
|
||||
|
||||
|
||||
class _FakeRedis:
|
||||
def __init__(self) -> None:
|
||||
self.last_stream: str | None = None
|
||||
self.last_payload: str | None = None
|
||||
self.last_block: int | None = None
|
||||
|
||||
async def xadd(self, stream: str, fields: dict[str, str]) -> str:
|
||||
self.last_stream = stream
|
||||
self.last_payload = fields.get("event")
|
||||
return "1743313300000-0"
|
||||
|
||||
async def xread(self, _streams: dict[str, str], count: int, block: int):
|
||||
del count
|
||||
self.last_block = block
|
||||
return [
|
||||
(
|
||||
"inbox:events:test",
|
||||
[
|
||||
(
|
||||
"1743313300000-0",
|
||||
{
|
||||
"event": '{"event_id":"e1","event_type":"INBOX_MESSAGE_CREATED","op":"created"}',
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish_inbox_message_created_writes_stream(monkeypatch) -> None:
|
||||
fake_redis = _FakeRedis()
|
||||
|
||||
async def _fake_get_redis():
|
||||
return fake_redis
|
||||
|
||||
monkeypatch.setattr(realtime, "get_or_init_redis_client", _fake_get_redis)
|
||||
message = InboxMessage(
|
||||
id=uuid4(),
|
||||
recipient_id=uuid4(),
|
||||
sender_id=uuid4(),
|
||||
message_type=InboxMessageType.CALENDAR,
|
||||
friendship_id=None,
|
||||
schedule_item_id=uuid4(),
|
||||
group_id=None,
|
||||
content={"type": "invite"},
|
||||
is_read=False,
|
||||
status=InboxMessageStatus.PENDING,
|
||||
created_by=uuid4(),
|
||||
)
|
||||
message.created_at = datetime(2026, 3, 30, 7, 0, tzinfo=UTC)
|
||||
message.updated_at = datetime(2026, 3, 30, 7, 0, tzinfo=UTC)
|
||||
|
||||
stream_id = await realtime.publish_inbox_message_created(message)
|
||||
|
||||
assert stream_id == "1743313300000-0"
|
||||
assert fake_redis.last_stream == f"inbox:events:{message.recipient_id}"
|
||||
assert fake_redis.last_payload is not None
|
||||
assert '"op":"created"' in fake_redis.last_payload
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_inbox_events_decodes_rows(monkeypatch) -> None:
|
||||
fake_redis = _FakeRedis()
|
||||
|
||||
async def _fake_get_redis():
|
||||
return fake_redis
|
||||
|
||||
monkeypatch.setattr(realtime, "get_or_init_redis_client", _fake_get_redis)
|
||||
|
||||
rows = await realtime.read_inbox_events(
|
||||
recipient_id=uuid4(),
|
||||
last_event_id=None,
|
||||
)
|
||||
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["id"] == "1743313300000-0"
|
||||
assert rows[0]["event"]["event_type"] == "INBOX_MESSAGE_CREATED"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_inbox_events_handles_redis_timeout(monkeypatch) -> None:
|
||||
class _TimeoutRedis(_FakeRedis):
|
||||
async def xread(self, _streams: dict[str, str], count: int, block: int):
|
||||
del _streams, count, block
|
||||
raise TimeoutError("read timeout")
|
||||
|
||||
fake_redis = _TimeoutRedis()
|
||||
|
||||
async def _fake_get_redis():
|
||||
return fake_redis
|
||||
|
||||
monkeypatch.setattr(realtime, "get_or_init_redis_client", _fake_get_redis)
|
||||
|
||||
rows = await realtime.read_inbox_events(recipient_id=uuid4(), last_event_id=None)
|
||||
|
||||
assert rows == []
|
||||
@@ -59,6 +59,9 @@ class FakeRepo:
|
||||
return self._item
|
||||
return None
|
||||
|
||||
async def get_item(self, item_id: UUID) -> ScheduleItem | None:
|
||||
return await self.get_by_id(item_id)
|
||||
|
||||
async def create(self, data: dict) -> ScheduleItem:
|
||||
return _create_mock_schedule_item(
|
||||
owner_id=data["owner_id"],
|
||||
@@ -74,6 +77,23 @@ class FakeRepo:
|
||||
self._item.title = data["title"]
|
||||
return self._item
|
||||
|
||||
async def update_item(self, item_id: UUID, data: dict) -> ScheduleItem | None:
|
||||
if self._item is None:
|
||||
return None
|
||||
if "title" in data:
|
||||
self._item.title = data["title"]
|
||||
if "description" in data:
|
||||
self._item.description = data["description"]
|
||||
if "start_at" in data:
|
||||
self._item.start_at = data["start_at"]
|
||||
if "end_at" in data:
|
||||
self._item.end_at = data["end_at"]
|
||||
if "timezone" in data:
|
||||
self._item.timezone = data["timezone"]
|
||||
if "extra_metadata" in data:
|
||||
self._item.extra_metadata = data["extra_metadata"]
|
||||
return self._item
|
||||
|
||||
async def delete_by_item_id(
|
||||
self, item_id: UUID, owner_id: UUID
|
||||
) -> ScheduleItem | None:
|
||||
@@ -81,6 +101,9 @@ class FakeRepo:
|
||||
return None
|
||||
return self._item
|
||||
|
||||
async def delete_item(self, item_id: UUID) -> None:
|
||||
del item_id
|
||||
|
||||
async def list_by_date_range(
|
||||
self, owner_id: UUID, start_at: datetime, end_at: datetime
|
||||
) -> list[ScheduleItem]:
|
||||
@@ -327,12 +350,11 @@ async def test_update_maps_metadata_to_extra_metadata(
|
||||
captured: dict | None = None
|
||||
|
||||
class CaptureRepo(FakeRepo):
|
||||
async def update_by_item_id(
|
||||
self, item_id: UUID, owner_id: UUID, data: dict
|
||||
) -> ScheduleItem | None:
|
||||
async def update_item(self, item_id: UUID, data: dict) -> ScheduleItem | None:
|
||||
nonlocal captured
|
||||
del item_id
|
||||
captured = data
|
||||
return await super().update_by_item_id(item_id, owner_id, data)
|
||||
return await super().update_item(item.id, data)
|
||||
|
||||
service = ScheduleItemService(
|
||||
repository=CaptureRepo(item),
|
||||
@@ -370,12 +392,11 @@ async def test_update_maps_null_metadata_to_extra_metadata_null(
|
||||
captured: dict | None = None
|
||||
|
||||
class CaptureRepo(FakeRepo):
|
||||
async def update_by_item_id(
|
||||
self, item_id: UUID, owner_id: UUID, data: dict
|
||||
) -> ScheduleItem | None:
|
||||
async def update_item(self, item_id: UUID, data: dict) -> ScheduleItem | None:
|
||||
nonlocal captured
|
||||
del item_id
|
||||
captured = data
|
||||
return await super().update_by_item_id(item_id, owner_id, data)
|
||||
return await super().update_item(item.id, data)
|
||||
|
||||
service = ScheduleItemService(
|
||||
repository=CaptureRepo(item),
|
||||
|
||||
@@ -157,6 +157,14 @@ class FriendshipRepoStub:
|
||||
return friendship
|
||||
|
||||
|
||||
class UserRepoStub:
|
||||
async def get_by_user_id(self, user_id: UUID):
|
||||
profile = MagicMock()
|
||||
profile.id = user_id
|
||||
profile.username = "owner"
|
||||
return profile
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_share_forbidden_when_not_owner() -> None:
|
||||
owner_id = UUID("00000000-0000-0000-0000-000000000001")
|
||||
@@ -172,6 +180,7 @@ async def test_share_forbidden_when_not_owner() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub()),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
@@ -204,6 +213,7 @@ async def test_share_success_creates_calendar_invitation_message() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub()),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
result = await service.share(
|
||||
@@ -223,7 +233,17 @@ async def test_share_success_creates_calendar_invitation_message() -> None:
|
||||
assert message.sender_id == owner_id
|
||||
assert message.schedule_item_id == item_id
|
||||
assert message.message_type == InboxMessageType.CALENDAR
|
||||
assert message.content == {"type": "invite", "permission": 5, "action": "pending"}
|
||||
assert message.content is not None
|
||||
assert message.content["type"] == "invite"
|
||||
assert message.content["schema_version"] == 2
|
||||
assert message.content["permission"] == 5
|
||||
assert message.content["item"]["id"] == str(item_id)
|
||||
assert message.content["item"]["title"] == "test"
|
||||
assert message.content["item"]["start_at"] == "2026-02-28T16:00:00+00:00"
|
||||
assert message.content["item"]["end_at"] is None
|
||||
assert message.content["item"]["timezone"] == "UTC"
|
||||
assert message.content["actor"]["username"] == "owner"
|
||||
assert message.content["actor"]["phone"] == "+8613810000000"
|
||||
session.commit.assert_awaited_once()
|
||||
|
||||
|
||||
@@ -237,6 +257,7 @@ async def test_share_returns_not_found_when_item_missing() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub()),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
@@ -268,6 +289,7 @@ async def test_share_invalid_auth_user_id_returns_503() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayInvalidIdStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub()),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
@@ -302,6 +324,7 @@ async def test_share_sqlalchemy_error_rolls_back() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub()),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
@@ -334,6 +357,7 @@ async def test_share_returns_forbidden_when_target_is_not_friend() -> None:
|
||||
auth_gateway=cast(Any, AuthGatewayStub()),
|
||||
inbox_repository=InboxRepoStub(),
|
||||
friendship_repository=cast(Any, FriendshipRepoStub(accepted=False)),
|
||||
user_repository=cast(Any, UserRepoStub()),
|
||||
)
|
||||
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
|
||||
Reference in New Issue
Block a user