2018-04-06 08:05:53 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2020-02-25 14:05:28 +01:00
|
|
|
import copy
|
2017-07-28 01:50:07 +02:00
|
|
|
import json
|
2016-09-14 08:30:59 +02:00
|
|
|
import pytest
|
|
|
|
import mock
|
|
|
|
from decimal import Decimal
|
2017-01-17 21:05:23 +01:00
|
|
|
from requests.exceptions import ConnectionError
|
2016-09-14 08:30:59 +02:00
|
|
|
|
2018-11-14 17:37:42 +01:00
|
|
|
from django.apps import apps
|
2016-09-14 08:30:59 +02:00
|
|
|
from django.test.client import RequestFactory
|
2018-04-06 08:05:53 +02:00
|
|
|
from django.test import override_settings
|
2020-04-01 16:18:28 +02:00
|
|
|
from django.urls import reverse
|
2016-07-22 18:06:45 +02:00
|
|
|
from django.conf import settings
|
2017-01-17 21:05:23 +01:00
|
|
|
from django.core.management import call_command
|
2020-06-03 11:27:09 +02:00
|
|
|
from django.utils.encoding import force_bytes, force_text
|
2018-07-25 16:32:50 +02:00
|
|
|
from django.utils.six.moves.urllib import parse as urlparse
|
2018-03-07 19:20:31 +01:00
|
|
|
from django.utils.timezone import timedelta, now
|
|
|
|
from django.contrib.auth.models import User
|
2016-09-14 08:30:59 +02:00
|
|
|
|
2016-07-22 18:06:45 +02:00
|
|
|
from combo.utils import check_query, aes_hex_encrypt
|
2016-09-14 08:30:59 +02:00
|
|
|
from combo.data.models import Page
|
2017-01-17 21:05:23 +01:00
|
|
|
from combo.apps.lingo.models import (Regie, ActiveItems, ItemsHistory, SelfDeclaredInvoicePayment,
|
2019-05-13 15:37:47 +02:00
|
|
|
Transaction, BasketItem, PaymentBackend)
|
2016-09-14 08:30:59 +02:00
|
|
|
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
|
|
|
|
|
|
INVOICES = [
|
|
|
|
{
|
|
|
|
'id': 'F201601',
|
|
|
|
'display_id': 'F-2016-One',
|
|
|
|
'label': 'invoice-one',
|
|
|
|
'regie': 'remote',
|
|
|
|
'created': '2016-02-02',
|
|
|
|
'pay_limit_date': '2999-12-31',
|
|
|
|
'total_amount': '123.45',
|
|
|
|
'amount': '123.45',
|
|
|
|
'has_pdf': True,
|
|
|
|
'online_payment': True,
|
|
|
|
'paid': False,
|
2018-05-25 09:40:29 +02:00
|
|
|
'payment_date': '1970-01-01',
|
2016-09-14 08:30:59 +02:00
|
|
|
'no_online_payment_reason': '',
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
2018-03-07 19:20:31 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def admin():
|
|
|
|
try:
|
|
|
|
admin = User.objects.get(username='foo')
|
|
|
|
except User.DoesNotExist:
|
|
|
|
admin = User.objects.create_user('foo', email=None, password='bar')
|
|
|
|
admin.email = 'foo@example.net'
|
|
|
|
admin.save()
|
|
|
|
return admin
|
|
|
|
|
2016-09-14 08:30:59 +02:00
|
|
|
@pytest.fixture
|
|
|
|
def remote_regie():
|
2019-05-13 15:37:47 +02:00
|
|
|
try:
|
|
|
|
payment_backend = PaymentBackend.objects.get(slug='test1')
|
|
|
|
except PaymentBackend.DoesNotExist:
|
|
|
|
payment_backend = PaymentBackend.objects.create(
|
|
|
|
label='test1', slug='test1', service='dummy', service_options={'siret': '1234'})
|
2016-09-14 08:30:59 +02:00
|
|
|
try:
|
|
|
|
regie = Regie.objects.get(slug='remote')
|
|
|
|
except Regie.DoesNotExist:
|
|
|
|
regie = Regie()
|
|
|
|
regie.label = 'Remote'
|
|
|
|
regie.slug = 'remote'
|
|
|
|
regie.description = 'remote'
|
|
|
|
regie.payment_min_amount = Decimal(2.0)
|
2019-05-13 15:37:47 +02:00
|
|
|
regie.payment_backend = payment_backend
|
2016-10-21 14:06:20 +02:00
|
|
|
regie.webservice_url = 'http://example.org/regie' # is_remote
|
2016-09-14 08:30:59 +02:00
|
|
|
regie.save()
|
|
|
|
return regie
|
|
|
|
|
|
|
|
|
2016-10-21 14:06:20 +02:00
|
|
|
class MockUser(object):
|
|
|
|
email = 'foo@example.net'
|
2019-10-14 13:43:08 +02:00
|
|
|
is_authenticated = True
|
2016-09-14 08:30:59 +02:00
|
|
|
|
2019-02-21 11:18:26 +01:00
|
|
|
def get_name_id(self):
|
|
|
|
return 'r2d2'
|
|
|
|
|
2016-10-21 14:06:20 +02:00
|
|
|
|
2019-08-05 18:57:06 +02:00
|
|
|
@mock.patch('combo.utils.requests_wrapper.RequestsSession.send')
|
|
|
|
def test_remote_regie_active_invoices_cell(mock_send, remote_regie):
|
2016-09-14 08:30:59 +02:00
|
|
|
assert remote_regie.is_remote() == True
|
|
|
|
|
|
|
|
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
|
|
|
|
page.save()
|
|
|
|
cell = ActiveItems(regie='remote', page=page, placeholder='content', order=0)
|
2018-01-01 12:35:01 +01:00
|
|
|
context = {'request': RequestFactory().get('/')}
|
2016-09-14 08:30:59 +02:00
|
|
|
context['synchronous'] = True # to get fresh content
|
|
|
|
|
2016-10-21 14:06:20 +02:00
|
|
|
user = MockUser()
|
2016-09-14 08:30:59 +02:00
|
|
|
context['user'] = user
|
2016-10-21 14:06:20 +02:00
|
|
|
context['request'].user = user
|
2016-09-14 08:30:59 +02:00
|
|
|
|
|
|
|
assert cell.is_relevant(context) is True
|
|
|
|
|
|
|
|
# show regie with an invoice
|
2017-07-28 01:50:07 +02:00
|
|
|
ws_invoices = {'err': 0, 'data': INVOICES}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
2019-08-05 18:57:06 +02:00
|
|
|
mock_send.return_value = mock_response
|
2016-09-14 08:30:59 +02:00
|
|
|
content = cell.render(context)
|
|
|
|
assert 'F-2016-One' in content
|
|
|
|
assert '123.45' in content
|
|
|
|
|
2018-04-03 14:17:24 +02:00
|
|
|
assert '?page=%s' % page.pk in content
|
2016-09-14 08:30:59 +02:00
|
|
|
# check if regie webservice has been correctly called
|
2019-08-05 18:57:06 +02:00
|
|
|
assert mock_send.call_args[0][0].method == 'GET'
|
|
|
|
url = mock_send.call_args[0][0].url
|
2016-09-14 08:30:59 +02:00
|
|
|
scheme, netloc, path, params, querystring, fragment = urlparse.urlparse(url)
|
|
|
|
assert scheme == 'http'
|
2016-10-21 14:06:20 +02:00
|
|
|
assert netloc == 'example.org'
|
2016-09-14 08:30:59 +02:00
|
|
|
assert path == '/regie/invoices/'
|
|
|
|
query = urlparse.parse_qs(querystring, keep_blank_values=True)
|
2016-10-21 14:06:20 +02:00
|
|
|
assert query['NameID'][0] == 'r2d2'
|
|
|
|
assert query['orig'][0] == 'combo'
|
|
|
|
assert check_query(querystring, 'combo') == True
|
2016-12-17 20:57:27 +01:00
|
|
|
|
|
|
|
# with no invoice
|
2017-07-28 01:50:07 +02:00
|
|
|
ws_invoices = {'err': 0, 'data': []}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
2019-08-05 18:57:06 +02:00
|
|
|
mock_send.return_value = mock_response
|
2016-12-17 20:57:27 +01:00
|
|
|
content = cell.render(context)
|
|
|
|
assert 'No items yet' in content
|
|
|
|
|
2020-06-12 10:35:26 +02:00
|
|
|
# regie is down
|
|
|
|
mock_send.side_effect = ConnectionError
|
|
|
|
content = cell.render(context)
|
|
|
|
assert 'Regie "Remote" is unavailable, please retry later.' in content
|
|
|
|
|
|
|
|
|
2019-08-05 18:57:06 +02:00
|
|
|
@mock.patch('combo.utils.requests_wrapper.RequestsSession.send')
|
|
|
|
def test_remote_regie_past_invoices_cell(mock_send, remote_regie):
|
2016-12-17 20:57:27 +01:00
|
|
|
assert remote_regie.is_remote() == True
|
|
|
|
|
|
|
|
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
|
|
|
|
page.save()
|
|
|
|
cell = ItemsHistory(regie='remote', page=page, placeholder='content', order=0)
|
2018-01-01 12:35:01 +01:00
|
|
|
context = {'request': RequestFactory().get('/')}
|
2016-12-17 20:57:27 +01:00
|
|
|
context['synchronous'] = True # to get fresh content
|
|
|
|
|
|
|
|
user = MockUser()
|
|
|
|
context['user'] = user
|
|
|
|
context['request'].user = user
|
|
|
|
|
|
|
|
assert cell.is_relevant(context) is True
|
|
|
|
|
|
|
|
# show regie with an invoice
|
2017-07-28 01:50:07 +02:00
|
|
|
ws_invoices = {'err': 0, 'data': INVOICES}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
2019-08-05 18:57:06 +02:00
|
|
|
mock_send.return_value = mock_response
|
2016-12-17 20:57:27 +01:00
|
|
|
content = cell.render(context)
|
|
|
|
assert 'F-2016-One' in content
|
|
|
|
assert '123.45' in content
|
2020-02-25 14:47:20 +01:00
|
|
|
assert 'class="invoice-payment-limit-date"' in content
|
|
|
|
|
|
|
|
# invoice without limit date
|
|
|
|
invoices = copy.deepcopy(INVOICES)
|
|
|
|
invoices[0]['pay_limit_date'] = ''
|
|
|
|
ws_invoices = {'err': 0, 'data': invoices}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
content = cell.render(context)
|
|
|
|
assert 'class="invoice-payment-limit-date"' not in content
|
2016-12-17 20:57:27 +01:00
|
|
|
|
2020-03-03 10:25:03 +01:00
|
|
|
# invoice with amount_paid
|
|
|
|
invoices = copy.deepcopy(INVOICES)
|
|
|
|
invoices[0]['amount'] = '100.00'
|
|
|
|
invoices[0]['amount_paid'] = '23.45'
|
|
|
|
ws_invoices = {'err': 0, 'data': invoices}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
content = cell.render(context)
|
|
|
|
assert '100.00' in content
|
|
|
|
assert '23.45' in content
|
|
|
|
assert 'class="invoice-amount-paid"' in content
|
|
|
|
|
|
|
|
# invoice with zero amount_paid
|
|
|
|
invoices = copy.deepcopy(INVOICES)
|
|
|
|
invoices[0]['amount'] = '123.45'
|
|
|
|
invoices[0]['amount_paid'] = '0.00'
|
|
|
|
ws_invoices = {'err': 0, 'data': invoices}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
content = cell.render(context)
|
|
|
|
assert '123.45' in content
|
|
|
|
assert 'class="invoice-amount-paid"' not in content
|
|
|
|
|
2016-12-17 20:57:27 +01:00
|
|
|
# check if regie webservice has been correctly called
|
2019-08-05 18:57:06 +02:00
|
|
|
assert mock_send.call_args[0][0].method == 'GET'
|
|
|
|
url = mock_send.call_args[0][0].url
|
2016-12-17 20:57:27 +01:00
|
|
|
scheme, netloc, path, params, querystring, fragment = urlparse.urlparse(url)
|
|
|
|
assert scheme == 'http'
|
|
|
|
assert netloc == 'example.org'
|
|
|
|
assert path == '/regie/invoices/history/'
|
|
|
|
query = urlparse.parse_qs(querystring, keep_blank_values=True)
|
|
|
|
assert query['NameID'][0] == 'r2d2'
|
|
|
|
assert query['orig'][0] == 'combo'
|
|
|
|
assert check_query(querystring, 'combo') == True
|
2016-09-14 08:30:59 +02:00
|
|
|
|
|
|
|
# with no invoice
|
2017-07-28 01:50:07 +02:00
|
|
|
ws_invoices = {'err': 0, 'data': []}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(ws_invoices))
|
|
|
|
mock_response.json.return_value = ws_invoices
|
2019-08-05 18:57:06 +02:00
|
|
|
mock_send.return_value = mock_response
|
2016-09-14 08:30:59 +02:00
|
|
|
content = cell.render(context)
|
|
|
|
assert 'No items yet' in content
|
2016-07-22 18:06:45 +02:00
|
|
|
|
2020-03-31 15:48:28 +02:00
|
|
|
cell.hide_if_empty = True
|
|
|
|
cell.save()
|
|
|
|
content = cell.render(context)
|
|
|
|
assert content.strip() == ''
|
|
|
|
|
2020-06-12 10:35:26 +02:00
|
|
|
# regie is down
|
|
|
|
mock_send.side_effect = ConnectionError
|
|
|
|
content = cell.render(context)
|
|
|
|
assert 'Regie "Remote" is unavailable, please retry later.' in content
|
|
|
|
|
2020-03-31 15:48:28 +02:00
|
|
|
|
2016-10-21 14:06:20 +02:00
|
|
|
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice')
|
2016-07-22 18:06:45 +02:00
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
2016-10-21 14:06:20 +02:00
|
|
|
def test_anonymous_successful_item_payment(mock_get, mock_pay_invoice, app, remote_regie):
|
2016-07-22 18:06:45 +02:00
|
|
|
assert remote_regie.is_remote() == True
|
2020-06-03 11:27:09 +02:00
|
|
|
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
2020-02-25 14:05:28 +01:00
|
|
|
# invoice with amount_paid
|
|
|
|
invoices = copy.deepcopy(INVOICES)
|
|
|
|
invoices[0]['amount'] = '100.00'
|
|
|
|
invoices[0]['amount_paid'] = '23.45'
|
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': invoices[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
mock_pay_invoice.return_value = mock.Mock(status_code=200)
|
|
|
|
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id))
|
|
|
|
assert 'Total amount: <span class="amount">123.45€</span>' in resp.text
|
|
|
|
assert 'Amount to pay: <span class="amount">100.00€</span>' in resp.text
|
|
|
|
assert 'Amount already paid: <span class="amount">23.45€</span>' in resp.text
|
|
|
|
|
|
|
|
# invoice without amount_paid
|
2016-07-22 18:06:45 +02:00
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
2016-10-21 14:06:20 +02:00
|
|
|
mock_pay_invoice.return_value = mock.Mock(status_code=200)
|
2016-07-22 18:06:45 +02:00
|
|
|
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id))
|
2020-02-25 14:05:28 +01:00
|
|
|
assert 'Total amount: <span class="amount">123.45€</span>' in resp.text
|
|
|
|
assert 'Amount to pay: <span class="amount">123.45€</span>' in resp.text
|
|
|
|
assert 'Amount already paid>' not in resp.text
|
|
|
|
|
2016-07-22 18:06:45 +02:00
|
|
|
form = resp.form
|
|
|
|
|
|
|
|
assert 'email' in form.fields
|
|
|
|
assert form['email'].value == ''
|
|
|
|
assert 'item_url' in form.fields
|
|
|
|
assert form['item_url'].value == '/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id)
|
|
|
|
assert 'item' in form.fields
|
|
|
|
assert form['item'].value == 'F201601'
|
|
|
|
assert 'regie' in form.fields
|
2018-07-25 21:18:16 +02:00
|
|
|
assert form['regie'].value == force_text(remote_regie.pk)
|
2016-07-22 18:06:45 +02:00
|
|
|
|
|
|
|
form['email'] = 'ghost@buster.com'
|
|
|
|
resp = form.submit()
|
|
|
|
|
|
|
|
assert resp.status_code == 302
|
|
|
|
location = resp.location
|
|
|
|
assert 'dummy-payment' in location
|
|
|
|
parsed = urlparse.urlparse(location)
|
|
|
|
# get return_url and transaction id from location
|
|
|
|
qs = urlparse.parse_qs(parsed.query)
|
|
|
|
args = {'transaction_id': qs['transaction_id'][0], 'signed': True,
|
|
|
|
'ok': True, 'reason': 'Paid'}
|
|
|
|
# make sure return url is the user return URL
|
2020-01-20 17:59:01 +01:00
|
|
|
return_url = qs['return_url'][0]
|
|
|
|
assert urlparse.urlparse(return_url).path.startswith('/lingo/return-payment-backend')
|
2016-07-22 18:06:45 +02:00
|
|
|
# simulate successful return URL
|
2017-03-11 16:13:15 +01:00
|
|
|
resp = app.get(qs['return_url'][0], params=args)
|
2019-12-24 13:42:47 +01:00
|
|
|
# redirect to payment status
|
2016-07-22 18:06:45 +02:00
|
|
|
assert resp.status_code == 302
|
2019-12-24 13:42:47 +01:00
|
|
|
assert urlparse.urlparse(resp.url).path.startswith('/lingo/payment-status')
|
|
|
|
resp = resp.follow()
|
|
|
|
assert urlparse.urlparse(resp.html.find('a', {'id': 'next-url'})['href']).path == '/'
|
2016-07-22 18:06:45 +02:00
|
|
|
# simulate successful call to callback URL
|
2017-03-11 16:13:15 +01:00
|
|
|
resp = app.get(reverse('lingo-callback', kwargs={'regie_pk': remote_regie.id}), params=args)
|
2016-12-06 18:41:16 +01:00
|
|
|
trans = Transaction.objects.all()
|
|
|
|
b_item = BasketItem.objects.all()
|
|
|
|
|
|
|
|
assert trans
|
|
|
|
assert b_item
|
|
|
|
trans = trans[0]
|
|
|
|
b_item = b_item[0]
|
2016-12-15 15:32:16 +01:00
|
|
|
assert b_item.subject == 'Invoice #%s' % INVOICES[0]['display_id']
|
2016-12-06 18:41:16 +01:00
|
|
|
assert b_item.amount == Decimal(INVOICES[0]['amount'])
|
|
|
|
assert b_item in trans.items.all()
|
|
|
|
|
2016-07-22 18:06:45 +02:00
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
|
|
|
def test_anonymous_item_payment_email_error(mock_get, app, remote_regie):
|
|
|
|
assert remote_regie.is_remote() == True
|
2020-06-03 11:27:09 +02:00
|
|
|
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
2016-07-22 18:06:45 +02:00
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id))
|
|
|
|
form = resp.form
|
|
|
|
resp = form.submit()
|
|
|
|
|
|
|
|
assert resp.status_code == 302
|
|
|
|
path = urlparse.urlparse(resp.location).path
|
|
|
|
assert path == '/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id)
|
|
|
|
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
|
|
|
def test_wrong_crypted_item(mock_get, remote_regie, app):
|
|
|
|
assert remote_regie.is_remote() == True
|
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, 'zrzer854sfaear45e6rzerzerzef'), status=404)
|
2016-10-20 10:04:59 +02:00
|
|
|
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
|
|
|
def test_self_declared_invoice(mock_get, app, remote_regie):
|
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
|
|
|
|
page = Page(title='xxx', slug='test-self-invoice', template_name='standard')
|
|
|
|
page.save()
|
|
|
|
cell = SelfDeclaredInvoicePayment(regie='remote', page=page, placeholder='content', order=0)
|
|
|
|
cell.save()
|
|
|
|
|
|
|
|
resp = app.get('/test-self-invoice/')
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert 'Sorry, no invoice were found with that number and amount.'
|
|
|
|
|
|
|
|
resp = app.get('/test-self-invoice/')
|
|
|
|
resp.form['invoice-number'] = 'F201601'
|
|
|
|
resp.form['invoice-amount'] = 'FOOBAR' # wrong format
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert 'Sorry, the provided amount is invalid.'
|
|
|
|
|
|
|
|
resp = app.get('/test-self-invoice/')
|
|
|
|
resp.form['invoice-number'] = 'F201602' # invalid number
|
|
|
|
resp.form['invoice-amount'] = '123.45'
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert 'Sorry, no invoice were found with that number and amount.'
|
|
|
|
|
|
|
|
resp = app.get('/test-self-invoice/')
|
|
|
|
resp.form['invoice-number'] = 'F201601'
|
|
|
|
resp.form['invoice-amount'] = '123.46' # invalid amount
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert 'Sorry, no invoice were found with that number and amount.'
|
|
|
|
|
|
|
|
resp = app.get('/test-self-invoice/')
|
|
|
|
resp.form['invoice-number'] = 'F201601'
|
|
|
|
resp.form['invoice-amount'] = '123.45'
|
|
|
|
resp = resp.form.submit()
|
|
|
|
path = urlparse.urlparse(resp.location).path
|
|
|
|
assert path.startswith('/lingo/item/%s/' % remote_regie.id)
|
|
|
|
resp = resp.follow()
|
2017-01-17 21:05:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice')
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.post')
|
|
|
|
def test_remote_item_payment_failure(mock_post, mock_get, mock_pay_invoice, app, remote_regie):
|
2018-04-03 14:17:24 +02:00
|
|
|
page = Page(title='xxx', slug='active-remote-invoices-page', template_name='standard')
|
|
|
|
page.save()
|
2017-01-17 21:05:23 +01:00
|
|
|
assert remote_regie.is_remote()
|
2020-06-03 11:27:09 +02:00
|
|
|
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
2017-01-17 21:05:23 +01:00
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
mock_pay_invoice.return_value = mock.Mock(status_code=200)
|
2018-04-03 14:17:24 +02:00
|
|
|
resp = app.get('/lingo/item/%s/%s/?page=%s' % (remote_regie.id, encrypt_id, page.pk))
|
2017-01-17 21:05:23 +01:00
|
|
|
form = resp.form
|
|
|
|
|
|
|
|
assert 'email' in form.fields
|
|
|
|
assert form['email'].value == ''
|
|
|
|
assert 'item_url' in form.fields
|
2018-04-03 14:17:24 +02:00
|
|
|
assert form['item_url'].value == '/lingo/item/%s/%s/?page=%s' % (remote_regie.id, encrypt_id, page.pk)
|
2017-01-17 21:05:23 +01:00
|
|
|
assert 'item' in form.fields
|
|
|
|
assert form['item'].value == 'F201601'
|
|
|
|
assert 'regie' in form.fields
|
2018-07-25 21:18:16 +02:00
|
|
|
assert form['regie'].value == force_text(remote_regie.pk)
|
2017-01-17 21:05:23 +01:00
|
|
|
|
|
|
|
form['email'] = 'test@example.net'
|
|
|
|
resp = form.submit()
|
|
|
|
|
|
|
|
assert resp.status_code == 302
|
|
|
|
location = resp.location
|
|
|
|
assert 'dummy-payment' in location
|
|
|
|
parsed = urlparse.urlparse(location)
|
|
|
|
# get return_url and transaction id from location
|
|
|
|
qs = urlparse.parse_qs(parsed.query)
|
|
|
|
args = {'transaction_id': qs['transaction_id'][0], 'signed': True,
|
|
|
|
'ok': True, 'reason': 'Paid'}
|
|
|
|
# make sure return url is the user return URL
|
2020-01-20 17:59:01 +01:00
|
|
|
return_url = qs['return_url'][0]
|
|
|
|
assert urlparse.urlparse(return_url).path.startswith(
|
|
|
|
'/lingo/return-payment-backend/%s/' % remote_regie.payment_backend.id)
|
2018-04-03 14:17:24 +02:00
|
|
|
# simulate payment failure
|
2018-02-20 09:39:58 +01:00
|
|
|
mock_get.side_effect = ConnectionError('where is my hostname?')
|
2017-01-17 21:05:23 +01:00
|
|
|
resp = app.get(qs['return_url'][0], params=args)
|
2019-12-24 13:42:47 +01:00
|
|
|
# redirect to payment status
|
2017-01-17 21:05:23 +01:00
|
|
|
assert resp.status_code == 302
|
2019-12-24 13:42:47 +01:00
|
|
|
assert urlparse.urlparse(resp.url).path.startswith('/lingo/payment-status')
|
|
|
|
resp = resp.follow()
|
|
|
|
assert urlparse.urlparse(resp.html.find('a', {'id': 'next-url'})['href']).path == \
|
|
|
|
'/active-remote-invoices-page/'
|
|
|
|
|
2017-01-17 21:05:23 +01:00
|
|
|
# simulate successful call to callback URL
|
|
|
|
resp = app.get(reverse('lingo-callback', kwargs={'regie_pk': remote_regie.id}), params=args)
|
|
|
|
trans = Transaction.objects.all()
|
|
|
|
b_item = BasketItem.objects.all()
|
|
|
|
|
|
|
|
assert trans.count() == 1
|
|
|
|
assert not b_item
|
|
|
|
assert trans[0].to_be_paid_remote_items
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
|
|
mock_get.side_effect = None
|
2018-11-14 17:37:42 +01:00
|
|
|
appconfig = apps.get_app_config('lingo')
|
|
|
|
appconfig.update_transactions()
|
2017-01-17 21:05:23 +01:00
|
|
|
|
|
|
|
assert Transaction.objects.count() == 1
|
|
|
|
assert BasketItem.objects.count() == 1
|
|
|
|
assert Transaction.objects.all()[0].to_be_paid_remote_items is None
|
|
|
|
|
2018-11-14 17:37:42 +01:00
|
|
|
appconfig.update_transactions()
|
2018-03-07 19:20:31 +01:00
|
|
|
|
2018-04-03 14:17:24 +02:00
|
|
|
|
|
|
|
@mock.patch('combo.apps.lingo.models.Regie.pay_invoice')
|
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
|
|
|
def test_remote_invoice_successfull_payment_redirect(mock_get, mock_pay_invoice, app, remote_regie):
|
|
|
|
page = Page(title='xxx', slug='active-remote-invoices-page', template_name='standard')
|
|
|
|
page.save()
|
|
|
|
assert remote_regie.is_remote()
|
2020-06-03 11:27:09 +02:00
|
|
|
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, force_bytes('F201601'))
|
2018-04-03 14:17:24 +02:00
|
|
|
mock_json = mock.Mock()
|
|
|
|
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
|
|
|
mock_get.return_value = mock_json
|
|
|
|
mock_pay_invoice.return_value = mock.Mock(status_code=200)
|
|
|
|
resp = app.get('/lingo/item/%s/%s/?page=%s' % (remote_regie.id, encrypt_id, page.pk))
|
|
|
|
form = resp.form
|
|
|
|
assert form['next_url'].value == '/active-remote-invoices-page/'
|
|
|
|
form['email'] = 'test@example.net'
|
|
|
|
resp = form.submit()
|
|
|
|
|
|
|
|
assert resp.status_code == 302
|
|
|
|
location = resp.location
|
|
|
|
assert 'dummy-payment' in location
|
|
|
|
parsed = urlparse.urlparse(location)
|
|
|
|
# get return_url and transaction id from location
|
|
|
|
qs = urlparse.parse_qs(parsed.query)
|
|
|
|
args = {'transaction_id': qs['transaction_id'][0], 'signed': True,
|
|
|
|
'ok': True, 'reason': 'Paid'}
|
|
|
|
resp = app.get(qs['return_url'][0], params=args)
|
2019-12-24 13:42:47 +01:00
|
|
|
# redirect to payment status
|
2018-04-03 14:17:24 +02:00
|
|
|
assert resp.status_code == 302
|
2019-12-24 13:42:47 +01:00
|
|
|
assert urlparse.urlparse(resp.url).path.startswith('/lingo/payment-status')
|
|
|
|
resp = resp.follow()
|
|
|
|
assert urlparse.urlparse(resp.html.find('a', {'id': 'next-url'})['href']).path == \
|
|
|
|
'/active-remote-invoices-page/'
|
2018-04-03 14:17:24 +02:00
|
|
|
|
|
|
|
|
2017-11-14 18:48:44 +01:00
|
|
|
@mock.patch('combo.apps.lingo.models.UserSAMLIdentifier')
|
2018-03-07 19:20:31 +01:00
|
|
|
@mock.patch('combo.apps.lingo.models.requests.get')
|
2017-11-14 18:48:44 +01:00
|
|
|
def test_send_new_remote_invoices_by_email(mock_get, user_saml, admin, app, remote_regie, mailoutbox):
|
|
|
|
mocked_objects = mock.Mock()
|
|
|
|
mocked_objects.get.return_value = mock.Mock(user=admin)
|
|
|
|
user_saml.objects = mocked_objects
|
2018-03-07 19:20:31 +01:00
|
|
|
invoice_now = now()
|
2018-04-03 11:33:37 +02:00
|
|
|
creation_date = (invoice_now - timedelta(days=1)).date().isoformat()
|
|
|
|
pay_limit_date = (invoice_now + timedelta(days=30)).date().isoformat()
|
2018-03-07 19:20:31 +01:00
|
|
|
FAKE_PENDING_INVOICES = {
|
|
|
|
'data' : {'foo': {'invoices': [{'id': '01', 'label': '010101', 'paid': False,
|
2018-04-06 08:02:01 +02:00
|
|
|
'amount': '37.26', 'total_amount': '37.26', 'online_payment': False,
|
2018-03-07 19:20:31 +01:00
|
|
|
'has_pdf': True, 'created': creation_date,
|
|
|
|
'pay_limit_date': pay_limit_date}]},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mock_response = mock.Mock(status_code=200, content=json.dumps(FAKE_PENDING_INVOICES))
|
|
|
|
mock_response.json.return_value = FAKE_PENDING_INVOICES
|
|
|
|
mock_get.return_value = mock_response
|
2018-04-06 08:05:53 +02:00
|
|
|
with override_settings(LANGUAGE_CODE='fr'):
|
|
|
|
call_command('notify_new_remote_invoices')
|
|
|
|
|
2018-03-07 19:20:31 +01:00
|
|
|
assert len(mailoutbox) == 1
|
|
|
|
assert mailoutbox[0].recipients() == ['foo@example.net']
|
|
|
|
assert mailoutbox[0].from_email == settings.DEFAULT_FROM_EMAIL
|
2018-04-06 08:05:53 +02:00
|
|
|
assert mailoutbox[0].subject == u'Nouvelle facture numéro 01 disponible'
|
2018-04-06 08:02:01 +02:00
|
|
|
html_message = mailoutbox[0].alternatives[0][0]
|
|
|
|
assert 'http://localhost' in mailoutbox[0].body
|
|
|
|
assert 'http://localhost' in html_message
|
2018-03-07 19:20:31 +01:00
|
|
|
assert mailoutbox[0].attachments[0][0] == '01.pdf'
|
|
|
|
assert mailoutbox[0].attachments[0][2] == 'application/pdf'
|