141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from django.test import TestCase
|
|
|
|
from core.models import (
|
|
ContactAvailabilityEvent,
|
|
ContactAvailabilitySettings,
|
|
ContactAvailabilitySpan,
|
|
Person,
|
|
PersonIdentifier,
|
|
User,
|
|
)
|
|
from core.presence.engine import (
|
|
AvailabilitySignal,
|
|
ensure_fading_state,
|
|
record_inferred_signal,
|
|
record_native_signal,
|
|
)
|
|
from core.presence.inference import now_ms
|
|
|
|
|
|
class PresenceEngineTests(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user("presence-user", "presence@example.com", "x")
|
|
self.person = Person.objects.create(user=self.user, name="Presence Person")
|
|
self.identifier = PersonIdentifier.objects.create(
|
|
user=self.user,
|
|
person=self.person,
|
|
service="signal",
|
|
identifier="+15550001111",
|
|
)
|
|
ContactAvailabilitySettings.objects.update_or_create(
|
|
user=self.user,
|
|
defaults={
|
|
"enabled": True,
|
|
"show_in_chat": True,
|
|
"show_in_groups": True,
|
|
"inference_enabled": True,
|
|
"retention_days": 90,
|
|
"fade_threshold_seconds": 1,
|
|
},
|
|
)
|
|
|
|
def test_read_receipt_signal_creates_available_event(self):
|
|
ts = now_ms()
|
|
event = record_native_signal(
|
|
AvailabilitySignal(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="signal",
|
|
source_kind="read_receipt",
|
|
availability_state="available",
|
|
confidence=0.95,
|
|
ts=ts,
|
|
payload={"origin": "test"},
|
|
)
|
|
)
|
|
self.assertIsNotNone(event)
|
|
self.assertEqual(1, ContactAvailabilityEvent.objects.filter(user=self.user).count())
|
|
self.assertEqual("available", event.availability_state)
|
|
|
|
def test_inactivity_transitions_to_fading(self):
|
|
base_ts = now_ms()
|
|
record_inferred_signal(
|
|
AvailabilitySignal(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="signal",
|
|
source_kind="read_receipt",
|
|
availability_state="available",
|
|
confidence=0.95,
|
|
ts=base_ts,
|
|
)
|
|
)
|
|
fade_event = ensure_fading_state(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="signal",
|
|
at_ts=base_ts + 10_000,
|
|
)
|
|
self.assertIsNotNone(fade_event)
|
|
self.assertEqual("fading", fade_event.availability_state)
|
|
|
|
def test_explicit_unavailable_blocks_fade_inference(self):
|
|
base_ts = now_ms()
|
|
record_native_signal(
|
|
AvailabilitySignal(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="xmpp",
|
|
source_kind="native_presence",
|
|
availability_state="unavailable",
|
|
confidence=1.0,
|
|
ts=base_ts,
|
|
)
|
|
)
|
|
fade_event = ensure_fading_state(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="xmpp",
|
|
at_ts=base_ts + 60_000,
|
|
)
|
|
self.assertIsNone(fade_event)
|
|
self.assertEqual(1, ContactAvailabilityEvent.objects.filter(user=self.user).count())
|
|
|
|
def test_adjacent_same_state_events_extend_single_span(self):
|
|
ts0 = now_ms()
|
|
record_native_signal(
|
|
AvailabilitySignal(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="signal",
|
|
source_kind="typing_start",
|
|
availability_state="available",
|
|
confidence=0.9,
|
|
ts=ts0,
|
|
)
|
|
)
|
|
record_native_signal(
|
|
AvailabilitySignal(
|
|
user=self.user,
|
|
person=self.person,
|
|
person_identifier=self.identifier,
|
|
service="signal",
|
|
source_kind="message_in",
|
|
availability_state="available",
|
|
confidence=0.8,
|
|
ts=ts0 + 5_000,
|
|
)
|
|
)
|
|
spans = list(ContactAvailabilitySpan.objects.filter(user=self.user).order_by("start_ts"))
|
|
self.assertEqual(1, len(spans))
|
|
self.assertEqual(ts0, spans[0].start_ts)
|
|
self.assertEqual(ts0 + 5_000, spans[0].end_ts)
|