share ticket and ticket message
This commit is contained in:
1 parent
39e64854b8
commit
79fe1cddbb
35 files changed
+1742
-95
No files matched your search
@@ -0,0 +1,160 @@
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from apps.messaging.models import Message
|
||||
from apps.staff.models import StaffProfile
|
||||
from apps.tickets.models import MessageShare, TicketAccessGrant
|
||||
from apps.tickets.policies import TicketPolicy
|
||||
from apps.tickets.services import change_priority, change_status
|
||||
from apps.tickets.sharing import share_staff_message, share_staff_ticket, revoke_staff_share
|
||||
from tests.test_staff_sharing import make_sharing_case
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_shared_queue_groups_messages_and_searches_normal_ticket_metadata(client):
|
||||
c = make_sharing_case()
|
||||
second = Message.objects.create(ticket=c['ticket'], author=c['customer'], body='رسید دوم')
|
||||
grants = [share_staff_message(m, c['sender'], c['recipient']) for m in (c['selected'], second)]
|
||||
client.force_login(c['recipient'])
|
||||
listing = reverse('staff_tickets:list')
|
||||
response = client.get(listing, {'tab': 'shared'})
|
||||
assert len(response.context['tickets']) == 1
|
||||
assert c['ticket'].subject in response.content.decode()
|
||||
assert len(client.get(listing, {'tab': 'shared', 'q': c['ticket'].subject}).context['tickets']) == 1
|
||||
assert len(client.get(listing, {'tab': 'shared', 'q': second.body}).context['tickets']) == 1
|
||||
assert len(client.get(listing, {'tab': 'shared', 'status': c['ticket'].status, 'category': c['ticket'].category_id}).context['tickets']) == 1
|
||||
assert len(client.get(listing, {'tab': 'shared', 'q': c['customer'].mobile}).context['tickets']) == 1
|
||||
assert client.get(listing, {'tab': 'shared', 'q': 'مکاتبه محرمانه قبل'}).context['tickets'] == []
|
||||
assert client.get(reverse('staff_tickets:shares_count')).json()['unread_shares'] == 2
|
||||
read = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
|
||||
assert client.post(read, {'message_ids': ['bad-uuid', str(c['ticket'].ticket_messages.first().public_id)]}).json()['unread_shares'] == 2
|
||||
assert client.post(read, {'message_ids': [str(second.public_id)]}).json()['unread_shares'] == 1
|
||||
grants[0].refresh_from_db()
|
||||
assert grants[0].first_viewed_at is None
|
||||
revoke_staff_share(grants[0], c['sender'])
|
||||
assert client.get(reverse('staff_tickets:shares_count')).json()['unread_shares'] == 0
|
||||
assert client.get(listing, {'tab': 'shared', 'unread': 1}).context['tickets'] == []
|
||||
|
||||
|
||||
def test_limited_poll_tracks_revocation_without_leaking_hidden_activity(client):
|
||||
c = make_sharing_case()
|
||||
grant = share_staff_message(c['selected'], c['sender'], c['recipient'])
|
||||
client.force_login(c['recipient'])
|
||||
url = reverse('staff_tickets:poll', args=[c['ticket'].public_id])
|
||||
baseline = client.get(url).json()
|
||||
assert baseline['count'] == 1 and c['selected'].body in baseline['html']
|
||||
assert 'مکاتبه محرمانه' not in baseline['html']
|
||||
Message.objects.create(ticket=c['ticket'], author=c['sender'], body='hidden future')
|
||||
same = client.get(url, {'version': baseline['version'], 'cursor': baseline['cursor']}).json()
|
||||
assert same['changed'] is False
|
||||
second = Message.objects.create(ticket=c['ticket'], author=c['customer'], body='second shared')
|
||||
share_staff_message(second, c['sender'], c['recipient'])
|
||||
revoke_staff_share(grant, c['sender'])
|
||||
changed = client.get(url, {'version': baseline['version'], 'cursor': baseline['cursor']}).json()
|
||||
assert changed['replace'] and changed['count'] == 1
|
||||
assert second.body in changed['html'] and c['selected'].body not in changed['html']
|
||||
older = client.get(reverse('staff_tickets:messages', args=[c['ticket'].public_id]), {'before': c['selected'].public_id})
|
||||
assert 'hidden future' not in older.content.decode()
|
||||
Message.objects.filter(pk=second.pk).update(deleted_at=timezone.now())
|
||||
assert client.get(url).status_code == 403
|
||||
assert client.get(reverse('staff_tickets:detail', args=[c['ticket'].public_id])).status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.parametrize('role', StaffProfile.Role.values)
|
||||
def test_every_active_staff_role_can_operate_and_share_accessible_tickets(client, role):
|
||||
c = make_sharing_case()
|
||||
sender = c['sender']
|
||||
sender.user_permissions.clear()
|
||||
StaffProfile.objects.filter(user=sender).update(role=role)
|
||||
sender.refresh_from_db()
|
||||
assert TicketPolicy.can_share(sender, c['ticket'])
|
||||
assert TicketPolicy.can_transfer(sender, c['ticket'])
|
||||
change_status(c['ticket'], sender, 'OPEN')
|
||||
change_priority(c['ticket'], sender, 'URGENT')
|
||||
share_staff_message(c['selected'], sender, c['recipient'])
|
||||
share_staff_ticket(c['ticket'], sender, c['outsider'], 'VIEW_ONLY')
|
||||
client.force_login(sender)
|
||||
body = client.get(reverse('staff_tickets:detail', args=[c['ticket'].public_id])).content.decode()
|
||||
assert 'ثبت وضعیت' in body and 'ثبت اولویت' in body and 'انتقال تیکت' in body
|
||||
assert 'اشتراک با کارشناس' in body
|
||||
assert not TicketPolicy.can_operate(c['recipient'], c['ticket'])
|
||||
assert TicketPolicy.can_operate(c['outsider'], c['ticket'])
|
||||
assert not TicketPolicy.can_operate(c['customer'], c['ticket'])
|
||||
StaffProfile.objects.filter(user=sender).update(is_active=False)
|
||||
sender.refresh_from_db()
|
||||
assert not TicketPolicy.can_share(sender, c['ticket'])
|
||||
|
||||
|
||||
def test_old_shared_messages_stay_unread_until_displayed_and_poll_keeps_pagination(client):
|
||||
c = make_sharing_case()
|
||||
share_staff_message(c['selected'], c['sender'], c['recipient'])
|
||||
for index in range(51):
|
||||
message = Message.objects.create(ticket=c['ticket'], author=c['customer'], body=f'رسید مجاز {index}')
|
||||
share_staff_message(message, c['sender'], c['recipient'])
|
||||
client.force_login(c['recipient'])
|
||||
response = client.get(reverse('staff_tickets:detail', args=[c['ticket'].public_id]))
|
||||
assert len(response.context['conversation']) == 50 and response.context['has_older']
|
||||
assert c['selected'].body not in response.content.decode()
|
||||
read = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
|
||||
last = response.context['conversation'][-1]
|
||||
assert client.post(read, {'message_ids': [str(last.public_id)]}).json()['unread_shares'] == 51
|
||||
old = client.get(reverse('staff_tickets:messages', args=[c['ticket'].public_id]), {'before': response.context['older_cursor']})
|
||||
assert c['selected'].body in old.content.decode() and 'مکاتبه محرمانه' not in old.content.decode()
|
||||
poll = client.get(reverse('staff_tickets:poll', args=[c['ticket'].public_id])).json()
|
||||
assert poll['staff_limited'] and 'data-load-older' in poll['html']
|
||||
|
||||
|
||||
def test_full_and_message_shares_have_one_row_and_full_access_preserves_conversation(client):
|
||||
c = make_sharing_case()
|
||||
share_staff_message(c['selected'], c['sender'], c['recipient'])
|
||||
share_staff_ticket(c['ticket'], c['sender'], c['recipient'], 'VIEW_ONLY')
|
||||
client.force_login(c['recipient'])
|
||||
queue = client.get(reverse('staff_tickets:list'), {'tab': 'shared'})
|
||||
assert len(queue.context['tickets']) == 1
|
||||
assert c['ticket'].subject in queue.content.decode()
|
||||
body = client.get(reverse('staff_tickets:detail', args=[c['ticket'].public_id])).content.decode()
|
||||
assert 'مکاتبه محرمانه قبل' in body and 'مکاتبه محرمانه بعد' in body
|
||||
assert client.get(reverse('staff_tickets:shares_count')).json()['unread_shares'] == 1
|
||||
response = client.post(reverse('staff_tickets:transfer', args=[c['ticket'].public_id]), {'expert': c['outsider'].staff_profile.pk})
|
||||
assert response.status_code == 302
|
||||
c['ticket'].refresh_from_db()
|
||||
assert c['ticket'].owner == c['outsider']
|
||||
|
||||
|
||||
def test_global_staff_access_does_not_duplicate_or_restrict_a_message_shared_ticket(client):
|
||||
from django.contrib.auth.models import Permission
|
||||
c = make_sharing_case()
|
||||
share_staff_message(c['selected'], c['sender'], c['recipient'])
|
||||
c['recipient'].user_permissions.add(Permission.objects.get(content_type__app_label='tickets', codename='view_all_tickets'))
|
||||
client.force_login(c['recipient'])
|
||||
queue = client.get(reverse('staff_tickets:list'), {'tab': 'shared'})
|
||||
assert len(queue.context['tickets']) == 1
|
||||
assert c['ticket'].subject in queue.content.decode()
|
||||
assert not getattr(queue.context['tickets'][0], 'limited_share', False)
|
||||
|
||||
|
||||
def test_message_recipient_has_normal_ticket_details_with_only_shared_message_content(client):
|
||||
from apps.customers.models import CustomerProfile
|
||||
from apps.tickets.models import Ticket
|
||||
c = make_sharing_case()
|
||||
CustomerProfile.objects.create(user=c['customer'], first_name='مشتری', last_name='اشتراک')
|
||||
Ticket.objects.filter(pk=c['ticket'].pk).update(province='اصفهان', city='گلپایگان', priority='URGENT')
|
||||
c['ticket'].refresh_from_db()
|
||||
share_staff_message(c['selected'], c['sender'], c['recipient'])
|
||||
client.force_login(c['recipient'])
|
||||
listing = client.get(reverse('staff_tickets:list'), {'tab': 'shared'})
|
||||
html = listing.content.decode()
|
||||
assert c['ticket'].subject in html and 'مشتری اشتراک' in html
|
||||
assert 'گلپایگان، اصفهان' in html and 'فوری' in html
|
||||
detail = client.get(reverse('staff_tickets:detail', args=[c['ticket'].public_id]))
|
||||
html = detail.content.decode()
|
||||
for value in (c['ticket'].subject, c['customer'].mobile, 'مشتری اشتراک', 'گلپایگان، اصفهان', 'فوری', 'سابقه اخیر', 'id="customer-info"', 'id="ticket-actions"', 'data-ticket-status'):
|
||||
assert value in html
|
||||
assert 'limited-shared-conversation' not in html
|
||||
assert c['selected'].body in html
|
||||
assert 'مکاتبه محرمانه قبل' not in html and 'مکاتبه محرمانه بعد' not in html
|
||||
assert not TicketAccessGrant.objects.filter(ticket=c['ticket'], user=c['recipient']).exists()
|
||||
assert not TicketPolicy.can_view(c['recipient'], c['ticket'])
|
||||
poll = client.get(reverse('staff_tickets:poll', args=[c['ticket'].public_id])).json()
|
||||
assert poll['status'] == c['ticket'].get_status_display() and poll['count'] == 1
|
||||
Reference in new issue
Block a user