1018 lines
33 KiB
Python
1018 lines
33 KiB
Python
import datetime
|
|
import uuid
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from django.shortcuts import redirect
|
|
from django.urls import reverse
|
|
from django.utils.timezone import now
|
|
from pyquery import PyQuery
|
|
|
|
from lingo.basket.models import Basket, BasketLine, BasketLineItem
|
|
from lingo.invoicing.models import (
|
|
Credit,
|
|
CreditAssignment,
|
|
CreditLine,
|
|
DraftInvoice,
|
|
DraftInvoiceLine,
|
|
Invoice,
|
|
InvoiceLine,
|
|
InvoiceLinePayment,
|
|
Payment,
|
|
PaymentType,
|
|
Regie,
|
|
)
|
|
from tests.utils import login
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
def test_basket_detail(app, simple_user):
|
|
resp = app.get('/basket/')
|
|
assert resp.location.endswith('/login/?next=/basket/')
|
|
app = login(app, username='user', password='user')
|
|
|
|
# no basket object
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# basket without lines
|
|
regie = Regie.objects.create(label='Foo')
|
|
invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
DraftInvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=invoice,
|
|
quantity=1,
|
|
unit_amount=6,
|
|
user_external_id='user:1',
|
|
)
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=invoice,
|
|
payer_nameid='ab' * 16,
|
|
payer_external_id='payer:1',
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
)
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# a not closed line
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=False,
|
|
user_external_id='user:1',
|
|
user_first_name='First1',
|
|
user_last_name='Last1',
|
|
information_message='Lorem ipsum',
|
|
)
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 0
|
|
assert resp.text.count('<p>Lorem ipsum</p>') == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# line is closed but empty
|
|
line.closed = True
|
|
line.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts li')) == 2
|
|
assert resp.text.count('<p>Lorem ipsum</p>') == 1
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket-amounts li')] == [
|
|
'Basket amount: 6.00€',
|
|
'Amount to pay: 6.00€',
|
|
]
|
|
assert '/basket/invoice/pdf/' in resp
|
|
assert '/basket/validate/' in resp
|
|
assert '/basket/cancel/' in resp
|
|
|
|
# add some items, group_items is False
|
|
BasketLineItem.objects.create(
|
|
line=line,
|
|
label='Repas',
|
|
subject='Réservation',
|
|
details='Lun 06/11, Mar 07/11',
|
|
quantity=2,
|
|
unit_amount=3,
|
|
)
|
|
BasketLineItem.objects.create(
|
|
line=line,
|
|
label='Repas',
|
|
subject='Réservation',
|
|
details='Jeu 09/11',
|
|
quantity=1,
|
|
unit_amount=3,
|
|
)
|
|
BasketLineItem.objects.create(
|
|
line=line,
|
|
label='Repas',
|
|
subject='Annulation',
|
|
details='Ven 10/11',
|
|
quantity=-1,
|
|
unit_amount=3,
|
|
)
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 3
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket li')] == [
|
|
'First1 Last1 - Repas - Annulation Ven 10/11 -3.00€',
|
|
'First1 Last1 - Repas - Réservation Jeu 09/11 3.00€',
|
|
'First1 Last1 - Repas - Réservation Lun 06/11, Mar 07/11 6.00€',
|
|
]
|
|
assert len(resp.pyquery('ul.basket-amounts li')) == 2
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket-amounts li')] == [
|
|
'Basket amount: 6.00€',
|
|
'Amount to pay: 6.00€',
|
|
]
|
|
assert '/basket/invoice/pdf/' in resp
|
|
assert '/basket/validate/' in resp
|
|
assert '/basket/cancel/' in resp
|
|
|
|
# group items
|
|
line.group_items = True
|
|
line.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 2
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket li')] == [
|
|
'First1 Last1 - Repas - Annulation Ven 10/11 -3.00€',
|
|
'First1 Last1 - Repas - Réservation Lun 06/11, Mar 07/11, Jeu 09/11 9.00€',
|
|
]
|
|
assert len(resp.pyquery('ul.basket-amounts li')) == 2
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket-amounts li')] == [
|
|
'Basket amount: 6.00€',
|
|
'Amount to pay: 6.00€',
|
|
]
|
|
assert '/basket/invoice/pdf/' in resp
|
|
assert '/basket/validate/' in resp
|
|
assert '/basket/cancel/' in resp
|
|
|
|
# with available credit
|
|
credit = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
resp = app.get('/basket/')
|
|
assert len(resp.pyquery('ul.basket-amounts li')) == 3
|
|
assert [PyQuery(li).text() for li in resp.pyquery('ul.basket-amounts li')] == [
|
|
'Basket amount: 6.00€',
|
|
'Credit: -1.00€',
|
|
'Amount to pay: 5.00€',
|
|
]
|
|
|
|
# not closed line
|
|
line.closed = False
|
|
line.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# basket payer_nameid is wrong
|
|
line.closed = True
|
|
line.save()
|
|
basket.payer_nameid = uuid.uuid4()
|
|
basket.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# check status
|
|
basket.payer_nameid = 'ab' * 16
|
|
basket.save()
|
|
for status in ['open', 'tobepaid']:
|
|
basket.status = status
|
|
basket.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 2
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 1
|
|
assert '/basket/invoice/pdf/' in resp
|
|
if status == 'open':
|
|
assert '/basket/validate/' in resp
|
|
else:
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' in resp
|
|
for status in ['cancelled', 'expired', 'completed']:
|
|
basket.status = status
|
|
basket.save()
|
|
resp = app.get('/basket/')
|
|
assert 'My basket' in resp
|
|
assert len(resp.pyquery('ul.basket li')) == 0
|
|
assert len(resp.pyquery('ul.basket-amounts')) == 0
|
|
assert '/basket/invoice/pdf/' not in resp
|
|
assert '/basket/validate/' not in resp
|
|
assert '/basket/cancel/' not in resp
|
|
|
|
# other lines with information_message
|
|
basket.status = 'open'
|
|
basket.save()
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:2',
|
|
user_first_name='First2',
|
|
user_last_name='Last2',
|
|
information_message='Lorem ipsum',
|
|
)
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:3',
|
|
user_first_name='First3',
|
|
user_last_name='Last3',
|
|
information_message='Lorem ipsum bis',
|
|
)
|
|
resp = app.get('/basket/')
|
|
assert resp.text.count('<p>Lorem ipsum</p>') == 1
|
|
assert resp.text.count('<p>Lorem ipsum bis</p>') == 1
|
|
|
|
|
|
def test_basket_invoice_pdf(app, simple_user):
|
|
resp = app.get('/basket/invoice/pdf/')
|
|
assert resp.location.endswith('/login/?next=/basket/invoice/pdf/')
|
|
app = login(app, username='user', password='user')
|
|
|
|
# no basket object
|
|
app.get('/basket/invoice/pdf/', status=404)
|
|
|
|
# basket
|
|
regie = Regie.objects.create(label='Foo')
|
|
invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
DraftInvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=invoice,
|
|
quantity=10,
|
|
unit_amount=1,
|
|
user_external_id='user:1',
|
|
)
|
|
basket = Basket.objects.create(regie=regie, draft_invoice=invoice, payer_nameid='ab' * 16)
|
|
app.get('/basket/invoice/pdf/', status=200)
|
|
|
|
# basket payer_nameid is wrong
|
|
basket.payer_nameid = uuid.uuid4()
|
|
basket.save()
|
|
app.get('/basket/invoice/pdf/', status=404)
|
|
|
|
# check status
|
|
basket.invoice = basket.draft_invoice.promote()
|
|
basket.payer_nameid = 'ab' * 16
|
|
basket.save()
|
|
for status in ['open', 'tobepaid']:
|
|
basket.status = status
|
|
basket.save()
|
|
resp = app.get('/basket/invoice/pdf/?html', status=200)
|
|
if status == 'open':
|
|
assert 'TEMPORARY-%s' % basket.draft_invoice.pk in resp
|
|
else:
|
|
assert 'F%02d-%s-0000001' % (regie.pk, basket.invoice.created_at.strftime('%y-%m')) in resp
|
|
assert 'Total amount to be paid before' not in resp
|
|
assert resp.pyquery('tfoot').text() == 'Total amount:\n10.00€'
|
|
for status in ['cancelled', 'expired', 'completed']:
|
|
basket.status = status
|
|
basket.save()
|
|
app.get('/basket/invoice/pdf/', status=404)
|
|
|
|
# invoice with credit and assignment
|
|
basket.status = 'tobepaid'
|
|
basket.save()
|
|
credit = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=10,
|
|
unit_amount=1,
|
|
)
|
|
CreditAssignment.objects.create(
|
|
invoice=basket.invoice,
|
|
credit=credit,
|
|
amount=1,
|
|
)
|
|
resp = app.get('/basket/invoice/pdf/?html', status=200)
|
|
assert resp.pyquery('tfoot').text() == 'Credit:\n-1.00€\nTotal amount:\n9.00€'
|
|
|
|
|
|
def test_basket_validate(app, simple_user):
|
|
resp = app.get('/basket/validate/')
|
|
assert resp.location.endswith('/login/?next=/basket/validate/')
|
|
app = login(app, username='user', password='user')
|
|
|
|
# no basket object
|
|
app.get('/basket/validate/', status=404)
|
|
|
|
# basket without line
|
|
regie = Regie.objects.create(label='Foo')
|
|
invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
)
|
|
app.get('/basket/validate/', status=404)
|
|
|
|
# a not closed line
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=False,
|
|
user_external_id='user:1',
|
|
)
|
|
app.get('/basket/validate/', status=404)
|
|
|
|
# line is closed, but wrong payer_nameid
|
|
line.closed = True
|
|
line.save()
|
|
basket.payer_nameid = uuid.uuid4()
|
|
basket.save()
|
|
app.get('/basket/validate/', status=404)
|
|
|
|
# good payer_nameid
|
|
basket.payer_nameid = 'ab' * 16
|
|
basket.save()
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.basket.views.pay_invoice') as pay_invoice, mock.patch(
|
|
'lingo.utils.requests_wrapper.RequestsSession.send'
|
|
) as mock_send:
|
|
pay_invoice.side_effect = lambda *args: redirect(reverse('lingo-basket-detail'))
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == set()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'completed'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is not None
|
|
assert basket.completed_at is not None
|
|
assert Invoice.objects.count() == 1
|
|
invoice = Invoice.objects.latest('pk')
|
|
assert basket.invoice == invoice
|
|
assert basket.credit is None
|
|
|
|
# wrong status
|
|
for status in ['tobepaid', 'cancelled', 'expired', 'completed']:
|
|
basket.status = status
|
|
basket.save()
|
|
app.get('/basket/validate/', status=404)
|
|
|
|
# check callback
|
|
basket.status = 'open'
|
|
basket.save()
|
|
line.validation_callback_url = 'http://validation1.com'
|
|
line.credit_callback_url = 'http://validation1.com'
|
|
line.save()
|
|
BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:2',
|
|
validation_callback_url='http://validation2.com',
|
|
credit_callback_url='http://validation2.com',
|
|
)
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.basket.views.pay_invoice') as pay_invoice, mock.patch(
|
|
'lingo.utils.requests_wrapper.RequestsSession.send'
|
|
) as mock_send:
|
|
pay_invoice.side_effect = lambda *args: redirect(reverse('lingo-basket-detail'))
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
|
'http://validation1.com/',
|
|
'http://validation2.com/',
|
|
}
|
|
|
|
# basket is expired
|
|
basket.expiry_at = now()
|
|
basket.status = 'open'
|
|
basket.save()
|
|
resp = app.get('/basket/validate/')
|
|
resp.form.submit()
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'open'
|
|
|
|
|
|
def test_basket_validate_generate_invoice(app, simple_user):
|
|
app = login(app, username='user', password='user')
|
|
|
|
regie = Regie.objects.create(label='Foo')
|
|
other_regie = Regie.objects.create(label='Bar')
|
|
draft_invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
DraftInvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=draft_invoice,
|
|
quantity=10,
|
|
unit_amount=1,
|
|
user_external_id='user:1',
|
|
)
|
|
# invoice total amount is positive
|
|
draft_invoice.refresh_from_db()
|
|
assert draft_invoice.total_amount == 10
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=draft_invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
payer_external_id='payer:1',
|
|
)
|
|
BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:1',
|
|
group_items=False,
|
|
)
|
|
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.basket.views.pay_invoice') as pay_invoice:
|
|
pay_invoice.side_effect = lambda *args, **kwargs: redirect(reverse('lingo-basket-detail'))
|
|
resp = resp.form.submit()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'tobepaid'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is None
|
|
assert basket.completed_at is None
|
|
invoice = Invoice.objects.latest('pk')
|
|
assert basket.invoice == invoice
|
|
assert invoice.total_amount == 10
|
|
assert Credit.objects.count() == 0
|
|
|
|
# with credits, generated invoice is partially paid with credit
|
|
credit1 = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit1,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
credit2 = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit2,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=3,
|
|
unit_amount=1,
|
|
)
|
|
other_credit1 = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:2',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=other_credit1,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
other_credit2 = Credit.objects.create(
|
|
regie=other_regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=other_credit2,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
basket.status = 'open'
|
|
basket.save()
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.basket.views.pay_invoice') as pay_invoice:
|
|
pay_invoice.side_effect = lambda *args, **kwargs: redirect(reverse('lingo-basket-detail'))
|
|
resp = resp.form.submit()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'tobepaid'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is None
|
|
assert basket.completed_at is None
|
|
invoice = Invoice.objects.latest('pk')
|
|
assert basket.invoice == invoice
|
|
assert invoice.total_amount == 10
|
|
assert invoice.paid_amount == 0
|
|
assert invoice.remaining_amount == 10
|
|
credit1.refresh_from_db()
|
|
assert credit1.remaining_amount == 0
|
|
assert credit1.assigned_amount == 1
|
|
credit2.refresh_from_db()
|
|
assert credit2.remaining_amount == 0
|
|
assert credit2.assigned_amount == 3
|
|
assert Payment.objects.count() == 0
|
|
assert CreditAssignment.objects.count() == 2
|
|
assignment1, assignment2 = CreditAssignment.objects.all().order_by('pk')
|
|
assert assignment1.amount == 1
|
|
assert assignment1.invoice == invoice
|
|
assert assignment1.payment is None
|
|
assert assignment1.credit == credit1
|
|
assert assignment2.amount == 3
|
|
assert assignment2.invoice == invoice
|
|
assert assignment2.payment is None
|
|
assert assignment2.credit == credit2
|
|
|
|
|
|
def test_basket_validate_generate_invoice_nothing_to_pay(app, simple_user):
|
|
app = login(app, username='user', password='user')
|
|
|
|
regie = Regie.objects.create(label='Foo')
|
|
draft_invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
DraftInvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=draft_invoice,
|
|
quantity=1,
|
|
unit_amount=1,
|
|
user_external_id='user:1',
|
|
)
|
|
invoice_line2 = DraftInvoiceLine.objects.create(
|
|
slug='event-b-foo-bar',
|
|
label='Event B',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=draft_invoice,
|
|
quantity=-1,
|
|
unit_amount=1,
|
|
user_external_id='user:1',
|
|
)
|
|
draft_invoice.refresh_from_db()
|
|
assert draft_invoice.total_amount == 0
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=draft_invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
payer_external_id='payer:1',
|
|
)
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:1',
|
|
group_items=False,
|
|
)
|
|
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == set()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'completed'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is not None
|
|
assert basket.completed_at is not None
|
|
invoice = Invoice.objects.latest('pk')
|
|
assert basket.invoice == invoice
|
|
assert invoice.total_amount == 0
|
|
assert Credit.objects.count() == 0
|
|
assert Payment.objects.count() == 0
|
|
|
|
# total is zero, but with credits
|
|
invoice_line2.delete()
|
|
line.validation_callback_url = 'http://validation1.com'
|
|
line.credit_callback_url = 'http://credit1.com'
|
|
line.payment_callback_url = 'http://payment1.com'
|
|
line.save()
|
|
credit = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=10,
|
|
unit_amount=1,
|
|
)
|
|
draft_invoice.refresh_from_db()
|
|
assert draft_invoice.total_amount == 1
|
|
basket.status = 'open'
|
|
basket.save()
|
|
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
|
'http://validation1.com/',
|
|
'http://payment1.com/',
|
|
}
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'completed'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is not None
|
|
assert basket.completed_at is not None
|
|
invoice = Invoice.objects.latest('pk')
|
|
assert basket.invoice == invoice
|
|
assert invoice.total_amount == 1
|
|
assert invoice.remaining_amount == 0
|
|
assert invoice.paid_amount == 1
|
|
assert Credit.objects.count() == 1
|
|
credit.refresh_from_db()
|
|
assert credit.remaining_amount == 9
|
|
assert credit.assigned_amount == 1
|
|
assert Payment.objects.count() == 1
|
|
payment = Payment.objects.latest('pk')
|
|
assert payment.amount == 1
|
|
assert payment.payment_type.slug == 'credit'
|
|
assert CreditAssignment.objects.count() == 1
|
|
assignment = CreditAssignment.objects.latest('pk')
|
|
assert assignment.amount == 1
|
|
assert assignment.invoice == invoice
|
|
assert assignment.payment == payment
|
|
assert assignment.credit == credit
|
|
assert payment.invoicelinepayment_set.count() == 1
|
|
invoicelinepayment = InvoiceLinePayment.objects.latest('pk')
|
|
assert invoicelinepayment.line == invoice.lines.get()
|
|
assert invoicelinepayment.amount == 1
|
|
|
|
|
|
def test_basket_validate_generate_credit(app, simple_user):
|
|
app = login(app, username='user', password='user')
|
|
|
|
regie = Regie.objects.create(label='Foo')
|
|
draft_invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
payer_external_id='payer:1',
|
|
payer_first_name='First',
|
|
payer_last_name='Last',
|
|
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
|
)
|
|
DraftInvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=draft_invoice,
|
|
quantity=-1,
|
|
unit_amount=1,
|
|
description='A description',
|
|
user_external_id='user:1',
|
|
user_first_name='First1',
|
|
user_last_name='Last1',
|
|
)
|
|
draft_invoice.refresh_from_db()
|
|
assert draft_invoice.total_amount == -1
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=draft_invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
)
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:1',
|
|
)
|
|
|
|
# credit is not used if basket amount is negative
|
|
credit = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == set()
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'completed'
|
|
assert basket.validated_at is not None
|
|
assert basket.paid_at is not None
|
|
assert basket.completed_at is not None
|
|
assert basket.invoice is None
|
|
credit = Credit.objects.latest('pk')
|
|
assert basket.credit == credit
|
|
assert credit.label == 'Credit from %s' % datetime.date.today().strftime('%d/%m/%Y')
|
|
assert credit.total_amount == 1
|
|
assert credit.regie == regie
|
|
assert credit.payer_external_id == 'payer:1'
|
|
assert credit.payer_first_name == 'First'
|
|
assert credit.payer_last_name == 'Last'
|
|
assert credit.payer_address == '41 rue des kangourous\n99999 Kangourou Ville'
|
|
assert credit.lines.count() == 1
|
|
(line1,) = credit.lines.all().order_by('pk')
|
|
assert line1.event_date == datetime.date(2022, 9, 1)
|
|
assert line1.slug == 'event-a-foo-bar'
|
|
assert line1.label == 'Event A'
|
|
assert line1.quantity == 1
|
|
assert line1.unit_amount == 1
|
|
assert line1.total_amount == 1
|
|
assert line1.user_external_id == 'user:1'
|
|
assert line1.user_first_name == 'First1'
|
|
assert line1.user_last_name == 'Last1'
|
|
assert line1.description == 'A description'
|
|
assert Invoice.objects.count() == 0
|
|
|
|
# check callback
|
|
basket.status = 'open'
|
|
basket.save()
|
|
line.validation_callback_url = 'http://validation1.com'
|
|
line.credit_callback_url = 'http://credit1.com'
|
|
line.save()
|
|
BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:2',
|
|
validation_callback_url='http://validation2.com',
|
|
credit_callback_url='http://credit2.com',
|
|
)
|
|
resp = app.get('/basket/validate/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
basket.refresh_from_db()
|
|
credit = Credit.objects.latest('pk')
|
|
assert basket.credit == credit
|
|
assert Invoice.objects.count() == 0
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
|
'http://validation1.com/',
|
|
'http://validation2.com/',
|
|
'http://credit1.com/',
|
|
'http://credit2.com/',
|
|
}
|
|
|
|
|
|
def test_basket_cancel(app, simple_user):
|
|
resp = app.get('/basket/cancel/')
|
|
assert resp.location.endswith('/login/?next=/basket/cancel/')
|
|
app = login(app, username='user', password='user')
|
|
|
|
# no basket object
|
|
app.get('/basket/cancel/', status=404)
|
|
|
|
# basket without line
|
|
regie = Regie.objects.create(label='Foo')
|
|
PaymentType.create_defaults(regie)
|
|
draft_invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
invoice = Invoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
InvoiceLine.objects.create(
|
|
slug='event-a-foo-bar',
|
|
label='Event A',
|
|
event_date=datetime.date(2022, 9, 1),
|
|
invoice=invoice,
|
|
quantity=10,
|
|
unit_amount=1,
|
|
user_external_id='user:1',
|
|
)
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=draft_invoice,
|
|
invoice=invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
)
|
|
app.get('/basket/cancel/', status=404)
|
|
# the invoice is partially paid with a credit
|
|
credit = Credit.objects.create(
|
|
regie=regie,
|
|
payer_external_id='payer:1',
|
|
)
|
|
CreditLine.objects.create(
|
|
credit=credit,
|
|
event_date=datetime.date(2022, 9, 1),
|
|
quantity=1,
|
|
unit_amount=1,
|
|
)
|
|
CreditAssignment.objects.create(
|
|
invoice=invoice,
|
|
credit=credit,
|
|
amount=1,
|
|
)
|
|
|
|
# a not closed line
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=False,
|
|
user_external_id='user:1',
|
|
cancel_information_message='Lorem ipsum',
|
|
)
|
|
app.get('/basket/cancel/', status=404)
|
|
|
|
# line is closed, but wrong payer_nameid
|
|
line.closed = True
|
|
line.save()
|
|
basket.payer_nameid = uuid.uuid4()
|
|
basket.save()
|
|
app.get('/basket/cancel/', status=404)
|
|
|
|
# good payer_nameid
|
|
assert CreditAssignment.objects.count() == 1
|
|
basket.payer_nameid = 'ab' * 16
|
|
basket.save()
|
|
resp = app.get('/basket/cancel/')
|
|
assert resp.text.count('<p>Lorem ipsum</p>') == 1
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == set()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'cancelled'
|
|
assert basket.cancelled_at is not None
|
|
invoice.refresh_from_db()
|
|
assert invoice.cancelled_at is not None
|
|
assert CreditAssignment.objects.count() == 0
|
|
|
|
basket.status = 'tobepaid'
|
|
basket.cancelled_at = None
|
|
basket.save()
|
|
resp = app.get('/basket/cancel/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == set()
|
|
assert resp.location.endswith('/basket/')
|
|
basket.refresh_from_db()
|
|
assert basket.status == 'cancelled'
|
|
assert basket.cancelled_at is not None
|
|
|
|
# wrong status
|
|
for status in ['cancelled', 'expired', 'completed']:
|
|
basket.status = status
|
|
basket.save()
|
|
app.get('/basket/cancel/', status=404)
|
|
|
|
# check callback
|
|
basket.status = 'open'
|
|
basket.save()
|
|
line.cancel_callback_url = 'http://cancellation1.com'
|
|
line.save()
|
|
BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:2',
|
|
cancel_callback_url='http://cancellation2.com',
|
|
)
|
|
resp = app.get('/basket/cancel/')
|
|
with mock.patch('lingo.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
resp = resp.form.submit()
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
|
'http://cancellation1.com/',
|
|
'http://cancellation2.com/',
|
|
}
|
|
|
|
# basket is expired
|
|
basket.expiry_at = now()
|
|
basket.status = 'open'
|
|
basket.save()
|
|
app.get('/basket/cancel/')
|
|
|
|
# other lines with information_message
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:3',
|
|
user_first_name='First3',
|
|
user_last_name='Last3',
|
|
cancel_information_message='Lorem ipsum',
|
|
)
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=True,
|
|
user_external_id='user:4',
|
|
user_first_name='First4',
|
|
user_last_name='Last4',
|
|
cancel_information_message='Lorem ipsum bis',
|
|
)
|
|
resp = app.get('/basket/cancel/')
|
|
assert resp.text.count('<p>Lorem ipsum</p>') == 1
|
|
assert resp.text.count('<p>Lorem ipsum bis</p>') == 1
|
|
|
|
|
|
def test_basket_status_js(app, simple_user):
|
|
resp = app.get('/basket/status.js')
|
|
assert resp.location.endswith('/login/?next=/basket/status.js')
|
|
app = login(app, username='user', password='user')
|
|
|
|
# no basket object
|
|
assert 'basket_entry_count.textContent = ""' in app.get('/basket/status.js').text
|
|
|
|
# basket without line
|
|
regie = Regie.objects.create(label='Foo')
|
|
invoice = DraftInvoice.objects.create(
|
|
regie=regie,
|
|
date_publication=datetime.date(2023, 4, 21),
|
|
date_payment_deadline=datetime.date(2023, 4, 22),
|
|
date_due=datetime.date(2023, 4, 23),
|
|
)
|
|
basket = Basket.objects.create(
|
|
regie=regie,
|
|
draft_invoice=invoice,
|
|
payer_nameid='ab' * 16,
|
|
expiry_at=now() + datetime.timedelta(hours=1),
|
|
)
|
|
assert 'basket_entry_count.textContent = ""' in app.get('/basket/status.js').text
|
|
|
|
# a not closed line
|
|
line = BasketLine.objects.create(
|
|
basket=basket,
|
|
closed=False,
|
|
user_external_id='user:1',
|
|
)
|
|
assert 'basket_entry_count.textContent = ""' in app.get('/basket/status.js').text
|
|
|
|
# line is closed, but wrong payer_nameid
|
|
line.closed = True
|
|
line.save()
|
|
basket.payer_nameid = uuid.uuid4()
|
|
basket.save()
|
|
assert 'basket_entry_count.textContent = ""' in app.get('/basket/status.js').text
|
|
|
|
# good payer_nameid
|
|
basket.payer_nameid = 'ab' * 16
|
|
basket.save()
|
|
assert 'basket_entry_count.textContent = "1"' in app.get('/basket/status.js').text
|
|
|
|
# basket is expired
|
|
basket.expiry_at = now()
|
|
basket.status = 'open'
|
|
basket.save()
|
|
assert 'basket_entry_count.textContent = ""' in app.get('/basket/status.js').text
|
|
|
|
|
|
def test_basket_detail_back_url(app, simple_user):
|
|
app = login(app, username='user', password='user')
|
|
resp = app.get('/basket/')
|
|
assert not resp.pyquery('.basket-back-link')
|
|
|
|
# set back link
|
|
resp = app.get('/basket/?back_url=https%3A//example.net/')
|
|
assert resp.pyquery('.basket-back-link')
|
|
|
|
# check it's maintained in session
|
|
resp = app.get('/basket/')
|
|
assert resp.pyquery('.basket-back-link')
|
|
assert resp.pyquery('.basket-back-link')[0].attrib['href'] == 'https://example.net/'
|