Merge lp:~ricardokirkner/ols-store-tests/more-acceptance-tests-1 into lp:~ubuntuone-pqm-team/ols-store-tests/store-acceptance-tests
- more-acceptance-tests-1
- Merge into store-acceptance-tests
Proposed by
Ricardo Kirkner
Status: | Merged |
---|---|
Merged at revision: | 20 |
Proposed branch: | lp:~ricardokirkner/ols-store-tests/more-acceptance-tests-1 |
Merge into: | lp:~ubuntuone-pqm-team/ols-store-tests/store-acceptance-tests |
Diff against target: |
492 lines (+304/-64) 6 files modified
tests/api/cpi/helpers.py (+41/-0) tests/api/cpi/test_metadata.py (+18/-17) tests/api/cpi/test_package.py (+1/-36) tests/api/cpi/test_recommendation.py (+35/-0) tests/api/cpi/test_search.py (+68/-11) tests/api/cpi/test_snaps.py (+141/-0) |
To merge this branch: | bzr merge lp:~ricardokirkner/ols-store-tests/more-acceptance-tests-1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matias Bordese (community) | Approve | ||
Review via email:
|
Commit message
added more smoke tests
added tests for:
- search
- snaps
- recommendations
Description of the change
To post a comment you must log in.
- 30. By Ricardo Kirkner
-
decouple click from snap search endpoints
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ricardo Kirkner (ricardokirkner) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tests/api/cpi/helpers.py' |
2 | --- tests/api/cpi/helpers.py 2016-07-07 15:41:03 +0000 |
3 | +++ tests/api/cpi/helpers.py 2016-07-07 19:50:23 +0000 |
4 | @@ -1,7 +1,10 @@ |
5 | import getpass |
6 | +import json |
7 | import os |
8 | import unittest |
9 | |
10 | +import requests |
11 | +import requests_oauthlib |
12 | |
13 | CPI_ROOT_URL = os.getenv( |
14 | 'CPI_ROOT_URL', 'https://search.apps.staging.ubuntu.com') |
15 | @@ -26,6 +29,39 @@ |
16 | PRIVATE_SNAP_PACKAGE_REVISION = 1 |
17 | |
18 | |
19 | +def get_oauth_token_data(email, password): |
20 | + url = '{}/api/v2/tokens/oauth'.format(SSO_ROOT_URL) |
21 | + data = { |
22 | + 'email': email, |
23 | + 'password': password, |
24 | + 'token_name': 'store-acceptance-tests', |
25 | + } |
26 | + headers = { |
27 | + 'Content-Type': 'application/json', |
28 | + 'Accept': 'application/json', |
29 | + } |
30 | + response = requests.post(url, data=json.dumps(data), headers=headers) |
31 | + assert response.ok, 'Failed to get oauth token: %r' % response.content |
32 | + |
33 | + data = response.json() |
34 | + return { |
35 | + 'client_key': data['consumer_key'], |
36 | + 'client_secret': data['consumer_secret'], |
37 | + 'resource_owner_key': data['token_key'], |
38 | + 'resource_owner_secret': data['token_secret'], |
39 | + } |
40 | + |
41 | + |
42 | +def get_oauth_client(): |
43 | + credentials = get_oauth_token_data(TEST_USER_EMAIL, TEST_USER_PASSWORD) |
44 | + client = requests_oauthlib.OAuth1Session(**credentials) |
45 | + client.headers.update({ |
46 | + 'Content-Type': 'application/json', |
47 | + 'Cache-Control': 'no-cache', |
48 | + }) |
49 | + return client |
50 | + |
51 | + |
52 | class APITestCase(unittest.TestCase): |
53 | |
54 | def assert_success(self, response, content_type='application/hal+json'): |
55 | @@ -50,3 +86,8 @@ |
56 | 'result': 'error', |
57 | 'errors': [message], |
58 | }) |
59 | + |
60 | + def assert_unauthorized(self, response): |
61 | + self.assertEqual(response.status_code, 401) |
62 | + self.assertEqual(response.headers['Content-Type'], 'text/plain') |
63 | + self.assertIn('WWW-Authenticate', response.headers) |
64 | |
65 | === modified file 'tests/api/cpi/test_metadata.py' |
66 | --- tests/api/cpi/test_metadata.py 2016-07-07 18:53:00 +0000 |
67 | +++ tests/api/cpi/test_metadata.py 2016-07-07 19:50:23 +0000 |
68 | @@ -85,19 +85,20 @@ |
69 | response, message='No JSON object could be decoded') |
70 | |
71 | |
72 | -def get_metadata(snaps=None, fields=None): |
73 | - url = '{}/api/v1/metadata'.format(CPI_ROOT_URL) |
74 | - data = {} |
75 | - if snaps is not None: |
76 | - data['snaps'] = snaps |
77 | - if fields is not None: |
78 | - data['fields'] = fields |
79 | - response = requests.post(url, data=json.dumps(data), |
80 | - headers={'Content-Type': 'application/json'}) |
81 | - return response |
82 | - |
83 | - |
84 | class MetadataTestCase(APITestCase): |
85 | + endpoint = '/api/v1/metadata' |
86 | + |
87 | + def get_metadata(self, snaps=None, fields=None): |
88 | + url = '{}{}'.format(CPI_ROOT_URL, self.endpoint) |
89 | + data = {} |
90 | + if snaps is not None: |
91 | + data['snaps'] = snaps |
92 | + if fields is not None: |
93 | + data['fields'] = fields |
94 | + response = requests.post( |
95 | + url, data=json.dumps(data), |
96 | + headers={'Content-Type': 'application/json'}) |
97 | + return response |
98 | |
99 | def assert_metadata(self, response, fields=None, snap_id=None): |
100 | body = response.json() |
101 | @@ -144,25 +145,25 @@ |
102 | self._assert_package_data(body, fields=[]) |
103 | |
104 | def test_get_metadata(self): |
105 | - response = get_metadata( |
106 | + response = self.get_metadata( |
107 | snaps=[{'snap_id': SNAP_PACKAGE_SNAP_ID}]) |
108 | self.assert_success(response) |
109 | self.assert_metadata(response, snap_id=SNAP_PACKAGE_SNAP_ID) |
110 | |
111 | def test_get_metadata_no_snaps(self): |
112 | - response = get_metadata() |
113 | + response = self.get_metadata() |
114 | self.assert_bad_request( |
115 | response, |
116 | message='Packages must be specified in parameter "snaps".') |
117 | |
118 | def test_get_metadata_missing_snap_id(self): |
119 | - response = get_metadata(snaps=[{}]) |
120 | + response = self.get_metadata(snaps=[{}]) |
121 | self.assert_bad_request( |
122 | response, message='Missing snap_id for snap.') |
123 | |
124 | def test_get_metadata_filter_fields(self): |
125 | fields = ['channel', 'revision', 'snap_id'] |
126 | - response = get_metadata( |
127 | + response = self.get_metadata( |
128 | snaps=[{'snap_id': SNAP_PACKAGE_SNAP_ID}], |
129 | fields=fields) |
130 | self.assert_success(response) |
131 | @@ -170,7 +171,7 @@ |
132 | response, fields=fields, snap_id=SNAP_PACKAGE_SNAP_ID) |
133 | |
134 | def test_get_metadata_invalid_package(self): |
135 | - response = get_metadata( |
136 | + response = self.get_metadata( |
137 | snaps=[{'snap_id': 'invalid'}]) |
138 | self.assert_success(response) |
139 | self.assert_no_results(response) |
140 | |
141 | === modified file 'tests/api/cpi/test_package.py' |
142 | --- tests/api/cpi/test_package.py 2016-07-07 15:41:03 +0000 |
143 | +++ tests/api/cpi/test_package.py 2016-07-07 19:50:23 +0000 |
144 | @@ -1,7 +1,4 @@ |
145 | -import json |
146 | - |
147 | import requests |
148 | -import requests_oauthlib |
149 | |
150 | from .helpers import ( |
151 | CPI_ROOT_URL, |
152 | @@ -16,6 +13,7 @@ |
153 | TEST_USER_EMAIL, |
154 | TEST_USER_PASSWORD, |
155 | APITestCase, |
156 | + get_oauth_client, |
157 | ) |
158 | |
159 | |
160 | @@ -35,39 +33,6 @@ |
161 | return response |
162 | |
163 | |
164 | -def get_oauth_token_data(email, password): |
165 | - url = '{}/api/v2/tokens/oauth'.format(SSO_ROOT_URL) |
166 | - data = { |
167 | - 'email': email, |
168 | - 'password': password, |
169 | - 'token_name': 'store-acceptance-tests', |
170 | - } |
171 | - headers = { |
172 | - 'Content-Type': 'application/json', |
173 | - 'Accept': 'application/json', |
174 | - } |
175 | - response = requests.post(url, data=json.dumps(data), headers=headers) |
176 | - assert response.ok, 'Failed to get oauth token: %r' % response.content |
177 | - |
178 | - data = response.json() |
179 | - return { |
180 | - 'client_key': data['consumer_key'], |
181 | - 'client_secret': data['consumer_secret'], |
182 | - 'resource_owner_key': data['token_key'], |
183 | - 'resource_owner_secret': data['token_secret'], |
184 | - } |
185 | - |
186 | - |
187 | -def get_oauth_client(): |
188 | - credentials = get_oauth_token_data(TEST_USER_EMAIL, TEST_USER_PASSWORD) |
189 | - client = requests_oauthlib.OAuth1Session(**credentials) |
190 | - client.headers.update({ |
191 | - 'Content-Type': 'application/json', |
192 | - 'Cache-Control': 'no-cache', |
193 | - }) |
194 | - return client |
195 | - |
196 | - |
197 | class PackageBaseTestCase(APITestCase): |
198 | |
199 | def assert_package(self, response, snap_id=None, name=None, |
200 | |
201 | === added file 'tests/api/cpi/test_recommendation.py' |
202 | --- tests/api/cpi/test_recommendation.py 1970-01-01 00:00:00 +0000 |
203 | +++ tests/api/cpi/test_recommendation.py 2016-07-07 19:50:23 +0000 |
204 | @@ -0,0 +1,35 @@ |
205 | +import requests |
206 | + |
207 | +from .helpers import ( |
208 | + CPI_ROOT_URL, |
209 | + APITestCase, |
210 | +) |
211 | + |
212 | + |
213 | +def get_recommendation(slug): |
214 | + url = '{}/api/v1/recommendations/{}'.format(CPI_ROOT_URL, slug) |
215 | + response = requests.get(url) |
216 | + return response |
217 | + |
218 | + |
219 | +class RecommendationsTestCase(APITestCase): |
220 | + |
221 | + def assert_recommendation(self, response, slug=None): |
222 | + body = response.json() |
223 | + # assert result data structure |
224 | + self.assertIn('applications', body) |
225 | + self.assertGreater(len(body['applications']), 0) |
226 | + self.assertIn('hotwords', body) |
227 | + self.assertIn('slug', body) |
228 | + if slug is not None: |
229 | + self.assertEqual(body['slug'], slug) |
230 | + |
231 | + def test_get_recommendation(self): |
232 | + response = get_recommendation('hello') |
233 | + self.assert_success(response) |
234 | + self.assert_recommendation(response, 'hello') |
235 | + |
236 | + def test_get_recommendation_not_found(self): |
237 | + response = get_recommendation('missing') |
238 | + self.assert_not_found( |
239 | + response, message="No such recommendation u'missing'") |
240 | |
241 | === modified file 'tests/api/cpi/test_search.py' |
242 | --- tests/api/cpi/test_search.py 2016-06-28 19:20:09 +0000 |
243 | +++ tests/api/cpi/test_search.py 2016-07-07 19:50:23 +0000 |
244 | @@ -1,24 +1,50 @@ |
245 | +from urllib.parse import urlencode |
246 | + |
247 | import requests |
248 | |
249 | from .helpers import ( |
250 | CPI_ROOT_URL, |
251 | + PRIVATE_SNAP_PACKAGE_NAME, |
252 | APITestCase, |
253 | + get_oauth_client, |
254 | ) |
255 | |
256 | |
257 | -def search(name=''): |
258 | - url = '{}/api/v1/search?q=name:{}'.format(CPI_ROOT_URL, name) |
259 | - response = requests.get(url) |
260 | - return response |
261 | - |
262 | - |
263 | class SearchTestCase(APITestCase): |
264 | + endpoint = '/api/v1/search' |
265 | |
266 | def setUp(self): |
267 | super(SearchTestCase, self).setUp() |
268 | self.name = 'hello' |
269 | |
270 | - def assert_body_success(self, response, name): |
271 | + def search(self, name=None, fields=None, auth=False): |
272 | + query_data = {} |
273 | + if name is not None: |
274 | + query_data['name'] = name |
275 | + |
276 | + query = {} |
277 | + if query_data: |
278 | + query['q'] = '&'.join( |
279 | + '{}:{}'.format(key, value) |
280 | + for key, value in query_data.items()) |
281 | + if fields: |
282 | + query['fields'] = fields |
283 | + |
284 | + return self._search(query=query, auth=auth) |
285 | + |
286 | + def _search(self, query=None, auth=False): |
287 | + url = '{}{}'.format(CPI_ROOT_URL, self.endpoint) |
288 | + if query: |
289 | + qs = urlencode(query) |
290 | + url += '?' + qs |
291 | + if auth: |
292 | + with get_oauth_client() as client: |
293 | + response = client.get(url) |
294 | + else: |
295 | + response = requests.get(url) |
296 | + return response |
297 | + |
298 | + def assert_results(self, response, name=None): |
299 | body = response.json() |
300 | # assert generic embedded data structure |
301 | self.assertIn('_embedded', body) |
302 | @@ -27,9 +53,40 @@ |
303 | self.assertGreater(len(body['_embedded']['clickindex:package']), 0) |
304 | # assert result data structure |
305 | item = body['_embedded']['clickindex:package'][0] |
306 | - self.assertIn(name, item['name']) |
307 | + if name is not None: |
308 | + self.assertIn(name, item['name']) |
309 | + |
310 | + def assert_no_results(self, response, name): |
311 | + body = response.json() |
312 | + embedded = body.get('_embedded', {}) |
313 | + results = embedded.get('clickindex:package', []) |
314 | + for item in results: |
315 | + self.assertNotIn(name, item['name']) |
316 | + |
317 | + def assert_recommendations(self, response): |
318 | + body = response.json() |
319 | + self.assertIn('_embedded', body) |
320 | + self.assertIn('clickindex:recommendation', body['_embedded']) |
321 | + # assert there is at least one result |
322 | + self.assertGreater( |
323 | + len(body['_embedded']['clickindex:recommendation']), 0) |
324 | |
325 | def test_search_by_name(self): |
326 | - response = search(name=self.name) |
327 | - self.assert_success(response) |
328 | - self.assert_body_success(response, self.name) |
329 | + response = self.search(name=self.name) |
330 | + self.assert_success(response) |
331 | + self.assert_results(response, self.name) |
332 | + |
333 | + def test_search_all(self): |
334 | + response = self.search() |
335 | + self.assert_success(response) |
336 | + self.assert_results(response) |
337 | + |
338 | + def test_search_private_not_authorized(self): |
339 | + response = self.search(name=PRIVATE_SNAP_PACKAGE_NAME) |
340 | + self.assert_success(response) |
341 | + self.assert_no_results(response, name=PRIVATE_SNAP_PACKAGE_NAME) |
342 | + |
343 | + def test_search_private(self): |
344 | + response = self.search(name=PRIVATE_SNAP_PACKAGE_NAME, auth=True) |
345 | + self.assert_success(response) |
346 | + self.assert_results(response, name=PRIVATE_SNAP_PACKAGE_NAME) |
347 | |
348 | === added file 'tests/api/cpi/test_snaps.py' |
349 | --- tests/api/cpi/test_snaps.py 1970-01-01 00:00:00 +0000 |
350 | +++ tests/api/cpi/test_snaps.py 2016-07-07 19:50:23 +0000 |
351 | @@ -0,0 +1,141 @@ |
352 | +import requests |
353 | + |
354 | +from .helpers import ( |
355 | + CPI_ROOT_URL, |
356 | + SNAP_PACKAGE_NAME, |
357 | + PRIVATE_SNAP_PACKAGE_NAME, |
358 | + APITestCase, |
359 | + get_oauth_client, |
360 | +) |
361 | +from .test_metadata import MetadataTestCase |
362 | +from .test_search import SearchTestCase |
363 | + |
364 | + |
365 | +def get_snap_detail(name, series='16', auth=False): |
366 | + url = '{}/api/v1/snaps/details/{}'.format(CPI_ROOT_URL, name) |
367 | + headers = {} |
368 | + if series: |
369 | + headers['X-Ubuntu-Series'] = series |
370 | + if auth: |
371 | + with get_oauth_client() as client: |
372 | + response = client.get(url, headers=headers) |
373 | + else: |
374 | + response = requests.get(url, headers=headers) |
375 | + return response |
376 | + |
377 | + |
378 | +class SnapDetailTestCase(APITestCase): |
379 | + |
380 | + def assert_snap_detail(self, response, name): |
381 | + body = response.json() |
382 | + fields = [ |
383 | + 'alias', |
384 | + 'allow_unauthenticated', |
385 | + 'anon_download_url', |
386 | + 'architecture', |
387 | + 'binary_filesize', |
388 | + 'blacklist_country_codes', |
389 | + 'changelog', |
390 | + 'channel', |
391 | + 'channels', |
392 | + 'click_framework', |
393 | + 'click_version', |
394 | + 'company_name', |
395 | + 'confinement', |
396 | + 'content', |
397 | + 'date_published', |
398 | + 'department', |
399 | + 'description', |
400 | + 'developer_id', |
401 | + 'developer_name', |
402 | + 'download_sha512', |
403 | + 'download_url', |
404 | + 'epoch', |
405 | + 'framework', |
406 | + 'icon_url', |
407 | + 'icon_urls', |
408 | + 'id', |
409 | + 'is_published', |
410 | + 'keywords', |
411 | + 'last_updated', |
412 | + 'license', |
413 | + 'name', |
414 | + 'origin', |
415 | + 'package_name', |
416 | + 'plugs', |
417 | + 'price', |
418 | + 'prices', |
419 | + 'promoted', |
420 | + 'publisher', |
421 | + 'ratings_average', |
422 | + 'release', |
423 | + 'revision', |
424 | + 'screenshot_url', |
425 | + 'screenshot_urls', |
426 | + 'slots', |
427 | + 'snap_id', |
428 | + 'status', |
429 | + 'summary', |
430 | + 'support_url', |
431 | + 'terms_of_service', |
432 | + 'title', |
433 | + 'version', |
434 | + 'video_urls', |
435 | + 'website', |
436 | + 'whitelist_country_codes', |
437 | + ] |
438 | + for field in fields: |
439 | + self.assertIn(field, body) |
440 | + self.assertEqual(body['package_name'], name) |
441 | + |
442 | + def test_get_snap_detail(self): |
443 | + response = get_snap_detail(SNAP_PACKAGE_NAME) |
444 | + self.assert_success(response) |
445 | + self.assert_snap_detail(response, SNAP_PACKAGE_NAME) |
446 | + |
447 | + def test_get_snap_detail_for_private_package(self): |
448 | + response = get_snap_detail(PRIVATE_SNAP_PACKAGE_NAME, auth=True) |
449 | + self.assert_success(response) |
450 | + self.assert_snap_detail(response, PRIVATE_SNAP_PACKAGE_NAME) |
451 | + |
452 | + def test_get_snap_detail_without_series(self): |
453 | + response = get_snap_detail(SNAP_PACKAGE_NAME, series=None) |
454 | + self.assert_bad_request( |
455 | + response, message='X-Ubuntu-Series header is required.') |
456 | + |
457 | + def test_get_snap_detail_unauthorized(self): |
458 | + response = get_snap_detail(PRIVATE_SNAP_PACKAGE_NAME, auth=False) |
459 | + self.assert_not_found(response, message='No such package') |
460 | + |
461 | + def test_get_snap_detail_not_found(self): |
462 | + response = get_snap_detail('missing') |
463 | + self.assert_not_found(response, message='No such package') |
464 | + |
465 | + |
466 | +class SnapMetadataTestCase(MetadataTestCase): |
467 | + endpoint = '/api/v1/snaps/metadata' |
468 | + |
469 | + |
470 | +class SnapSearchTestCase(SearchTestCase): |
471 | + endpoint = '/api/v1/snaps/search' |
472 | + |
473 | + def search(self, name=None, fields=None, private=False, auth=False): |
474 | + query = {} |
475 | + if name is not None: |
476 | + query['name'] = name |
477 | + if private: |
478 | + query['private'] = 'true' |
479 | + if fields is not None: |
480 | + query['fields'] = fields |
481 | + return super(SnapSearchTestCase, self)._search( |
482 | + query=query, auth=auth) |
483 | + |
484 | + def test_search_private_not_authorized(self): |
485 | + response = self.search(name=PRIVATE_SNAP_PACKAGE_NAME, private=True) |
486 | + self.assert_unauthorized(response) |
487 | + |
488 | + def test_search_private(self): |
489 | + response = self.search(name=PRIVATE_SNAP_PACKAGE_NAME, private=True, |
490 | + auth=True) |
491 | + self.assert_success(response) |
492 | + self.assert_results(response, name=PRIVATE_SNAP_PACKAGE_NAME) |
LGTM, with a suggestion.