Implement tasks
This commit is contained in:
@@ -81,7 +81,7 @@ class BPFallbackTests(TransactionTestCase):
|
||||
model="gpt-4o-mini",
|
||||
)
|
||||
|
||||
def test_bp_falls_back_to_draft_when_ai_fails(self):
|
||||
def test_bp_fails_fast_when_ai_fails(self):
|
||||
anchor = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
@@ -119,11 +119,11 @@ class BPFallbackTests(TransactionTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(result.ok)
|
||||
self.assertFalse(result.ok)
|
||||
run = CommandRun.objects.get(trigger_message=trigger, profile=self.profile)
|
||||
self.assertEqual("ok", run.status)
|
||||
self.assertEqual("failed", run.status)
|
||||
self.assertIn("bp_ai_failed", str(run.error))
|
||||
self.assertTrue(BusinessPlanDocument.objects.filter(trigger_message=trigger).exists())
|
||||
self.assertFalse(BusinessPlanDocument.objects.filter(trigger_message=trigger).exists())
|
||||
|
||||
def test_bp_uses_same_ai_selection_order_as_compose(self):
|
||||
AI.objects.create(
|
||||
|
||||
205
core/tests/test_bp_subcommands.py
Normal file
205
core/tests/test_bp_subcommands.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from django.test import TransactionTestCase, override_settings
|
||||
|
||||
from core.commands.base import CommandContext
|
||||
from core.commands.handlers.bp import BPCommandHandler, parse_bp_subcommand
|
||||
from core.models import (
|
||||
BusinessPlanDocument,
|
||||
ChatSession,
|
||||
CommandAction,
|
||||
CommandChannelBinding,
|
||||
CommandProfile,
|
||||
Message,
|
||||
Person,
|
||||
PersonIdentifier,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(BP_SUBCOMMANDS_V1=True)
|
||||
class BPSubcommandTests(TransactionTestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="bp-sub-user",
|
||||
email="bp-sub@example.com",
|
||||
password="x",
|
||||
)
|
||||
self.person = Person.objects.create(user=self.user, name="Sub Person")
|
||||
self.identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="whatsapp",
|
||||
identifier="120363402761690215",
|
||||
)
|
||||
self.session = ChatSession.objects.create(user=self.user, identifier=self.identifier)
|
||||
self.profile = CommandProfile.objects.create(
|
||||
user=self.user,
|
||||
slug="bp",
|
||||
name="Business Plan",
|
||||
enabled=True,
|
||||
trigger_token="#bp#",
|
||||
reply_required=True,
|
||||
exact_match_only=True,
|
||||
)
|
||||
CommandChannelBinding.objects.create(
|
||||
profile=self.profile,
|
||||
direction="ingress",
|
||||
service="whatsapp",
|
||||
channel_identifier="120363402761690215",
|
||||
enabled=True,
|
||||
)
|
||||
for action_type, position in (("extract_bp", 0), ("save_document", 1)):
|
||||
CommandAction.objects.create(
|
||||
profile=self.profile,
|
||||
action_type=action_type,
|
||||
enabled=True,
|
||||
position=position,
|
||||
)
|
||||
|
||||
def _ctx(self, trigger: Message, text: str) -> CommandContext:
|
||||
return CommandContext(
|
||||
service="whatsapp",
|
||||
channel_identifier="120363402761690215",
|
||||
message_id=str(trigger.id),
|
||||
user_id=self.user.id,
|
||||
message_text=text,
|
||||
payload={},
|
||||
)
|
||||
|
||||
def test_parser_detects_set_and_remainder(self):
|
||||
parsed = parse_bp_subcommand(" #BP set# addendum text ")
|
||||
self.assertEqual("set", parsed.command)
|
||||
self.assertEqual("addendum text", parsed.remainder_text)
|
||||
|
||||
def test_parser_detects_set_range(self):
|
||||
parsed = parse_bp_subcommand("#bp set range# now")
|
||||
self.assertEqual("set_range", parsed.command)
|
||||
|
||||
def test_set_standalone_uses_remainder_only(self):
|
||||
trigger = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="#bp set# direct body",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
with patch("core.commands.handlers.bp.ai_runner.run_prompt", new=AsyncMock()) as mocked_ai:
|
||||
result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, trigger.text))
|
||||
self.assertTrue(result.ok)
|
||||
mocked_ai.assert_not_awaited()
|
||||
doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
|
||||
self.assertEqual("direct body", doc.content_markdown)
|
||||
self.assertEqual("Generated from 1 message.", doc.structured_payload.get("annotation"))
|
||||
|
||||
def test_set_reply_only_uses_anchor(self):
|
||||
anchor = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="anchor body",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
trigger = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="#bp set#",
|
||||
ts=2000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
reply_to=anchor,
|
||||
)
|
||||
result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, trigger.text))
|
||||
self.assertTrue(result.ok)
|
||||
doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
|
||||
self.assertEqual("anchor body", doc.content_markdown)
|
||||
self.assertEqual("Generated from 1 message.", doc.structured_payload.get("annotation"))
|
||||
|
||||
def test_set_reply_plus_addendum_uses_divider(self):
|
||||
anchor = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="base body",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
trigger = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="#bp set# extra text",
|
||||
ts=2000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
reply_to=anchor,
|
||||
)
|
||||
result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, trigger.text))
|
||||
self.assertTrue(result.ok)
|
||||
doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
|
||||
self.assertIn("base body", doc.content_markdown)
|
||||
self.assertIn("--- Addendum (newer message text) ---", doc.content_markdown)
|
||||
self.assertIn("extra text", doc.content_markdown)
|
||||
self.assertEqual(
|
||||
"Generated from 1 message + 1 addendum.",
|
||||
doc.structured_payload.get("annotation"),
|
||||
)
|
||||
|
||||
def test_set_range_requires_reply(self):
|
||||
trigger = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="#bp set range#",
|
||||
ts=3000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, trigger.text))
|
||||
self.assertFalse(result.ok)
|
||||
self.assertEqual("failed", result.status)
|
||||
self.assertEqual("bp_set_range_requires_reply_target", result.error)
|
||||
|
||||
def test_set_range_exports_text_only_lines(self):
|
||||
anchor = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="line 1",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="",
|
||||
ts=1500,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
)
|
||||
trigger = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="#bp set range#",
|
||||
ts=2000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215",
|
||||
reply_to=anchor,
|
||||
)
|
||||
result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, trigger.text))
|
||||
self.assertTrue(result.ok)
|
||||
doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
|
||||
self.assertEqual("line 1\n(no text)\n#bp set range#", doc.content_markdown)
|
||||
self.assertEqual("Generated from 3 messages.", doc.structured_payload.get("annotation"))
|
||||
138
core/tests/test_repeat_answer_and_tasks.py
Normal file
138
core/tests/test_repeat_answer_and_tasks.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from core.assist.repeat_answer import find_repeat_answer, learn_from_message
|
||||
from core.models import (
|
||||
AnswerSuggestionEvent,
|
||||
ChatSession,
|
||||
ChatTaskSource,
|
||||
DerivedTask,
|
||||
DerivedTaskEvent,
|
||||
Person,
|
||||
PersonIdentifier,
|
||||
TaskCompletionPattern,
|
||||
TaskProject,
|
||||
User,
|
||||
Message,
|
||||
)
|
||||
from core.tasks.engine import process_inbound_task_intelligence
|
||||
|
||||
|
||||
class RepeatAnswerTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user("repeat-user", "repeat@example.com", "x")
|
||||
self.person = Person.objects.create(user=self.user, name="Repeat Person")
|
||||
self.identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="whatsapp",
|
||||
identifier="120363402761690215@g.us",
|
||||
)
|
||||
self.session = ChatSession.objects.create(user=self.user, identifier=self.identifier)
|
||||
|
||||
def test_suggest_only_for_repeated_group_question(self):
|
||||
q1 = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="What is the deploy command?",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
)
|
||||
a1 = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="me",
|
||||
text="Use make deploy-prod.",
|
||||
ts=1200,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
reply_to=q1,
|
||||
)
|
||||
async_to_sync(learn_from_message)(a1)
|
||||
|
||||
q2 = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="What is the deploy command?",
|
||||
ts=2000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
)
|
||||
suggestion = async_to_sync(find_repeat_answer)(self.user, q2)
|
||||
self.assertIsNotNone(suggestion)
|
||||
self.assertIn("deploy", suggestion.answer_text.lower())
|
||||
self.assertTrue(
|
||||
AnswerSuggestionEvent.objects.filter(message=q2, status="suggested").exists()
|
||||
)
|
||||
|
||||
|
||||
@override_settings(TASK_DERIVATION_USE_AI=False)
|
||||
class TaskEngineTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user("task-user", "task@example.com", "x")
|
||||
self.person = Person.objects.create(user=self.user, name="Task Person")
|
||||
self.identifier = PersonIdentifier.objects.create(
|
||||
user=self.user,
|
||||
person=self.person,
|
||||
service="whatsapp",
|
||||
identifier="120363402761690215@g.us",
|
||||
)
|
||||
self.session = ChatSession.objects.create(user=self.user, identifier=self.identifier)
|
||||
self.project = TaskProject.objects.create(user=self.user, name="Ops")
|
||||
ChatTaskSource.objects.create(
|
||||
user=self.user,
|
||||
service="whatsapp",
|
||||
channel_identifier="120363402761690215@g.us",
|
||||
project=self.project,
|
||||
enabled=True,
|
||||
)
|
||||
TaskCompletionPattern.objects.create(user=self.user, phrase="done", enabled=True)
|
||||
|
||||
def test_creates_derived_task_on_task_like_message(self):
|
||||
m = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="Task: rotate credentials tonight",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
)
|
||||
async_to_sync(process_inbound_task_intelligence)(m)
|
||||
task = DerivedTask.objects.get(origin_message=m)
|
||||
self.assertEqual("open", task.status_snapshot)
|
||||
self.assertTrue(task.reference_code)
|
||||
self.assertTrue(DerivedTaskEvent.objects.filter(task=task, event_type="created").exists())
|
||||
|
||||
def test_marks_completion_from_regex_marker(self):
|
||||
seed = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text="task: patch kernel",
|
||||
ts=1000,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
)
|
||||
async_to_sync(process_inbound_task_intelligence)(seed)
|
||||
task = DerivedTask.objects.get(origin_message=seed)
|
||||
marker = Message.objects.create(
|
||||
user=self.user,
|
||||
session=self.session,
|
||||
sender_uuid="peer",
|
||||
text=f"done #{task.reference_code}",
|
||||
ts=1100,
|
||||
source_service="whatsapp",
|
||||
source_chat_id="120363402761690215@g.us",
|
||||
)
|
||||
async_to_sync(process_inbound_task_intelligence)(marker)
|
||||
task.refresh_from_db()
|
||||
self.assertEqual("completed", task.status_snapshot)
|
||||
self.assertTrue(
|
||||
DerivedTaskEvent.objects.filter(task=task, event_type="completion_marked").exists()
|
||||
)
|
||||
Reference in New Issue
Block a user