Increase platform abstraction cohesion
This commit is contained in:
67
core/tests/test_compose_send_capabilities.py
Normal file
67
core/tests/test_compose_send_capabilities.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from core.models import User
|
||||
|
||||
|
||||
class ComposeSendCapabilityTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user("compose-send", "send@example.com", "pw")
|
||||
self.client.force_login(self.user)
|
||||
|
||||
@patch("core.views.compose.transport.enqueue_runtime_command")
|
||||
@patch("core.views.compose.transport.send_message_raw", new_callable=AsyncMock)
|
||||
def test_unsupported_send_fails_fast_without_dispatch(
|
||||
self,
|
||||
mocked_send_message_raw,
|
||||
mocked_enqueue_runtime_command,
|
||||
):
|
||||
response = self.client.post(
|
||||
reverse("compose_send"),
|
||||
{
|
||||
"service": "xmpp",
|
||||
"identifier": "person@example.com",
|
||||
"text": "hello",
|
||||
"failsafe_arm": "1",
|
||||
"failsafe_confirm": "1",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
payload = json.loads(response.headers["HX-Trigger"])["composeSendResult"]
|
||||
self.assertFalse(payload["ok"])
|
||||
self.assertEqual("warning", payload["level"])
|
||||
self.assertIn("Send not supported:", payload["message"])
|
||||
mocked_send_message_raw.assert_not_awaited()
|
||||
mocked_enqueue_runtime_command.assert_not_called()
|
||||
|
||||
def test_compose_page_displays_send_disabled_reason_for_unsupported_service(self):
|
||||
response = self.client.get(
|
||||
reverse("compose_page"),
|
||||
{
|
||||
"service": "xmpp",
|
||||
"identifier": "person@example.com",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
content = response.content.decode("utf-8")
|
||||
self.assertIn("Send disabled:", content)
|
||||
self.assertIn("compose-send-btn", content)
|
||||
|
||||
def test_compose_page_uses_humanized_browser_title(self):
|
||||
response = self.client.get(
|
||||
reverse("compose_page"),
|
||||
{
|
||||
"service": "signal",
|
||||
"identifier": "+15551230000",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertContains(response, "<title>Compose Page · GIA</title>", html=False)
|
||||
239
core/tests/test_mcp_tools.py
Normal file
239
core/tests/test_mcp_tools.py
Normal file
@@ -0,0 +1,239 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from core.mcp.tools import execute_tool, tool_specs
|
||||
from core.models import (
|
||||
AIRequest,
|
||||
MCPToolAuditLog,
|
||||
MemoryItem,
|
||||
TaskProject,
|
||||
User,
|
||||
WorkspaceConversation,
|
||||
DerivedTask,
|
||||
DerivedTaskEvent,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(MEMORY_SEARCH_BACKEND="django")
|
||||
class MCPToolTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_superuser(
|
||||
username="mcp-tools-user",
|
||||
email="mcp-tools@example.com",
|
||||
password="pw",
|
||||
)
|
||||
self.conversation = WorkspaceConversation.objects.create(
|
||||
user=self.user,
|
||||
platform_type="signal",
|
||||
title="MCP Memory Scope",
|
||||
platform_thread_id="mcp-thread-1",
|
||||
)
|
||||
request = AIRequest.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
window_spec={},
|
||||
operation="memory_propose",
|
||||
)
|
||||
self.memory = MemoryItem.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
memory_kind="fact",
|
||||
status="active",
|
||||
content={"text": "Prefers concise implementation notes."},
|
||||
source_request=request,
|
||||
confidence_score=0.8,
|
||||
)
|
||||
self.project = TaskProject.objects.create(user=self.user, name="MCP Project")
|
||||
self.task = DerivedTask.objects.create(
|
||||
user=self.user,
|
||||
project=self.project,
|
||||
title="Wire MCP server",
|
||||
source_service="signal",
|
||||
source_channel="team-chat",
|
||||
status_snapshot="open",
|
||||
immutable_payload={"scope": "memory"},
|
||||
)
|
||||
DerivedTaskEvent.objects.create(
|
||||
task=self.task,
|
||||
event_type="created",
|
||||
actor_identifier="agent",
|
||||
payload={"note": "task created"},
|
||||
)
|
||||
|
||||
def test_tool_specs_include_memory_task_wiki_tools(self):
|
||||
names = {item["name"] for item in tool_specs()}
|
||||
self.assertIn("manticore.status", names)
|
||||
self.assertIn("memory.propose", names)
|
||||
self.assertIn("tasks.link_artifact", names)
|
||||
self.assertIn("wiki.create_article", names)
|
||||
self.assertIn("project.get_runbook", names)
|
||||
|
||||
def test_manticore_query_and_tasks_tools(self):
|
||||
memory_payload = execute_tool(
|
||||
"manticore.query",
|
||||
{"user_id": self.user.id, "query": "concise implementation"},
|
||||
)
|
||||
self.assertGreaterEqual(int(memory_payload.get("count") or 0), 1)
|
||||
first_hit = (memory_payload.get("hits") or [{}])[0]
|
||||
self.assertEqual(str(self.memory.id), str(first_hit.get("memory_id")))
|
||||
|
||||
list_payload = execute_tool("tasks.list", {"user_id": self.user.id, "limit": 10})
|
||||
self.assertEqual(1, int(list_payload.get("count") or 0))
|
||||
self.assertEqual(str(self.task.id), str((list_payload.get("items") or [{}])[0].get("id")))
|
||||
|
||||
search_payload = execute_tool(
|
||||
"tasks.search",
|
||||
{"user_id": self.user.id, "query": "wire"},
|
||||
)
|
||||
self.assertEqual(1, int(search_payload.get("count") or 0))
|
||||
|
||||
events_payload = execute_tool("tasks.events", {"task_id": str(self.task.id), "limit": 5})
|
||||
self.assertEqual(1, int(events_payload.get("count") or 0))
|
||||
self.assertEqual("created", str((events_payload.get("items") or [{}])[0].get("event_type")))
|
||||
|
||||
def test_memory_proposal_review_flow(self):
|
||||
propose_payload = execute_tool(
|
||||
"memory.propose",
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"conversation_id": str(self.conversation.id),
|
||||
"memory_kind": "fact",
|
||||
"content": {"field": "likes", "text": "short status bullets"},
|
||||
"reason": "Operator memory capture",
|
||||
"requested_by_identifier": "unit-test",
|
||||
},
|
||||
)
|
||||
request_id = str((propose_payload.get("request") or {}).get("id") or "")
|
||||
self.assertTrue(request_id)
|
||||
|
||||
pending_payload = execute_tool("memory.pending", {"user_id": self.user.id})
|
||||
self.assertGreaterEqual(int(pending_payload.get("count") or 0), 1)
|
||||
|
||||
review_payload = execute_tool(
|
||||
"memory.review",
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"request_id": request_id,
|
||||
"decision": "approve",
|
||||
"reviewer_identifier": "admin",
|
||||
},
|
||||
)
|
||||
memory_data = review_payload.get("memory") or {}
|
||||
self.assertEqual("active", str(memory_data.get("status") or ""))
|
||||
|
||||
list_payload = execute_tool(
|
||||
"memory.list",
|
||||
{"user_id": self.user.id, "query": "status bullets"},
|
||||
)
|
||||
self.assertGreaterEqual(int(list_payload.get("count") or 0), 1)
|
||||
|
||||
def test_wiki_and_task_artifact_tools(self):
|
||||
article_create = execute_tool(
|
||||
"wiki.create_article",
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"title": "MCP Integration Notes",
|
||||
"markdown": "Initial setup steps.",
|
||||
"related_task_id": str(self.task.id),
|
||||
"tags": ["mcp", "memory"],
|
||||
"status": "published",
|
||||
},
|
||||
)
|
||||
article = article_create.get("article") or {}
|
||||
self.assertEqual("mcp-integration-notes", str(article.get("slug") or ""))
|
||||
|
||||
article_update = execute_tool(
|
||||
"wiki.update_article",
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"article_id": str(article.get("id") or ""),
|
||||
"markdown": "Updated setup steps.",
|
||||
"approve_overwrite": True,
|
||||
"summary": "Expanded usage instructions.",
|
||||
},
|
||||
)
|
||||
revision = article_update.get("revision") or {}
|
||||
self.assertEqual(2, int(revision.get("revision") or 0))
|
||||
|
||||
list_payload = execute_tool(
|
||||
"wiki.list",
|
||||
{"user_id": self.user.id, "query": "integration"},
|
||||
)
|
||||
self.assertEqual(1, int(list_payload.get("count") or 0))
|
||||
|
||||
get_payload = execute_tool(
|
||||
"wiki.get",
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"article_id": str(article.get("id") or ""),
|
||||
"include_revisions": True,
|
||||
},
|
||||
)
|
||||
self.assertGreaterEqual(len(get_payload.get("revisions") or []), 2)
|
||||
|
||||
note_payload = execute_tool(
|
||||
"tasks.create_note",
|
||||
{
|
||||
"task_id": str(self.task.id),
|
||||
"user_id": self.user.id,
|
||||
"note": "Implemented wiki tooling.",
|
||||
},
|
||||
)
|
||||
self.assertEqual("progress", str((note_payload.get("event") or {}).get("event_type")))
|
||||
|
||||
artifact_payload = execute_tool(
|
||||
"tasks.link_artifact",
|
||||
{
|
||||
"task_id": str(self.task.id),
|
||||
"user_id": self.user.id,
|
||||
"kind": "wiki",
|
||||
"path": "artifacts/wiki/mcp-integration-notes.md",
|
||||
"summary": "Reference docs",
|
||||
},
|
||||
)
|
||||
self.assertTrue(str((artifact_payload.get("artifact") or {}).get("id") or ""))
|
||||
|
||||
task_payload = execute_tool(
|
||||
"tasks.get",
|
||||
{"task_id": str(self.task.id), "user_id": self.user.id},
|
||||
)
|
||||
self.assertGreaterEqual(len(task_payload.get("artifact_links") or []), 1)
|
||||
self.assertGreaterEqual(len(task_payload.get("knowledge_articles") or []), 1)
|
||||
|
||||
def test_docs_append_run_note_writes_file(self):
|
||||
target = Path("/tmp/gia-mcp-test-notes.md")
|
||||
if target.exists():
|
||||
target.unlink()
|
||||
payload = execute_tool(
|
||||
"docs.append_run_note",
|
||||
{
|
||||
"title": "MCP Integration",
|
||||
"content": "Connected manticore memory tools.",
|
||||
"task_id": str(self.task.id),
|
||||
"path": str(target),
|
||||
},
|
||||
)
|
||||
self.assertTrue(payload.get("ok"))
|
||||
content = target.read_text(encoding="utf-8")
|
||||
self.assertIn("MCP Integration", content)
|
||||
self.assertIn("Connected manticore memory tools.", content)
|
||||
target.unlink()
|
||||
|
||||
def test_audit_logs_record_success_and_failures(self):
|
||||
execute_tool("tasks.list", {"user_id": self.user.id})
|
||||
with self.assertRaises(ValueError):
|
||||
execute_tool("tasks.search", {"user_id": self.user.id})
|
||||
|
||||
success_row = MCPToolAuditLog.objects.filter(
|
||||
tool_name="tasks.list",
|
||||
ok=True,
|
||||
).first()
|
||||
self.assertIsNotNone(success_row)
|
||||
failure_row = MCPToolAuditLog.objects.filter(
|
||||
tool_name="tasks.search",
|
||||
ok=False,
|
||||
).first()
|
||||
self.assertIsNotNone(failure_row)
|
||||
97
core/tests/test_memory_pipeline_commands.py
Normal file
97
core/tests/test_memory_pipeline_commands.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from io import StringIO
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from core.models import MemoryChangeRequest, MemoryItem, MessageEvent, User, WorkspaceConversation
|
||||
|
||||
|
||||
class MemoryPipelineCommandTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="memory-pipeline-user",
|
||||
email="memory-pipeline@example.com",
|
||||
password="pw",
|
||||
)
|
||||
self.conversation = WorkspaceConversation.objects.create(
|
||||
user=self.user,
|
||||
platform_type="signal",
|
||||
title="Pipeline Scope",
|
||||
platform_thread_id="pipeline-thread-1",
|
||||
)
|
||||
|
||||
def test_memory_suggest_from_messages_creates_pending_request(self):
|
||||
MessageEvent.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
ts=1700000000000,
|
||||
direction="in",
|
||||
text="I prefer short status updates and bullet points.",
|
||||
source_system="signal",
|
||||
)
|
||||
|
||||
out = StringIO()
|
||||
call_command(
|
||||
"memory_suggest_from_messages",
|
||||
user_id=str(self.user.id),
|
||||
limit_messages=50,
|
||||
max_items=10,
|
||||
stdout=out,
|
||||
)
|
||||
rendered = out.getvalue()
|
||||
self.assertIn("memory-suggest-from-messages", rendered)
|
||||
self.assertGreaterEqual(MemoryItem.objects.filter(user=self.user).count(), 1)
|
||||
self.assertGreaterEqual(
|
||||
MemoryChangeRequest.objects.filter(user=self.user, status="pending").count(),
|
||||
1,
|
||||
)
|
||||
|
||||
def test_memory_hygiene_expires_and_detects_contradictions(self):
|
||||
expired = MemoryItem.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
memory_kind="fact",
|
||||
status="active",
|
||||
content={"field": "likes", "text": "calls in the evening"},
|
||||
confidence_score=0.6,
|
||||
expires_at=timezone.now() - timedelta(days=1),
|
||||
)
|
||||
MemoryItem.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
memory_kind="fact",
|
||||
status="active",
|
||||
content={"field": "timezone", "text": "UTC+1"},
|
||||
confidence_score=0.7,
|
||||
)
|
||||
MemoryItem.objects.create(
|
||||
user=self.user,
|
||||
conversation=self.conversation,
|
||||
memory_kind="fact",
|
||||
status="active",
|
||||
content={"field": "timezone", "text": "UTC-5"},
|
||||
confidence_score=0.7,
|
||||
)
|
||||
|
||||
out = StringIO()
|
||||
call_command(
|
||||
"memory_hygiene",
|
||||
user_id=str(self.user.id),
|
||||
stdout=out,
|
||||
)
|
||||
rendered = out.getvalue()
|
||||
self.assertIn("memory-hygiene", rendered)
|
||||
|
||||
expired.refresh_from_db()
|
||||
self.assertEqual("deprecated", expired.status)
|
||||
contradiction_requests = MemoryChangeRequest.objects.filter(
|
||||
user=self.user,
|
||||
status="pending",
|
||||
action="update",
|
||||
reason__icontains="Contradiction",
|
||||
).count()
|
||||
self.assertGreaterEqual(contradiction_requests, 1)
|
||||
@@ -5,7 +5,7 @@ from django.test import TestCase
|
||||
from core.models import Person, PersonIdentifier, User
|
||||
from core.presence import AvailabilitySignal, latest_state_for_people, record_native_signal
|
||||
from core.presence.inference import now_ms
|
||||
from core.views.compose import _context_base
|
||||
from core.views.compose import _compose_availability_payload, _context_base
|
||||
|
||||
|
||||
class PresenceQueryAndComposeContextTests(TestCase):
|
||||
@@ -48,3 +48,30 @@ class PresenceQueryAndComposeContextTests(TestCase):
|
||||
)
|
||||
self.assertIsNotNone(base["person_identifier"])
|
||||
self.assertEqual(str(self.person.id), str(base["person"].id))
|
||||
|
||||
def test_compose_availability_payload_falls_back_to_cross_service(self):
|
||||
ts_value = now_ms()
|
||||
record_native_signal(
|
||||
AvailabilitySignal(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
person_identifier=self.identifier,
|
||||
service="whatsapp",
|
||||
source_kind="message_in",
|
||||
availability_state="available",
|
||||
confidence=0.9,
|
||||
ts=ts_value,
|
||||
)
|
||||
)
|
||||
enabled, slices, summary = _compose_availability_payload(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="signal",
|
||||
range_start=ts_value - 60000,
|
||||
range_end=ts_value + 60000,
|
||||
)
|
||||
self.assertTrue(enabled)
|
||||
self.assertGreaterEqual(len(slices), 1)
|
||||
self.assertEqual("whatsapp", str(slices[0].get("service")))
|
||||
self.assertEqual("available", str(summary.get("state")))
|
||||
self.assertTrue(bool(summary.get("is_cross_service")))
|
||||
|
||||
@@ -124,3 +124,66 @@ class ReactionNormalizationTests(TestCase):
|
||||
self.assertEqual("❤️", serialized["text"])
|
||||
self.assertEqual([], list(serialized.get("reactions") or []))
|
||||
self.assertEqual(str(anchor.id), serialized["reply_to_id"])
|
||||
|
||||
def test_apply_message_edit_tracks_history_and_updates_text(self):
|
||||
message = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=1700000004000,
|
||||
sender_uuid="author-3",
|
||||
text="before",
|
||||
source_service="signal",
|
||||
source_message_id="1700000004000",
|
||||
)
|
||||
|
||||
updated = async_to_sync(history.apply_message_edit)(
|
||||
self.user,
|
||||
self.identifier,
|
||||
target_ts=1700000004000,
|
||||
new_text="after",
|
||||
source_service="signal",
|
||||
actor="+15550000000",
|
||||
payload={"origin": "test"},
|
||||
)
|
||||
|
||||
self.assertIsNotNone(updated)
|
||||
message.refresh_from_db()
|
||||
self.assertEqual("after", str(message.text or ""))
|
||||
edit_history = list((message.receipt_payload or {}).get("edit_history") or [])
|
||||
self.assertEqual(1, len(edit_history))
|
||||
self.assertEqual("before", str(edit_history[0].get("previous_text") or ""))
|
||||
self.assertEqual("after", str(edit_history[0].get("new_text") or ""))
|
||||
|
||||
def test_serialize_message_marks_deleted_and_preserves_edit_history(self):
|
||||
message = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=1700000005000,
|
||||
sender_uuid="author-4",
|
||||
text="keep me",
|
||||
source_service="signal",
|
||||
source_message_id="1700000005000",
|
||||
receipt_payload={
|
||||
"edit_history": [
|
||||
{
|
||||
"edited_ts": 1700000005100,
|
||||
"source_service": "signal",
|
||||
"actor": "+15550000000",
|
||||
"previous_text": "old",
|
||||
"new_text": "keep me",
|
||||
}
|
||||
],
|
||||
"is_deleted": True,
|
||||
"deleted": {
|
||||
"deleted_ts": 1700000005200,
|
||||
"source_service": "signal",
|
||||
"actor": "+15550000000",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
serialized = _serialize_message(message)
|
||||
self.assertTrue(bool(serialized.get("is_deleted")))
|
||||
self.assertEqual("(message deleted)", str(serialized.get("display_text") or ""))
|
||||
self.assertEqual(1, int(serialized.get("edit_count") or 0))
|
||||
self.assertEqual(1, len(list(serialized.get("edit_history") or [])))
|
||||
|
||||
@@ -267,6 +267,76 @@ class SignalInboundReplyLinkTests(TransactionTestCase):
|
||||
"Expected sync reaction to be applied via destination-number fallback resolution.",
|
||||
)
|
||||
|
||||
def test_process_raw_inbound_event_applies_edit(self):
|
||||
fake_ur = Mock()
|
||||
fake_ur.message_received = AsyncMock(return_value=None)
|
||||
fake_ur.xmpp = Mock()
|
||||
fake_ur.xmpp.client = Mock()
|
||||
fake_ur.xmpp.client.apply_external_reaction = AsyncMock(return_value=None)
|
||||
client = SignalClient.__new__(SignalClient)
|
||||
client.service = "signal"
|
||||
client.ur = fake_ur
|
||||
client.log = Mock()
|
||||
client.client = Mock()
|
||||
client.client.bot_uuid = ""
|
||||
client.client.phone_number = ""
|
||||
client._resolve_signal_identifiers = AsyncMock(return_value=[self.identifier])
|
||||
client._auto_link_single_user_signal_identifier = AsyncMock(return_value=[])
|
||||
|
||||
payload = {
|
||||
"envelope": {
|
||||
"sourceNumber": "+15550002000",
|
||||
"sourceUuid": "756078fd-d447-426d-a620-581a86d64f51",
|
||||
"timestamp": 1772545466000,
|
||||
"dataMessage": {
|
||||
"editMessage": {
|
||||
"targetSentTimestamp": 1772545458187,
|
||||
"dataMessage": {"message": "anchor edited"},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
async_to_sync(client._process_raw_inbound_event)(json.dumps(payload))
|
||||
|
||||
self.anchor.refresh_from_db()
|
||||
self.assertEqual("anchor edited", str(self.anchor.text or ""))
|
||||
edits = list((self.anchor.receipt_payload or {}).get("edit_history") or [])
|
||||
self.assertEqual(1, len(edits))
|
||||
|
||||
def test_process_raw_inbound_event_applies_delete_tombstone_flag(self):
|
||||
fake_ur = Mock()
|
||||
fake_ur.message_received = AsyncMock(return_value=None)
|
||||
fake_ur.xmpp = Mock()
|
||||
fake_ur.xmpp.client = Mock()
|
||||
fake_ur.xmpp.client.apply_external_reaction = AsyncMock(return_value=None)
|
||||
client = SignalClient.__new__(SignalClient)
|
||||
client.service = "signal"
|
||||
client.ur = fake_ur
|
||||
client.log = Mock()
|
||||
client.client = Mock()
|
||||
client.client.bot_uuid = ""
|
||||
client.client.phone_number = ""
|
||||
client._resolve_signal_identifiers = AsyncMock(return_value=[self.identifier])
|
||||
client._auto_link_single_user_signal_identifier = AsyncMock(return_value=[])
|
||||
|
||||
payload = {
|
||||
"envelope": {
|
||||
"sourceNumber": "+15550002000",
|
||||
"sourceUuid": "756078fd-d447-426d-a620-581a86d64f51",
|
||||
"timestamp": 1772545467000,
|
||||
"dataMessage": {
|
||||
"delete": {
|
||||
"targetSentTimestamp": 1772545458187,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
async_to_sync(client._process_raw_inbound_event)(json.dumps(payload))
|
||||
|
||||
self.anchor.refresh_from_db()
|
||||
self.assertTrue(bool((self.anchor.receipt_payload or {}).get("is_deleted")))
|
||||
self.assertTrue(bool((self.anchor.receipt_payload or {}).get("deleted") or {}))
|
||||
|
||||
|
||||
class SignalRuntimeCommandWritebackTests(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from asgiref.sync import async_to_sync
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from core.clients import transport
|
||||
from core.transports.capabilities import capability_snapshot, supports, unsupported_reason
|
||||
|
||||
|
||||
@@ -15,3 +17,11 @@ class TransportCapabilitiesTests(SimpleTestCase):
|
||||
snapshot = capability_snapshot()
|
||||
self.assertIn("schema_version", snapshot)
|
||||
self.assertIn("services", snapshot)
|
||||
|
||||
def test_transport_send_fails_fast_when_unsupported(self):
|
||||
result = async_to_sync(transport.send_message_raw)(
|
||||
"xmpp",
|
||||
"person@example.com",
|
||||
text="hello",
|
||||
)
|
||||
self.assertFalse(result)
|
||||
|
||||
Reference in New Issue
Block a user