344 lines
13 KiB
Python
344 lines
13 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from core.models import (
|
|
ChatSession,
|
|
ChatTaskSource,
|
|
DerivedTask,
|
|
DerivedTaskEvent,
|
|
Message,
|
|
Person,
|
|
PersonIdentifier,
|
|
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)
|
|
self.person = Person.objects.create(user=self.user, name="Scope Person")
|
|
self.pid_whatsapp = PersonIdentifier.objects.create(
|
|
user=self.user,
|
|
person=self.person,
|
|
service="whatsapp",
|
|
identifier="120363402761690215@g.us",
|
|
)
|
|
self.pid_signal = PersonIdentifier.objects.create(
|
|
user=self.user,
|
|
person=self.person,
|
|
service="signal",
|
|
identifier="+15551230000",
|
|
)
|
|
|
|
def test_tasks_hub_can_create_project_name_only(self):
|
|
response = self.client.post(
|
|
reverse("tasks_hub"),
|
|
{"action": "project_create", "name": "Ops"},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
project = TaskProject.objects.get(user=self.user, name="Ops")
|
|
self.assertIsNotNone(project)
|
|
self.assertFalse(
|
|
ChatTaskSource.objects.filter(user=self.user, project=project).exists()
|
|
)
|
|
|
|
def test_tasks_hub_can_map_identifier_to_selected_project(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Mapped")
|
|
response = self.client.post(
|
|
reverse("tasks_hub"),
|
|
{
|
|
"action": "project_map_identifier",
|
|
"project_id": str(project.id),
|
|
"person_identifier_id": str(self.pid_signal.id),
|
|
"person": str(self.person.id),
|
|
"service": "whatsapp",
|
|
"identifier": "120363402761690215@g.us",
|
|
},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertTrue(
|
|
ChatTaskSource.objects.filter(
|
|
user=self.user,
|
|
project=project,
|
|
service="signal",
|
|
channel_identifier="+15551230000",
|
|
enabled=True,
|
|
).exists()
|
|
)
|
|
|
|
def test_tasks_hub_settings_link_preserves_scope_context(self):
|
|
response = self.client.get(
|
|
f"{reverse('tasks_hub')}?person={self.person.id}&service=signal&identifier=147e75bd-91b7-4014-b9e5-12a44b978f7b"
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertContains(
|
|
response,
|
|
f"{reverse('tasks_settings')}?person={self.person.id}&service=signal&identifier=147e75bd-91b7-4014-b9e5-12a44b978f7b",
|
|
)
|
|
|
|
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_project_page_can_assign_and_clear_task_epic(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Roadmap")
|
|
epic = TaskEpic.objects.create(project=project, name="Sprint A")
|
|
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
|
|
origin = Message.objects.create(
|
|
user=self.user,
|
|
session=session,
|
|
ts=1_700_000_000_100,
|
|
text="task: assign epic",
|
|
sender_uuid="+15551230000",
|
|
custom_author="OTHER",
|
|
source_service="signal",
|
|
source_chat_id="+15551230000",
|
|
)
|
|
task = DerivedTask.objects.create(
|
|
user=self.user,
|
|
project=project,
|
|
title="Assign me",
|
|
source_service="signal",
|
|
source_channel="+15551230000",
|
|
origin_message=origin,
|
|
reference_code="9",
|
|
status_snapshot="open",
|
|
)
|
|
assign_response = self.client.post(
|
|
reverse("tasks_project", kwargs={"project_id": str(project.id)}),
|
|
{
|
|
"action": "task_set_epic",
|
|
"task_id": str(task.id),
|
|
"epic_id": str(epic.id),
|
|
},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, assign_response.status_code)
|
|
task.refresh_from_db()
|
|
self.assertEqual(epic.id, task.epic_id)
|
|
|
|
clear_response = self.client.post(
|
|
reverse("tasks_project", kwargs={"project_id": str(project.id)}),
|
|
{
|
|
"action": "task_set_epic",
|
|
"task_id": str(task.id),
|
|
"epic_id": "",
|
|
},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, clear_response.status_code)
|
|
task.refresh_from_db()
|
|
self.assertIsNone(task.epic_id)
|
|
|
|
@patch("core.views.tasks.send_message_raw", new_callable=AsyncMock)
|
|
def test_project_epic_create_announces_to_project_chats(self, mocked_send):
|
|
project = TaskProject.objects.create(user=self.user, name="Roadmap")
|
|
ChatTaskSource.objects.create(
|
|
user=self.user,
|
|
service="whatsapp",
|
|
channel_identifier="120363402761690215@g.us",
|
|
project=project,
|
|
enabled=True,
|
|
)
|
|
response = self.client.post(
|
|
reverse("tasks_project", kwargs={"project_id": str(project.id)}),
|
|
{
|
|
"action": "epic_create",
|
|
"name": "Phase 2",
|
|
},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertTrue(
|
|
TaskEpic.objects.filter(project=project, name="Phase 2").exists()
|
|
)
|
|
self.assertTrue(mocked_send.await_count >= 1)
|
|
payloads = [
|
|
str(call.kwargs.get("text") or "") for call in mocked_send.await_args_list
|
|
]
|
|
self.assertTrue(any("whatsapp usage" in row.lower() for row in payloads))
|
|
self.assertTrue(any("add task to epic" in row.lower() for row in payloads))
|
|
|
|
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()
|
|
)
|
|
|
|
def test_tasks_hub_shows_human_creator_label(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Creator Test")
|
|
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
|
|
origin = Message.objects.create(
|
|
user=self.user,
|
|
session=session,
|
|
ts=1_700_000_000_000,
|
|
text="task: write docs",
|
|
sender_uuid="+15551230000",
|
|
custom_author="OTHER",
|
|
source_service="signal",
|
|
source_chat_id="+15551230000",
|
|
)
|
|
DerivedTask.objects.create(
|
|
user=self.user,
|
|
project=project,
|
|
title="Write docs",
|
|
source_service="signal",
|
|
source_channel="+15551230000",
|
|
origin_message=origin,
|
|
reference_code="1",
|
|
status_snapshot="open",
|
|
)
|
|
|
|
response = self.client.get(reverse("tasks_hub"))
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertContains(response, "Scope Person")
|
|
|
|
def test_tasks_hub_can_create_manual_task_without_chat_source(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Manual Project")
|
|
response = self.client.post(
|
|
reverse("tasks_hub"),
|
|
{
|
|
"action": "task_create",
|
|
"project_id": str(project.id),
|
|
"title": "Manual web task",
|
|
"due_date": "2026-03-10",
|
|
"assignee_identifier": "@operator",
|
|
},
|
|
follow=True,
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
task = DerivedTask.objects.get(user=self.user, project=project, title="Manual web task")
|
|
self.assertEqual("web", task.source_service)
|
|
self.assertEqual("@operator", task.assignee_identifier)
|
|
self.assertEqual("2026-03-10", task.due_date.isoformat())
|
|
event = task.events.order_by("-created_at").first()
|
|
self.assertEqual("created", event.event_type)
|
|
self.assertEqual("web_ui", str((event.payload or {}).get("via") or ""))
|
|
|
|
def test_project_page_creator_column_links_to_compose(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Creator Link Test")
|
|
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
|
|
origin = Message.objects.create(
|
|
user=self.user,
|
|
session=session,
|
|
ts=1_700_000_000_111,
|
|
text="task: creator link",
|
|
sender_uuid="+15551230000",
|
|
custom_author="OTHER",
|
|
source_service="signal",
|
|
source_chat_id="+15551230000",
|
|
)
|
|
DerivedTask.objects.create(
|
|
user=self.user,
|
|
project=project,
|
|
title="Creator link task",
|
|
source_service="signal",
|
|
source_channel="+15551230000",
|
|
origin_message=origin,
|
|
reference_code="2",
|
|
status_snapshot="open",
|
|
)
|
|
response = self.client.get(
|
|
reverse("tasks_project", kwargs={"project_id": str(project.id)})
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertContains(
|
|
response,
|
|
f'{reverse("compose_page")}?service=signal&identifier=%2B15551230000&person={self.person.id}',
|
|
)
|
|
|
|
def test_task_detail_renders_payload_summary_and_json(self):
|
|
project = TaskProject.objects.create(user=self.user, name="Payload Test")
|
|
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
|
|
origin = Message.objects.create(
|
|
user=self.user,
|
|
session=session,
|
|
ts=1_700_000_000_000,
|
|
text="origin",
|
|
sender_uuid="+15551230000",
|
|
custom_author="OTHER",
|
|
source_service="signal",
|
|
source_chat_id="+15551230000",
|
|
)
|
|
task = DerivedTask.objects.create(
|
|
user=self.user,
|
|
project=project,
|
|
title="Payload detail",
|
|
source_service="signal",
|
|
source_channel="+15551230000",
|
|
origin_message=origin,
|
|
reference_code="42",
|
|
status_snapshot="open",
|
|
)
|
|
DerivedTaskEvent.objects.create(
|
|
task=task,
|
|
event_type="reaction_captured",
|
|
payload={"source": "signal", "emoji": "❤️", "reason": "heart_reaction"},
|
|
)
|
|
|
|
response = self.client.get(
|
|
reverse("tasks_task", kwargs={"task_id": str(task.id)})
|
|
)
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertContains(response, "View payload JSON")
|
|
self.assertContains(response, "<strong>source</strong>: signal", html=True)
|
|
self.assertContains(response, ""emoji": "❤️"")
|