Merge lp:~elopio/u1-test-utils/pay_client_return_response into lp:u1-test-utils

Proposed by Leo Arias
Status: Merged
Approved by: Leo Arias
Approved revision: 43
Merged at revision: 40
Proposed branch: lp:~elopio/u1-test-utils/pay_client_return_response
Merge into: lp:u1-test-utils
Diff against target: 297 lines (+123/-99)
5 files modified
requirements.txt (+1/-1)
u1testutils/pay/__init__.py (+23/-0)
u1testutils/pay/client.py (+59/-75)
u1testutils/pay/data.py (+5/-0)
u1testutils/pay/selftests/unit/test_pay_api_client.py (+35/-23)
To merge this branch: bzr merge lp:~elopio/u1-test-utils/pay_client_return_response
Reviewer Review Type Date Requested Status
Matias Bordese (community) Approve
Review via email: mp+155609@code.launchpad.net

Commit message

Return the response from the pay api client, instead of a boolean.

Description of the change

With this change, the pay API test will be able to use the helpers, reducing duplication.
This branch requires that https://code.launchpad.net/~elopio/payclient/creditcardrequest_equality/+merge/155637 is merged first.

To post a comment you must log in.
43. By Leo Arias

Do not inherit from PaymentServiceAPI, use it as an attribute instead.

Revision history for this message
Matias Bordese (matiasb) wrote :

+1

review: Approve
Revision history for this message
Leo Arias (elopio) wrote :
Download full text (9.0 KiB)

<matiasb> pindonga: ok, I'll try the fix above (about create) but... if we are passing a CardRequest object as data, can we check using assert_called_with? and related to this, can you take a look at https://code.launchpad.net/~elopio/u1-test-utils/pay_client_return_response? I think we are kind of reinventing the client calls there because of this, by adding a wrapper around the methods to make them more "friendly" (all payclient methods expect a
<matiasb> elopio: ^
<elopio> sounds fair :)
<pindonga> matiasb, you can assert that the card request object was the one passed in
<pindonga> and since you just set the value on that object, it can be assumed it will have the right value
<pindonga> matiasb, you mean that the mp there is duplicating some code by adding this wrapper?
<matiasb> pindonga: ok, I'll take a look, that may be possible; about that META dict, that is required when setting up the payment to add the credit card; the payment requires some session info, and session info is pulled from the request (that's the IP address taken from the request)
<pindonga> ah, ok
<matiasb> pindonga: I mean that it looks like we need to wrap the payclient calls to use them... and it sounds weird, have an API client that we can't use easily here, I think
<pindonga> matiasb, what I don't understand is why you say we *need* to wrap them
<pindonga> surely the api client can be used as it is
<matiasb> pindonga: right, more like a philosophical question, I guess; if you check l.114 of that MP, in this case, we are getting the shop_id, billing address and card info, and then reorganizing all that to create a CardRequest to pass to the client method as data... wondering if it won't be better at some point to be able to pass data as kwargs
<matiasb> or maybe we need more friendly ways to create/initialize the *Request objects (*: Payment, Card, etc)
<nessita> matiasb, pindonga: IMHO, we should tweak the payment client api so kwargs can be passed
<matiasb> I mean, we usually get the billing address, user info, and card info from different sources, but we need to join them all to create a request object to pass as data argument for api calls
<elopio> this are the kind of objects we use in acceptance testing, because we need easy ways to get a unique object and then edit some fields from it.
<nessita> instead of needing to create a FooRequest object each time, outside the call
<elopio> I'm not sure if the same typo of objects would make sense for the real software.
<nessita> elopio, why not?
<matiasb> elopio: yes, if you use the payclient, it will validate you are passing the right kind of object as arg to the method call
<nessita> elopio, as matiasb says, the FooRequest object have to be created by any software wanting to call the payclient library
<elopio> I think I should understand better what's your proposal :)
<elopio> matiasb, nessita: so, you are against the payclient receiving Request objects?
<pindonga> matiasb, I think I understand
<nessita> elopio, the payclient already does that, no?
<pindonga> so having a single CreditCardRequest object is "bad" as it forces us to create the object
<pindonga> it would be better to just have kwargs as params
<pindon...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'requirements.txt'
2--- requirements.txt 2013-03-26 05:16:33 +0000
3+++ requirements.txt 2013-03-27 20:56:21 +0000
4@@ -5,6 +5,6 @@
5 pyflakes
6 Twisted
7 bzr+ssh://bazaar.launchpad.net/~bloodearnest/localmail/trunk
8-bzr+http://bazaar.launchpad.net/~ubuntuone-hackers/payclient/trunk@1
9+bzr+http://bazaar.launchpad.net/~ubuntuone-hackers/payclient/trunk@3
10 bzr+http://bazaar.launchpad.net/~canonical-isd-qa/selenium-simple-test/trunk@367
11 bzr+http://bazaar.launchpad.net/~canonical-isd-hackers/canonical-identity-provider/ssoclient@3
12
13=== modified file 'u1testutils/pay/__init__.py'
14--- u1testutils/pay/__init__.py 2013-03-26 05:48:26 +0000
15+++ u1testutils/pay/__init__.py 2013-03-27 20:56:21 +0000
16@@ -0,0 +1,23 @@
17+# Copyright 2013 Canonical Ltd.
18+#
19+# This program is free software: you can redistribute it and/or modify it
20+# under the terms of the GNU Lesser General Public License version 3, as
21+# published by the Free Software Foundation.
22+#
23+# This program is distributed in the hope that it will be useful, but
24+# WITHOUT ANY WARRANTY; without even the implied warranties of
25+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
26+# PURPOSE. See the GNU Lesser General Public License for more details.
27+#
28+# You should have received a copy of the GNU General Public License along
29+# with this program. If not, see <http://www.gnu.org/licenses/>.
30+
31+__all__ = [
32+ 'APIClient',
33+ 'data',
34+ 'environment',
35+]
36+
37+import data
38+import environment
39+from client import APIClient
40
41=== modified file 'u1testutils/pay/client.py'
42--- u1testutils/pay/client.py 2013-03-26 06:19:46 +0000
43+++ u1testutils/pay/client.py 2013-03-27 20:56:21 +0000
44@@ -14,83 +14,67 @@
45
46 import payclient
47 from django.conf import settings
48-from piston_mini_client import auth, failhandlers
49
50 import u1testutils.pay.environment
51-
52-
53-def get_pay_client(pay_server_url=None):
54- """Return the Payment Service API client.
55-
56- pay_server_url -- The URL of the Pay server that provides the API. Default
57- is None, which means that the URL will be taken from value of the
58- Django settings PAY_SERVER_URL option.
59+from piston_mini_client import auth
60+
61+
62+class APIClient(object):
63+ """API client helper for payment tests.
64+
65+ It extends the PaymentServiceAPI to take the credentials from the Django
66+ configuration file and to make it easier for the tests to call the API
67+ methods.
68
69 """
70- user = settings.PAY_API_USERNAME
71- password = settings.PAY_API_PASSWORD
72- basic_auth = auth.BasicAuthorizer(user, password)
73- pay_server_url = _get_pay_server_url(pay_server_url)
74- pay_api_url = '{0}/api/2.0/'.format(pay_server_url)
75- return payclient.PaymentServiceAPI(
76- auth=basic_auth, service_root=pay_api_url)
77-
78-
79-def _get_pay_server_url(pay_server_url=None):
80- if pay_server_url is None:
81- pay_server_url = \
82- u1testutils.pay.environment.get_pay_base_url()
83- else:
84- pay_server_url = pay_server_url.rstrip('/')
85- return pay_server_url
86-
87-
88-def add_new_credit_card(shop_id, user, credit_card, billing_address,
89- pay_server_url=None):
90- """Add a new credit card through the Pay API.
91-
92- Keyword arguments:
93- shop_id -- The identifier of the shop that will add the credit card.
94- user -- The user that will have the new credit card. It must have the
95- openid attribute.
96- credit_card -- The credit card information. It must have the attributes
97+
98+ def __init__(self, pay_server_url=None):
99+ pay_server_url = self._get_pay_server_url(pay_server_url)
100+ user = settings.PAY_API_USERNAME
101+ password = settings.PAY_API_PASSWORD
102+ basic_auth = auth.BasicAuthorizer(user, password)
103+ pay_api_url = '{0}/api/2.0/'.format(pay_server_url)
104+ self.client = payclient.PaymentServiceAPI(
105+ auth=basic_auth, service_root=pay_api_url)
106+
107+ def _get_pay_server_url(self, pay_server_url=None):
108+ if pay_server_url is None:
109+ pay_server_url = u1testutils.pay.environment.get_pay_base_url()
110+ else:
111+ pay_server_url = pay_server_url.rstrip('/')
112+ return pay_server_url
113+
114+ def add_new_credit_card(self, shop_id, user, credit_card, billing_address):
115+ """Add a new credit card through the Pay API.
116+
117+ Keyword arguments:
118+ shop_id -- The identifier of the shop that will add the credit card.
119+ user -- The user that will have the new credit card. It must have the
120+ openid attribute.
121+ credit_card -- The credit card information. It must have the attributes
122 card_type, name_on_cad, card_number, ccv_number, expiration_month and
123- expiration_year.
124- billing_address -- The billing address for the card. It must have the
125- attributes street_line1, state, country and postal_code. The country
126- is the name of the country in english.
127- pay_server_url -- The URL of the Pay server that provides the API. Default
128- is None.
129-
130- """
131- billing_address_dict = {
132- 'street': billing_address.street_line1,
133- 'state': billing_address.state,
134- 'country': _get_country_code(billing_address.country),
135- 'postal': billing_address.postal_code
136- }
137- kwargs = dict(
138- shop_id=shop_id,
139- open_id=user.openid,
140- card_type=credit_card.card_type,
141- card_holder=credit_card.name_on_card,
142- card_number=credit_card.card_number,
143- card_ccv=credit_card.ccv_number,
144- card_expiration_month=credit_card.expiration_month,
145- card_expiration_year=credit_card.expiration_year,
146- billing_address=billing_address_dict
147- )
148- credit_card_request = payclient.CreditCardRequest(**kwargs)
149- pay_client = get_pay_client(pay_server_url)
150- try:
151- response = pay_client.new_credit_card(data=credit_card_request)
152- return isinstance(response, payclient.CreditCardResponse)
153- except failhandlers.APIError:
154- # TODO log the error.
155- return False
156-
157-
158-def _get_country_code(country_name):
159- # TODO add more?
160- country_data = {'United States': 'US'}
161- return country_data.get(country_name)
162+ expiration_year.
163+ billing_address -- The billing address for the card. It must have the
164+ attributes street_line1, state, country and postal_code. The
165+ country is the name of the country in english.
166+
167+ """
168+ billing_address_dict = {
169+ 'street': billing_address.street_line1,
170+ 'state': billing_address.state,
171+ 'country': billing_address.get_country_code(),
172+ 'postal': billing_address.postal_code
173+ }
174+ kwargs = dict(
175+ shop_id=shop_id,
176+ open_id=user.openid,
177+ card_type=credit_card.card_type,
178+ card_holder=credit_card.name_on_card,
179+ card_number=credit_card.card_number,
180+ card_ccv=credit_card.ccv_number,
181+ card_expiration_month=credit_card.expiration_month,
182+ card_expiration_year=credit_card.expiration_year,
183+ billing_address=billing_address_dict
184+ )
185+ credit_card_request = payclient.CreditCardRequest(**kwargs)
186+ return self.client.new_credit_card(data=credit_card_request)
187
188=== modified file 'u1testutils/pay/data.py'
189--- u1testutils/pay/data.py 2013-03-26 05:48:26 +0000
190+++ u1testutils/pay/data.py 2013-03-27 20:56:21 +0000
191@@ -69,3 +69,8 @@
192 country = "United States"
193 return cls(
194 street_line1, street_line2, city, state, postal_code, country)
195+
196+ def get_country_code(self):
197+ # TODO add more?
198+ country_data = {'United States': 'US'}
199+ return country_data.get(self.country)
200
201=== modified file 'u1testutils/pay/selftests/unit/test_pay_api_client.py'
202--- u1testutils/pay/selftests/unit/test_pay_api_client.py 2013-03-26 15:11:44 +0000
203+++ u1testutils/pay/selftests/unit/test_pay_api_client.py 2013-03-27 20:56:21 +0000
204@@ -16,7 +16,6 @@
205
206 import mock
207 import payclient
208-from piston_mini_client import failhandlers
209
210 import u1testutils.django
211 from u1testutils.pay import client
212@@ -27,34 +26,35 @@
213 class APIClientTestCase(unittest.TestCase):
214
215 def test_get_api_client(self):
216- api_client = client.get_pay_client()
217- self.assertTrue(isinstance(api_client, payclient.PaymentServiceAPI))
218+ api_client = client.APIClient()
219+ self.assertTrue(
220+ isinstance(api_client.client, payclient.PaymentServiceAPI))
221
222 def test_get_api_client_with_default_url(self):
223- api_client = client.get_pay_client()
224+ api_client = client.APIClient()
225 self.assertEquals(
226- api_client._service_root, 'http://localhost:8000/api/2.0/')
227+ api_client.client._service_root, 'http://localhost:8000/api/2.0/')
228
229 def test_get_api_client_with_settings_url(self):
230 with u1testutils.django.patch_settings(
231 PAY_SERVER_URL='http://settings.pay.local:8000'):
232- api_client = client.get_pay_client()
233+ api_client = client.APIClient()
234 self.assertEquals(
235- api_client._service_root,
236+ api_client.client._service_root,
237 'http://settings.pay.local:8000/api/2.0/')
238
239 def test_get_api_client_with_argument_url(self):
240 pay_server_url = 'http://argument.pay.local:8000'
241- api_client = client.get_pay_client(pay_server_url)
242+ api_client = client.APIClient(pay_server_url)
243 self.assertEquals(
244- api_client._service_root,
245+ api_client.client._service_root,
246 'http://argument.pay.local:8000/api/2.0/')
247
248 def test_get_api_client_with_argument_url_and_trailing_slash(self):
249 pay_server_url = 'http://argument.pay.local:8000/'
250- api_client = client.get_pay_client(pay_server_url)
251+ api_client = client.APIClient(pay_server_url)
252 self.assertEquals(
253- api_client._service_root,
254+ api_client.client._service_root,
255 'http://argument.pay.local:8000/api/2.0/')
256
257
258@@ -67,15 +67,27 @@
259 self.billing_address = pay_data.Address.make_unique()
260
261 def test_new_credit_card_added(self):
262- with mock.patch('payclient.PaymentServiceAPI.new_credit_card',
263- return_value=payclient.CreditCardResponse()):
264- added = client.add_new_credit_card(
265- 'TEST', self.user, self.credit_card, self.billing_address)
266- self.assertTrue(added)
267-
268- def test_add_new_credit_card_failed(self):
269- with mock.patch('payclient.PaymentServiceAPI.new_credit_card',
270- side_effect=failhandlers.APIError('test')):
271- added = client.add_new_credit_card(
272- 'TEST', self.user, self.credit_card, self.billing_address)
273- self.assertFalse(added)
274+ shop_id = 'TEST'
275+ api_client = client.APIClient()
276+ with mock.patch('payclient.PaymentServiceAPI.new_credit_card') \
277+ as mock_api_call:
278+ api_client.add_new_credit_card(
279+ shop_id, self.user, self.credit_card, self.billing_address)
280+ expected_params = {
281+ 'shop_id': shop_id,
282+ 'open_id': self.user.openid,
283+ 'card_type': self.credit_card.card_type,
284+ 'card_holder': self.credit_card.name_on_card,
285+ 'card_number': self.credit_card.card_number,
286+ 'card_ccv': self.credit_card.ccv_number,
287+ 'card_expiration_month': self.credit_card.expiration_month,
288+ 'card_expiration_year': self.credit_card.expiration_year,
289+ 'billing_address': {
290+ 'street': self.billing_address.street_line1,
291+ 'state': self.billing_address.state,
292+ 'postal': self.billing_address.postal_code,
293+ 'country': 'US',
294+ }
295+ }
296+ mock_api_call.assert_called_with(
297+ data=payclient.CreditCardRequest(**expected_params))

Subscribers

People subscribed via source and target branches

to all changes: