from __future__ import annotations from asgiref.sync import async_to_sync from django.test import TestCase from core.commands.base import CommandContext from core.commands.engine import process_inbound_message from core.commands.handlers.codex import parse_codex_command from core.models import ( ChatSession, CommandChannelBinding, CommandProfile, CodexPermissionRequest, CodexRun, DerivedTask, ExternalSyncEvent, Message, Person, PersonIdentifier, TaskProject, TaskProviderConfig, User, ) class CodexCommandParserTests(TestCase): def test_parse_variants(self): self.assertEqual("default", parse_codex_command("#codex# run this").command) self.assertEqual("plan", parse_codex_command("#codex plan# run this").command) self.assertEqual("status", parse_codex_command("#codex status#").command) parsed = parse_codex_command("#codex approve abc123#") self.assertEqual("approve", parsed.command) self.assertEqual("abc123", parsed.approval_key) self.assertEqual("default", parse_codex_command(".codex run this").command) self.assertEqual("plan", parse_codex_command(".CODEX plan run this").command) self.assertEqual("status", parse_codex_command(".codex status").command) parsed_dot = parse_codex_command(".codex approve abc123") self.assertEqual("approve", parsed_dot.command) self.assertEqual("abc123", parsed_dot.approval_key) class CodexCommandExecutionTests(TestCase): def setUp(self): self.user = User.objects.create_user("codex-cmd-user", "codex-cmd@example.com", "x") self.person = Person.objects.create(user=self.user, name="Codex Cmd") self.identifier = PersonIdentifier.objects.create( user=self.user, person=self.person, service="web", identifier="web-chan-1", ) self.session = ChatSession.objects.create(user=self.user, identifier=self.identifier) self.project = TaskProject.objects.create(user=self.user, name="Project A") self.task = DerivedTask.objects.create( user=self.user, project=self.project, epic=None, title="Task A", source_service="web", source_channel="web-chan-1", reference_code="1", status_snapshot="open", ) self.profile = CommandProfile.objects.create( user=self.user, slug="codex", name="Codex", enabled=True, trigger_token="#codex#", reply_required=False, exact_match_only=False, ) CommandChannelBinding.objects.create( profile=self.profile, direction="ingress", service="web", channel_identifier="web-chan-1", enabled=True, ) TaskProviderConfig.objects.create( user=self.user, provider="codex_cli", enabled=True, settings={ "command": "codex", "workspace_root": "", "default_profile": "", "timeout_seconds": 60, "chat_link_mode": "task-sync", "instance_label": "default", "approver_mode": "channel", "approver_service": "web", "approver_identifier": "approver-chan", }, ) def _msg(self, text: str, *, source_chat_id: str = "web-chan-1", reply_to=None): return Message.objects.create( user=self.user, session=self.session, sender_uuid="", text=text, ts=1000 + Message.objects.filter(user=self.user).count(), source_service="web", source_chat_id=source_chat_id, reply_to=reply_to, message_meta={}, ) def test_default_submission_creates_run_and_event(self): trigger = self._msg("#codex# please update #1") results = async_to_sync(process_inbound_message)( CommandContext( service="web", channel_identifier="web-chan-1", message_id=str(trigger.id), user_id=self.user.id, message_text=str(trigger.text), payload={}, ) ) self.assertEqual(1, len(results)) self.assertTrue(results[0].ok) run = CodexRun.objects.order_by("-created_at").first() self.assertIsNotNone(run) self.assertEqual("waiting_approval", run.status) event = ExternalSyncEvent.objects.order_by("-created_at").first() self.assertEqual("waiting_approval", event.status) self.assertEqual("default", str((event.payload or {}).get("provider_payload", {}).get("mode") or "")) self.assertTrue( CodexPermissionRequest.objects.filter( user=self.user, codex_run=run, status="pending", ).exists() ) def test_plan_requires_reply_anchor(self): trigger = self._msg("#codex plan# #1") results = async_to_sync(process_inbound_message)( CommandContext( service="web", channel_identifier="web-chan-1", message_id=str(trigger.id), user_id=self.user.id, message_text=str(trigger.text), payload={}, ) ) self.assertEqual(1, len(results)) self.assertFalse(results[0].ok) self.assertEqual("reply_required_for_codex_plan", results[0].error) def test_approve_command_queues_resume_event(self): waiting_event = ExternalSyncEvent.objects.create( user=self.user, task=self.task, provider="codex_cli", status="waiting_approval", payload={}, error="", ) run = CodexRun.objects.create( user=self.user, task=self.task, project=self.project, source_service="web", source_channel="web-chan-1", status="waiting_approval", request_payload={"action": "append_update", "provider_payload": {"task_id": str(self.task.id)}}, result_payload={}, ) req = CodexPermissionRequest.objects.create( user=self.user, codex_run=run, external_sync_event=waiting_event, approval_key="ak-123", summary="Need approval", requested_permissions={"items": ["write"]}, resume_payload={"resume": True}, status="pending", ) CommandChannelBinding.objects.create( profile=self.profile, direction="ingress", service="web", channel_identifier="approver-chan", enabled=True, ) trigger = self._msg("#codex approve ak-123#", source_chat_id="approver-chan") results = async_to_sync(process_inbound_message)( CommandContext( service="web", channel_identifier="approver-chan", message_id=str(trigger.id), user_id=self.user.id, message_text=str(trigger.text), payload={}, ) ) self.assertEqual(1, len(results)) self.assertTrue(results[0].ok) req.refresh_from_db() run.refresh_from_db() waiting_event.refresh_from_db() self.assertEqual("approved", req.status) self.assertEqual("approved_waiting_resume", run.status) self.assertEqual("ok", waiting_event.status) self.assertTrue( ExternalSyncEvent.objects.filter(idempotency_key="codex_approval:ak-123:approved", status="pending").exists() ) def test_approve_pre_submit_request_queues_original_action(self): waiting_event = ExternalSyncEvent.objects.create( user=self.user, task=self.task, provider="codex_cli", status="waiting_approval", payload={}, error="", ) run = CodexRun.objects.create( user=self.user, task=self.task, project=self.project, source_service="web", source_channel="web-chan-1", status="waiting_approval", request_payload={"action": "append_update", "provider_payload": {"task_id": str(self.task.id)}}, result_payload={}, ) CodexPermissionRequest.objects.create( user=self.user, codex_run=run, external_sync_event=waiting_event, approval_key="pre-ak-1", summary="pre submit", requested_permissions={"type": "pre_submit"}, resume_payload={ "gate_type": "pre_submit", "action": "append_update", "provider_payload": {"task_id": str(self.task.id), "mode": "default"}, "idempotency_key": "codex_cmd:resume:1", }, status="pending", ) CommandChannelBinding.objects.get_or_create( profile=self.profile, direction="ingress", service="web", channel_identifier="approver-chan", defaults={"enabled": True}, ) trigger = self._msg(".codex approve pre-ak-1", source_chat_id="approver-chan") results = async_to_sync(process_inbound_message)( CommandContext( service="web", channel_identifier="approver-chan", message_id=str(trigger.id), user_id=self.user.id, message_text=str(trigger.text), payload={}, ) ) self.assertEqual(1, len(results)) self.assertTrue(results[0].ok) resume = ExternalSyncEvent.objects.filter(idempotency_key="codex_cmd:resume:1").first() self.assertIsNotNone(resume) self.assertEqual("pending", resume.status) self.assertEqual("append_update", str((resume.payload or {}).get("action") or ""))