2020-11-18 20:57:38 +00:00
|
|
|
"""test_transaction.py - Unit tests for high-level Transaction access"""
|
|
|
|
# Copyright © 2020 Brett Smith
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import re
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import requests
|
|
|
|
|
|
|
|
from . import MockSession, MockResponse, FLOAT_NUMBERS, WHOLE_NUMBERS
|
|
|
|
|
|
|
|
from paypal_rest import errors
|
|
|
|
from paypal_rest import transaction as txn_mod
|
|
|
|
|
|
|
|
DATE1 = datetime.datetime(2020, 10, 2, 14, 15, 16, tzinfo=datetime.timezone.utc)
|
|
|
|
DATE2 = DATE1.replace(second=56)
|
|
|
|
|
|
|
|
def item_source(
|
|
|
|
name=None,
|
|
|
|
description=None,
|
|
|
|
code=None,
|
|
|
|
quantity=1,
|
|
|
|
number='15.99',
|
|
|
|
currency='USD',
|
|
|
|
):
|
|
|
|
if description is None:
|
|
|
|
description = name
|
|
|
|
if code is None:
|
|
|
|
code = name
|
|
|
|
total_number = str(quantity * Decimal(number))
|
|
|
|
retval = {
|
|
|
|
'item_quantity': str(quantity),
|
|
|
|
'item_unit_price': {'value': number, 'currency_code': currency},
|
|
|
|
'item_item_amount': {'value': total_number, 'currency_code': currency},
|
|
|
|
'item_amount': {'value': total_number, 'currency_code': currency},
|
|
|
|
}
|
|
|
|
if name is not None:
|
|
|
|
retval['item_name'] = name
|
|
|
|
if description is not None:
|
|
|
|
retval['item_description'] = description
|
|
|
|
if code is not None:
|
|
|
|
retval['item_code'] = code
|
|
|
|
return retval
|
|
|
|
|
|
|
|
def cart_info(*item_kwargs):
|
|
|
|
return {'item_details': [item_source(**kwargs) for kwargs in item_kwargs]}
|
|
|
|
|
|
|
|
def payer_info(
|
|
|
|
given_name='Payer',
|
|
|
|
surname='Smith',
|
|
|
|
account_id='PAYER12345678',
|
|
|
|
payer_status='Y',
|
|
|
|
address_status='Y',
|
|
|
|
country_code='US',
|
|
|
|
email_address='payer@example.org',
|
|
|
|
):
|
|
|
|
retval = locals().copy()
|
|
|
|
del retval['given_name'], retval['surname']
|
|
|
|
retval['payer_name'] = {
|
|
|
|
'given_name': given_name,
|
|
|
|
'surname': surname,
|
|
|
|
'alternate_full_name': f'{given_name} {surname}',
|
|
|
|
}
|
|
|
|
return retval
|
|
|
|
|
|
|
|
def transaction_info(
|
|
|
|
transaction_id='TRANSACTION123456',
|
|
|
|
number='5.00',
|
|
|
|
currency='USD',
|
|
|
|
fee_number='-0.49',
|
|
|
|
fee_currency=None,
|
|
|
|
init_date=DATE1,
|
|
|
|
update_date=None,
|
|
|
|
status='S',
|
|
|
|
subject=None,
|
|
|
|
note=None,
|
|
|
|
):
|
|
|
|
retval = {
|
|
|
|
'transaction_id': transaction_id,
|
|
|
|
'transaction_status': status,
|
|
|
|
'transaction_initiation_date': init_date.isoformat(timespec='seconds'),
|
|
|
|
'transaction_updated_date': (update_date or init_date).isoformat(timespec='seconds'),
|
|
|
|
'transaction_amount': {'value': number, 'currency_code': currency},
|
|
|
|
'fee_amount': {'value': fee_number, 'currency_code': fee_currency or currency},
|
|
|
|
}
|
|
|
|
if subject is not None:
|
|
|
|
retval['transaction_subject'] = subject
|
|
|
|
if note is not None:
|
|
|
|
retval['transaction_note'] = note
|
|
|
|
return retval
|
|
|
|
|
|
|
|
def test_mapping():
|
|
|
|
source = {
|
|
|
|
'transaction_info': transaction_info(),
|
|
|
|
'payer_info': payer_info(),
|
|
|
|
}
|
|
|
|
txn = txn_mod.Transaction(source.copy())
|
|
|
|
assert len(txn) == len(source)
|
|
|
|
assert frozenset(txn) == frozenset(source)
|
|
|
|
assert txn == source
|
|
|
|
assert txn.get('test_key') is None
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('number', FLOAT_NUMBERS)
|
|
|
|
def test_amount_float(number):
|
|
|
|
source = transaction_info(number=number, currency='USD')
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.amount() == (Decimal(number), 'USD')
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('number', WHOLE_NUMBERS)
|
|
|
|
def test_amount_whole(number):
|
|
|
|
source = transaction_info(number=number, currency='JPY')
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.amount() == (int(number), 'JPY')
|
|
|
|
|
|
|
|
def test_cart_items():
|
|
|
|
source = cart_info(
|
|
|
|
{'number': '5.99'},
|
|
|
|
{'name': 'Test', 'number': '7.99', 'quantity': 2},
|
|
|
|
)
|
|
|
|
txn = txn_mod.Transaction({'cart_info': source})
|
|
|
|
item1, item2 = txn.cart_items()
|
|
|
|
item1_price = (Decimal('5.99'), 'USD')
|
|
|
|
assert item1 == (
|
|
|
|
None, None, None, 1, item1_price, item1_price,
|
|
|
|
)
|
|
|
|
assert item2 == (
|
|
|
|
'Test', 'Test', 'Test', 2,
|
|
|
|
(Decimal('7.99'), 'USD'),
|
|
|
|
(Decimal('15.98'), 'USD'),
|
|
|
|
)
|
|
|
|
|
2020-11-18 21:56:15 +00:00
|
|
|
@pytest.mark.parametrize('subject', [
|
|
|
|
'test subject',
|
|
|
|
'$6.99 Subscription',
|
|
|
|
None,
|
|
|
|
])
|
|
|
|
def test_unnamed_cart_item_uses_transaction_subject(subject):
|
|
|
|
source = {
|
|
|
|
'cart_info': cart_info({'number': '6.99'}),
|
|
|
|
'transaction_info': transaction_info(subject=subject),
|
|
|
|
}
|
|
|
|
txn = txn_mod.Transaction(source)
|
|
|
|
item1, = txn.cart_items()
|
|
|
|
assert item1.name == subject
|
|
|
|
|
2020-11-18 20:57:38 +00:00
|
|
|
def test_cart_empty():
|
|
|
|
txn = txn_mod.Transaction({'cart_info': {}})
|
|
|
|
assert not any(txn.cart_items())
|
|
|
|
|
|
|
|
def test_cart_quantity_only():
|
|
|
|
# Refunds are structured this way.
|
|
|
|
source = {'item_details': [{'item_quantity': '1.000'}]}
|
|
|
|
txn = txn_mod.Transaction({'cart_info': source})
|
|
|
|
assert not any(txn.cart_items())
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('number', FLOAT_NUMBERS)
|
|
|
|
def test_fee_amount_float(number):
|
|
|
|
source = transaction_info(fee_number=number, fee_currency='USD')
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.fee_amount() == (Decimal(number), 'USD')
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('number', WHOLE_NUMBERS)
|
|
|
|
def test_fee_amount_whole(number):
|
|
|
|
source = transaction_info(fee_number=number, fee_currency='JPY')
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.fee_amount() == (int(number), 'JPY')
|
|
|
|
|
2020-11-18 21:28:56 +00:00
|
|
|
def test_fee_amount_none():
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': {}})
|
|
|
|
assert txn.fee_amount() is None
|
|
|
|
|
2020-11-18 20:57:38 +00:00
|
|
|
def test_payer_email():
|
|
|
|
email = 'test@example.net'
|
|
|
|
source = payer_info(email_address=email)
|
|
|
|
txn = txn_mod.Transaction({'payer_info': source})
|
|
|
|
assert txn.payer_email() == email
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('given_name', ['Robin', 'Sawyer'])
|
|
|
|
def test_payer_fullname(given_name):
|
|
|
|
txn = txn_mod.Transaction({'payer_info': payer_info(given_name)})
|
|
|
|
assert txn.payer_fullname() == f'{given_name} Smith'
|
|
|
|
|
|
|
|
def test_initiation_date():
|
|
|
|
source = transaction_info(init_date=DATE2)
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.initiation_date() == DATE2
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('key', 'DFPSV')
|
|
|
|
def test_status(key):
|
|
|
|
source = transaction_info(status=key)
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.status() is txn_mod.TransactionStatus[key]
|
|
|
|
|
|
|
|
def test_transaction_id():
|
|
|
|
test_id = 'TESTTXNID12345678'
|
|
|
|
source = transaction_info(transaction_id=test_id)
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.transaction_id() == test_id
|
|
|
|
|
|
|
|
def test_updated_date():
|
|
|
|
source = transaction_info(update_date=DATE2)
|
|
|
|
txn = txn_mod.Transaction({'transaction_info': source})
|
|
|
|
assert txn.updated_date() == DATE2
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('method_name', [
|
|
|
|
'amount',
|
|
|
|
'cart_items',
|
|
|
|
'fee_amount',
|
|
|
|
'payer_email',
|
|
|
|
'payer_fullname',
|
|
|
|
'initiation_date',
|
|
|
|
'status',
|
|
|
|
'transaction_id',
|
|
|
|
'updated_date',
|
|
|
|
])
|
|
|
|
def test_info_missing(method_name):
|
|
|
|
txn = txn_mod.Transaction({})
|
|
|
|
with pytest.raises(errors.MissingFieldError):
|
2020-11-18 21:56:15 +00:00
|
|
|
result = getattr(txn, method_name)()
|
|
|
|
try:
|
|
|
|
do_next = iter(result) is result
|
|
|
|
except TypeError:
|
|
|
|
do_next = False
|
|
|
|
if do_next:
|
|
|
|
next(result)
|