816 lines
45 KiB
Python
816 lines
45 KiB
Python
import base64
|
|
import uuid
|
|
import jdatetime
|
|
import pytest
|
|
from django.contrib.auth.models import Permission
|
|
from django.core.exceptions import PermissionDenied, ValidationError
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from apps.accounts.models import User
|
|
from apps.staff.models import StaffProfile, OrganizationalUnit
|
|
from apps.attachments.models import AttachmentPolicy
|
|
from apps.notifications.models import NotificationJob, SMSTemplate
|
|
from apps.integrations.sms.providers import SMSResult
|
|
from apps.payments import services, policies
|
|
from apps.payments.forms import ReceiptForm, NoticeForm
|
|
from apps.payments.models import (BankInstitution, CompanyBankAccount, PaymentApprovalPolicy, PaymentReceipt,
|
|
PaymentAccessConfiguration, PaymentAuditEvent, ReceiptDecision, PaymentNotificationAttempt)
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
PNG = base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=')
|
|
|
|
|
|
def upload():
|
|
return SimpleUploadedFile('receipt.png', PNG, content_type='image/png')
|
|
|
|
|
|
def grant(user, code):
|
|
user.user_permissions.add(Permission.objects.get(content_type__app_label='payments',codename=code))
|
|
for attr in ['_perm_cache','_user_perm_cache','_group_perm_cache']:
|
|
user.__dict__.pop(attr,None)
|
|
|
|
|
|
@pytest.fixture
|
|
def case(settings, tmp_path):
|
|
settings.MEDIA_ROOT = tmp_path
|
|
unit = OrganizationalUnit.objects.create(name='فروش مالی',slug='payment-sales')
|
|
users = {}
|
|
for key in ['creator','first','second','senior','outsider']:
|
|
user = User.objects.create_user(username='pay-'+key,account_type='STAFF',password='TestPayment123!')
|
|
StaffProfile.objects.create(user=user,display_name=key,primary_unit=unit)
|
|
users[key] = user
|
|
customer = User.objects.create_user(username='pay-customer',mobile='09120009999',account_type='CUSTOMER')
|
|
# Transactional PostgreSQL tests flush seed data between cases.
|
|
bank, _ = BankInstitution.objects.get_or_create(code='melli',defaults={'name':'ملی ایران'})
|
|
account = CompanyBankAccount.objects.create(name='حساب فروش ملی',holder='شرکت نمونه',bank=bank,account_number='000123456',requires_deposit_id=True)
|
|
AttachmentPolicy.objects.update_or_create(attachment_type='IMAGE',defaults={'max_file_size':10*1024*1024,'allowed_extensions':['.png'],'allowed_mime_types':['image/png']})
|
|
policy = PaymentApprovalPolicy.objects.create(name='دو تأیید',unit=unit)
|
|
policy.reviewers.add(users['first'],users['second']);policy.seniors.add(users['senior'])
|
|
for key in ['first','second']:
|
|
grant(users[key],'approve_paymentreceipt')
|
|
grant(users['senior'],'senior_approve_paymentreceipt')
|
|
notice = services.issue_notice(users['creator'],{'customer':customer,'account':account,'subject':'پرداخت فاکتور','amount':'۲۸۰٬۰۰۰٬۰۰۰','deposit_id':'۰۰۰۱۲۳','invoice_number':'INV-1','description':'توضیح همیشه نمایان'})
|
|
return {**users,'customer':customer,'account':account,'notice':notice,'policy':policy,'unit':unit}
|
|
|
|
|
|
def data(case, linked=True, **extra):
|
|
today = jdatetime.date.fromgregorian(date=timezone.localdate())
|
|
return {'receipt_mode':'linked' if linked else 'independent','notice_id':case['notice'].pk if linked else '',
|
|
'paid_amount':'۱۲۳۴۵','destination_account':case['account'].pk,'deposit_id':'000123',
|
|
'paid_day':str(today.day),'paid_month':str(today.month),'paid_year':str(today.year),
|
|
'submission_key':str(uuid.uuid4()), **extra}
|
|
|
|
|
|
def submit(case, linked=True):
|
|
form = ReceiptForm(data(case,linked),{'receipt_image':upload()},customer=case['customer'])
|
|
assert form.is_valid(), form.errors
|
|
return services.submit_receipt(case['customer'],case['customer'],form.cleaned_data)
|
|
|
|
|
|
def test_identifier_required_in_form_and_service(case):
|
|
values = {'customer':case['customer'].pk,'account':case['account'].pk,'subject':'فاکتور','amount':'100'}
|
|
form = NoticeForm(values)
|
|
assert not form.is_valid() and 'deposit_id' in form.errors
|
|
with pytest.raises(ValidationError):
|
|
services.issue_notice(case['creator'],{'customer':case['customer'],'account':case['account'],'subject':'فاکتور','amount':100})
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_NOTICE_CREATED').count() == 1
|
|
|
|
|
|
def test_multireviewer_and_final_sms_are_exactly_one(case):
|
|
receipt = submit(case)
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'APPROVE')
|
|
receipt.refresh_from_db();assert receipt.status == 'PARTIAL'
|
|
assert not NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').exists()
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'APPROVE')
|
|
assert ReceiptDecision.objects.count() == 1
|
|
services.decide_receipt(case['second'],receipt.public_id,1,'APPROVE')
|
|
receipt.refresh_from_db();assert receipt.status == 'APPROVED'
|
|
services.decide_receipt(case['second'],receipt.public_id,1,'APPROVE')
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').count() == 1
|
|
assert receipt.revisions.get().cycle.finalized_by == case['second']
|
|
|
|
|
|
def test_senior_explicit_override_and_reason(case):
|
|
receipt = submit(case)
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['senior'],receipt.public_id,1,'SENIOR_APPROVE')
|
|
services.decide_receipt(case['senior'],receipt.public_id,1,'SENIOR_APPROVE','بررسی تطبیق انجام شد')
|
|
receipt.refresh_from_db();assert receipt.status == 'APPROVED'
|
|
assert ReceiptDecision.objects.get().actor_name == 'senior'
|
|
assert PaymentAuditEvent.objects.filter(action='SENIOR_APPROVE').exists()
|
|
|
|
|
|
@pytest.mark.parametrize('reviewer_count', [1, 2])
|
|
def test_policy_reviewers_can_approve_without_extra_permissions(case, client, reviewer_count):
|
|
case['policy'].reviewers.set([case['first'], case['second']][:reviewer_count])
|
|
for key in ['first', 'second']:
|
|
case[key].user_permissions.clear()
|
|
receipt = submit(case)
|
|
endpoint = reverse('staff_payments:decision', args=[receipt.public_id])
|
|
detail = reverse('staff_payments:receipt_detail', args=[receipt.public_id])
|
|
for index, key in enumerate(['first', 'second'][:reviewer_count]):
|
|
client.force_login(case[key])
|
|
page = client.get(detail)
|
|
assert page.context['can_approve']
|
|
assert not page.context['can_senior']
|
|
assert 'ثبت تأیید من' in page.content.decode()
|
|
assert 'رد فیش' in page.content.decode()
|
|
response = client.post(endpoint, {'version': 1, 'action': 'APPROVE'})
|
|
assert response.status_code == 302
|
|
receipt.refresh_from_db()
|
|
final = index == reviewer_count - 1
|
|
assert receipt.status == ('APPROVED' if final else 'PARTIAL')
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').count() == int(final)
|
|
assert ReceiptDecision.objects.filter(cycle__revision__receipt=receipt).count() == reviewer_count
|
|
|
|
|
|
@pytest.mark.parametrize('also_required', [False, True])
|
|
def test_policy_senior_can_finalize_alone_without_extra_permissions(case, client, also_required):
|
|
case['senior'].user_permissions.clear()
|
|
if also_required:
|
|
case['policy'].reviewers.add(case['senior'])
|
|
receipt = submit(case)
|
|
client.force_login(case['senior'])
|
|
page = client.get(reverse('staff_payments:receipt_detail', args=[receipt.public_id]))
|
|
assert page.context['can_senior']
|
|
assert page.context['can_approve'] == also_required
|
|
assert 'تأیید نهایی ارشد' in page.content.decode()
|
|
endpoint = reverse('staff_payments:decision', args=[receipt.public_id])
|
|
client.post(endpoint, {'version': 1, 'action': 'SENIOR_APPROVE'})
|
|
assert not ReceiptDecision.objects.exists()
|
|
response = client.post(endpoint, {'version': 1, 'action': 'SENIOR_APPROVE', 'reason': 'تطبیق انجام شد'})
|
|
assert response.status_code == 302
|
|
receipt.refresh_from_db()
|
|
assert receipt.status == 'APPROVED'
|
|
assert ReceiptDecision.objects.get().user == case['senior']
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').count() == 1
|
|
|
|
|
|
@pytest.mark.parametrize('key', ['first', 'senior'])
|
|
def test_policy_members_can_reject_without_extra_permissions(case, client, key):
|
|
case[key].user_permissions.clear()
|
|
receipt = submit(case)
|
|
client.force_login(case[key])
|
|
detail = client.get(reverse('staff_payments:receipt_detail', args=[receipt.public_id]))
|
|
assert 'رد فیش' in detail.content.decode()
|
|
response = client.post(reverse('staff_payments:decision', args=[receipt.public_id]),
|
|
{'version': 1, 'action': 'REJECT', 'reason': 'تصویر ناخوانا است'})
|
|
assert response.status_code == 302
|
|
receipt.refresh_from_db()
|
|
assert receipt.status == 'REJECTED'
|
|
assert ReceiptDecision.objects.get().action == 'REJECT'
|
|
assert not NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').exists()
|
|
|
|
|
|
@pytest.mark.parametrize('who',['customer','creator','outsider'])
|
|
def test_view_and_create_never_imply_approval(case,who):
|
|
receipt = submit(case)
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(case[who],receipt.public_id,1,'APPROVE')
|
|
|
|
|
|
@pytest.mark.parametrize('action', ['APPROVE', 'SENIOR_APPROVE', 'REJECT'])
|
|
def test_django_permissions_without_assignment_do_not_allow_decisions(case, client, action):
|
|
grant(case['outsider'], 'approve_paymentreceipt')
|
|
grant(case['outsider'], 'senior_approve_paymentreceipt')
|
|
receipt = submit(case)
|
|
client.force_login(case['outsider'])
|
|
page = client.get(reverse('staff_payments:receipt_detail', args=[receipt.public_id]))
|
|
assert not page.context['can_approve'] and not page.context['can_senior']
|
|
response = client.post(reverse('staff_payments:decision', args=[receipt.public_id]),
|
|
{'version': 1, 'action': action, 'reason': 'بررسی انجام شد'})
|
|
assert response.status_code == 403
|
|
assert not ReceiptDecision.objects.exists()
|
|
|
|
|
|
@pytest.mark.parametrize('blocked_by', ['visibility', 'own_submission', 'inactive_user', 'inactive_profile'])
|
|
def test_policy_assignment_preserves_decision_restrictions(case, blocked_by):
|
|
receipt = submit(case)
|
|
if blocked_by == 'visibility':
|
|
PaymentAccessConfiguration.objects.update_or_create(pk=1, defaults={'open_view': False})
|
|
elif blocked_by == 'own_submission':
|
|
PaymentReceipt.objects.filter(pk=receipt.pk).update(submitted_by=case['first'])
|
|
elif blocked_by == 'inactive_user':
|
|
User.objects.filter(pk=case['first'].pk).update(is_active=False)
|
|
else:
|
|
StaffProfile.objects.filter(user=case['first']).update(is_active=False)
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(case['first'], receipt.public_id, 1, 'APPROVE')
|
|
assert not ReceiptDecision.objects.exists()
|
|
|
|
|
|
def test_rejection_ends_cycle_and_preserves_evidence(case):
|
|
receipt = submit(case)
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'REJECT','تصویر پرداخت خوانا نیست')
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['second'],receipt.public_id,1,'APPROVE')
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['senior'],receipt.public_id,1,'SENIOR_APPROVE','override')
|
|
assert not NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').exists()
|
|
|
|
|
|
def test_policy_snapshot_and_stale_version(case):
|
|
receipt = submit(case)
|
|
case['policy'].reviewers.clear()
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['first'],receipt.public_id,2,'APPROVE')
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'APPROVE')
|
|
assert receipt.revisions.get().cycle.assignments.filter(is_senior=False).count() == 2
|
|
|
|
|
|
def test_snapshot_survives_bank_change(case,client):
|
|
case['account'].account_number = '9999999';case['account'].save()
|
|
case['notice'].refresh_from_db();assert case['notice'].account_snapshot['account_number'] == '000123456'
|
|
client.force_login(case['customer'])
|
|
page = client.get(reverse('payments:notices')).content.decode()
|
|
assert '000123456' in page and '9999999' not in page and 'توضیح همیشه نمایان' in page
|
|
|
|
|
|
def test_independent_receipt_and_server_date(case,client):
|
|
# Independent policies use global fallback, not an arbitrary unit's ticket rights.
|
|
case['policy'].unit=None;case['policy'].save()
|
|
receipt = submit(case,False)
|
|
assert receipt.notice is None
|
|
assert receipt.revisions.get().paid_date == timezone.localdate()
|
|
client.force_login(case['outsider'])
|
|
response = client.get(reverse('staff_payments:desk'),{'kind':'independent'})
|
|
assert receipt.number in response.content.decode()
|
|
assert client.get(reverse('staff_payments:count')).json()['pending_receipts'] == 1
|
|
|
|
|
|
@pytest.mark.parametrize('changes,field',[
|
|
({'paid_amount':'0'},'paid_amount'),({'paid_amount':'12.5'},'paid_amount'),({'paid_amount':'1,23'},'paid_amount'),
|
|
({'paid_day':''},'paid_day'),({'paid_date':'1990-01-01'},'paid_day'),({'destination_account':''},'destination_account')])
|
|
def test_validation_exact_fields(case,changes,field):
|
|
form = ReceiptForm(data(case,**changes),{'receipt_image':upload()},customer=case['customer'])
|
|
assert not form.is_valid() and field in form.errors
|
|
|
|
|
|
def test_cross_customer_notice_and_image_protection(case,client):
|
|
receipt=submit(case)
|
|
other=User.objects.create_user(username='other-customer',account_type='CUSTOMER')
|
|
client.force_login(other)
|
|
assert client.get(reverse('payments:receipt_detail',args=[receipt.public_id])).status_code == 404
|
|
assert client.get(reverse('payments:receipt_image',args=[receipt.public_id])).status_code == 403
|
|
form=ReceiptForm(data(case),{'receipt_image':upload()},customer=other)
|
|
assert not form.is_valid() and 'notice_id' in form.errors
|
|
client.force_login(case['customer'])
|
|
response=client.get(reverse('payments:receipt_image',args=[receipt.public_id]))
|
|
assert response.status_code == 200 and 'no-store' in response['Cache-Control']
|
|
assert b''.join(response.streaming_content) == PNG
|
|
|
|
|
|
def test_nonce_duplicate_is_idempotent_and_different_values_rejected(case):
|
|
values=data(case)
|
|
for i in range(2):
|
|
form=ReceiptForm(values,{'receipt_image':upload()},customer=case['customer']);assert form.is_valid()
|
|
services.submit_receipt(case['customer'],case['customer'],form.cleaned_data)
|
|
assert PaymentReceipt.objects.count() == 1
|
|
values['paid_amount']='999'
|
|
form=ReceiptForm(values,{'receipt_image':upload()},customer=case['customer']);assert form.is_valid()
|
|
with pytest.raises(ValidationError):
|
|
services.submit_receipt(case['customer'],case['customer'],form.cleaned_data)
|
|
|
|
|
|
def test_destination_flags_and_bank_permission(case,client):
|
|
case['account'].available_to_customers=False;case['account'].save()
|
|
linked=ReceiptForm(data(case),{'receipt_image':upload()},customer=case['customer']);assert linked.is_valid()
|
|
independent=ReceiptForm(data(case,False),{'receipt_image':upload()},customer=case['customer']);assert not independent.is_valid()
|
|
client.force_login(case['outsider']);assert client.get(reverse('staff_payments:banks')).status_code == 403
|
|
grant(case['creator'],'manage_company_bankaccounts');client.force_login(case['creator'])
|
|
assert client.get(reverse('staff_payments:banks')).status_code == 200
|
|
client.post(reverse('staff_payments:bank_bulk'),{'accounts':[case['account'].pk],'field':'is_active','value':'0'})
|
|
case['account'].refresh_from_db();assert not case['account'].is_active
|
|
assert PaymentAuditEvent.objects.filter(action='BANK_FLAG_CHANGED').exists()
|
|
|
|
|
|
def test_missing_policy_stays_unapproved_until_manager_assigns(case):
|
|
case['policy'].is_active=False;case['policy'].save()
|
|
receipt=submit(case)
|
|
assert not receipt.revisions.get().cycle.assignments.exists()
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'APPROVE')
|
|
case['policy'].is_active=True;case['policy'].save();grant(case['creator'],'manage_payment_approval_policy')
|
|
services.bind_unassigned_cycle(case['creator'],receipt)
|
|
assert receipt.revisions.get().cycle.assignments.count() == 3
|
|
|
|
|
|
def test_sms_uses_database_template_and_attempt_history(case,monkeypatch):
|
|
from apps.notifications.management.commands import process_notifications
|
|
from apps.payments.models import PaymentNotification
|
|
template=SMSTemplate.objects.get(event_type='PAYMENT_NOTICE_CREATED',unit__isnull=True)
|
|
template.body='متن سفارشی {notice_number} {payment_link}';template.save()
|
|
captured=[]
|
|
class Provider:
|
|
def send_message(self,mobile,message):
|
|
captured.append(message);return SMSResult(True,'123')
|
|
monkeypatch.setattr(process_notifications,'get_sms_provider',lambda:Provider())
|
|
job=NotificationJob.objects.get(event_type='PAYMENT_NOTICE_CREATED')
|
|
process_notifications._send_sms(job)
|
|
template.body='جدید';template.save()
|
|
process_notifications._send_sms(job)
|
|
notification = PaymentNotification.objects.get(job=job)
|
|
assert captured[0] == captured[1] and 'متن سفارشی' in captured[0]
|
|
assert '/p/' in notification.context['payment_link']
|
|
assert notification.context['payment_link'] in captured[0]
|
|
assert PaymentNotificationAttempt.objects.filter(success=True).count() == 2
|
|
assert job.ticket is None
|
|
|
|
|
|
def test_explicit_access_and_inactive_reviewer(case):
|
|
receipt=submit(case)
|
|
PaymentAccessConfiguration.objects.update(open_view=False,open_create=False)
|
|
assert not policies.visible(PaymentReceipt.objects.all(),case['outsider']).exists()
|
|
assert not policies.can_create(case['outsider'])
|
|
StaffProfile.objects.filter(user=case['first']).update(is_active=False)
|
|
actor=User.objects.get(pk=case['first'].pk)
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(actor,receipt.public_id,1,'APPROVE')
|
|
|
|
|
|
def test_login_hub_and_existing_new_route(case,client):
|
|
client.force_login(case['customer'])
|
|
assert client.get('/').url == reverse('payments:home')
|
|
assert client.get(reverse('payments:home')).status_code == 200
|
|
assert client.get(reverse('tickets:new')).status_code == 200
|
|
assert reverse('tickets:new') == '/tickets/new/'
|
|
client.logout()
|
|
assert client.get(reverse('payments:home')).status_code == 302
|
|
|
|
|
|
def test_upload_spoof_and_missing_fields(case):
|
|
form=ReceiptForm(data(case),{'receipt_image':SimpleUploadedFile('fake.png',b'%PDF-1.4',content_type='image/png')},customer=case['customer'])
|
|
assert not form.is_valid() and 'receipt_image' in form.errors
|
|
form=ReceiptForm({},customer=case['customer'])
|
|
assert not form.is_valid()
|
|
assert {'paid_amount','paid_day','paid_month','paid_year','receipt_image','destination_account'} <= set(form.errors)
|
|
|
|
|
|
def test_revision_preserves_old_votes_and_restarts_full_policy(case):
|
|
receipt=submit(case)
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'REJECT','نیاز به تصویر تازه')
|
|
form=ReceiptForm(data(case),{'receipt_image':upload()},customer=case['customer']);assert form.is_valid()
|
|
services.revise_receipt(case['customer'],receipt.public_id,1,form.cleaned_data)
|
|
receipt.refresh_from_db();assert receipt.status=='PENDING' and receipt.current_version==2
|
|
assert receipt.revisions.count()==2
|
|
assert receipt.revisions.get(version=1).cycle.decisions.count()==1
|
|
assert receipt.revisions.get(version=2).cycle.decisions.count()==0
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['second'],receipt.public_id,1,'APPROVE')
|
|
services.decide_receipt(case['first'],receipt.public_id,2,'APPROVE')
|
|
receipt.refresh_from_db();assert receipt.status=='PARTIAL'
|
|
|
|
|
|
def test_rollback_cleans_uploaded_file(case,settings):
|
|
case['policy'].reviewers.clear()
|
|
with pytest.raises(ValidationError):
|
|
submit(case)
|
|
assert PaymentReceipt.objects.count()==0
|
|
assert not list(settings.MEDIA_ROOT.rglob('*.png'))
|
|
|
|
|
|
def test_invalid_notice_query_is_not_server_error(case,client):
|
|
client.force_login(case['customer'])
|
|
assert client.get(reverse('payments:receipt_new'),{'notice':'not-a-uuid'}).status_code==404
|
|
|
|
|
|
def test_safe_login_next_and_magic_link_not_financial_session(case,client):
|
|
from apps.accounts.models import TicketMagicLink
|
|
from apps.tickets.models import TicketCategory
|
|
from apps.tickets.services import create_ticket
|
|
case['customer'].set_password('TestFinancial123!');case['customer'].save()
|
|
target=reverse('payments:notices')
|
|
client.get(reverse('accounts:password_login'),{'next':target})
|
|
response=client.post(reverse('accounts:password_login'),{'mobile':case['customer'].local_mobile,'password':'TestFinancial123!'})
|
|
assert response.url==target
|
|
client.logout();client.get(reverse('accounts:password_login'),{'next':'https://evil.example/chaharsu/'})
|
|
response=client.post(reverse('accounts:password_login'),{'mobile':case['customer'].local_mobile,'password':'TestFinancial123!'})
|
|
assert response.url==reverse('payments:home')
|
|
client.logout()
|
|
category=TicketCategory.objects.create(title='magic finance guard',slug='magic-finance-guard')
|
|
ticket=create_ticket(customer=case['customer'],category=category,subject='صرفاً تیکت',body='متن')
|
|
client.get(reverse('accounts:magic',args=[TicketMagicLink.issue(ticket)]))
|
|
assert client.get(reverse('payments:notices')).status_code==302
|
|
|
|
|
|
def test_notice_nonce_prevents_duplicate_issuance_and_sms(case):
|
|
values = {'customer':case['customer'], 'account':case['account'], 'subject':'دستور تازه',
|
|
'amount':1000, 'deposit_id':'0042', 'submission_key':uuid.uuid4()}
|
|
first = services.issue_notice(case['creator'], values)
|
|
second = services.issue_notice(case['creator'], values)
|
|
assert first.pk == second.pk
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_NOTICE_CREATED').count() == 2
|
|
with pytest.raises(ValidationError):
|
|
services.issue_notice(case['creator'], {**values, 'amount':2000})
|
|
|
|
|
|
def test_revision_retains_notice_snapshot_and_identifier(case):
|
|
receipt = submit(case)
|
|
services.decide_receipt(case['first'], receipt.public_id, 1, 'REJECT', 'مدرک تازه لازم است')
|
|
case['account'].account_number = '999999';case['account'].save()
|
|
form = ReceiptForm(data(case, deposit_id=''), {'receipt_image':upload()}, customer=case['customer'])
|
|
assert form.is_valid(), form.errors
|
|
services.revise_receipt(case['customer'], receipt.public_id, 1, form.cleaned_data)
|
|
revision = receipt.revisions.get(version=2)
|
|
assert revision.account_snapshot['account_number'] == '000123456'
|
|
assert revision.deposit_id == '000123'
|
|
|
|
|
|
def test_financial_unit_sms_template_accepts_financial_tags(case):
|
|
from apps.tickets.forms import UnitSMSTemplateForm
|
|
form = UnitSMSTemplateForm({'unit':case['unit'].pk, 'event_type':'PAYMENT_NOTICE_CREATED',
|
|
'title':'مالی', 'body':'{notice_number} {amount_rial} {payment_link}', 'is_active':True},
|
|
user=case['creator'], unit_ids=[case['unit'].pk])
|
|
assert form.is_valid(), form.errors
|
|
assert 'payment_link' in form.save().allowed_tags
|
|
|
|
|
|
def test_payment_posts_enforce_csrf_and_revoked_approval(case):
|
|
from django.test import Client
|
|
receipt = submit(case)
|
|
client = Client(enforce_csrf_checks=True)
|
|
client.force_login(case['first'])
|
|
endpoint = reverse('staff_payments:decision',args=[receipt.public_id])
|
|
assert client.post(endpoint, {'version':1,'action':'APPROVE'}).status_code == 403
|
|
StaffProfile.objects.filter(user=case['first']).update(is_active=False)
|
|
actor = User.objects.get(pk=case['first'].pk)
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(actor,receipt.public_id,1,'APPROVE')
|
|
receipt.refresh_from_db();assert receipt.status == 'PENDING'
|
|
assert not ReceiptDecision.objects.exists()
|
|
|
|
|
|
def test_receipt_identifier_is_optional_even_for_a_different_identifier_account(case):
|
|
other = CompanyBankAccount.objects.create(name='حساب شناسهدار دیگر',holder='شرکت',
|
|
bank=case['account'].bank,account_number='1111',requires_deposit_id=True)
|
|
form = ReceiptForm(data(case,destination_account=other.pk,deposit_id=''),
|
|
{'receipt_image':upload()},customer=case['customer'])
|
|
assert form.is_valid(), form.errors
|
|
receipt = services.submit_receipt(case['customer'], case['customer'], form.cleaned_data)
|
|
assert receipt.revisions.get(version=1).deposit_id == ''
|
|
|
|
|
|
@pytest.mark.parametrize('day,month,year,valid', [
|
|
('30','12','1403',True),('30','12','1402',False),('31','7','1403',False),
|
|
('31','6','1403',True),('','1','1403',False),
|
|
])
|
|
def test_jalali_calendar_validation_and_gregorian_result(day,month,year,valid):
|
|
from apps.payments import validators
|
|
import datetime
|
|
if valid:
|
|
assert isinstance(validators.paid_date(day,month,year), datetime.date)
|
|
else:
|
|
with pytest.raises(ValidationError):
|
|
validators.paid_date(day,month,year)
|
|
|
|
|
|
def test_failed_sms_template_preparation_has_attempt_history(case):
|
|
from apps.payments.notifications import send_payment_sms
|
|
SMSTemplate.objects.filter(event_type='PAYMENT_NOTICE_CREATED').update(is_active=False)
|
|
job = NotificationJob.objects.get(event_type='PAYMENT_NOTICE_CREATED')
|
|
with pytest.raises(SMSTemplate.DoesNotExist):
|
|
send_payment_sms(job,provider_factory=lambda:None)
|
|
attempt = PaymentNotificationAttempt.objects.get()
|
|
assert attempt.success is False and attempt.finished_at is not None
|
|
assert 'قالب پیامک' in attempt.error
|
|
|
|
|
|
def test_stale_bank_form_does_not_overwrite_new_flags(case,client):
|
|
grant(case['creator'],'manage_company_bankaccounts')
|
|
client.force_login(case['creator'])
|
|
account = case['account']
|
|
token = account.updated_at.isoformat()
|
|
account.is_active = False;account.save()
|
|
response = client.post(reverse('staff_payments:bank_edit',args=[account.public_id]),{
|
|
'revision_token':token,'name':'نام قدیمی','bank':account.bank_id,'holder':account.holder,
|
|
'account_number':account.account_number,'sort_order':0,'is_active':'on'})
|
|
assert response.status_code == 200
|
|
assert 'تغییر کرده است' in response.content.decode()
|
|
account.refresh_from_db();assert not account.is_active and account.name != 'نام قدیمی'
|
|
|
|
|
|
def test_customer_receipt_list_and_revised_action_queue(case,client):
|
|
receipt=submit(case)
|
|
services.decide_receipt(case['first'],receipt.public_id,1,'REJECT','مدرک تازه')
|
|
form=ReceiptForm(data(case),{'receipt_image':upload()},customer=case['customer']);assert form.is_valid()
|
|
services.revise_receipt(case['customer'],receipt.public_id,1,form.cleaned_data)
|
|
client.force_login(case['first'])
|
|
assert receipt.number in client.get(reverse('staff_payments:desk'),{'mine':'1'}).content.decode()
|
|
client.force_login(case['customer'])
|
|
assert receipt.number in client.get(reverse('payments:receipt_list')).content.decode()
|
|
|
|
|
|
def test_pwa_entry_routes_each_authenticated_role(case,client):
|
|
assert client.get(reverse('web_manifest')).json()['start_url'] == '/'
|
|
client.force_login(case['customer'])
|
|
assert client.get('/').url == reverse('payments:home')
|
|
client.force_login(case['creator'])
|
|
assert client.get('/').url == reverse('staff_tickets:list')
|
|
|
|
|
|
def test_financial_sms_model_clean_supplies_admin_readonly_tags(case):
|
|
template=SMSTemplate(unit=case['unit'],event_type='PAYMENT_RECEIPT_APPROVED',title='مالی',
|
|
body='{receipt_number} {payment_link}')
|
|
template.full_clean()
|
|
assert 'payment_link' in template.allowed_tags
|
|
template.body='{magic_link}'
|
|
with pytest.raises(ValidationError):
|
|
template.full_clean()
|
|
|
|
|
|
def test_cached_user_cannot_vote_after_staff_deactivation(case):
|
|
receipt=submit(case)
|
|
actor=case['first']
|
|
assert actor.has_perm('payments.approve_paymentreceipt')
|
|
assert actor.staff_profile.is_active
|
|
StaffProfile.objects.filter(user=actor).update(is_active=False)
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(actor,receipt.public_id,1,'APPROVE')
|
|
assert not ReceiptDecision.objects.exists()
|
|
|
|
|
|
def test_independent_receipt_bank_numbers_are_only_in_staff_review(case,client):
|
|
receipt=submit(case,False)
|
|
client.force_login(case['customer'])
|
|
content=client.get(reverse('payments:receipt_detail',args=[receipt.public_id])).content.decode()
|
|
assert 'شماره حساب مقصد ثبتشده' not in content
|
|
client.force_login(case['first'])
|
|
content=client.get(reverse('staff_payments:receipt_detail',args=[receipt.public_id])).content.decode()
|
|
assert 'شماره حساب مقصد ثبتشده' in content and '000123456' in content
|
|
|
|
|
|
def test_audit_permission_opens_readonly_evidence_admin(case):
|
|
from django.contrib import admin
|
|
from django.test import RequestFactory
|
|
from apps.payments.admin import EvidenceAdmin
|
|
request=RequestFactory().get('/x5admin/payments/paymentauditevent/')
|
|
request.user=case['outsider']
|
|
controller=EvidenceAdmin(PaymentAuditEvent,admin.site)
|
|
assert not controller.has_view_permission(request)
|
|
grant(request.user,'view_payment_audit')
|
|
assert controller.has_view_permission(request)
|
|
assert controller.get_queryset(request).exists()
|
|
assert not controller.has_change_permission(request)
|
|
assert not controller.has_delete_permission(request)
|
|
|
|
|
|
def test_admin_bank_audit_contains_previous_and_new_flags(case):
|
|
from django.contrib import admin
|
|
from django.test import RequestFactory
|
|
from apps.payments.admin import AccountAdmin
|
|
from apps.payments.forms import BankAccountForm
|
|
account=case['account']
|
|
grant(case['creator'],'manage_company_bankaccounts')
|
|
values={'revision_token':account.updated_at.isoformat(),'name':'حساب ویرایششده','bank':account.bank_id,
|
|
'holder':account.holder,'account_number':account.account_number,'sort_order':0,
|
|
'requires_deposit_id':'on','available_to_staff':'on','available_to_customers':'on'}
|
|
form=BankAccountForm(values,instance=account)
|
|
assert form.is_valid(),form.errors
|
|
request=RequestFactory().post('/x5admin/payments/companybankaccount/')
|
|
request.user=case['creator']
|
|
AccountAdmin(CompanyBankAccount,admin.site).save_model(request,form.save(commit=False),form,True)
|
|
event=PaymentAuditEvent.objects.get(action='CONFIG_SAVED')
|
|
assert event.details['before']['is_active'] is True
|
|
assert event.details['after']['is_active'] is False
|
|
assert event.actor_id==request.user.pk
|
|
|
|
|
|
def test_customer_search_identity_history_and_permissions(case, client):
|
|
from apps.customers.models import CustomerProfile
|
|
from apps.tickets.models import Ticket, TicketCategory
|
|
customer = case['customer']
|
|
CustomerProfile.objects.create(user=customer, first_name='محمد', last_name='رضایی',
|
|
organization_name='شرکت آفتاب', city='قم')
|
|
category = TicketCategory.objects.create(title='جستوجوی مالی', slug='payment-picker')
|
|
visible = Ticket.objects.create(customer=customer, category=category, subject='سابقه خرید قابل مشاهده',
|
|
owner=case['creator'], selected_expert=case['creator'], origin_visibility='SELECTED', owning_unit=case['unit'], last_activity_at=timezone.now())
|
|
Ticket.objects.create(customer=customer, category=category, subject='سابقه محرمانه دیگر',
|
|
owner=case['outsider'], selected_expert=case['outsider'], origin_visibility='SELECTED', owning_unit=case['unit'], last_activity_at=timezone.now())
|
|
inactive = User.objects.create_user(username='inactive-picker', mobile='09123334444', is_active=False)
|
|
CustomerProfile.objects.create(user=inactive, first_name='محمد', last_name='رضایی')
|
|
client.force_login(case['creator'])
|
|
url = reverse('staff_payments:customer_search')
|
|
for query in ['محمد رضایی', 'شرکت آفتاب', '۰۹۱۲۰۰۰۹۹۹۹', '0912', visible.ticket_number, 'سابقه خرید قابل مشاهده']:
|
|
response = client.get(url, {'q': query})
|
|
assert response.status_code == 200
|
|
assert 'no-store' in response['Cache-Control']
|
|
result = response.json()['results'][0]
|
|
assert result['id'] == str(customer.public_id)
|
|
assert result['name'] == 'محمد رضایی'
|
|
assert result['mobile'] == customer.local_mobile
|
|
assert result['ticket_count'] == 1
|
|
assert [row['subject'] for row in result['history']] == [visible.subject]
|
|
assert client.get(url, {'q': 'سابقه محرمانه دیگر'}).json()['results'] == []
|
|
assert client.get(url, {'q': 'م'}).json()['results'] == []
|
|
configuration = PaymentAccessConfiguration.objects.first()
|
|
configuration.open_create = False
|
|
configuration.save()
|
|
assert client.get(url, {'q': 'محمد'}).status_code == 403
|
|
client.force_login(customer)
|
|
assert client.get(url, {'q': 'محمد'}).status_code == 403
|
|
|
|
|
|
def test_notice_customer_prefill_uses_uuid_and_no_full_directory(case, client):
|
|
client.force_login(case['creator'])
|
|
url = reverse('staff_payments:notice_new')
|
|
response = client.get(url, {'customer': str(case['customer'].public_id)})
|
|
assert response.status_code == 200
|
|
assert response.context['form']['customer'].value() == case['customer'].pk
|
|
assert response.context['customer_selection']['id'] == str(case['customer'].public_id)
|
|
html = response.content.decode()
|
|
assert 'type="hidden" name="customer"' in html
|
|
assert '<select name="customer"' not in html
|
|
assert client.get(url, {'customer': 'invalid'}).status_code == 404
|
|
assert client.get(url, {'customer': str(uuid.uuid4())}).status_code == 404
|
|
case['customer'].is_active = False
|
|
case['customer'].save()
|
|
assert client.get(url, {'customer': str(case['customer'].public_id)}).status_code == 404
|
|
|
|
|
|
def test_payment_shortcuts_on_contact_and_ticket(case, client):
|
|
from apps.tickets.models import Ticket, TicketCategory
|
|
ticket = Ticket.objects.create(customer=case['customer'], category=TicketCategory.objects.create(title='میانبر مالی', slug='payment-shortcut'),
|
|
subject='تیکت برای ایجاد اعلامیه', owner=case['creator'], selected_expert=case['creator'],
|
|
origin_visibility='SELECTED', owning_unit=case['unit'], last_activity_at=timezone.now())
|
|
client.force_login(case['creator'])
|
|
target = reverse('staff_payments:notice_new') + '?customer=' + str(case['customer'].public_id)
|
|
for url in [reverse('staff_tickets:detail', args=[ticket.public_id]), reverse('staff_tickets:contacts')]:
|
|
response = client.get(url)
|
|
assert response.status_code == 200
|
|
assert target in response.content.decode()
|
|
configuration = PaymentAccessConfiguration.objects.first()
|
|
configuration.open_create = False
|
|
configuration.save()
|
|
assert target not in client.get(reverse('staff_tickets:detail', args=[ticket.public_id])).content.decode()
|
|
|
|
|
|
def test_ticket_recent_notices_are_customer_scoped_and_permission_filtered(case, client):
|
|
from apps.payments.models import PaymentNotice
|
|
from apps.tickets.models import Ticket, TicketCategory
|
|
|
|
ticket = Ticket.objects.create(customer=case['customer'],
|
|
category=TicketCategory.objects.create(title='اعلامیهها', slug='recent-notices'),
|
|
subject='اعلامیههای مشتری', owner=case['creator'], selected_expert=case['creator'],
|
|
origin_visibility='SELECTED', owning_unit=case['unit'], last_activity_at=timezone.now())
|
|
notices = [services.issue_notice(case['creator'], {
|
|
'customer': case['customer'], 'account': case['account'], 'subject': f'اعلامیه اخیر {index}',
|
|
'amount': 12345, 'deposit_id': '000123',
|
|
}) for index in range(3)]
|
|
notices[-1].cancelled_at = timezone.now()
|
|
notices[-1].save(update_fields=['cancelled_at'])
|
|
other = User.objects.create_user(username='other-notice-customer', account_type='CUSTOMER')
|
|
foreign = services.issue_notice(case['creator'], {'customer': other, 'account': case['account'],
|
|
'subject': 'اعلامیه مشتری دیگر', 'amount': 100, 'deposit_id': '000123'})
|
|
receipt = submit(case)
|
|
# Only confirmed current receipt amounts count towards settlement.
|
|
PaymentReceipt.objects.filter(pk=receipt.pk).update(notice=notices[0], status='APPROVED')
|
|
client.force_login(case['creator'])
|
|
url = reverse('staff_tickets:detail', args=[ticket.public_id])
|
|
response = client.get(url)
|
|
assert response.status_code == 200
|
|
assert [notice.pk for notice in response.context['customer_notices']] == [n.pk for n in reversed(notices)]
|
|
html = response.content.decode()
|
|
assert case['notice'].number not in html
|
|
assert foreign.number not in html
|
|
assert 'لغو شده' in html and 'تسویه شده' in html and 'در انتظار واریز' in html
|
|
for notice in notices:
|
|
assert reverse('staff_payments:notice_detail', args=[notice.public_id]) in html
|
|
more = reverse('staff_payments:desk') + f'?tab=notices&customer={case["customer"].pk}'
|
|
assert more.replace('&', '&') in html
|
|
assert client.get(more).context['page_obj'].paginator.count == 4
|
|
configuration = PaymentAccessConfiguration.objects.first()
|
|
configuration.open_view = False
|
|
configuration.save()
|
|
hidden = client.get(url)
|
|
assert not list(hidden.context['customer_notices'])
|
|
assert all(notice.number not in hidden.content.decode() for notice in notices)
|
|
|
|
|
|
def test_customer_notice_menu_is_ten_recent_pending_and_retains_old_link(case, client):
|
|
from apps.payments.models import PaymentNotice
|
|
for index in range(12):
|
|
services.issue_notice(case['creator'], {'customer': case['customer'], 'account': case['account'],
|
|
'subject': f'واریز {index}', 'amount': 20000, 'deposit_id': '123'})
|
|
cancelled = PaymentNotice.objects.filter(customer=case['customer']).first()
|
|
cancelled.cancelled_at = timezone.now()
|
|
cancelled.save()
|
|
client.force_login(case['customer'])
|
|
response = client.get(reverse('payments:receipt_new'))
|
|
choices = response.context['notice_data']
|
|
assert len(choices) == 10
|
|
assert str(cancelled.pk) not in {n['id'] for n in choices}
|
|
assert str(case['notice'].pk) not in {n['id'] for n in choices}
|
|
direct = client.get(reverse('payments:receipt_new'), {'notice': str(case['notice'].public_id)})
|
|
assert len(direct.context['notice_data']) == 10
|
|
assert str(case['notice'].pk) in {n['id'] for n in direct.context['notice_data']}
|
|
|
|
|
|
def test_linked_receipt_rejection_history_and_staff_identity(case, client):
|
|
from apps.customers.models import CustomerProfile
|
|
CustomerProfile.objects.update_or_create(user=case['customer'], defaults={
|
|
'first_name': 'مریم', 'last_name': 'امینی', 'province': 'اصفهان', 'city': 'گلپایگان'})
|
|
receipt = submit(case)
|
|
services.decide_receipt(case['first'], receipt.public_id, 1, 'APPROVE', 'مبلغ مطابقت دارد')
|
|
services.decide_receipt(case['second'], receipt.public_id, 1, 'REJECT', 'تصویر رسید خوانا نیست')
|
|
client.force_login(case['customer'])
|
|
overview = client.get(reverse('payments:notices'), {'notice': str(case['notice'].public_id)}).content.decode()
|
|
assert str(receipt.public_id) in overview and 'تصویر رسید خوانا نیست' in overview
|
|
detail = client.get(reverse('payments:receipt_detail', args=[receipt.public_id])).content.decode()
|
|
assert 'مبلغ مطابقت دارد' in detail and 'تصویر رسید خوانا نیست' in detail
|
|
assert 'افراد تعیینشده' not in detail and 'سوابق ارسال و اصلاح' in detail
|
|
client.force_login(case['second'])
|
|
staff = client.get(reverse('staff_payments:receipt_detail', args=[receipt.public_id])).content.decode()
|
|
assert all(value in staff for value in ['مریم', 'امینی', 'اصفهان', 'گلپایگان', case['customer'].local_mobile])
|
|
assert 'افراد تعیینشده' not in staff
|
|
listing = client.get(reverse('staff_payments:desk'), {'q': 'گلپایگان'}).content.decode()
|
|
assert 'مریم' in listing and 'ایجادکننده اعلامیه: creator' in listing
|
|
|
|
|
|
def test_settled_notice_excluded_and_approvers_shown_on_staff_list(case, client):
|
|
from apps.payments.models import PaymentNotice
|
|
receipt = submit(case)
|
|
PaymentNotice.objects.filter(pk=case['notice'].pk).update(amount=12345)
|
|
services.decide_receipt(case['first'], receipt.public_id, 1, 'APPROVE', 'تطبیق مبلغ')
|
|
services.decide_receipt(case['second'], receipt.public_id, 1, 'APPROVE', 'تطبیق تصویر')
|
|
client.force_login(case['customer'])
|
|
response = client.get(reverse('payments:receipt_new'))
|
|
assert not response.context['notice_data']
|
|
client.force_login(case['first'])
|
|
for tab in ['receipts', 'notices']:
|
|
content = client.get(reverse('staff_payments:desk'), {'tab': tab}).content.decode()
|
|
assert 'first' in content and 'second' in content and 'creator' in content
|
|
number = receipt.number if tab == 'receipts' else case['notice'].number
|
|
found = client.get(reverse('staff_payments:desk'), {'tab': tab, 'q': number})
|
|
assert found.context['page_obj'].paginator.count == 1
|
|
settled = client.get(reverse('staff_payments:desk'), {'tab': 'notices', 'notice_state': 'settled'})
|
|
assert settled.context['page_obj'].paginator.count == 1
|
|
pending = client.get(reverse('staff_payments:desk'), {'tab': 'notices', 'notice_state': 'pending'})
|
|
assert pending.context['page_obj'].paginator.count == 0
|
|
|
|
|
|
def test_assigned_senior_can_reject_with_reason_and_unassigned_cannot(case, client):
|
|
receipt = submit(case)
|
|
client.force_login(case['senior'])
|
|
page = client.get(reverse('staff_payments:receipt_detail', args=[receipt.public_id])).content.decode()
|
|
assert 'value="REJECT"' in page and 'value="SENIOR_APPROVE"' in page
|
|
with pytest.raises(ValidationError):
|
|
services.decide_receipt(case['senior'], receipt.public_id, 1, 'REJECT', '')
|
|
grant(case['outsider'], 'senior_approve_paymentreceipt')
|
|
with pytest.raises(PermissionDenied):
|
|
services.decide_receipt(case['outsider'], receipt.public_id, 1, 'REJECT', 'توضیح')
|
|
services.decide_receipt(case['senior'], receipt.public_id, 1, 'REJECT', 'اطلاعات رسید با پرداخت مطابقت ندارد')
|
|
receipt.refresh_from_db()
|
|
assert receipt.status == 'REJECTED'
|
|
assert ReceiptDecision.objects.get(cycle__revision__receipt=receipt).actor_name == 'senior'
|
|
assert not NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').exists()
|
|
|
|
|
|
def test_customer_notice_has_independent_owned_detail_url(case, client):
|
|
receipt = submit(case)
|
|
url = reverse('payments:notice_detail', args=[case['notice'].public_id])
|
|
client.force_login(case['customer'])
|
|
listing = client.get(reverse('payments:notices')).content.decode()
|
|
assert url in listing
|
|
detail = client.get(url)
|
|
assert detail.status_code == 200
|
|
content = detail.content.decode()
|
|
assert 'توضیح همیشه نمایان' in content and str(receipt.public_id) in content
|
|
form = client.get(reverse('payments:receipt_new'), {'notice': str(case['notice'].public_id)})
|
|
assert form.context['notice_data'][0]['url'] == url
|
|
other = User.objects.create_user(username='other-notice-customer', account_type='CUSTOMER')
|
|
client.force_login(other)
|
|
assert client.get(url).status_code == 404
|
|
client.force_login(case['creator'])
|
|
assert client.get(url).status_code == 403
|
|
staff = client.get(reverse('staff_payments:receipt_new', args=[case['customer'].public_id]), {'notice': str(case['notice'].public_id)})
|
|
assert staff.context['notice_data'][0]['url'] == reverse('staff_payments:notice_detail', args=[case['notice'].public_id])
|
|
|
|
|
|
@pytest.mark.parametrize('mobile,expected', [('09120009999','0912000999938'), ('09120009021','0912000902169'), ('09000000000','0900000000090')])
|
|
def test_deposit_identifier_reference_vectors(mobile, expected):
|
|
from apps.payments.deposit_identifier import generate_id_algorithm
|
|
assert generate_id_algorithm(mobile) == expected
|
|
|
|
|
|
def test_deposit_identifier_endpoint_uses_stored_mobile_and_permissions(case, client):
|
|
from django.test import Client
|
|
endpoint = reverse('staff_payments:generate_identifier')
|
|
client.force_login(case['creator'])
|
|
before = case['customer'].payment_notices.count()
|
|
response = client.post(endpoint, {'customer': case['customer'].pk, 'mobile': '09000000000'})
|
|
assert response.status_code == 200
|
|
assert response.json()['identifier'] == '0912000999938'
|
|
assert response['Cache-Control'] == 'no-store'
|
|
assert case['customer'].payment_notices.count() == before
|
|
assert client.get(endpoint).status_code == 405
|
|
assert client.post(endpoint, {'customer': case['outsider'].pk}).status_code == 400
|
|
csrf_client = Client(enforce_csrf_checks=True)
|
|
csrf_client.force_login(case['creator'])
|
|
assert csrf_client.post(endpoint, {'customer': case['customer'].pk}).status_code == 403
|
|
client.force_login(case['customer'])
|
|
assert client.post(endpoint, {'customer': case['customer'].pk}).status_code == 403
|
|
client.force_login(case['creator'])
|
|
PaymentAccessConfiguration.objects.update_or_create(singleton=1, defaults={'open_create':False})
|
|
assert client.post(endpoint, {'customer': case['customer'].pk}).status_code == 403
|