74 lines
3.5 KiB
Python
74 lines
3.5 KiB
Python
"""Contract tests for the runtime-facing direct MessageLedger append path."""
|
|
from __future__ import annotations
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from tempfile import TemporaryDirectory
|
|
import unittest
|
|
|
|
from aios_v2r1.message_ledger import MessageLedger
|
|
from aios_v2r1.tenant_context import CanonicalMessage, ConflictError
|
|
|
|
|
|
def message(*, tenant_id: str = "tenant-a", cell_id: str = "cell-a",
|
|
event_id: str = "event-a", message_id: str = "message-a",
|
|
body: str = "hello", reply_to_message_id: str | None = None) -> CanonicalMessage:
|
|
return CanonicalMessage(
|
|
tenant_id=tenant_id, cell_id=cell_id, principal_id="user-a",
|
|
principal_type="authenticated_user", origin_type="channel_user",
|
|
channel="weixin", channel_account_id="account-a", peer_id="peer-a",
|
|
conversation_id="conversation-a", session_id="session-a", task_id=None,
|
|
event_id=event_id, message_id=message_id, reply_to_message_id=reply_to_message_id,
|
|
direction="inbound", command_capable=True,
|
|
occurred_at="2026-07-13T00:00:00+00:00",
|
|
received_at="2026-07-13T00:00:01+00:00", body=body,
|
|
quoted_body=None, adapter_name="weixin", adapter_version="v2",
|
|
)
|
|
|
|
|
|
class MessageLedgerAppendContractTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.directory = TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.ledger = MessageLedger(f"{self.directory.name}/ledger.sqlite3")
|
|
|
|
def test_append_writes_directly_without_using_remember(self) -> None:
|
|
# A compatibility wrapper may remain, but the runtime append path may not
|
|
# route through it: this catches append() -> remember() regressions.
|
|
self.ledger.remember = lambda _: (_ for _ in ()).throw(AssertionError("remember called")) # type: ignore[method-assign]
|
|
record = self.ledger.append(message())
|
|
self.assertEqual("message-a", record.message_id)
|
|
self.assertEqual(1, self.ledger.count())
|
|
|
|
def test_append_rejects_same_message_id_with_different_content(self) -> None:
|
|
self.ledger.append(message(body="first"))
|
|
with self.assertRaises(ConflictError):
|
|
self.ledger.append(message(event_id="event-b", body="second"))
|
|
self.assertEqual(1, self.ledger.count())
|
|
|
|
def test_append_is_idempotent_when_only_event_id_changes(self) -> None:
|
|
stored = self.ledger.append(message(event_id="event-a", body="same body"))
|
|
replay = self.ledger.append(message(event_id="event-b", body="same body"))
|
|
|
|
self.assertEqual(stored, replay)
|
|
self.assertEqual("event-a", replay.event_id)
|
|
self.assertEqual(1, self.ledger.count())
|
|
|
|
def test_append_keeps_tenants_isolated(self) -> None:
|
|
self.ledger.append(message())
|
|
self.ledger.append(message(tenant_id="tenant-b", cell_id="cell-b"))
|
|
self.assertEqual(2, self.ledger.count())
|
|
self.assertEqual("hello", self.ledger.resolve_quote(
|
|
message(tenant_id="tenant-b", cell_id="cell-b", event_id="event-c",
|
|
message_id="message-c", body="reply", reply_to_message_id="message-a")
|
|
))
|
|
|
|
def test_concurrent_identical_append_is_one_record(self) -> None:
|
|
with ThreadPoolExecutor(max_workers=16) as executor:
|
|
records = list(executor.map(lambda _: self.ledger.append(message()), range(16)))
|
|
self.assertEqual(1, self.ledger.count())
|
|
self.assertEqual({"message-a"}, {record.message_id for record in records})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|