fix button force anoncecall

This commit is contained in:
javad committed 2026-09-28 21:13:19 +03:30
1 parent 9af91941d9
commit 4525ec3e6a
9 files changed
+99 -48

No files matched your search

+3 -3
View File
@@ -11,12 +11,12 @@ class TicketConversationPage(BasePage):
def request_immediate_announcement(self):
card = self.page.get_by_role("region", name="اطلاع‌رسانی تلفنی فوری")
expect(card).to_be_visible()
expect(card).to_contain_text("پیام اطلاع‌رسانی از پیش ضبط‌شده")
button = card.get_by_role("button", name="ارسال اطلاع‌رسانی تلفنی فوری")
expect(card).to_contain_text("پیام صوتی از پیش ضبط‌شده")
button = card.get_by_role("button", name="اطلاع‌رسانی تلفنی فوری")
expect(button).to_be_enabled()
with self.page.expect_navigation(wait_until="domcontentloaded"):
button.click()
expect(self.page.get_by_text("درخواست اطلاع‌رسانی تلفنی فوری در صف قرار گرفت.")).to_be_visible()
expect(self.page.get_by_text("درخواست تماس توسط سرویس پذیرفته شد.")).to_be_visible()
expect(self.page.get_by_role("region", name="وضعیت اطلاع‌رسانی‌های تلفنی")).to_contain_text("درخواست فوری")
def send_reply(self, body):
+4 -1
View File
@@ -1,4 +1,5 @@
import base64
from unittest.mock import Mock, patch
import pytest
from django.contrib.auth.models import Permission
@@ -41,7 +42,9 @@ def test_staff_can_request_immediate_announcement_from_ticket_sidebar(live_serve
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
conversation = TicketConversationPage(page, live_server.url)
conversation.goto(reverse("staff_tickets:detail", args=[ticket.public_id]))
conversation.request_immediate_announcement()
with patch("apps.integrations.announcements.requests.post", return_value=Mock(content=b"")) as post:
conversation.request_immediate_announcement()
post.assert_called_once()
def test_staff_operations_pages_are_authorized_and_visually_styled(live_server, browser_page):
+56 -25
View File
@@ -11,7 +11,7 @@ from apps.accounts.models import User
from apps.core.admin import VoipConfigurationAdmin
from apps.core.models import VoipConfiguration
from apps.messaging.models import MessageReadReceipt
from apps.notifications.models import AnnouncementJob
from apps.notifications.models import AnnouncementJob, NotificationJob
from apps.staff.models import OrganizationalUnit
from apps.tickets.models import TicketAccessGrant, TicketCategory
from apps.tickets.services import create_ticket, reply_ticket
@@ -34,7 +34,7 @@ def announcement_people(db):
@pytest.fixture
def announcement_data(announcement_catalog, announcement_people):
configuration = VoipConfiguration.objects.create(
announcement_url="http://192.168.138.215:8000/v1/announcements",
announcement_url="http://37.255.224.143:8009/v1/announcements",
announcement_id="custom/fereshte1_G711",
announcement_delay_minutes=90,
)
@@ -236,28 +236,26 @@ def test_staff_can_queue_immediate_call_and_see_it_in_ticket(client, announcemen
detail = client.get(detail_url).content.decode()
assert call_url in detail
assert "اطلاع‌رسانی تلفنی فوری" in detail
assert "پیام اطلاع‌رسانی از پیش ضبط‌شده" in detail
assert "پیام صوتی از پیش ضبط‌شده" in detail
assert detail.index("وضعیت پیامک‌های مشتری") < detail.index('aria-label="اطلاع‌رسانی تلفنی فوری"')
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()
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.status == AnnouncementJob.Status.SENT
assert automatic.status == AnnouncementJob.Status.PENDING
assert "درخواست فوری" in response.content.decode()
assert AnnouncementJob.objects.get(pk=automatic.pk).status == AnnouncementJob.Status.PENDING
post.assert_called_once()
assert post.call_args.args == (configuration.announcement_url,)
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
assert post.call_args.kwargs["headers"] == {"Idempotency-Key": job.idempotency_key}
def test_immediate_call_coalesces_repeated_clicks(client, announcement_data):
@@ -265,10 +263,12 @@ def test_immediate_call_coalesces_repeated_clicks(client, 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)
with patch("apps.integrations.announcements.requests.post", return_value=Mock(content=b"")) as post:
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
post.assert_called_once()
def test_immediate_call_requires_staff_reply_access_and_configuration(client, announcement_data):
@@ -300,11 +300,14 @@ def test_immediate_call_is_processed_before_older_scheduled_calls(client, announ
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]))
with patch("apps.integrations.announcements.requests.post", return_value=Mock(content=b"")) as immediate_post:
client.post(reverse("staff_tickets:announcement_now", args=[ticket.public_id]))
immediate = AnnouncementJob.objects.get(ticket=ticket, source=AnnouncementJob.Source.MANUAL)
assert immediate.status == AnnouncementJob.Status.SENT
immediate_post.assert_called_once()
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
assert all(call.kwargs["headers"]["Idempotency-Key"] != immediate.idempotency_key for call in post.call_args_list)
def test_immediate_call_retries_provider_outage_without_breaking_ticket(client, announcement_data):
@@ -312,11 +315,10 @@ def test_immediate_call_retries_provider_outage_without_breaking_ticket(client,
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()
result = client.post(url, follow=True)
job = AnnouncementJob.objects.get(ticket=ticket, source=AnnouncementJob.Source.MANUAL)
assert "تلاش مجدد در صف قرار گرفت" in result.content.decode()
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
@@ -327,3 +329,32 @@ def test_immediate_call_retries_provider_outage_without_breaking_ticket(client,
job.refresh_from_db()
assert job.status == AnnouncementJob.Status.SENT
assert retry.call_args.kwargs["headers"]["Idempotency-Key"] == job.idempotency_key
@pytest.mark.django_db
def test_worker_skips_sms_provider_when_no_delivery_check_is_due():
with patch("apps.notifications.management.commands.process_notifications.get_sms_provider", side_effect=RuntimeError("SMS provider unavailable")) as sms_provider:
call_command("process_notifications", once=True)
sms_provider.assert_not_called()
def test_sms_delivery_provider_error_does_not_stop_worker(announcement_data):
category, staff, customer, _ = announcement_data
ticket = create_ticket(customer=customer, created_by=staff, category=category, subject="تماس فوری", body="پیام")
sms_job = NotificationJob.objects.create(
channel=NotificationJob.Channel.SMS,
event_type="CUSTOMER_TICKET_CREATED",
ticket=ticket,
customer=customer,
status=NotificationJob.Status.SENT,
next_attempt_at=timezone.now(),
delivery_status=NotificationJob.DeliveryStatus.PENDING,
delivery_next_check_at=timezone.now() - timedelta(seconds=1),
provider_message_id="provider-id",
dedupe_key="sms-delivery-failure-does-not-stop-worker",
)
with patch("apps.notifications.management.commands.process_notifications.get_sms_provider", side_effect=RuntimeError("provider unavailable")):
call_command("process_notifications", once=True)
sms_job.refresh_from_db()
assert sms_job.delivery_status == NotificationJob.DeliveryStatus.PENDING
assert sms_job.delivery_next_check_at > timezone.now()