import pytest from datetime import timedelta from pathlib import Path from django.contrib.auth.models import Permission from django.urls import reverse from django.utils import timezone from apps.accounts.models import User from apps.staff.models import OrganizationalUnit, StaffProfile from apps.tickets.models import Ticket, TicketCategory from apps.tickets.services import create_ticket, claim_ticket, reply_ticket from apps.messaging.models import Message,MessageReadReceipt from apps.notifications.models import NotificationJob from apps.customers.models import CustomerProfile from apps.core.persian_text import normalize_persian_text @pytest.mark.django_db def test_customer_detail_is_forbidden_to_other_customer(client): category = TicketCategory.objects.create(title="قیمت", slug="test-detail-price") owner = User.objects.create_user(username="one", mobile="09121111111", account_type="CUSTOMER") other = User.objects.create_user(username="two", mobile="09122222222", account_type="CUSTOMER") ticket = create_ticket(customer=owner, category=category, subject="استعلام محصول", body="توضیح") client.force_login(other) assert client.get(reverse("tickets:detail", args=[ticket.public_id])).status_code == 403 @pytest.mark.django_db def test_locked_public_ticket_only_leaks_metadata(client): unit = OrganizationalUnit.objects.create(name="فروش قفل‌شده", slug="locked-sales") unit.permissions.add( Permission.objects.get(content_type__app_label="tickets", codename="view_ticket") ) category = TicketCategory.objects.create(unit=unit, title="قیمت", slug="test-locked-price") customer = User.objects.create_user(username="customer", mobile="09123333333", account_type="CUSTOMER") expert = User.objects.create_user(username="expert", mobile="09124444444", account_type="STAFF") observer = User.objects.create_user(username="observer", mobile="09125555555", account_type="STAFF") StaffProfile.objects.create(user=expert, primary_unit=unit, display_name="کارشناس اول") StaffProfile.objects.create(user=observer, primary_unit=unit, display_name="کارشناس دوم") for user in (expert, observer): for code in ("view_ticket", "claim_ticket"): user.user_permissions.add(Permission.objects.get(content_type__app_label="tickets", codename=code)) ticket = create_ticket(customer=customer, category=category, subject="موضوع قابل مشاهده", body="محتوای محرمانه") claim_ticket(ticket.pk, expert) client.force_login(observer) listing = client.get(reverse("staff_tickets:list")) assert normalize_persian_text("موضوع قابل مشاهده") not in listing.content.decode() assert "محتوای محرمانه" not in listing.content.decode() assert client.get(reverse("staff_tickets:detail", args=[ticket.public_id])).status_code == 403 @pytest.mark.django_db @pytest.mark.parametrize("tab,label",[("","میز کار"),("new","صف جدید"),("waiting_staff","در انتظار اقدام من"),("mine","تیکت‌های من"),("shared","اشتراک‌ها")]) def test_staff_sidebar_marks_only_current_queue_active(client,tab,label): expert=User.objects.create_user(username=f"nav-{tab or 'desk'}",mobile=f"0913{len(tab):07d}",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="کارشناس ناوبری") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) client.force_login(expert) response=client.get(reverse("staff_tickets:list"),{"tab":tab} if tab else {}) content=response.content.decode() assert response.status_code==200 assert content.count('aria-current="page"')==2 # desktop sidebar + mobile dock expected=reverse("staff_tickets:list")+(f"?tab={tab}" if tab else "") assert f'class="active" href="{expected}" aria-current="page"' in content assert f"

{label}

" in content @pytest.mark.django_db def test_staff_filters_keep_selected_values_and_active_tab(client): category=TicketCategory.objects.create(title="فروش سازمانی",slug="enterprise-filter") expert=User.objects.create_user(username="filter-expert",mobile="09130000009",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="کارشناس فیلتر") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) client.force_login(expert) content=client.get(reverse("staff_tickets:list"),{"tab":"mine","category":category.pk,"owner":expert.pk,"unread":"1"}).content.decode() assert f'' in content assert f'value="{category.pk}" selected' in content assert f'value="{expert.pk}" selected' in content assert 'name="unread" value="1" checked' in content @pytest.mark.django_db def test_staff_desk_prioritizes_fresh_actionable_activity_and_renders_distinct_statuses(client): unit=OrganizationalUnit.objects.create(name="فروش اولویت",slug="priority-sales") unit.permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) category=TicketCategory.objects.create(unit=unit,title="پیگیری فروش",slug="staff-priority-order") customer=User.objects.create_user(username="priority-customer",mobile="09130000010",account_type="CUSTOMER") CustomerProfile.objects.create(user=customer,first_name="مشتری",last_name="نمونه") expert=User.objects.create_user(username="priority-expert",mobile="09130000011",account_type="STAFF") StaffProfile.objects.create(user=expert,primary_unit=unit,display_name="کارشناس اولویت") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) waiting_customer=create_ticket(customer=customer,category=category,subject="منتظر پاسخ مشتری",body="شرح اول") fresh_customer_reply=create_ticket(customer=customer,category=category,subject="پاسخ تازه مشتری",body="شرح دوم") new_ticket=create_ticket(customer=customer,category=category,subject="تیکت تازه ثبت شده",body="شرح سوم") older_urgent=create_ticket(customer=customer,category=category,subject="تیکت فوری قدیمی",body="شرح چهارم") now=timezone.now() type(waiting_customer).objects.filter(pk=waiting_customer.pk).update(status="WAITING_CUSTOMER",last_activity_at=now+timedelta(hours=1)) type(fresh_customer_reply).objects.filter(pk=fresh_customer_reply.pk).update(status="WAITING_STAFF",last_activity_at=now) type(new_ticket).objects.filter(pk=new_ticket.pk).update(status="NEW",last_activity_at=now-timedelta(minutes=1)) type(older_urgent).objects.filter(pk=older_urgent.pk).update(status="WAITING_STAFF",priority="URGENT",last_activity_at=now-timedelta(days=1)) client.force_login(expert) response=client.get(reverse("staff_tickets:list")) content=response.content.decode() assert [ticket.pk for ticket in response.context["tickets"][:4]]==[ fresh_customer_reply.pk,new_ticket.pk,older_urgent.pk,waiting_customer.pk, ] assert "status-row-waiting_staff" in content and "status-waiting_staff" in content assert "status-row-waiting_customer" in content and "status-waiting_customer" in content assert "action-required" in content assert "جست‌وجو و فیلتر تیکت‌ها" in content @pytest.mark.django_db def test_new_ticket_page_has_expert_cards_voice_and_file_preview(client): unit=OrganizationalUnit.objects.create(name="فروش نمایش کارشناس",slug="expert-display-sales") TicketCategory.objects.create(unit=unit,title="قیمت", slug="test-expert-display-price") customer = User.objects.create_user(username="new-customer", mobile="09126666666", account_type="CUSTOMER") expert = User.objects.create_user(username="selectable", mobile="09127777777", account_type="STAFF") StaffProfile.objects.create(user=expert, primary_unit=unit, display_name="کارشناس نمونه", is_customer_selectable=True) client.force_login(customer) content = client.get(reverse("tickets:new"),{"unit":unit.pk}).content.decode() assert "کارشناس نمونه" in content assert "data-ticket-voice-recorder" in content assert "data-file-preview" in content assert "data-ticket-wizard" in content assert "customer-ticket-wizard.js" in content assert reverse("tickets:expert_avatar", args=[expert.public_id]) in content @pytest.mark.django_db def test_customer_ticket_wizard_keeps_server_validation_and_submitted_values(client): unit = OrganizationalUnit.objects.create(name="واحد اعتبارسنجی مرحله‌ای", slug="wizard-validation-unit") category = TicketCategory.objects.create(unit=unit, title="دسته مرحله‌ای", slug="wizard-validation-category") customer = User.objects.create_user( username="wizard-validation-customer", mobile="09126666001", account_type="CUSTOMER", ) client.force_login(customer) response = client.post( reverse("tickets:new"), { "unit": unit.pk, "category": category.pk, "subject": "موضوع حفظ‌شده پس از خطای سرور", "province": "", "city": "", "selected_expert": "", "body": "", }, ) assert response.status_code == 200 assert "متن یا پیوست الزامی است." in response.context["form"].non_field_errors() content = response.content.decode() assert "data-step-server-errors" in content assert "موضوع حفظ‌شده پس از خطای سرور" in content assert not Ticket.objects.filter(subject="موضوع حفظ‌شده پس از خطای سرور").exists() @pytest.mark.django_db def test_profile_location_prefills_ticket_but_can_be_overridden_per_ticket(client): unit=OrganizationalUnit.objects.create(name="فروش تست موقعیت",slug="location-sales") category=TicketCategory.objects.create(unit=unit,title="فروش",slug="location-sale") customer=User.objects.create_user(username="located-customer",mobile="09121110000",account_type="CUSTOMER") CustomerProfile.objects.create(user=customer,province="تهران",city="تهران") client.force_login(customer) new_page=client.get(reverse("tickets:new")) content=new_page.content.decode() assert 'data-location-picker' in content assert 'value="تهران"' in content assert 'data/iran-locations.json' in content response=client.post(reverse("tickets:new"),{"unit":unit.pk,"category":category.pk,"subject":"درخواست ارسال به مقصد","province":"گیلان","city":"رشت","selected_expert":"","body":"شرح کامل درخواست برای مقصد جدید"}) assert response.status_code==302 ticket=customer.customer_tickets.get() assert (ticket.province,ticket.city)==("گیلان","رشت") customer.customer_profile.refresh_from_db() assert (customer.customer_profile.province,customer.customer_profile.city)==("تهران","تهران") @pytest.mark.django_db def test_invalid_city_for_province_is_rejected(client): category=TicketCategory.objects.create(title="اعتبارسنجی",slug="location-validation") customer=User.objects.create_user(username="invalid-location",mobile="09121110001",account_type="CUSTOMER") CustomerProfile.objects.create(user=customer) client.force_login(customer) response=client.post(reverse("tickets:new"),{"category":category.pk,"subject":"بررسی موقعیت نامعتبر","province":"تهران","city":"رشت","selected_expert":"","body":"شرح کافی برای بررسی اعتبارسنجی"}) assert response.status_code==200 assert "شهر انتخاب‌شده متعلق به این استان نیست" in response.content.decode() assert not customer.customer_tickets.exists() @pytest.mark.django_db def test_customer_conversation_workspace_and_message_pagination_are_protected(client): category=TicketCategory.objects.create(title="پشتیبانی",slug="support") customer=User.objects.create_user(username="conversation-owner",mobile="09120000001",account_type="CUSTOMER") other=User.objects.create_user(username="conversation-other",mobile="09120000002",account_type="CUSTOMER") ticket=create_ticket(customer=customer,category=category,subject="بررسی تجربه مکالمه",body="پیام آغازین") client.force_login(customer) detail=client.get(reverse("tickets:detail",args=[ticket.public_id])) content=detail.content.decode() assert detail.status_code==200 assert "data-conversation-workspace" in content assert "data-conversation-end" in content assert "data-ticket-composer" in content assert 'data-chat-coach-own' in content created_at=ticket.ticket_messages.order_by("created_at").first().created_at assert f'data-chat-coach-at="{int(created_at.timestamp())}"' in content assert "conversation.js" in content assert 'accept="image/*"' in content and "data-gallery-input" in content and "data-document-input" in content assert "ticket-company-logo" not in content client.force_login(other) assert client.get(reverse("tickets:messages",args=[ticket.public_id])).status_code==403 @pytest.mark.django_db def test_customer_can_submit_multiple_replies_with_success_feedback(client): category=TicketCategory.objects.create(title="پیگیری",slug="follow-up") customer=User.objects.create_user(username="reply-owner",mobile="09120000003",account_type="CUSTOMER") ticket=create_ticket(customer=customer,category=category,subject="پیگیری چند مرحله‌ای",body="شرح اولیه کامل") client.force_login(customer) initial=ticket.ticket_messages.count() first=client.post(reverse("tickets:reply",args=[ticket.public_id]),{"body":"پاسخ تکمیلی اول"},follow=True) assert first.status_code==200 assert "پاسخ شما با موفقیت در تیکت ثبت شد" in first.content.decode() assert "reply_result=reply" in first.redirect_chain[-1][0] assert "data-composer-toggle" in first.content.decode() assert 'data-reply-result="reply"' in first.content.decode() assert 'composer is-collapsed' in first.content.decode() second=client.post(reverse("tickets:reply",args=[ticket.public_id]),{"body":"پاسخ تکمیلی دوم"},follow=True) assert second.status_code==200 assert ticket.ticket_messages.count()==initial+2 @pytest.mark.django_db def test_invalid_reply_shows_error_and_reopens_composer(client): category=TicketCategory.objects.create(title="خطا",slug="reply-error") customer=User.objects.create_user(username="reply-error-owner",mobile="09120000004",account_type="CUSTOMER") ticket=create_ticket(customer=customer,category=category,subject="بررسی خطای پاسخ",body="شرح اولیه") client.force_login(customer) response=client.post(reverse("tickets:reply",args=[ticket.public_id]),{"body":""},follow=True) content=response.content.decode() assert response.status_code==200 assert "پاسخ ثبت نشد" in content assert "reply_result=error" in response.redirect_chain[-1][0] assert 'data-reply-result="error"' in content assert 'composer is-collapsed' not in content @pytest.mark.django_db def test_staff_reply_feedback_and_new_reply_action_remain_available(client): category=TicketCategory.objects.create(title="کارشناس",slug="staff-reply") customer=User.objects.create_user(username="staff-reply-customer",mobile="09120000005",account_type="CUSTOMER") expert=User.objects.create_user(username="staff-reply-expert",mobile="09120000006",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="کارشناس پاسخ‌گو") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) ticket=create_ticket(customer=customer,category=category,subject="پاسخ رسمی کارشناس",selected_expert=expert,body="شرح مشتری") client.force_login(expert) response=client.post(reverse("staff_tickets:reply",args=[ticket.public_id]),{"body":"پاسخ رسمی و کامل کارشناس"},follow=True) content=response.content.decode() assert response.status_code==200 assert "پاسخ رسمی با موفقیت در تیکت ثبت" in content assert "reply_result=reply" in response.redirect_chain[-1][0] assert "ثبت اقدام جدید در تیکت" in content assert 'data-reply-result="reply"' in content assert "data-conversation-end" in content assert 'composer staff-composer is-collapsed' in content assert 'accept="image/*"' in content and "data-gallery-input" in content and "data-document-input" in content @pytest.mark.django_db def test_staff_ticket_sms_status_is_compact_expandable_and_time_precise(client): from apps.core.templatetags.jalali import jalali_datetime category=TicketCategory.objects.create(title="رهگیری پیامک",slug="sms-timeline") customer=User.objects.create_user(username="sms-timeline-customer",mobile="09120000031",account_type="CUSTOMER") expert=User.objects.create_user(username="sms-timeline-expert",mobile="09120000032",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="کارشناس رهگیری") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) ticket=create_ticket(customer=customer,category=category,subject="نمایش دقیق وضعیت پیامک",selected_expert=expert,body="شرح") job=NotificationJob.objects.get(ticket=ticket,event_type="CUSTOMER_TICKET_CREATED") sent_at=timezone.now()-timedelta(minutes=4) delivered_at=timezone.now()-timedelta(minutes=1) NotificationJob.objects.filter(pk=job.pk).update( status=NotificationJob.Status.SENT, sent_at=sent_at, provider_message_id="123456789", delivery_status=NotificationJob.DeliveryStatus.DELIVERED, delivery_status_code="5", delivery_description="پیامک به گوشی گیرنده رسیده است.", delivery_checked_at=delivered_at, delivery_next_check_at=None, ) client.force_login(expert) content=client.get(reverse("staff_tickets:detail",args=[ticket.public_id])).content.decode() assert 'class="sms-delivery-item"' in content assert 'aria-label="نمایش جزئیات زمان‌بندی پیامک"' in content for label in ("ثبت درخواست ارسال","پذیرش توسط درگاه پیامک","تأیید رسیدن به گوشی"): assert label in content assert jalali_datetime(sent_at) in content assert jalali_datetime(delivered_at) in content assert "123456789" in content @pytest.mark.django_db def test_staff_can_manually_retry_only_failed_sms_for_the_current_ticket(client): category=TicketCategory.objects.create(title="تلاش مجدد پیامک",slug="manual-sms-retry") customer=User.objects.create_user(username="sms-retry-customer",mobile="09120000041",account_type="CUSTOMER") expert=User.objects.create_user(username="sms-retry-expert",mobile="09120000042",account_type="STAFF") outsider=User.objects.create_user(username="sms-retry-outsider",mobile="09120000043",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="کارشناس تلاش مجدد") StaffProfile.objects.create(user=outsider,display_name="کارشناس نامرتبط") ticket=create_ticket(customer=customer,category=category,subject="بررسی retry پیامک",selected_expert=expert,body="شرح") failed_job=NotificationJob.objects.get(ticket=ticket,event_type="CUSTOMER_TICKET_CREATED") NotificationJob.objects.filter(pk=failed_job.pk).update( status=NotificationJob.Status.FAILED,attempts=4,last_error="خطای درگاه", provider_message_id="failed-provider-id",provider_status_code="-19", delivery_status=NotificationJob.DeliveryStatus.ERROR,delivery_status_code="-19", delivery_description="ارسال قطعی ناموفق بود.",delivery_checked_at=timezone.now(), delivery_check_attempts=3,delivery_is_final=True, ) failed_job.refresh_from_db() provisional_job=NotificationJob.objects.create( channel=NotificationJob.Channel.SMS,event_type="STAFF_REPLY",ticket=ticket,customer=customer, status=NotificationJob.Status.SENT,next_attempt_at=timezone.now(),dedupe_key="test:provisional-manual-retry", delivery_status=NotificationJob.DeliveryStatus.UNKNOWN,delivery_next_check_at=timezone.now()+timedelta(minutes=3), ) retry_url=reverse("staff_tickets:sms_retry",args=[ticket.public_id,failed_job.pk]) provisional_retry_url=reverse("staff_tickets:sms_retry",args=[ticket.public_id,provisional_job.pk]) client.force_login(expert) content=client.get(reverse("staff_tickets:detail",args=[ticket.public_id])).content.decode() assert retry_url in content assert 'aria-label="تلاش مجدد برای ارسال این پیامک"' in content assert provisional_retry_url not in content assert client.get(retry_url).status_code==405 client.force_login(outsider) assert client.post(retry_url).status_code==403 failed_job.refresh_from_db() assert failed_job.status==NotificationJob.Status.FAILED client.force_login(expert) response=client.post(retry_url,follow=True) assert response.status_code==200 assert "پیامک ناموفق دوباره در صف ارسال قرار گرفت." in response.content.decode() failed_job.refresh_from_db(); provisional_job.refresh_from_db() assert failed_job.status==NotificationJob.Status.PENDING assert failed_job.attempts==0 and failed_job.next_attempt_at<=timezone.now() assert failed_job.last_error=="" and failed_job.provider_message_id=="" and failed_job.provider_status_code=="" assert failed_job.sent_at is None and failed_job.delivery_status=="" and failed_job.delivery_status_code=="" assert failed_job.delivery_description=="" and failed_job.delivery_checked_at is None assert failed_job.delivery_check_attempts==0 and failed_job.delivery_next_check_at is None assert failed_job.delivery_is_final is False assert provisional_job.status==NotificationJob.Status.SENT @pytest.mark.django_db def test_staff_sidebar_hides_admin_link_and_shows_developer_credit(client): expert=User.objects.create_superuser(username="sidebar-admin",mobile="09384999655",password="test-pass",account_type="STAFF") StaffProfile.objects.create(user=expert,display_name="مدیر آزمایشی") client.force_login(expert) content=client.get(reverse("staff_tickets:list")).content.decode() assert "مدیریت سامانه" not in content assert "برنامه نویسی و توسعه: محمد جواد فصاحت" in content assert "09202202129" in content assert "واحد فناوری اطلاعات" not in content assert "الگوهای پیامک" not in content assert reverse("staff_tickets:sms_templates") not in content @pytest.mark.django_db def test_root_redirects_to_login_or_authenticated_workspace(client): response = client.get("/") assert response.status_code == 302 assert response.url == reverse("accounts:login") customer = User.objects.create_user( username="root-customer", mobile="09120000991", account_type="CUSTOMER" ) client.force_login(customer) assert client.get("/").url == reverse("tickets:list") staff = User.objects.create_user( username="root-staff", mobile="09120000992", account_type="STAFF" ) StaffProfile.objects.create(user=staff, display_name="کارشناس ریشه") client.force_login(staff) assert client.get("/").url == reverse("staff_tickets:list") @pytest.mark.django_db def test_admin_is_only_mounted_at_private_path(client): assert reverse("admin:index") == "/x5admin/" assert client.get("/admin/").status_code == 404 @pytest.mark.django_db def test_customer_can_set_password_and_login(client): customer=User.objects.create_user(username="password-user",mobile="09128888888",account_type="CUSTOMER") CustomerProfile.objects.create(user=customer) client.force_login(customer) response=client.post(reverse("accounts:profile"),{"first_name":"علی","last_name":"رضایی","customer_type":"PERSON","province":"خراسان رضوی","city":"مشهد","password":"strong-pass-123"}) assert response.status_code==302 customer.customer_profile.refresh_from_db() assert (customer.customer_profile.province,customer.customer_profile.city)==("خراسان رضوی","مشهد") client.logout() response=client.post(reverse("accounts:password_login"),{"mobile":"09128888888","password":"strong-pass-123"}) assert response.status_code==302 and response.url==reverse("tickets:list") @pytest.mark.django_db def test_magic_link_is_reusable_for_seven_days_and_only_grants_one_ticket(client): from apps.accounts.models import TicketMagicLink category=TicketCategory.objects.create(title="فروش",slug="sale") customer=User.objects.create_user(username="magic-user",mobile="09129999999",account_type="CUSTOMER") ticket=create_ticket(customer=customer,category=category,subject="ورود جادویی",body="متن") other_ticket=create_ticket(customer=customer,category=category,subject="تیکت دیگر",body="متن دوم") token=TicketMagicLink.issue(ticket) url=reverse("accounts:magic",args=[token]) response=client.get(url) assert response.status_code==302 and response.url==reverse("tickets:detail",args=[ticket.public_id]) assert "_auth_user_id" not in client.session detail=client.get(response.url) assert detail.status_code==200 and "فقط به همین تیکت دسترسی دارید" in detail.content.decode() assert client.get(reverse("tickets:poll",args=[ticket.public_id])).status_code==200 assert client.get(reverse("tickets:list")).status_code==302 assert client.get(reverse("tickets:new")).status_code==302 assert client.get(reverse("accounts:profile")).status_code==302 assert client.get(reverse("tickets:detail",args=[other_ticket.public_id])).status_code==302 reply=client.post(reverse("tickets:reply",args=[ticket.public_id]),{"body":"پاسخ از لینک امن"}) assert reply.status_code==302 assert ticket.ticket_messages.filter(author=customer,body="پاسخ از لینک امن").exists() client.session.flush() repeated=client.get(url) assert repeated.status_code==302 and repeated.url==reverse("tickets:detail",args=[ticket.public_id]) @pytest.mark.django_db def test_ticket_polling_is_versioned_incremental_and_permission_safe(client): unit=OrganizationalUnit.objects.create(name="فروش Polling",slug="polling-sales") unit.permissions.add( Permission.objects.get(content_type__app_label="tickets",codename="view_ticket"), Permission.objects.get(content_type__app_label="messaging",codename="add_message"), ) category=TicketCategory.objects.create(unit=unit,title="Polling",slug="polling") customer=User.objects.create_user(username="poll-customer",mobile="09129990001",account_type="CUSTOMER") expert=User.objects.create_user(username="poll-expert",mobile="09129990002",account_type="STAFF") StaffProfile.objects.create(user=expert,primary_unit=unit,display_name="کارشناس پولینگ") expert.user_permissions.add(Permission.objects.get(content_type__app_label="tickets",codename="view_ticket")) ticket=create_ticket(customer=customer,category=category,subject="بررسی پاسخ تازه",body="پیام نخست") client.force_login(customer) detail=client.get(reverse("tickets:detail",args=[ticket.public_id])) content=detail.content.decode() assert reverse("tickets:poll",args=[ticket.public_id]) in content assert 'data-poll-version=' in content and 'class="composer-send-icon" role="button" tabindex="0" data-scroll-to-submit' in content assert '