Merge lp:~elachuni/canonical-identity-provider/ssoclient-tests into lp:~elachuni/canonical-identity-provider/ssoclient

Proposed by Anthony Lenton
Status: Merged
Approved by: Anthony Lenton
Approved revision: 7
Merged at revision: 5
Proposed branch: lp:~elachuni/canonical-identity-provider/ssoclient-tests
Merge into: lp:~elachuni/canonical-identity-provider/ssoclient
Diff against target: 350 lines (+217/-30)
2 files modified
ssoclient.py (+33/-24)
tests/test_ssoclient.py (+184/-6)
To merge this branch: bzr merge lp:~elachuni/canonical-identity-provider/ssoclient-tests
Reviewer Review Type Date Requested Status
David Owen (community) Approve
Anthony Lenton Pending
Review via email: mp+68303@code.launchpad.net

Commit message

Added missing tests for all api calls.

Description of the change

Overview
========
This branch adds tests for all api calls that were missing tests in SingleSignOnAPI, and fixes the issues that these tests found.

Details
=======
Tests are written against SSO's mock server, you'll need to have that installed in your PYTHONPATH for the tests to work. Making running the tests easier would be something fo an interesting follow-up branch.

The issues that the tests uncovered were mainly two kinds:
 - Methods that were using POST that should use GET instead.
 - Once the method was switched over to GET, query arguments needed to be manually json-encoded. json_encode_query() was added to make this easier.

To Test
=======
Make SSO's mockserver, piston_mini_client 0.4, httplib2 0.6.0 and wsgi_intercept available on your pythonpath, and then run:

python setup.py test

To post a comment you must log in.
Revision history for this message
David Owen (dsowen) wrote :

When you say that some methods should be GET instead of POST, is that to match what the lazr.restful server expected, or something else?

review: Needs Information
Revision history for this message
Anthony Lenton (elachuni) wrote :

Hi David,

> When you say that some methods should be GET instead of POST, is that to match
> what the lazr.restful server expected, or something else?

Yes, it's to match what lazr.restful expects. I'd first notice the test would fail against the mock server, so then I'd manually check each method against production's lazr.restful API. Staging's Piston API will accept either method, but on production using the wrong method fails.

Revision history for this message
David Owen (dsowen) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ssoclient.py'
2--- ssoclient.py 2011-06-17 16:52:13 +0000
3+++ ssoclient.py 2011-07-18 21:42:38 +0000
4@@ -94,28 +94,28 @@
5 @basic_protected
6 @returns_json
7 def authenticate(self, token_name):
8- args = {'ws.op': 'authenticate',
9+ args = self.json_encode_query({'ws.op': 'authenticate',
10 'token_name': token_name,
11- }
12+ })
13 return self._get('authentications', args=args)
14
15
16 @basic_protected
17 @returns_json
18 def list_tokens(self, consumer_key):
19- data = {'ws.op': 'list_tokens',
20+ args = self.json_encode_query({'ws.op': 'list_tokens',
21 'consumer_key': consumer_key,
22- }
23- return self._post('authentications', data=data)
24+ })
25+ return self._get('authentications', args=args)
26
27 @basic_protected
28 @returns_json
29 def validate_token(self, consumer_key, token):
30- data = {'ws.op': 'validate_token',
31+ data = self.json_encode_query({'ws.op': 'validate_token',
32 'consumer_key': consumer_key,
33 'token': token,
34- }
35- return self._post('authentications', data=data)
36+ })
37+ return self._get('authentications', args=data)
38
39 @basic_protected
40 @returns_json
41@@ -129,46 +129,55 @@
42 @basic_protected
43 @returns_json
44 def team_memberships_by_openid(self, openid_identifier, team_names):
45- data = {'ws.op': 'team_memberships',
46+ args = self.json_encode_query({'ws.op': 'team_memberships',
47 'openid_identifier': openid_identifier,
48 'team_names': team_names,
49- }
50- return self._post('authentications', data=data)
51+ })
52+ return self._get('authentications', args=args)
53
54 @basic_protected
55 @returns(AccountResponse, none_allowed=True)
56 def account_by_email(self, email):
57- data = {'ws.op': 'account_by_email',
58+ args = self.json_encode_query({'ws.op': 'account_by_email',
59 'email': email,
60- }
61- return self._post('authentications', data=data)
62+ })
63+ return self._get('authentications', args=args)
64
65 @basic_protected
66 @returns(AccountResponse, none_allowed=True)
67 def account_by_openid(self, openid):
68- data = {'ws.op': 'account_by_openid',
69+ args = self.json_encode_query({'ws.op': 'account_by_openid',
70 'openid': openid,
71- }
72- return self._post('authentications', data=data)
73+ })
74+ return self._get('authentications', args=args)
75
76 @oauth_protected
77 @returns(AccountResponse)
78 def me(self):
79- data = {'ws.op': 'me'}
80- return self._post('accounts', data=data)
81+ args = self.json_encode_query({'ws.op': 'me'})
82+ return self._get('accounts', args=args)
83
84 @oauth_protected
85 @returns_json
86 def my_team_memberships(self, team_names):
87- data = {'ws.op': 'team_memberships',
88+ args = self.json_encode_query({'ws.op': 'team_memberships',
89 'team_names': team_names,
90- }
91- return self._post('accounts', data=data)
92+ })
93+ return self._get('accounts', args=args)
94
95 @oauth_protected
96 @returns_json
97 def validate_email(self, email_token):
98- args = {'ws.op': 'validate_email',
99+ args = self.json_encode_query({'ws.op': 'validate_email',
100 'email_token': email_token,
101- }
102+ })
103 return self._get('accounts', args=args)
104+
105+ def json_encode_query(self, data):
106+ result = {}
107+ for key, value in data.items():
108+ if key == 'ws.op':
109+ result[key] = value
110+ else:
111+ result[key] = json.dumps(value)
112+ return result
113
114=== modified file 'tests/test_ssoclient.py'
115--- tests/test_ssoclient.py 2011-05-24 22:24:03 +0000
116+++ tests/test_ssoclient.py 2011-07-18 21:42:38 +0000
117@@ -1,19 +1,28 @@
118 #!/usr/bin/python
119
120 import unittest
121-from mockssoservice.mockserver import MockSSOServer, new_token
122+
123+from mockssoservice import mockserver
124 from wsgi_intercept import add_wsgi_intercept, remove_wsgi_intercept
125 from wsgi_intercept.httplib2_intercept import install, uninstall
126-
127+from piston_mini_client.auth import BasicAuthorizer, OAuthAuthorizer
128+from piston_mini_client.failhandlers import APIError
129 from ssoclient import SingleSignOnAPI
130
131 class SSOTestCase(unittest.TestCase):
132 def mock_sso(self, *args):
133- return MockSSOServer('login.ubuntu.com', 443, '/api/1.0',
134+ return mockserver.MockSSOServer('login.ubuntu.com', 443, '/api/1.0',
135 scheme='https')
136
137 def setUp(self):
138 install()
139+ self.service_root = 'https://login.ubuntu.com/api/1.0'
140+ self.basic_server_auth = BasicAuthorizer(username='MyUsername',
141+ password='password')
142+ self.basic_user_auth = BasicAuthorizer(username='mail@somedomain.com',
143+ password='OMGTh1sIs5trong')
144+ self.oauth_auth=OAuthAuthorizer('tokenkey', 'tokensecret',
145+ 'consumerkey', 'consumersecret')
146 add_wsgi_intercept('login.ubuntu.com', 443, self.mock_sso)
147 super(SSOTestCase, self).setUp()
148
149@@ -25,7 +34,7 @@
150
151 class CaptchaTestCase(SSOTestCase):
152 def test_new_captcha(self):
153- api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')
154+ api = SingleSignOnAPI(self.service_root)
155
156 captcha = api.new_captcha()
157
158@@ -35,7 +44,7 @@
159
160 class RegisterTestCase(SSOTestCase):
161 def test_register_weak_password(self):
162- api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')
163+ api = SingleSignOnAPI(self.service_root)
164 kwargs = {'email': 'foo@bar.baz',
165 'captcha_id': 'someid',
166 'captcha_solution': 'omg lol',
167@@ -53,7 +62,7 @@
168 self.assertEqual(['password'], r['errors'].keys())
169
170 def test_invalid_email(self):
171- api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')
172+ api = SingleSignOnAPI(self.service_root)
173 r = api.register(password='H1l4riuos',
174 captcha_id='someid',
175 captcha_solution='omg lol',
176@@ -62,5 +71,174 @@
177 self.assertEqual(['email'], r['errors'].keys())
178
179
180+class RequestPasswordResetTokenTestCase(SSOTestCase):
181+ def test_request_password_reset_token(self):
182+ api = SingleSignOnAPI(self.service_root)
183+ result = api.request_password_reset_token('myemail@test.com')
184+ expected = {'status': 'ok', 'message': 'Password reset token sent.'}
185+ self.assertEqual(expected, result)
186+
187+
188+class SetNewPasswordTestCase(SSOTestCase):
189+ def test_set_new_password_invalid_password(self):
190+ api = SingleSignOnAPI(self.service_root)
191+ invalid_passwords = [
192+ 'Shor7',
193+ 'onlylowercase',
194+ 'etc...',
195+ ]
196+ expected = {
197+ 'status': 'error',
198+ 'errors': [
199+ 'Password must be at least 8 characters long, and must '
200+ 'contain at least one number and an upper case letter.'
201+ ],
202+ }
203+ for passwd in invalid_passwords:
204+ result = api.set_new_password('foobar@baz.com', passwd, 'token')
205+ self.assertEqual(expected, result)
206+
207+ def test_set_new_password_success(self):
208+ api = SingleSignOnAPI(self.service_root)
209+ result = api.set_new_password('foobar@baz.com', 'Q1w2E3r4', 'token')
210+ expected = {'message': 'Password changed', 'status': 'ok'}
211+ self.assertEqual(expected, result)
212+
213+
214+class AuthenticateTestCase(SSOTestCase):
215+ def test_authenticate_failure(self):
216+ # Attempt to authenticate an APIUser fails using the mock server
217+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
218+ self.assertRaises(APIError, api.authenticate, token_name='foobar')
219+
220+ def test_authenticate_success(self):
221+ api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
222+ result = api.authenticate(token_name='foobar')
223+ expected = set(['consumer_secret', 'token', 'consumer_key', 'name',
224+ 'token_secret'])
225+ self.assertEqual(expected, set(result.keys()))
226+
227+
228+class ListTokensTestCase(SSOTestCase):
229+ def test_list_tokens_auth_failure(self):
230+ api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
231+ self.assertRaises(APIError, api.list_tokens, 'openid-123')
232+
233+ def test_list_tokens_no_tokens(self):
234+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
235+ self.assertEqual([], api.list_tokens('openid-123'))
236+
237+ def test_list_tokens_some_tokens(self):
238+ mockserver.tokens.clear()
239+ mockserver.new_token('foobar')
240+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
241+ self.assertEqual(1, len(api.list_tokens('name12_oid')))
242+
243+
244+class ValidateTokenTestCase(SSOTestCase):
245+ def test_validate_token_auth_faliure(self):
246+ api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
247+ self.assertRaises(APIError, api.validate_token, 'openid-123', 'token')
248+
249+ def test_validate_token_success(self):
250+ mockserver.tokens.clear()
251+ token = '0123456789abcdef'
252+ mockserver.new_token('token-name', token=token)
253+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
254+ expected = set(['consumer_secret', 'token', 'consumer_key', 'name',
255+ 'token_secret'])
256+
257+ result = api.validate_token(consumer_key='name12_oid', token=token)
258+
259+ self.assertEqual(expected, set(result.keys()))
260+
261+
262+class InvalidateTokenTestCase(SSOTestCase):
263+ def test_invalidate_token_auth_faliure(self):
264+ api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
265+ self.assertRaises(APIError, api.invalidate_token, 'someoid', 'token')
266+
267+ def test_invalidate_token_success(self):
268+ mockserver.tokens.clear()
269+ token = '0123456789abcdef'
270+ mockserver.new_token('token-name', token=token)
271+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
272+ result = api.invalidate_token(consumer_key='name12_oid', token=token)
273+ self.assertEqual(None, result)
274+
275+
276+class TeamMembershipsByOpenid(SSOTestCase):
277+ def test_team_memberships_by_openid_auth_failure(self):
278+ api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
279+ self.assertRaises(APIError, api.invalidate_token, 'someoid', 'token')
280+
281+ def test_team_memberships_by_openid_success(self):
282+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
283+ result = api.team_memberships_by_openid('someoid',
284+ ["ubuntu-team", "myteam", "someotherteam"])
285+
286+ self.assertEqual(["ubuntu-team", "myteam"], result)
287+
288+
289+class AccountByEmailTestCase(SSOTestCase):
290+ def test_account_by_email_invalid_email(self):
291+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
292+
293+ result = api.account_by_email(email='someemail@bla.com')
294+ self.assertEqual(None, result)
295+
296+ def test_account_by_email_verified(self):
297+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
298+
299+ result = api.account_by_email(email='blu@bli.com')
300+ self.assertEqual('username', result.username)
301+ self.assertEqual('Blu Bli', result.displayname)
302+ self.assertEqual('blu@bli.com', result.preferred_email)
303+ self.assertEqual([], result.verified_emails)
304+ self.assertEqual([], result.unverified_emails)
305+
306+class AccountByOpenIDTestCase(SSOTestCase):
307+ def test_account_by_openid_invalid_identifier(self):
308+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
309+
310+ result = api.account_by_openid(openid='oid_1234')
311+ self.assertEqual(None, result)
312+
313+ def test_account_by_openid_success(self):
314+ api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
315+
316+ result = api.account_by_openid(openid='openid_identifier')
317+ self.assertEqual('username', result.username)
318+ self.assertEqual('Blu Bli', result.displayname)
319+ self.assertEqual('openid_identifier', result.openid_identifier)
320+
321+
322+class MeTestCase(SSOTestCase):
323+ def test_me(self):
324+ api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
325+ result = api.me()
326+ self.assertEqual('Blu Bli', result.displayname)
327+
328+
329+class MyTeamMembershipsTestcase(SSOTestCase):
330+ def test_my_team_memberships(self):
331+ api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
332+ result = api.my_team_memberships(['foobar'])
333+ self.assertEqual([], result)
334+
335+
336+class ValidateEmailTestCase(SSOTestCase):
337+ def test_validate_email_fail(self):
338+ api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
339+ result = api.validate_email('abcdefghijkl1234567890')
340+ expected = {'errors': {'email_token': ['Bad email token!']}}
341+ self.assertEqual(expected, result)
342+
343+ def test_validate_email_success(self):
344+ api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
345+ result = api.validate_email('jJRkmngbHjmnJDEK')
346+ self.assertEqual({'email': 'blu@bli.com'}, result)
347+
348+
349 if __name__ == "__main__":
350 unittest.main()

Subscribers

People subscribed via source and target branches

to all changes: