button force anoncecall

This commit is contained in:
javad committed 2026-09-28 20:35:27 +03:30
1 parent c7993aae44
commit 9af91941d9
15 files changed
+294 -23

No files matched your search

+106 -1
View File
@@ -4,6 +4,7 @@ from unittest.mock import Mock, patch
import pytest
import requests
from django.core.management import call_command
from django.urls import reverse
from django.utils import timezone
from apps.accounts.models import User
@@ -12,7 +13,7 @@ from apps.core.models import VoipConfiguration
from apps.messaging.models import MessageReadReceipt
from apps.notifications.models import AnnouncementJob
from apps.staff.models import OrganizationalUnit
from apps.tickets.models import TicketCategory
from apps.tickets.models import TicketAccessGrant, TicketCategory
from apps.tickets.services import create_ticket, reply_ticket
@@ -222,3 +223,107 @@ def test_staff_ticket_shows_call_history_without_claiming_connection(client, ann
assert "پذیرش درخواست، به‌تنهایی برقرار شدن" in content
assert configuration.announcement_url not in content
assert configuration.announcement_api_key not in content
def test_staff_can_queue_immediate_call_and_see_it_in_ticket(client, announcement_data):
category, staff, customer, configuration = announcement_data
ticket = create_ticket(customer=customer, created_by=staff, category=category, subject="تماس فوری", body="پیام نخست")
automatic = AnnouncementJob.objects.get(message__ticket=ticket)
MessageReadReceipt.objects.create(message=automatic.message, user=customer)
detail_url = reverse("staff_tickets:detail", args=[ticket.public_id])
call_url = reverse("staff_tickets:announcement_now", args=[ticket.public_id])
client.force_login(staff)
detail = client.get(detail_url).content.decode()
assert call_url in detail
assert "اطلاع‌رسانی تلفنی فوری" in detail
assert "پیام اطلاع‌رسانی از پیش ضبط‌شده" in detail
assert client.get(call_url).status_code == 405
response = client.post(call_url, follow=True)
assert response.status_code == 200
assert "درخواست اطلاع‌رسانی تلفنی فوری در صف قرار گرفت." in response.content.decode()
job = AnnouncementJob.objects.get(ticket=ticket, source=AnnouncementJob.Source.MANUAL)
assert job.message is None and job.requested_by == staff
assert job.next_attempt_at <= timezone.now()
assert "درخواست فوری" in response.content.decode()
assert AnnouncementJob.objects.get(pk=automatic.pk).status == AnnouncementJob.Status.PENDING
provider_response = Mock(content=b"{}")
provider_response.json.return_value = {}
with patch("apps.integrations.announcements.requests.post", return_value=provider_response) as post:
call_command("process_notifications", once=True)
job.refresh_from_db()
automatic.refresh_from_db()
assert job.status == AnnouncementJob.Status.SENT
assert automatic.status == AnnouncementJob.Status.PENDING
assert post.call_args.kwargs["json"] == {"customer_number": customer.local_mobile, "announcement_id": configuration.announcement_id}
assert post.call_args.kwargs["headers"]["Idempotency-Key"] == job.idempotency_key
def test_immediate_call_coalesces_repeated_clicks(client, announcement_data):
category, staff, customer, _ = announcement_data
ticket = create_ticket(customer=customer, category=category, subject="تماس فوری", body="پیام مشتری")
url = reverse("staff_tickets:announcement_now", args=[ticket.public_id])
client.force_login(staff)
assert client.post(url).status_code == 302
second = client.post(url, follow=True)
assert "در دقیقهٔ گذشته ثبت شده است" in second.content.decode()
assert AnnouncementJob.objects.filter(ticket=ticket, source=AnnouncementJob.Source.MANUAL).count() == 1
def test_immediate_call_requires_staff_reply_access_and_configuration(client, announcement_data):
category, staff, customer, configuration = announcement_data
ticket = create_ticket(customer=customer, category=category, subject="تماس فوری", body="پیام مشتری")
url = reverse("staff_tickets:announcement_now", args=[ticket.public_id])
outsider = User.objects.create_user(username="announcement-viewer", account_type="STAFF")
TicketAccessGrant.objects.create(ticket=ticket, user=outsider, granted_by=staff, permission_level=TicketAccessGrant.Level.VIEW_ONLY)
client.force_login(outsider)
assert url not in client.get(reverse("staff_tickets:detail", args=[ticket.public_id])).content.decode()
assert client.post(url).status_code == 403
client.force_login(customer)
assert client.post(url).status_code == 403
assert not AnnouncementJob.objects.filter(ticket=ticket).exists()
client.force_login(staff)
configuration.announcement_url = ""
configuration.save()
page = client.get(reverse("staff_tickets:detail", args=[ticket.public_id])).content.decode()
assert "اطلاع‌رسانی تلفنی فوری" in page and "disabled" in page
result = client.post(url, follow=True)
assert "تنظیمات اطلاع‌رسانی تلفنی کامل نیست." in result.content.decode()
assert not AnnouncementJob.objects.filter(ticket=ticket).exists()
def test_immediate_call_is_processed_before_older_scheduled_calls(client, announcement_data):
category, staff, customer, _ = announcement_data
ticket = create_ticket(customer=customer, created_by=staff, category=category, subject="تماس فوری", body="پیام نخست")
for message in ticket.ticket_messages.all():
due(AnnouncementJob.objects.get(message=message))
client.force_login(staff)
client.post(reverse("staff_tickets:announcement_now", args=[ticket.public_id]))
immediate = AnnouncementJob.objects.get(ticket=ticket, source=AnnouncementJob.Source.MANUAL)
with patch("apps.integrations.announcements.requests.post", return_value=Mock(content=b"")) as post:
call_command("process_notifications", once=True)
assert post.call_args_list[0].kwargs["headers"]["Idempotency-Key"] == immediate.idempotency_key
def test_immediate_call_retries_provider_outage_without_breaking_ticket(client, announcement_data):
category, staff, customer, _ = announcement_data
ticket = create_ticket(customer=customer, category=category, subject="تماس فوری", body="پیام مشتری")
url = reverse("staff_tickets:announcement_now", args=[ticket.public_id])
client.force_login(staff)
client.post(url)
job = AnnouncementJob.objects.get(ticket=ticket, source=AnnouncementJob.Source.MANUAL)
with patch("apps.integrations.announcements.requests.post", side_effect=requests.ConnectionError("provider secret")) as post:
call_command("process_notifications", once=True)
job.refresh_from_db()
assert job.status == AnnouncementJob.Status.PENDING
assert job.last_error == "service_unreachable" and job.attempts == 1
assert post.call_args.kwargs["headers"]["Idempotency-Key"] == job.idempotency_key
assert client.get(reverse("staff_tickets:detail", args=[ticket.public_id])).status_code == 200
due(job)
with patch("apps.integrations.announcements.requests.post", return_value=Mock(content=b"")) as retry:
call_command("process_notifications", once=True)
job.refresh_from_db()
assert job.status == AnnouncementJob.Status.SENT
assert retry.call_args.kwargs["headers"]["Idempotency-Key"] == job.idempotency_key