button force anoncecall
This commit is contained in:
1 parent
c7993aae44
commit
9af91941d9
15 files changed
+294
-23
No files matched your search
@@ -8,6 +8,17 @@ from tests.e2e.pages.base import BasePage
|
||||
class TicketConversationPage(BasePage):
|
||||
guidance_text = "لطفاً همه توضیحات و سؤالهای خود را در یک پیام کامل بنویسید و سپس ارسال کنید."
|
||||
|
||||
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(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_role("region", name="وضعیت اطلاعرسانیهای تلفنی")).to_contain_text("درخواست فوری")
|
||||
|
||||
def send_reply(self, body):
|
||||
self.page.locator("[data-composer-toggle]").click()
|
||||
reply = self.page.locator('form[data-chat-coach] textarea[name="body"]')
|
||||
|
||||
@@ -8,7 +8,7 @@ from playwright.sync_api import expect
|
||||
|
||||
from apps.accounts.models import TicketMagicLink, User
|
||||
from apps.customers.models import ContactGroup, CustomerProfile
|
||||
from apps.core.models import PageTitle, SiteBranding
|
||||
from apps.core.models import PageTitle, SiteBranding, VoipConfiguration
|
||||
from apps.messaging.models import Message
|
||||
from apps.notifications.models import NotificationJob, SMSTemplate
|
||||
from apps.staff.models import OrganizationalUnit, StaffProfile
|
||||
@@ -22,6 +22,28 @@ pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
|
||||
PASSWORD = "E2e-safe-password-2026"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def immediate_announcement_case():
|
||||
unit = OrganizationalUnit.objects.create(name="واحد تماس فوری", slug="immediate-call-e2e")
|
||||
category = TicketCategory.objects.create(unit=unit, title="تماس فوری", slug="immediate-call-e2e")
|
||||
staff = User.objects.create_superuser(username="immediate-call-e2e", password=PASSWORD, account_type=User.AccountType.STAFF)
|
||||
StaffProfile.objects.create(user=staff, display_name="کارشناس تماس فوری", primary_unit=unit)
|
||||
customer = User.objects.create_user(username="immediate-call-customer-e2e", mobile="09120009122", account_type=User.AccountType.CUSTOMER)
|
||||
ticket = create_ticket(customer=customer, created_by=staff, category=category, subject="پیگیری تلفنی فوری", body="پیام نخست")
|
||||
VoipConfiguration.objects.create(announcement_url="http://127.0.0.1:9000/v1/announcements", announcement_id="custom/test")
|
||||
|
||||
return staff, ticket
|
||||
|
||||
|
||||
def test_staff_can_request_immediate_announcement_from_ticket_sidebar(live_server, browser_page, immediate_announcement_case):
|
||||
staff, ticket = immediate_announcement_case
|
||||
with browser_page() as page:
|
||||
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()
|
||||
|
||||
|
||||
def test_staff_operations_pages_are_authorized_and_visually_styled(live_server, browser_page):
|
||||
branding = SiteBranding.load()
|
||||
branding.staff_brand_title = "مرکز عملیات E2E"
|
||||
|
||||
+106
-1
@@ -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
|
||||
Reference in new issue
Block a user