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
=== modified file 'ssoclient.py'
--- ssoclient.py 2011-06-17 16:52:13 +0000
+++ ssoclient.py 2011-07-18 21:42:38 +0000
@@ -94,28 +94,28 @@
94 @basic_protected94 @basic_protected
95 @returns_json95 @returns_json
96 def authenticate(self, token_name):96 def authenticate(self, token_name):
97 args = {'ws.op': 'authenticate',97 args = self.json_encode_query({'ws.op': 'authenticate',
98 'token_name': token_name,98 'token_name': token_name,
99 }99 })
100 return self._get('authentications', args=args)100 return self._get('authentications', args=args)
101101
102102
103 @basic_protected103 @basic_protected
104 @returns_json104 @returns_json
105 def list_tokens(self, consumer_key):105 def list_tokens(self, consumer_key):
106 data = {'ws.op': 'list_tokens',106 args = self.json_encode_query({'ws.op': 'list_tokens',
107 'consumer_key': consumer_key,107 'consumer_key': consumer_key,
108 }108 })
109 return self._post('authentications', data=data)109 return self._get('authentications', args=args)
110110
111 @basic_protected111 @basic_protected
112 @returns_json112 @returns_json
113 def validate_token(self, consumer_key, token):113 def validate_token(self, consumer_key, token):
114 data = {'ws.op': 'validate_token',114 data = self.json_encode_query({'ws.op': 'validate_token',
115 'consumer_key': consumer_key,115 'consumer_key': consumer_key,
116 'token': token,116 'token': token,
117 }117 })
118 return self._post('authentications', data=data)118 return self._get('authentications', args=data)
119119
120 @basic_protected120 @basic_protected
121 @returns_json121 @returns_json
@@ -129,46 +129,55 @@
129 @basic_protected129 @basic_protected
130 @returns_json130 @returns_json
131 def team_memberships_by_openid(self, openid_identifier, team_names):131 def team_memberships_by_openid(self, openid_identifier, team_names):
132 data = {'ws.op': 'team_memberships',132 args = self.json_encode_query({'ws.op': 'team_memberships',
133 'openid_identifier': openid_identifier,133 'openid_identifier': openid_identifier,
134 'team_names': team_names,134 'team_names': team_names,
135 }135 })
136 return self._post('authentications', data=data)136 return self._get('authentications', args=args)
137137
138 @basic_protected138 @basic_protected
139 @returns(AccountResponse, none_allowed=True)139 @returns(AccountResponse, none_allowed=True)
140 def account_by_email(self, email):140 def account_by_email(self, email):
141 data = {'ws.op': 'account_by_email',141 args = self.json_encode_query({'ws.op': 'account_by_email',
142 'email': email,142 'email': email,
143 }143 })
144 return self._post('authentications', data=data)144 return self._get('authentications', args=args)
145145
146 @basic_protected146 @basic_protected
147 @returns(AccountResponse, none_allowed=True)147 @returns(AccountResponse, none_allowed=True)
148 def account_by_openid(self, openid):148 def account_by_openid(self, openid):
149 data = {'ws.op': 'account_by_openid',149 args = self.json_encode_query({'ws.op': 'account_by_openid',
150 'openid': openid,150 'openid': openid,
151 }151 })
152 return self._post('authentications', data=data)152 return self._get('authentications', args=args)
153153
154 @oauth_protected154 @oauth_protected
155 @returns(AccountResponse)155 @returns(AccountResponse)
156 def me(self):156 def me(self):
157 data = {'ws.op': 'me'}157 args = self.json_encode_query({'ws.op': 'me'})
158 return self._post('accounts', data=data)158 return self._get('accounts', args=args)
159159
160 @oauth_protected160 @oauth_protected
161 @returns_json161 @returns_json
162 def my_team_memberships(self, team_names):162 def my_team_memberships(self, team_names):
163 data = {'ws.op': 'team_memberships',163 args = self.json_encode_query({'ws.op': 'team_memberships',
164 'team_names': team_names,164 'team_names': team_names,
165 }165 })
166 return self._post('accounts', data=data)166 return self._get('accounts', args=args)
167167
168 @oauth_protected168 @oauth_protected
169 @returns_json169 @returns_json
170 def validate_email(self, email_token):170 def validate_email(self, email_token):
171 args = {'ws.op': 'validate_email',171 args = self.json_encode_query({'ws.op': 'validate_email',
172 'email_token': email_token,172 'email_token': email_token,
173 }173 })
174 return self._get('accounts', args=args)174 return self._get('accounts', args=args)
175
176 def json_encode_query(self, data):
177 result = {}
178 for key, value in data.items():
179 if key == 'ws.op':
180 result[key] = value
181 else:
182 result[key] = json.dumps(value)
183 return result
175184
=== modified file 'tests/test_ssoclient.py'
--- tests/test_ssoclient.py 2011-05-24 22:24:03 +0000
+++ tests/test_ssoclient.py 2011-07-18 21:42:38 +0000
@@ -1,19 +1,28 @@
1#!/usr/bin/python1#!/usr/bin/python
22
3import unittest3import unittest
4from mockssoservice.mockserver import MockSSOServer, new_token4
5from mockssoservice import mockserver
5from wsgi_intercept import add_wsgi_intercept, remove_wsgi_intercept6from wsgi_intercept import add_wsgi_intercept, remove_wsgi_intercept
6from wsgi_intercept.httplib2_intercept import install, uninstall7from wsgi_intercept.httplib2_intercept import install, uninstall
78from piston_mini_client.auth import BasicAuthorizer, OAuthAuthorizer
9from piston_mini_client.failhandlers import APIError
8from ssoclient import SingleSignOnAPI10from ssoclient import SingleSignOnAPI
911
10class SSOTestCase(unittest.TestCase):12class SSOTestCase(unittest.TestCase):
11 def mock_sso(self, *args):13 def mock_sso(self, *args):
12 return MockSSOServer('login.ubuntu.com', 443, '/api/1.0',14 return mockserver.MockSSOServer('login.ubuntu.com', 443, '/api/1.0',
13 scheme='https')15 scheme='https')
1416
15 def setUp(self):17 def setUp(self):
16 install()18 install()
19 self.service_root = 'https://login.ubuntu.com/api/1.0'
20 self.basic_server_auth = BasicAuthorizer(username='MyUsername',
21 password='password')
22 self.basic_user_auth = BasicAuthorizer(username='mail@somedomain.com',
23 password='OMGTh1sIs5trong')
24 self.oauth_auth=OAuthAuthorizer('tokenkey', 'tokensecret',
25 'consumerkey', 'consumersecret')
17 add_wsgi_intercept('login.ubuntu.com', 443, self.mock_sso)26 add_wsgi_intercept('login.ubuntu.com', 443, self.mock_sso)
18 super(SSOTestCase, self).setUp()27 super(SSOTestCase, self).setUp()
1928
@@ -25,7 +34,7 @@
2534
26class CaptchaTestCase(SSOTestCase):35class CaptchaTestCase(SSOTestCase):
27 def test_new_captcha(self):36 def test_new_captcha(self):
28 api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')37 api = SingleSignOnAPI(self.service_root)
2938
30 captcha = api.new_captcha()39 captcha = api.new_captcha()
3140
@@ -35,7 +44,7 @@
3544
36class RegisterTestCase(SSOTestCase):45class RegisterTestCase(SSOTestCase):
37 def test_register_weak_password(self):46 def test_register_weak_password(self):
38 api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')47 api = SingleSignOnAPI(self.service_root)
39 kwargs = {'email': 'foo@bar.baz',48 kwargs = {'email': 'foo@bar.baz',
40 'captcha_id': 'someid',49 'captcha_id': 'someid',
41 'captcha_solution': 'omg lol',50 'captcha_solution': 'omg lol',
@@ -53,7 +62,7 @@
53 self.assertEqual(['password'], r['errors'].keys())62 self.assertEqual(['password'], r['errors'].keys())
5463
55 def test_invalid_email(self):64 def test_invalid_email(self):
56 api = SingleSignOnAPI(service_root='https://login.ubuntu.com/api/1.0')65 api = SingleSignOnAPI(self.service_root)
57 r = api.register(password='H1l4riuos',66 r = api.register(password='H1l4riuos',
58 captcha_id='someid',67 captcha_id='someid',
59 captcha_solution='omg lol',68 captcha_solution='omg lol',
@@ -62,5 +71,174 @@
62 self.assertEqual(['email'], r['errors'].keys())71 self.assertEqual(['email'], r['errors'].keys())
6372
6473
74class RequestPasswordResetTokenTestCase(SSOTestCase):
75 def test_request_password_reset_token(self):
76 api = SingleSignOnAPI(self.service_root)
77 result = api.request_password_reset_token('myemail@test.com')
78 expected = {'status': 'ok', 'message': 'Password reset token sent.'}
79 self.assertEqual(expected, result)
80
81
82class SetNewPasswordTestCase(SSOTestCase):
83 def test_set_new_password_invalid_password(self):
84 api = SingleSignOnAPI(self.service_root)
85 invalid_passwords = [
86 'Shor7',
87 'onlylowercase',
88 'etc...',
89 ]
90 expected = {
91 'status': 'error',
92 'errors': [
93 'Password must be at least 8 characters long, and must '
94 'contain at least one number and an upper case letter.'
95 ],
96 }
97 for passwd in invalid_passwords:
98 result = api.set_new_password('foobar@baz.com', passwd, 'token')
99 self.assertEqual(expected, result)
100
101 def test_set_new_password_success(self):
102 api = SingleSignOnAPI(self.service_root)
103 result = api.set_new_password('foobar@baz.com', 'Q1w2E3r4', 'token')
104 expected = {'message': 'Password changed', 'status': 'ok'}
105 self.assertEqual(expected, result)
106
107
108class AuthenticateTestCase(SSOTestCase):
109 def test_authenticate_failure(self):
110 # Attempt to authenticate an APIUser fails using the mock server
111 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
112 self.assertRaises(APIError, api.authenticate, token_name='foobar')
113
114 def test_authenticate_success(self):
115 api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
116 result = api.authenticate(token_name='foobar')
117 expected = set(['consumer_secret', 'token', 'consumer_key', 'name',
118 'token_secret'])
119 self.assertEqual(expected, set(result.keys()))
120
121
122class ListTokensTestCase(SSOTestCase):
123 def test_list_tokens_auth_failure(self):
124 api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
125 self.assertRaises(APIError, api.list_tokens, 'openid-123')
126
127 def test_list_tokens_no_tokens(self):
128 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
129 self.assertEqual([], api.list_tokens('openid-123'))
130
131 def test_list_tokens_some_tokens(self):
132 mockserver.tokens.clear()
133 mockserver.new_token('foobar')
134 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
135 self.assertEqual(1, len(api.list_tokens('name12_oid')))
136
137
138class ValidateTokenTestCase(SSOTestCase):
139 def test_validate_token_auth_faliure(self):
140 api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
141 self.assertRaises(APIError, api.validate_token, 'openid-123', 'token')
142
143 def test_validate_token_success(self):
144 mockserver.tokens.clear()
145 token = '0123456789abcdef'
146 mockserver.new_token('token-name', token=token)
147 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
148 expected = set(['consumer_secret', 'token', 'consumer_key', 'name',
149 'token_secret'])
150
151 result = api.validate_token(consumer_key='name12_oid', token=token)
152
153 self.assertEqual(expected, set(result.keys()))
154
155
156class InvalidateTokenTestCase(SSOTestCase):
157 def test_invalidate_token_auth_faliure(self):
158 api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
159 self.assertRaises(APIError, api.invalidate_token, 'someoid', 'token')
160
161 def test_invalidate_token_success(self):
162 mockserver.tokens.clear()
163 token = '0123456789abcdef'
164 mockserver.new_token('token-name', token=token)
165 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
166 result = api.invalidate_token(consumer_key='name12_oid', token=token)
167 self.assertEqual(None, result)
168
169
170class TeamMembershipsByOpenid(SSOTestCase):
171 def test_team_memberships_by_openid_auth_failure(self):
172 api = SingleSignOnAPI(self.service_root, auth=self.basic_user_auth)
173 self.assertRaises(APIError, api.invalidate_token, 'someoid', 'token')
174
175 def test_team_memberships_by_openid_success(self):
176 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
177 result = api.team_memberships_by_openid('someoid',
178 ["ubuntu-team", "myteam", "someotherteam"])
179
180 self.assertEqual(["ubuntu-team", "myteam"], result)
181
182
183class AccountByEmailTestCase(SSOTestCase):
184 def test_account_by_email_invalid_email(self):
185 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
186
187 result = api.account_by_email(email='someemail@bla.com')
188 self.assertEqual(None, result)
189
190 def test_account_by_email_verified(self):
191 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
192
193 result = api.account_by_email(email='blu@bli.com')
194 self.assertEqual('username', result.username)
195 self.assertEqual('Blu Bli', result.displayname)
196 self.assertEqual('blu@bli.com', result.preferred_email)
197 self.assertEqual([], result.verified_emails)
198 self.assertEqual([], result.unverified_emails)
199
200class AccountByOpenIDTestCase(SSOTestCase):
201 def test_account_by_openid_invalid_identifier(self):
202 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
203
204 result = api.account_by_openid(openid='oid_1234')
205 self.assertEqual(None, result)
206
207 def test_account_by_openid_success(self):
208 api = SingleSignOnAPI(self.service_root, auth=self.basic_server_auth)
209
210 result = api.account_by_openid(openid='openid_identifier')
211 self.assertEqual('username', result.username)
212 self.assertEqual('Blu Bli', result.displayname)
213 self.assertEqual('openid_identifier', result.openid_identifier)
214
215
216class MeTestCase(SSOTestCase):
217 def test_me(self):
218 api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
219 result = api.me()
220 self.assertEqual('Blu Bli', result.displayname)
221
222
223class MyTeamMembershipsTestcase(SSOTestCase):
224 def test_my_team_memberships(self):
225 api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
226 result = api.my_team_memberships(['foobar'])
227 self.assertEqual([], result)
228
229
230class ValidateEmailTestCase(SSOTestCase):
231 def test_validate_email_fail(self):
232 api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
233 result = api.validate_email('abcdefghijkl1234567890')
234 expected = {'errors': {'email_token': ['Bad email token!']}}
235 self.assertEqual(expected, result)
236
237 def test_validate_email_success(self):
238 api = SingleSignOnAPI(self.service_root, auth=self.oauth_auth)
239 result = api.validate_email('jJRkmngbHjmnJDEK')
240 self.assertEqual({'email': 'blu@bli.com'}, result)
241
242
65if __name__ == "__main__":243if __name__ == "__main__":
66 unittest.main()244 unittest.main()

Subscribers

People subscribed via source and target branches

to all changes: