source: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py @ 14086

Last change on this file since 14086 was 12813, checked in by uli, 10 years ago

Aggregate payment-related testing components.

  • Property svn:keywords set to Id
File size: 14.1 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_payment.py 12813 2015-03-23 15:56:55Z uli $
3##
4## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19import datetime
20import decimal
21import re
22import unittest
23from decimal import Decimal
24from zope.component import (
25    getUtilitiesFor, getSiteManager, queryUtility, getGlobalSiteManager,
26    )
27from zope.component.hooks import setSite
28from zope.interface import implements
29from zope.interface.verify import verifyClass, verifyObject
30from waeup.ikoba.payments.interfaces import (
31    IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED,
32    IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister,
33    IPayableFinder, IPayerFinder,
34    )
35from waeup.ikoba.app import Company
36from waeup.ikoba.payments.payment import (
37    Payment, get_payment_providers, PaymentItem, format_payment_item_values,
38    get_payment, get_payments_from_payer_id, find_payable_from_payable_id,
39    find_payer_from_payer_id, get_payments_from_payable_id, format_amount,
40    )
41from waeup.ikoba.payments.testing import (
42    FakePayer, FakePayable, FAKE_PAYMENT_ITEMS
43    )
44from waeup.ikoba.testing import FunctionalLayer, FunctionalTestCase
45
46
47class HelperTests(unittest.TestCase):
48
49    def tearDown(self):
50        # unregister any IPaymentGatewayServices
51        sm = getSiteManager(None)
52        for name, util in getUtilitiesFor(IPaymentGatewayService):
53            sm.unregisterUtility(util, name=name)
54
55    def test_get_payment_providers_no_providers(self):
56        # we can get a dict of all payment providers
57        result = get_payment_providers()
58        assert isinstance(result, dict)
59        assert result == {}
60
61    def test_get_payment_providers(self):
62        # we get any payment providers registered
63        sm = getSiteManager(None)
64
65        class FakeUtil(object):
66            implements(IPaymentGatewayService)
67
68        fake_util = FakeUtil()
69        sm.registerUtility(fake_util, name=u'some_name')
70        result = get_payment_providers()
71        assert isinstance(result, dict)
72        assert result.keys() == ['some_name', ]
73        assert result['some_name'] is fake_util
74
75    def test_format_payment_item_values(self):
76        # we can format lists of payment item values
77        result = format_payment_item_values(
78            [(u'Item 1', 'USD', decimal.Decimal("12.123")),
79             (u'Item 2', 'USD', decimal.Decimal("12.002")),
80             ], 'USD')
81        self.assertEqual(
82            result, [(u'Item 1', 'USD 12.12'),
83                     (u'Item 2', 'USD 12.00'),
84                     (u'Total', 'USD 24.12')]
85            )
86
87    def test_format_payment_item_values_req_single_currency(self):
88        # we require one currency for all items, yet.
89        self.assertRaises(
90            ValueError, format_payment_item_values,
91            [(u'Item 1', 'USD', decimal.Decimal("12.12")),
92             (u'Item 2', 'EUR', decimal.Decimal("50")),
93             ],
94            'USD')
95
96    def test_format_amount(self):
97        # we can make amounts readable
98        D = decimal.Decimal
99        self.assertEqual(format_amount(D("0"), 'USD'), u"US$ 0.00")
100        self.assertEqual(format_amount(D("0.1"), 'EUR'), u"€ 0.10")
101        self.assertEqual(format_amount(D("-1.2"), 'NGN'), u"₦ -1.20")
102        self.assertEqual(format_amount(D("1234.5"), 'YEN'), u"YEN 1,234.50")
103
104
105class FunctionalHelperTests(FunctionalTestCase):
106
107    layer = FunctionalLayer
108
109    def test_services_lister_is_registered(self):
110        # a lister of gateway services is registered on startup
111        util = queryUtility(IPaymentGatewayServicesLister)
112        assert util is not None
113
114    def test_services_are_really_listed(self):
115        # we can really get locally registered gateways when calling
116        util = queryUtility(IPaymentGatewayServicesLister)
117        assert len(util()) > 0
118
119    def test_get_payment(self):
120        # we can lookup payments.
121        self.getRootFolder()['app'] = Company()
122        app = self.getRootFolder()['app']
123        setSite(app)
124        p1 = Payment(FakePayer(), FakePayable())
125        app['payments']['1'] = p1
126        p_id = p1.payment_id
127        result = get_payment(p_id)
128        self.assertTrue(result is p1)
129        self.assertTrue(get_payment('not-valid') is None)
130
131    def test_get_payments_from_payer_id(self):
132        # we can lookup payments from payer ids.
133        self.getRootFolder()['app'] = Company()
134        app = self.getRootFolder()['app']
135        setSite(app)
136        p1 = Payment(FakePayer(), FakePayable())
137        app['payments']['1'] = p1
138        result = get_payments_from_payer_id('PAYER_01')
139        self.assertTrue(result[0] is p1)
140        self.assertTrue(get_payments_from_payer_id('not-valid') is None)
141
142    def test_get_payments_from_payable_id(self):
143        # we can lookup payments from payable ids.
144        self.getRootFolder()['app'] = Company()
145        app = self.getRootFolder()['app']
146        setSite(app)
147        p1 = Payment(FakePayer(), FakePayable())
148        app['payments']['1'] = p1
149        result = get_payments_from_payable_id('PAYABLE_01')
150        self.assertTrue(result[0] is p1)
151        self.assertTrue(get_payments_from_payable_id('not-valid') is None)
152
153    def test_find_payable_from_payable_id(self):
154        # we can find payables.
155        obj1 = object()
156        obj2 = object()
157
158        class FakeFinder(object):
159            valid = {'id1': obj1, 'id3': obj2}
160
161            def get_payable_by_id(self, the_id):
162                return self.valid.get(the_id)
163
164        finder1 = FakeFinder()
165        finder1.valid = {'id1': obj1}
166        finder2 = FakeFinder()
167        finder2.valid = {'id2': obj2}
168        gsm = getGlobalSiteManager()
169        try:
170            gsm.registerUtility(finder1, provided=IPayableFinder, name='f1')
171            gsm.registerUtility(finder2, provided=IPayableFinder, name='f2')
172            result1 = find_payable_from_payable_id('id1')
173            result2 = find_payable_from_payable_id('id2')
174            result3 = find_payable_from_payable_id('id3')
175        finally:
176            gsm.unregisterUtility(finder1, IPayableFinder)
177            gsm.unregisterUtility(finder2, IPayableFinder)
178        self.assertTrue(result1 is obj1)
179        self.assertTrue(result2 is obj2)
180        self.assertTrue(result3 is None)
181
182    def test_find_payer_from_payer_id(self):
183        # we can find payables.
184        obj1 = object()
185        obj2 = object()
186
187        class FakeFinder(object):
188            valid = {'id1': obj1, 'id3': obj2}
189
190            def get_payer_by_id(self, the_id):
191                return self.valid.get(the_id)
192
193        finder1 = FakeFinder()
194        finder1.valid = {'id1': obj1}
195        finder2 = FakeFinder()
196        finder2.valid = {'id2': obj2}
197        gsm = getGlobalSiteManager()
198        try:
199            gsm.registerUtility(finder1, provided=IPayerFinder, name='f1')
200            gsm.registerUtility(finder2, provided=IPayerFinder, name='f2')
201            result1 = find_payer_from_payer_id('id1')
202            result2 = find_payer_from_payer_id('id2')
203            result3 = find_payer_from_payer_id('id3')
204        finally:
205            gsm.unregisterUtility(finder1, IPayerFinder)
206            gsm.unregisterUtility(finder2, IPayerFinder)
207        self.assertTrue(result1 is obj1)
208        self.assertTrue(result2 is obj2)
209        self.assertTrue(result3 is None)
210
211
212class PaymentTests(FunctionalTestCase):
213
214    layer = FunctionalLayer
215
216    def setUp(self):
217        super(PaymentTests, self).setUp()
218        self.payer = FakePayer()
219        self.payable = FakePayable()
220
221    def test_iface(self):
222        # Payments fullfill any interface contracts
223        obj = Payment(self.payer, self.payable)
224        verifyClass(IPayment, Payment)
225        verifyObject(IPayment, obj)
226
227    def test_initial_values(self):
228        # important attributes are set initially
229        payer = self.payer
230        payer.payer_id = u'PAYER_ID'
231        payable = self.payable
232        payable.payable_id = u'PAYABLE_ID'
233        payable.title = u'PAYABLE-TITLE'
234        payable.currency = 'NGN'
235        payment = Payment(payer, payable)
236        assert payment.payer_id == u'PAYER_ID'
237        assert payment.payable_id == u'PAYABLE_ID'
238        assert payment.title == u'PAYABLE-TITLE'
239        assert payment.currency == 'NGN'
240        assert isinstance(payment.creation_date, datetime.datetime)
241        assert payment.payment_date is None
242
243    def test_payment_id_unique(self):
244        # we get unique payment ids
245        p1 = Payment(self.payer, self.payable)
246        p2 = Payment(self.payer, self.payable)
247        id1, id2 = p1.payment_id, p2.payment_id
248        assert id1 != id2
249
250    def test_payment_id_format(self):
251        # payment ids have a special format: "<32 hex digits>"
252        id1 = Payment(self.payer, self.payable).payment_id
253        assert isinstance(id1, basestring)
254        assert re.match('[0-9a-f]{32}', id1)
255
256    def test_initial_state_is_unpaid(self):
257        # the initial state of payments is <unpaid>
258        p1 = Payment(self.payer, self.payable)
259        assert p1.state == STATE_UNPAID
260
261    def test_approve(self):
262        # we can approve payments
263        p1 = Payment(self.payer, self.payable)
264        p1.approve()
265        assert p1.state == STATE_PAID
266        assert p1.payment_date is not None
267        assert isinstance(p1.payment_date, datetime.datetime)
268
269    def test_approve_datetime_given(self):
270        # we can give a datetime
271        p1 = Payment(self.payer, self.payable)
272        some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0)
273        p1.approve(payment_date=some_datetime)
274        assert p1.payment_date == some_datetime
275
276    def test_approve_datetime_automatic(self):
277        # if we do not give a datetime, current one will be used
278        current = datetime.datetime.utcnow()
279        p1 = Payment(self.payer, self.payable)
280        p1.approve()
281        assert p1.payment_date >= current
282
283    def test_mark_failed(self):
284        # we can mark payments as failed
285        p1 = Payment(self.payer, self.payable)
286        p1.mark_failed()
287        assert p1.state == STATE_FAILED
288
289    def test_amount(self):
290        # the amount of a payment is the sum of amounts of its items
291        payable = self.payable
292        payable.payment_items[0].amount = decimal.Decimal("12.25")
293        payable.payment_items[1].amount = decimal.Decimal("0.5")
294        p1 = Payment(self.payer, self.payable)
295        assert p1.amount == decimal.Decimal("12.75")
296
297    def test_amount_negative(self):
298        # we can sum up negative numbers
299        payable = self.payable
300        payable.payment_items[0].amount = decimal.Decimal("2.21")
301        payable.payment_items[1].amount = decimal.Decimal("-3.23")
302        p1 = Payment(self.payer, payable)
303        assert p1.amount == decimal.Decimal("-1.02")
304
305    def test_amount_empty(self):
306        # the amount of zero items is None.
307        payable = FakePayable(payment_items=())
308        p1 = Payment(self.payer, payable)
309        self.assertEqual(p1.amount, decimal.Decimal("0.00"))
310
311    def test_amount_changed_after_init(self):
312        # amount is dynamic: changes to payment items are reflected
313        payable = FakePayable(payment_items=())
314        p1 = Payment(self.payer, payable)
315        assert p1.amount == decimal.Decimal("0.00")
316        p1.payment_items = (PaymentItem(amount=decimal.Decimal("5.55")), )
317        assert p1.amount == decimal.Decimal("5.55")
318        p1.payment_items = (
319            PaymentItem(amount=decimal.Decimal("5.55")),
320            PaymentItem(amount=decimal.Decimal("1.11")),
321            )
322        assert p1.amount == decimal.Decimal("6.66")
323
324    def test_payment_items(self):
325        # we can get payment items from a payment
326        payable = FakePayable(payment_items=FAKE_PAYMENT_ITEMS)
327        p1 = Payment(self.payer, payable)
328        assert isinstance(p1.payment_items, tuple)
329
330    def test_payment_items_number(self):
331        # payment item values are respected
332        payable = FakePayable()
333        item = PaymentItem(amount=decimal.Decimal("9.99"))
334        payable.payment_items = [item, ]
335        payment = Payment(self.payer, payable)
336        assert len(payment.payment_items) == 1
337        assert payment.payment_items[0] is item
338
339
340class PaymentItemTests(unittest.TestCase):
341
342    def test_iface(self):
343        # PaymentItems fullfill any interface contracts
344        obj = PaymentItem()
345        verifyClass(IPaymentItem, PaymentItem)
346        verifyObject(IPaymentItem, obj)
347
348    def test_to_string(self):
349        # we can turn default PaymentItems into strings
350        obj = PaymentItem()
351        self.assertEqual(
352            obj.to_string(), u"(u'', u'0.00')")
353
354    def test_to_string_none_values(self):
355        # 'None' values are properly represented by to_string()
356        obj = PaymentItem()
357        obj.item_id = None
358        self.assertEqual(
359            obj.to_string(), u"(u'', u'0.00')")
360
361    def test_to_string_enocded_values(self):
362        # to_string() copes with encoded strings
363        obj = PaymentItem()
364        obj.title = u'ümläut'
365        self.assertEqual(
366            obj.to_string(), u"(u'ümläut', u'0.00')")
367
368    def test_repr(self):
369        # we can get a proper representation of PaymentItem
370        obj = PaymentItem()
371        self.assertEqual(
372            repr(obj),
373            "PaymentItem(title=u'', amount=Decimal('0.00'))")
374
375    def test_repr_can_be_evaled(self):
376        # we can eval() representations
377        obj = PaymentItem(title=u'My Title', amount=decimal.Decimal("1.99"))
378        representation = repr(obj)
379        self.assertEqual(
380            representation,
381            "PaymentItem(title=u'My Title', amount=Decimal('1.99'))"
382            )
383        new_obj = eval(representation)
384        assert new_obj is not obj
385        assert new_obj.title == obj.title == u"My Title"
386        assert new_obj.amount == obj.amount == Decimal("1.99")
Note: See TracBrowser for help on using the repository browser.