Improve tasks and backdate insights
This commit is contained in:
@@ -3,7 +3,12 @@ from __future__ import annotations
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from core.models import ContactAvailabilitySettings, User
|
||||
from core.models import (
|
||||
ContactAvailabilityEvent,
|
||||
ContactAvailabilitySettings,
|
||||
Person,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
class AvailabilitySettingsPageTests(TestCase):
|
||||
@@ -36,3 +41,36 @@ class AvailabilitySettingsPageTests(TestCase):
|
||||
self.assertTrue(row.inference_enabled)
|
||||
self.assertEqual(120, row.retention_days)
|
||||
self.assertEqual(300, row.fade_threshold_seconds)
|
||||
|
||||
def test_contact_event_stats_are_aggregated(self):
|
||||
person = Person.objects.create(user=self.user, name="Alice")
|
||||
ContactAvailabilityEvent.objects.create(
|
||||
user=self.user,
|
||||
person=person,
|
||||
service="whatsapp",
|
||||
source_kind="message_in",
|
||||
availability_state="available",
|
||||
confidence=0.9,
|
||||
ts=1000,
|
||||
payload={},
|
||||
)
|
||||
ContactAvailabilityEvent.objects.create(
|
||||
user=self.user,
|
||||
person=person,
|
||||
service="whatsapp",
|
||||
source_kind="inferred_timeout",
|
||||
availability_state="fading",
|
||||
confidence=0.5,
|
||||
ts=2000,
|
||||
payload={},
|
||||
)
|
||||
response = self.client.get(reverse("availability_settings"))
|
||||
self.assertEqual(200, response.status_code)
|
||||
stats = list(response.context["contact_stats"])
|
||||
self.assertEqual(1, len(stats))
|
||||
self.assertEqual("Alice", stats[0]["person__name"])
|
||||
self.assertEqual(2, stats[0]["total_events"])
|
||||
self.assertEqual(1, stats[0]["available_events"])
|
||||
self.assertEqual(1, stats[0]["fading_events"])
|
||||
self.assertEqual(1, stats[0]["message_activity_events"])
|
||||
self.assertEqual(1, stats[0]["inferred_timeout_events"])
|
||||
|
||||
131
core/tests/test_reconcile_workspace_metric_history.py
Normal file
131
core/tests/test_reconcile_workspace_metric_history.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from core.models import (
|
||||
ChatSession,
|
||||
Message,
|
||||
Person,
|
||||
PersonIdentifier,
|
||||
User,
|
||||
WorkspaceMetricSnapshot,
|
||||
)
|
||||
from core.views.workspace import _conversation_for_person
|
||||
|
||||
|
||||
class ReconcileWorkspaceMetricHistoryCommandTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
"reconcile-user",
|
||||
"reconcile@example.com",
|
||||
"x",
|
||||
)
|
||||
self.person = Person.objects.create(user=self.user, name="Reconcile Person")
|
||||
self.identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="whatsapp",
|
||||
identifier="15551230000@s.whatsapp.net",
|
||||
)
|
||||
self.session = ChatSession.objects.create(user=self.user, identifier=self.identifier)
|
||||
base_ts = 1_700_000_000_000
|
||||
for idx in range(10):
|
||||
inbound = idx % 2 == 0
|
||||
Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=base_ts + (idx * 60_000),
|
||||
text=f"m{idx}",
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
sender_uuid=f"actor-{idx}@s.whatsapp.net",
|
||||
custom_author="OTHER" if inbound else "USER",
|
||||
)
|
||||
|
||||
def test_reconcile_builds_checkpoints_and_no_reset_is_idempotent(self):
|
||||
call_command(
|
||||
"reconcile_workspace_metric_history",
|
||||
"--person-id",
|
||||
str(self.person.id),
|
||||
"--service",
|
||||
"whatsapp",
|
||||
"--days",
|
||||
"36500",
|
||||
"--step-messages",
|
||||
"2",
|
||||
)
|
||||
conversation = _conversation_for_person(self.user, self.person)
|
||||
first_count = WorkspaceMetricSnapshot.objects.filter(
|
||||
conversation=conversation
|
||||
).count()
|
||||
self.assertGreaterEqual(first_count, 5)
|
||||
|
||||
call_command(
|
||||
"reconcile_workspace_metric_history",
|
||||
"--person-id",
|
||||
str(self.person.id),
|
||||
"--service",
|
||||
"whatsapp",
|
||||
"--days",
|
||||
"36500",
|
||||
"--step-messages",
|
||||
"2",
|
||||
"--no-reset",
|
||||
)
|
||||
second_count = WorkspaceMetricSnapshot.objects.filter(
|
||||
conversation=conversation
|
||||
).count()
|
||||
self.assertEqual(first_count, second_count)
|
||||
latest = conversation.metric_snapshots.first()
|
||||
self.assertEqual(5, int(latest.inbound_messages or 0))
|
||||
self.assertEqual(5, int(latest.outbound_messages or 0))
|
||||
|
||||
def test_no_reset_does_not_duplicate_when_messages_share_timestamp(self):
|
||||
Message.objects.all().delete()
|
||||
base_ts = 1_700_100_000_000
|
||||
for idx in range(8):
|
||||
inbound = idx % 2 == 0
|
||||
Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
ts=base_ts + ((idx // 2) * 60_000),
|
||||
text=f"d{idx}",
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
sender_uuid=f"dup-{idx}@s.whatsapp.net",
|
||||
custom_author="OTHER" if inbound else "USER",
|
||||
)
|
||||
|
||||
call_command(
|
||||
"reconcile_workspace_metric_history",
|
||||
"--person-id",
|
||||
str(self.person.id),
|
||||
"--service",
|
||||
"whatsapp",
|
||||
"--days",
|
||||
"36500",
|
||||
"--step-messages",
|
||||
"2",
|
||||
)
|
||||
conversation = _conversation_for_person(self.user, self.person)
|
||||
first_count = WorkspaceMetricSnapshot.objects.filter(
|
||||
conversation=conversation
|
||||
).count()
|
||||
|
||||
call_command(
|
||||
"reconcile_workspace_metric_history",
|
||||
"--person-id",
|
||||
str(self.person.id),
|
||||
"--service",
|
||||
"whatsapp",
|
||||
"--days",
|
||||
"36500",
|
||||
"--step-messages",
|
||||
"2",
|
||||
"--no-reset",
|
||||
)
|
||||
second_count = WorkspaceMetricSnapshot.objects.filter(
|
||||
conversation=conversation
|
||||
).count()
|
||||
self.assertEqual(first_count, second_count)
|
||||
113
core/tests/test_tasks_pages_management.py
Normal file
113
core/tests/test_tasks_pages_management.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from core.models import ChatTaskSource, TaskEpic, TaskProject, User
|
||||
|
||||
|
||||
class TasksPagesManagementTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user("tasks-pages-user", "tasks-pages@example.com", "x")
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_tasks_hub_requires_group_scope_for_project_create(self):
|
||||
create_response = self.client.post(
|
||||
reverse("tasks_hub"),
|
||||
{
|
||||
"action": "project_create",
|
||||
"name": "Ops",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, create_response.status_code)
|
||||
self.assertFalse(TaskProject.objects.filter(user=self.user, name="Ops").exists())
|
||||
|
||||
def test_tasks_hub_can_create_scoped_project_and_delete(self):
|
||||
create_response = self.client.post(
|
||||
reverse("tasks_hub"),
|
||||
{
|
||||
"action": "project_create",
|
||||
"name": "Ops",
|
||||
"service": "whatsapp",
|
||||
"channel_identifier": "120363402761690215@g.us",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, create_response.status_code)
|
||||
project = TaskProject.objects.get(user=self.user, name="Ops")
|
||||
self.assertIsNotNone(project)
|
||||
self.assertTrue(
|
||||
ChatTaskSource.objects.filter(
|
||||
user=self.user,
|
||||
service="whatsapp",
|
||||
channel_identifier="120363402761690215@g.us",
|
||||
project=project,
|
||||
).exists()
|
||||
)
|
||||
|
||||
delete_response = self.client.post(
|
||||
reverse("tasks_hub"),
|
||||
{
|
||||
"action": "project_delete",
|
||||
"project_id": str(project.id),
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, delete_response.status_code)
|
||||
self.assertFalse(TaskProject.objects.filter(user=self.user, name="Ops").exists())
|
||||
|
||||
def test_project_page_can_create_and_delete_epic(self):
|
||||
project = TaskProject.objects.create(user=self.user, name="Roadmap")
|
||||
create_response = self.client.post(
|
||||
reverse("tasks_project", kwargs={"project_id": str(project.id)}),
|
||||
{
|
||||
"action": "epic_create",
|
||||
"name": "Phase 1",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, create_response.status_code)
|
||||
epic = TaskEpic.objects.get(project=project, name="Phase 1")
|
||||
self.assertIsNotNone(epic)
|
||||
|
||||
delete_response = self.client.post(
|
||||
reverse("tasks_project", kwargs={"project_id": str(project.id)}),
|
||||
{
|
||||
"action": "epic_delete",
|
||||
"epic_id": str(epic.id),
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, delete_response.status_code)
|
||||
self.assertFalse(TaskEpic.objects.filter(project=project, name="Phase 1").exists())
|
||||
|
||||
def test_group_page_create_and_map_project(self):
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"tasks_group",
|
||||
kwargs={
|
||||
"service": "whatsapp",
|
||||
"identifier": "120363402761690215@g.us",
|
||||
},
|
||||
),
|
||||
{
|
||||
"action": "group_project_create",
|
||||
"project_name": "Group Project",
|
||||
"epic_name": "Sprint A",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
project = TaskProject.objects.get(user=self.user, name="Group Project")
|
||||
epic = TaskEpic.objects.get(project=project, name="Sprint A")
|
||||
self.assertTrue(
|
||||
ChatTaskSource.objects.filter(
|
||||
user=self.user,
|
||||
service="whatsapp",
|
||||
channel_identifier="120363402761690215@g.us",
|
||||
project=project,
|
||||
epic=epic,
|
||||
enabled=True,
|
||||
).exists()
|
||||
)
|
||||
@@ -10,6 +10,7 @@ from core.models import (
|
||||
ChatSession,
|
||||
ChatTaskSource,
|
||||
DerivedTask,
|
||||
ExternalChatLink,
|
||||
Message,
|
||||
Person,
|
||||
PersonIdentifier,
|
||||
@@ -203,3 +204,67 @@ class TaskSettingsViewActionsTests(TestCase):
|
||||
self.assertFalse(
|
||||
ChatTaskSource.objects.filter(id=self.source.id, user=self.user).exists()
|
||||
)
|
||||
|
||||
|
||||
class TaskSettingsExternalChatLinkScopeTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user("task-link-user", "task-link@example.com", "x")
|
||||
self.client.force_login(self.user)
|
||||
self.group_person = Person.objects.create(user=self.user, name="Scoped Group")
|
||||
self.group_identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.group_person,
|
||||
service="whatsapp",
|
||||
identifier="120363402761690215@g.us",
|
||||
)
|
||||
self.group_identifier_bare = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.group_person,
|
||||
service="whatsapp",
|
||||
identifier="120363402761690215",
|
||||
)
|
||||
self.other_person = Person.objects.create(user=self.user, name="Other Group")
|
||||
self.other_identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.other_person,
|
||||
service="whatsapp",
|
||||
identifier="120399999999999999@g.us",
|
||||
)
|
||||
|
||||
def test_scoped_settings_limits_contact_identifier_options(self):
|
||||
response = self.client.get(
|
||||
reverse("tasks_settings"),
|
||||
{
|
||||
"service": "whatsapp",
|
||||
"identifier": "120363402761690215@g.us",
|
||||
},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
options = list(response.context["external_link_person_identifiers"])
|
||||
self.assertTrue(any(row.id == self.group_identifier.id for row in options))
|
||||
self.assertTrue(any(row.id == self.group_identifier_bare.id for row in options))
|
||||
self.assertFalse(any(row.id == self.other_identifier.id for row in options))
|
||||
self.assertTrue(bool(response.context["external_link_scoped"]))
|
||||
|
||||
def test_scoped_upsert_rejects_out_of_scope_identifier(self):
|
||||
response = self.client.post(
|
||||
reverse("tasks_settings"),
|
||||
{
|
||||
"action": "external_chat_link_upsert",
|
||||
"provider": "codex_cli",
|
||||
"person_identifier_id": str(self.other_identifier.id),
|
||||
"external_chat_id": "codex-chat-abc",
|
||||
"enabled": "1",
|
||||
"prefill_service": "whatsapp",
|
||||
"prefill_identifier": "120363402761690215@g.us",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertFalse(
|
||||
ExternalChatLink.objects.filter(
|
||||
user=self.user,
|
||||
provider="codex_cli",
|
||||
external_chat_id="codex-chat-abc",
|
||||
).exists()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user