fix payement link and gallery image

This commit is contained in:
javad committed 2026-10-06 11:18:31 +03:30
1 parent de3135b588
commit 1cbbbbd0dc
24 files changed
+654 -116

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
import json
import uuid
from datetime import timedelta
import pytest
from django.test import Client
from django.urls import reverse
from django.utils import timezone
from apps.accounts.models import TicketMagicLink
from apps.messaging.models import Message, MessageReadReceipt
from apps.tickets.models import TicketReadState
from apps.tickets.sharing import revoke_staff_share, share_staff_message, share_staff_ticket
from tests.test_staff_sharing import make_sharing_case
pytestmark = pytest.mark.django_db
@pytest.fixture
def seen_case(settings):
settings.PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
case = make_sharing_case()
case['reply'] = Message.objects.create(ticket=case['ticket'], author=case['sender'], body='پاسخ قابل مشاهده')
return case
@pytest.mark.parametrize('route', ['detail', 'messages', 'poll'])
def test_staff_get_requests_never_mark_messages_or_ticket_read(client, seen_case, route):
c = seen_case
share_staff_ticket(c['ticket'], c['sender'], c['recipient'], 'VIEW_ONLY')
client.force_login(c['recipient'])
response = client.get(reverse('staff_tickets:' + route, args=[c['ticket'].public_id]))
assert response.status_code == 200
assert not MessageReadReceipt.objects.filter(user=c['recipient']).exists()
assert not TicketReadState.objects.filter(user=c['recipient']).exists()
def test_staff_acknowledgement_is_scoped_idempotent_and_rechecked_after_revocation(client, seen_case):
c = seen_case
grant = share_staff_message(c['selected'], c['sender'], c['recipient'])
client.force_login(c['recipient'])
url = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
values = [str(c['selected'].public_id), str(c['reply'].public_id), str(uuid.uuid4()), 'bad-uuid']
for _ in range(2):
response = client.post(url, {'message_ids': values})
assert response.status_code == 200
assert response.json()['read_ids'] == [str(c['selected'].public_id)]
assert list(MessageReadReceipt.objects.filter(user=c['recipient']).values_list('message_id', flat=True)) == [c['selected'].pk]
assert not TicketReadState.objects.filter(user=c['recipient']).exists()
grant.refresh_from_db()
assert grant.view_count == 1
revoke_staff_share(grant, c['sender'])
assert client.post(url, {'message_ids': values}).status_code == 403
assert client.get(reverse('staff_tickets:poll', args=[c['ticket'].public_id])).status_code == 403
assert not MessageReadReceipt.objects.filter(message=c['reply'], user=c['recipient']).exists()
def test_staff_ticket_read_state_requires_acknowledging_the_latest_message(client, seen_case):
c = seen_case
client.force_login(c['sender'])
url = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
client.post(url, {'message_ids': [str(c['selected'].public_id)]})
assert not TicketReadState.objects.filter(user=c['sender']).exists()
client.post(url, {'message_ids': [str(c['reply'].public_id)]})
assert TicketReadState.objects.get(user=c['sender']).last_read_message_id == c['reply'].pk
assert not MessageReadReceipt.objects.filter(message=c['reply'], user=c['sender']).exists()
def test_another_colleagues_read_state_does_not_hide_my_unread_ticket(seen_case):
from apps.tickets.selectors import filter_staff_tickets, staff_ticket_rows
c = seen_case
share_staff_ticket(c['ticket'], c['sender'], c['recipient'], 'VIEW_ONLY')
TicketReadState.objects.create(ticket=c['ticket'], user=c['sender'], last_read_at=timezone.now(), last_read_message=c['reply'])
unread = filter_staff_tickets(staff_ticket_rows(c['recipient']), c['recipient'], {'unread': '1'})
assert unread.filter(pk=c['ticket'].pk).exists()
own_state = TicketReadState.objects.create(ticket=c['ticket'], user=c['recipient'], last_read_at=timezone.now(), last_read_message=c['reply'])
assert not unread.filter(pk=c['ticket'].pk).exists()
own_state.last_read_at = c['ticket'].last_activity_at - timedelta(seconds=1)
own_state.save(update_fields=['last_read_at'])
assert unread.filter(pk=c['ticket'].pk).exists()
def test_customer_receipts_exclude_own_deleted_internal_and_other_ticket_messages(client, seen_case):
c = seen_case
deleted = Message.objects.create(ticket=c['ticket'], author=c['sender'], body='حذف‌شده', deleted_at=timezone.now())
from apps.tickets.services import create_ticket
other = create_ticket(customer=c['customer'], category=c['ticket'].category, subject='تیکت دیگر', body='پیام تیکت دیگر')
foreign = Message.objects.create(ticket=other, author=c['sender'], body='پاسخ خصوصی تیکت دیگر')
client.force_login(c['customer'])
for route in ['detail', 'messages', 'poll']:
assert client.get(reverse('tickets:' + route, args=[c['ticket'].public_id])).status_code == 200
assert not MessageReadReceipt.objects.filter(user=c['customer']).exists()
identifiers = [str(value) for value in c['ticket'].ticket_messages.values_list('public_id', flat=True)]
identifiers += [str(foreign.public_id), str(uuid.uuid4()), 'not-a-uuid', 42, None]
url = reverse('tickets:read', args=[c['ticket'].public_id])
response = client.post(url, json.dumps({'message_ids': identifiers}), content_type='application/json')
assert response.status_code == 200
assert list(MessageReadReceipt.objects.filter(user=c['customer']).values_list('message_id', flat=True)) == [c['reply'].pk]
assert not MessageReadReceipt.objects.filter(message=deleted).exists()
@pytest.mark.parametrize('payload', ['not json', 'null', '[]', '{"message_ids":"bad"}', '{"message_ids":{}}'])
@pytest.mark.parametrize('staff', [True, False])
def test_malformed_read_payloads_return_400_without_recording_reads(client, seen_case, payload, staff):
c = seen_case
client.force_login(c['sender'] if staff else c['customer'])
url = reverse('staff_tickets:read_shares' if staff else 'tickets:read', args=[c['ticket'].public_id])
assert client.post(url, payload, content_type='application/json').status_code == 400
assert not MessageReadReceipt.objects.exists()
values = [str(c['reply'].public_id)] * 101
assert client.post(url, json.dumps({'message_ids': values}), content_type='application/json').status_code == 400
def test_read_acknowledgements_keep_csrf_and_magic_link_scope(seen_case):
c = seen_case
strict = Client(enforce_csrf_checks=True)
strict.force_login(c['sender'])
staff_url = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
assert strict.post(staff_url, {'message_ids': [str(c['selected'].public_id)]}).status_code == 403
anonymous = Client()
token = TicketMagicLink.issue(c['ticket'])
assert anonymous.get(reverse('accounts:magic', args=[token])).status_code == 302
url = reverse('tickets:read', args=[c['ticket'].public_id])
assert anonymous.post(url, json.dumps({'message_ids': [str(c['reply'].public_id)]}), content_type='application/json').status_code == 200
assert MessageReadReceipt.objects.filter(message=c['reply'], user=c['customer']).exists()
assert '_auth_user_id' not in anonymous.session
def test_polling_backlog_does_not_skip_messages_after_the_first_batch(client, seen_case):
c = seen_case
client.force_login(c['sender'])
url = reverse('staff_tickets:poll', args=[c['ticket'].public_id])
initial = client.get(url).json()
batch = [Message.objects.create(ticket=c['ticket'], author=c['customer'], body=f'پیام صف {i}') for i in range(55)]
first = client.get(url, {'version': initial['version'], 'cursor': initial['cursor']}).json()
assert not first['replace'] and first['cursor'] == str(batch[49].public_id)
second = client.get(url, {'version': first['version'], 'cursor': first['cursor']}).json()
assert not second['replace'] and second['cursor'] == str(batch[-1].public_id)
assert batch[-1].body in second['html']
assert not MessageReadReceipt.objects.exists()
def test_polling_detects_editing_an_older_message_even_with_a_new_reply(client, seen_case):
from apps.messaging.services import edit_message
c = seen_case
client.force_login(c['customer'])
url = reverse('tickets:poll', args=[c['ticket'].public_id])
initial = client.get(url).json()
edit_message(c['selected'].pk, c['customer'], 'متن اصلاح‌شده پیام قبلی')
Message.objects.create(ticket=c['ticket'], author=c['sender'], body='پاسخ همزمان')
response = client.get(url, {'version': initial['version'], 'cursor': initial['cursor']}).json()
assert response['changed'] and response['replace']
assert 'متن اصلاح‌شده پیام قبلی' in response['html'] and 'پاسخ همزمان' in response['html']