Implement Manticore fully and re-theme
This commit is contained in:
219
core/tests/test_gia_analysis.py
Normal file
219
core/tests/test_gia_analysis.py
Normal file
@@ -0,0 +1,219 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import SimpleTestCase, TestCase, override_settings
|
||||
|
||||
from core.events.behavior import ComposingTracker, summarize_metrics
|
||||
from core.events.manticore import ManticoreEventLedgerBackend
|
||||
from core.models import ChatSession, ConversationEvent, Person, PersonIdentifier, User
|
||||
|
||||
|
||||
class ComposingTrackerTests(SimpleTestCase):
|
||||
def test_tracker_emits_abandoned_after_window(self):
|
||||
tracker = ComposingTracker(window_ms=300000)
|
||||
tracker.observe_started("session-1", 1000)
|
||||
abandoned = tracker.observe_stopped("session-1", 301500)
|
||||
self.assertIsNotNone(abandoned)
|
||||
self.assertEqual(300500, abandoned["duration_ms"])
|
||||
self.assertTrue(abandoned["abandoned"])
|
||||
|
||||
|
||||
class BehavioralMetricSummaryTests(SimpleTestCase):
|
||||
def test_summarize_metrics_computes_core_intervals(self):
|
||||
rows = [
|
||||
{
|
||||
"session_id": "s1",
|
||||
"kind": "message_delivered",
|
||||
"ts": 1000,
|
||||
"payload": {"message_id": "m1"},
|
||||
},
|
||||
{
|
||||
"session_id": "s1",
|
||||
"kind": "message_read",
|
||||
"ts": 1600,
|
||||
"payload": {"message_id": "m1"},
|
||||
},
|
||||
{"session_id": "s1", "kind": "presence_available", "ts": 2000},
|
||||
{"session_id": "s1", "kind": "composing_started", "ts": 2100},
|
||||
{"session_id": "s1", "kind": "composing_started", "ts": 2200},
|
||||
{"session_id": "s1", "kind": "message_sent", "ts": 2600},
|
||||
{"session_id": "s2", "kind": "composing_started", "ts": 4000},
|
||||
{"session_id": "s2", "kind": "composing_abandoned", "ts": 710000},
|
||||
]
|
||||
|
||||
metrics = summarize_metrics(rows, rows)
|
||||
|
||||
self.assertEqual(600, metrics["delay_b"]["value_ms"])
|
||||
self.assertEqual(500, metrics["delay_c"]["value_ms"])
|
||||
self.assertEqual(150, metrics["delay_f"]["value_ms"])
|
||||
self.assertEqual(2, metrics["revision"]["value_ms"])
|
||||
self.assertEqual(333, metrics["abandoned_rate"]["value_ms"])
|
||||
|
||||
|
||||
@override_settings(
|
||||
EVENT_LEDGER_DUAL_WRITE=True,
|
||||
MANTICORE_HTTP_URL="http://manticore.test:9308",
|
||||
MANTICORE_EVENT_TABLE="gia_events_test",
|
||||
MANTICORE_METRIC_TABLE="gia_metrics_test",
|
||||
)
|
||||
class GiaAnalysisCommandTests(TestCase):
|
||||
def setUp(self):
|
||||
ManticoreEventLedgerBackend._table_ready_cache.clear()
|
||||
self.user = User.objects.create_user(
|
||||
username="analysis-user",
|
||||
email="analysis@example.com",
|
||||
password="x",
|
||||
)
|
||||
self.person = Person.objects.create(user=self.user, name="Analysis Person")
|
||||
self.identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="whatsapp",
|
||||
identifier="15550009999",
|
||||
)
|
||||
self.session = ChatSession.objects.create(
|
||||
user=self.user,
|
||||
identifier=self.identifier,
|
||||
)
|
||||
|
||||
@patch("core.events.manticore.requests.post")
|
||||
def test_metrics_table_upsert_and_analysis_command(self, mocked_post):
|
||||
now_ms = int(time.time() * 1000)
|
||||
mocked_response = type(
|
||||
"Response",
|
||||
(),
|
||||
{
|
||||
"raise_for_status": lambda self: None,
|
||||
"json": lambda self: {},
|
||||
},
|
||||
)
|
||||
mocked_post.return_value = mocked_response()
|
||||
|
||||
ConversationEvent.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=now_ms - 5000,
|
||||
event_type="presence_available",
|
||||
direction="system",
|
||||
origin_transport="whatsapp",
|
||||
payload={},
|
||||
)
|
||||
ConversationEvent.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=now_ms - 4500,
|
||||
event_type="typing_started",
|
||||
direction="in",
|
||||
origin_transport="whatsapp",
|
||||
payload={},
|
||||
)
|
||||
ConversationEvent.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=now_ms - 3900,
|
||||
event_type="message_created",
|
||||
direction="in",
|
||||
origin_transport="whatsapp",
|
||||
payload={"message_id": "m1"},
|
||||
)
|
||||
ConversationEvent.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=now_ms - 3600,
|
||||
event_type="delivery_receipt",
|
||||
direction="system",
|
||||
origin_transport="whatsapp",
|
||||
payload={"message_id": "m1"},
|
||||
)
|
||||
ConversationEvent.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=now_ms - 3000,
|
||||
event_type="read_receipt",
|
||||
direction="system",
|
||||
origin_transport="whatsapp",
|
||||
payload={"message_id": "m1"},
|
||||
)
|
||||
|
||||
out = StringIO()
|
||||
with patch(
|
||||
"core.management.commands.gia_analysis.get_event_ledger_backend"
|
||||
) as mocked_backend:
|
||||
backend = mocked_backend.return_value
|
||||
backend.list_event_targets.return_value = [
|
||||
{"user_id": self.user.id, "person_id": str(self.person.id)}
|
||||
]
|
||||
backend.fetch_events.return_value = [
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"person_id": str(self.person.id),
|
||||
"session_id": str(self.session.id),
|
||||
"kind": "presence_available",
|
||||
"direction": "system",
|
||||
"ts": now_ms - 5000,
|
||||
"payload": {},
|
||||
},
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"person_id": str(self.person.id),
|
||||
"session_id": str(self.session.id),
|
||||
"kind": "composing_started",
|
||||
"direction": "in",
|
||||
"ts": now_ms - 4500,
|
||||
"payload": {},
|
||||
},
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"person_id": str(self.person.id),
|
||||
"session_id": str(self.session.id),
|
||||
"kind": "message_sent",
|
||||
"direction": "in",
|
||||
"ts": now_ms - 3900,
|
||||
"payload": {"message_id": "m1"},
|
||||
},
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"person_id": str(self.person.id),
|
||||
"session_id": str(self.session.id),
|
||||
"kind": "message_delivered",
|
||||
"direction": "system",
|
||||
"ts": now_ms - 3600,
|
||||
"payload": {"message_id": "m1"},
|
||||
},
|
||||
{
|
||||
"user_id": self.user.id,
|
||||
"person_id": str(self.person.id),
|
||||
"session_id": str(self.session.id),
|
||||
"kind": "message_read",
|
||||
"direction": "system",
|
||||
"ts": now_ms - 3000,
|
||||
"payload": {"message_id": "m1"},
|
||||
},
|
||||
]
|
||||
|
||||
call_command("gia_analysis", "--once", "--user-id", str(self.user.id), stdout=out)
|
||||
|
||||
self.assertGreaterEqual(backend.upsert_metric.call_count, 3)
|
||||
self.assertIn("gia-analysis wrote=", out.getvalue())
|
||||
|
||||
@patch("core.events.manticore.requests.post")
|
||||
def test_list_event_targets_uses_group_by_query(self, mocked_post):
|
||||
mocked_response = type(
|
||||
"Response",
|
||||
(),
|
||||
{
|
||||
"raise_for_status": lambda self: None,
|
||||
"json": lambda self: {"data": []},
|
||||
},
|
||||
)
|
||||
mocked_post.return_value = mocked_response()
|
||||
|
||||
backend = ManticoreEventLedgerBackend()
|
||||
backend.list_event_targets(user_id=1)
|
||||
|
||||
query = mocked_post.call_args.kwargs["data"]["query"]
|
||||
self.assertIn("GROUP BY user_id, person_id", query)
|
||||
Reference in New Issue
Block a user