Merge lp:~ricardokirkner/ols-store-tests/more-acceptance-tests into lp:~ubuntuone-pqm-team/ols-store-tests/store-acceptance-tests
- more-acceptance-tests
- Merge into store-acceptance-tests
Proposed by
Ricardo Kirkner
Status: | Merged |
---|---|
Merged at revision: | 19 |
Proposed branch: | lp:~ricardokirkner/ols-store-tests/more-acceptance-tests |
Merge into: | lp:~ubuntuone-pqm-team/ols-store-tests/store-acceptance-tests |
Diff against target: |
765 lines (+705/-3) 8 files modified
Makefile (+1/-1) tests/api/cpi/helpers.py (+40/-2) tests/api/cpi/test_channel.py (+56/-0) tests/api/cpi/test_department.py (+82/-0) tests/api/cpi/test_framework.py (+60/-0) tests/api/cpi/test_highlight.py (+65/-0) tests/api/cpi/test_metadata.py (+176/-0) tests/api/cpi/test_package.py (+225/-0) |
To merge this branch: | bzr merge lp:~ricardokirkner/ols-store-tests/more-acceptance-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Tait (community) | Approve | ||
Matias Bordese (community) | Approve | ||
Review via email: mp+299342@code.launchpad.net |
Commit message
added basic smoke tests for cpi APIs
added tests for following endpoints:
- package
- click metadata
- snap metadata
- highlights
- frameworks
- departments
- channels
Description of the change
To post a comment you must log in.
- 25. By Ricardo Kirkner
-
refactor tests to use constant
- 26. By Ricardo Kirkner
-
fixes per review
Revision history for this message
Ricardo Kirkner (ricardokirkner) : | # |
Revision history for this message
James Tait (jamestait) wrote : | # |
Yep, looks good to me. I've made a couple of suggestions, but they are just that - this is good to land as-is, IMO.
As an aside, we're hard-coding some assumptions about URL structure (e.g. appending click-package-name or snap-package-id to /api/v1/package), which may not hold true longer-term. I think that's probably OK for these tests, since we *want* to know if URLs change, and I don't think *any* of the clients are actually parsing the links from the root resource anyway.
review:
Approve
- 27. By Ricardo Kirkner
-
fixes per review
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2016-07-05 14:42:00 +0000 | |||
3 | +++ Makefile 2016-07-07 18:53:29 +0000 | |||
4 | @@ -3,7 +3,7 @@ | |||
5 | 3 | PYTHON = python3 | 3 | PYTHON = python3 |
6 | 4 | VM = store-acceptance-tests | 4 | VM = store-acceptance-tests |
7 | 5 | VSSH = ols-vms shell ${VM} | 5 | VSSH = ols-vms shell ${VM} |
9 | 6 | TEST_TARGET := discover tests/api | 6 | TEST_TARGET ?= discover tests/api |
10 | 7 | 7 | ||
11 | 8 | vm-setup: ols-vms.conf | 8 | vm-setup: ols-vms.conf |
12 | 9 | if [ `ols-vms status ${VM}` = 'RUNNING' ] ; then ols-vms stop ${VM} ; fi | 9 | if [ `ols-vms status ${VM}` = 'RUNNING' ] ; then ols-vms stop ${VM} ; fi |
13 | 10 | 10 | ||
14 | === modified file 'tests/api/cpi/helpers.py' | |||
15 | --- tests/api/cpi/helpers.py 2016-06-28 19:20:09 +0000 | |||
16 | +++ tests/api/cpi/helpers.py 2016-07-07 18:53:29 +0000 | |||
17 | @@ -1,14 +1,52 @@ | |||
18 | 1 | import getpass | ||
19 | 1 | import os | 2 | import os |
20 | 2 | import unittest | 3 | import unittest |
21 | 3 | 4 | ||
22 | 4 | 5 | ||
23 | 5 | CPI_ROOT_URL = os.getenv( | 6 | CPI_ROOT_URL = os.getenv( |
24 | 6 | 'CPI_ROOT_URL', 'https://search.apps.staging.ubuntu.com') | 7 | 'CPI_ROOT_URL', 'https://search.apps.staging.ubuntu.com') |
25 | 8 | SSO_ROOT_URL = os.getenv( | ||
26 | 9 | 'SSO_ROOT_URL', 'https://login.staging.ubuntu.com') | ||
27 | 10 | |||
28 | 11 | TEST_USER_EMAIL = os.getenv('TEST_USER_EMAIL', None) | ||
29 | 12 | if TEST_USER_EMAIL is None: | ||
30 | 13 | TEST_USER_EMAIL = input('Test user email: ') | ||
31 | 14 | TEST_USER_PASSWORD = os.getenv('TEST_USER_PASSWORD', None) | ||
32 | 15 | if TEST_USER_PASSWORD is None: | ||
33 | 16 | TEST_USER_PASSWORD = getpass.getpass('Test user password: ') | ||
34 | 17 | |||
35 | 18 | CLICK_PACKAGE_NAME = 'demo37.ricardokirkner' | ||
36 | 19 | SNAP_PACKAGE_NAME = 'hello-world' | ||
37 | 20 | SNAP_PACKAGE_CHANNEL = 'stable' | ||
38 | 21 | SNAP_PACKAGE_REVISION = 6 | ||
39 | 22 | SNAP_PACKAGE_SNAP_ID = 'JtwEnisYi8Mmk51vNLZPSOwSOFLwGdhs' | ||
40 | 23 | PRIVATE_SNAP_PACKAGE_NAME = 'ricardokirkner-test1' | ||
41 | 24 | PRIVATE_SNAP_PACKAGE_SNAP_ID = 'qrwoXeYOXAZla2ga3XtBSogV1vAUKqZh' | ||
42 | 25 | PRIVATE_SNAP_PACKAGE_CHANNEL = 'stable' | ||
43 | 26 | PRIVATE_SNAP_PACKAGE_REVISION = 1 | ||
44 | 7 | 27 | ||
45 | 8 | 28 | ||
46 | 9 | class APITestCase(unittest.TestCase): | 29 | class APITestCase(unittest.TestCase): |
47 | 10 | 30 | ||
49 | 11 | def assert_success(self, response): | 31 | def assert_success(self, response, content_type='application/hal+json'): |
50 | 12 | self.assertEqual(response.status_code, 200) | 32 | self.assertEqual(response.status_code, 200) |
51 | 13 | self.assertEqual(response.headers['Content-Type'], | 33 | self.assertEqual(response.headers['Content-Type'], |
53 | 14 | 'application/hal+json') | 34 | content_type) |
54 | 35 | |||
55 | 36 | def assert_not_found(self, response, message=None): | ||
56 | 37 | self.assertEqual(response.status_code, 404) | ||
57 | 38 | self.assertEqual(response.headers['Content-Type'], | ||
58 | 39 | 'application/hal+json') | ||
59 | 40 | self.assertEqual(response.json(), { | ||
60 | 41 | 'result': 'error', | ||
61 | 42 | 'errors': [message or 'Not found'], | ||
62 | 43 | }) | ||
63 | 44 | |||
64 | 45 | def assert_bad_request(self, response, message): | ||
65 | 46 | self.assertEqual(response.status_code, 400) | ||
66 | 47 | self.assertEqual(response.headers['Content-Type'], | ||
67 | 48 | 'application/hal+json') | ||
68 | 49 | self.assertEqual(response.json(), { | ||
69 | 50 | 'result': 'error', | ||
70 | 51 | 'errors': [message], | ||
71 | 52 | }) | ||
72 | 15 | 53 | ||
73 | === added file 'tests/api/cpi/test_channel.py' | |||
74 | --- tests/api/cpi/test_channel.py 1970-01-01 00:00:00 +0000 | |||
75 | +++ tests/api/cpi/test_channel.py 2016-07-07 18:53:29 +0000 | |||
76 | @@ -0,0 +1,56 @@ | |||
77 | 1 | import requests | ||
78 | 2 | |||
79 | 3 | from .helpers import ( | ||
80 | 4 | CPI_ROOT_URL, | ||
81 | 5 | APITestCase, | ||
82 | 6 | ) | ||
83 | 7 | |||
84 | 8 | |||
85 | 9 | def get_channels(): | ||
86 | 10 | url = '{}/api/v1/channels'.format(CPI_ROOT_URL) | ||
87 | 11 | response = requests.get(url) | ||
88 | 12 | return response | ||
89 | 13 | |||
90 | 14 | |||
91 | 15 | def get_channel(name): | ||
92 | 16 | url = '{}/api/v1/channels/{}'.format(CPI_ROOT_URL, name) | ||
93 | 17 | response = requests.get(url) | ||
94 | 18 | return response | ||
95 | 19 | |||
96 | 20 | |||
97 | 21 | class ChannelsTestCase(APITestCase): | ||
98 | 22 | |||
99 | 23 | def assert_channels(self, response): | ||
100 | 24 | body = response.json() | ||
101 | 25 | # assert generic embedded data structure | ||
102 | 26 | self.assertIn('_embedded', body) | ||
103 | 27 | self.assertIn('clickindex:channel', body['_embedded']) | ||
104 | 28 | # assert there is at least one result | ||
105 | 29 | self.assertGreater(len(body['_embedded']['clickindex:channel']), 0) | ||
106 | 30 | # assert result data structure | ||
107 | 31 | channel_data = body['_embedded']['clickindex:channel'] | ||
108 | 32 | channel_names = ('stable', 'candidate', 'beta', 'edge') | ||
109 | 33 | for item, name in zip(channel_data, channel_names): | ||
110 | 34 | self._assert_channel_data(item, name=name) | ||
111 | 35 | |||
112 | 36 | def assert_channel(self, response, name): | ||
113 | 37 | body = response.json() | ||
114 | 38 | # assert result data structure | ||
115 | 39 | self._assert_channel_data(body, name=name) | ||
116 | 40 | |||
117 | 41 | def _assert_channel_data(self, data, name=None): | ||
118 | 42 | self.assertIn('promotion_order', data) | ||
119 | 43 | self.assertIn('display_name', data) | ||
120 | 44 | self.assertIn('name', data) | ||
121 | 45 | if name is not None: | ||
122 | 46 | self.assertEqual(data['name'], name) | ||
123 | 47 | |||
124 | 48 | def test_get_channels(self): | ||
125 | 49 | response = get_channels() | ||
126 | 50 | self.assert_success(response) | ||
127 | 51 | self.assert_channels(response) | ||
128 | 52 | |||
129 | 53 | def test_get_channel(self): | ||
130 | 54 | response = get_channel('edge') | ||
131 | 55 | self.assert_success(response) | ||
132 | 56 | self.assert_channel(response, 'edge') | ||
133 | 0 | 57 | ||
134 | === added file 'tests/api/cpi/test_department.py' | |||
135 | --- tests/api/cpi/test_department.py 1970-01-01 00:00:00 +0000 | |||
136 | +++ tests/api/cpi/test_department.py 2016-07-07 18:53:29 +0000 | |||
137 | @@ -0,0 +1,82 @@ | |||
138 | 1 | import requests | ||
139 | 2 | |||
140 | 3 | from .helpers import ( | ||
141 | 4 | CPI_ROOT_URL, | ||
142 | 5 | APITestCase, | ||
143 | 6 | ) | ||
144 | 7 | |||
145 | 8 | |||
146 | 9 | def get_departments(include_subdepartments=None): | ||
147 | 10 | url = '{}/api/v1/departments'.format(CPI_ROOT_URL) | ||
148 | 11 | if include_subdepartments is True: | ||
149 | 12 | url += '?include_subdepartments=true' | ||
150 | 13 | elif include_subdepartments is False: | ||
151 | 14 | url += '?include_subdepartments=false' | ||
152 | 15 | response = requests.get(url) | ||
153 | 16 | return response | ||
154 | 17 | |||
155 | 18 | |||
156 | 19 | def get_department(slug): | ||
157 | 20 | url = '{}/api/v1/departments/{}'.format(CPI_ROOT_URL, slug) | ||
158 | 21 | response = requests.get(url) | ||
159 | 22 | return response | ||
160 | 23 | |||
161 | 24 | |||
162 | 25 | class DepartmentsTestCase(APITestCase): | ||
163 | 26 | |||
164 | 27 | def assert_departments(self, response): | ||
165 | 28 | body = response.json() | ||
166 | 29 | # assert generic embedded data structure | ||
167 | 30 | self.assertIn('_embedded', body) | ||
168 | 31 | self.assertIn('clickindex:department', body['_embedded']) | ||
169 | 32 | # assert there is at least one result | ||
170 | 33 | self.assertGreater(len(body['_embedded']['clickindex:department']), 0) | ||
171 | 34 | # assert result data structure | ||
172 | 35 | item = body['_embedded']['clickindex:department'][0] | ||
173 | 36 | self._assert_department_data(item) | ||
174 | 37 | |||
175 | 38 | def assert_department(self, response, slug): | ||
176 | 39 | body = response.json() | ||
177 | 40 | # assert result data structure | ||
178 | 41 | self._assert_department_data(body, slug=slug) | ||
179 | 42 | |||
180 | 43 | def _assert_department_data(self, data, slug=None): | ||
181 | 44 | self.assertIn('has_children', data) | ||
182 | 45 | self.assertIn('name', data) | ||
183 | 46 | self.assertIn('slug', data) | ||
184 | 47 | if slug is not None: | ||
185 | 48 | self.assertEqual(data['slug'], slug) | ||
186 | 49 | |||
187 | 50 | def find_subdepartment(self, response, department, slug): | ||
188 | 51 | body = response.json() | ||
189 | 52 | for item in body.get('_embedded', {}).get('clickindex:department', []): | ||
190 | 53 | department_match = item.get('slug') == department | ||
191 | 54 | if department_match and item.get('has_children', False): | ||
192 | 55 | for subitem in item.get('_embedded', {}).get( | ||
193 | 56 | 'clickindex:department', []): | ||
194 | 57 | if subitem.get('slug') == slug: | ||
195 | 58 | return subitem | ||
196 | 59 | |||
197 | 60 | def test_get_departments(self): | ||
198 | 61 | response = get_departments() | ||
199 | 62 | self.assert_success(response) | ||
200 | 63 | self.assert_departments(response) | ||
201 | 64 | |||
202 | 65 | def test_get_department(self): | ||
203 | 66 | response = get_department(slug='games') | ||
204 | 67 | self.assert_success(response) | ||
205 | 68 | self.assert_department(response, slug='games') | ||
206 | 69 | |||
207 | 70 | def test_include_subdepartments_by_default(self): | ||
208 | 71 | response = get_departments() | ||
209 | 72 | self.assert_success(response) | ||
210 | 73 | department = self.find_subdepartment( | ||
211 | 74 | response, 'games', slug='fps') | ||
212 | 75 | self.assertIsNotNone(department) | ||
213 | 76 | self._assert_department_data(department, slug='fps') | ||
214 | 77 | |||
215 | 78 | def test_exclude_subdepartments(self): | ||
216 | 79 | response = get_departments(include_subdepartments=False) | ||
217 | 80 | self.assert_success(response) | ||
218 | 81 | department = self.find_subdepartment(response, 'games', slug='fps') | ||
219 | 82 | self.assertIsNone(department) | ||
220 | 0 | 83 | ||
221 | === added file 'tests/api/cpi/test_framework.py' | |||
222 | --- tests/api/cpi/test_framework.py 1970-01-01 00:00:00 +0000 | |||
223 | +++ tests/api/cpi/test_framework.py 2016-07-07 18:53:29 +0000 | |||
224 | @@ -0,0 +1,60 @@ | |||
225 | 1 | import requests | ||
226 | 2 | |||
227 | 3 | from .helpers import ( | ||
228 | 4 | CPI_ROOT_URL, | ||
229 | 5 | APITestCase, | ||
230 | 6 | ) | ||
231 | 7 | |||
232 | 8 | |||
233 | 9 | def get_frameworks(): | ||
234 | 10 | url = '{}/api/v1/frameworks'.format(CPI_ROOT_URL) | ||
235 | 11 | response = requests.get(url) | ||
236 | 12 | return response | ||
237 | 13 | |||
238 | 14 | |||
239 | 15 | def get_framework(name): | ||
240 | 16 | url = '{}/api/v1/frameworks/{}'.format(CPI_ROOT_URL, name) | ||
241 | 17 | response = requests.get(url) | ||
242 | 18 | return response | ||
243 | 19 | |||
244 | 20 | |||
245 | 21 | class FrameworksTestCase(APITestCase): | ||
246 | 22 | |||
247 | 23 | def assert_frameworks(self, response): | ||
248 | 24 | body = response.json() | ||
249 | 25 | # assert generic embedded data structure | ||
250 | 26 | self.assertIn('_embedded', body) | ||
251 | 27 | self.assertIn('clickindex:framework', body['_embedded']) | ||
252 | 28 | # assert there is at least one result | ||
253 | 29 | self.assertGreater(len(body['_embedded']['clickindex:framework']), 0) | ||
254 | 30 | # assert result data structure | ||
255 | 31 | item = body['_embedded']['clickindex:framework'][0] | ||
256 | 32 | self._assert_framework_data(item) | ||
257 | 33 | |||
258 | 34 | def assert_framework(self, response, name): | ||
259 | 35 | body = response.json() | ||
260 | 36 | # assert result data structure | ||
261 | 37 | self._assert_framework_data(body, name=name) | ||
262 | 38 | |||
263 | 39 | def _assert_framework_data(self, data, name=None): | ||
264 | 40 | self.assertIn('enable_unauthenticated_downloads', data) | ||
265 | 41 | self.assertIn('policy_vendor', data) | ||
266 | 42 | self.assertIn('policy_version', data) | ||
267 | 43 | self.assertIn('state', data) | ||
268 | 44 | self.assertIn('name', data) | ||
269 | 45 | if name is not None: | ||
270 | 46 | self.assertEqual(data['name'], name) | ||
271 | 47 | |||
272 | 48 | def test_get_frameworks(self): | ||
273 | 49 | response = get_frameworks() | ||
274 | 50 | self.assert_success(response) | ||
275 | 51 | self.assert_frameworks(response) | ||
276 | 52 | |||
277 | 53 | def test_get_framework(self): | ||
278 | 54 | response = get_framework(name='ubuntu-sdk-15.04') | ||
279 | 55 | self.assert_success(response) | ||
280 | 56 | self.assert_framework(response, name='ubuntu-sdk-15.04') | ||
281 | 57 | |||
282 | 58 | def test_get_framework_not_found(self): | ||
283 | 59 | response = get_framework(name='invalid') | ||
284 | 60 | self.assert_not_found(response, message='No such framework') | ||
285 | 0 | 61 | ||
286 | === added file 'tests/api/cpi/test_highlight.py' | |||
287 | --- tests/api/cpi/test_highlight.py 1970-01-01 00:00:00 +0000 | |||
288 | +++ tests/api/cpi/test_highlight.py 2016-07-07 18:53:29 +0000 | |||
289 | @@ -0,0 +1,65 @@ | |||
290 | 1 | import requests | ||
291 | 2 | |||
292 | 3 | from .helpers import ( | ||
293 | 4 | CPI_ROOT_URL, | ||
294 | 5 | APITestCase, | ||
295 | 6 | ) | ||
296 | 7 | |||
297 | 8 | |||
298 | 9 | def get_highlights(): | ||
299 | 10 | url = '{}/api/v1/highlights'.format(CPI_ROOT_URL) | ||
300 | 11 | response = requests.get(url) | ||
301 | 12 | return response | ||
302 | 13 | |||
303 | 14 | |||
304 | 15 | def get_highlight(slug): | ||
305 | 16 | url = '{}/api/v1/highlights/{}'.format(CPI_ROOT_URL, slug) | ||
306 | 17 | response = requests.get(url) | ||
307 | 18 | return response | ||
308 | 19 | |||
309 | 20 | |||
310 | 21 | class HighlightsTestCase(APITestCase): | ||
311 | 22 | |||
312 | 23 | def assert_highlights(self, response): | ||
313 | 24 | body = response.json() | ||
314 | 25 | # assert generic embedded data structure | ||
315 | 26 | self.assertIn('_embedded', body) | ||
316 | 27 | self.assertIn('clickindex:highlight', body['_embedded']) | ||
317 | 28 | # assert there is at least one result | ||
318 | 29 | self.assertGreater(len(body['_embedded']['clickindex:highlight']), 0) | ||
319 | 30 | # assert result data structure | ||
320 | 31 | item = body['_embedded']['clickindex:highlight'][0] | ||
321 | 32 | self._assert_highlight_data(item) | ||
322 | 33 | |||
323 | 34 | def assert_highlight(self, response, slug): | ||
324 | 35 | body = response.json() | ||
325 | 36 | # assert result data structure | ||
326 | 37 | self._assert_highlight_data(body, slug=slug) | ||
327 | 38 | |||
328 | 39 | def _assert_highlight_data(self, data, slug=None): | ||
329 | 40 | self.assertIn('name', data) | ||
330 | 41 | self.assertIn('description', data) | ||
331 | 42 | self.assertIn('country_code', data) | ||
332 | 43 | self.assertIn('slug', data) | ||
333 | 44 | if slug is not None: | ||
334 | 45 | self.assertEqual(data['slug'], slug) | ||
335 | 46 | |||
336 | 47 | def test_get_highlights(self): | ||
337 | 48 | response = get_highlights() | ||
338 | 49 | self.assert_success(response) | ||
339 | 50 | self.assert_highlights(response) | ||
340 | 51 | |||
341 | 52 | def test_get_highlight(self): | ||
342 | 53 | response = get_highlight(slug='top-apps') | ||
343 | 54 | self.assert_success(response) | ||
344 | 55 | self.assert_highlight(response, slug='top-apps') | ||
345 | 56 | |||
346 | 57 | def test_get_highlight_not_found(self): | ||
347 | 58 | response = get_highlight(slug='missing') | ||
348 | 59 | self.assert_not_found( | ||
349 | 60 | response, message="No such highlight u'missing'") | ||
350 | 61 | |||
351 | 62 | def test_get_highlight_invalid_slug(self): | ||
352 | 63 | response = get_highlight(slug='top-apps:en') | ||
353 | 64 | self.assert_not_found( | ||
354 | 65 | response, message="No such highlight u'top-apps:en'") | ||
355 | 0 | 66 | ||
356 | === added file 'tests/api/cpi/test_metadata.py' | |||
357 | --- tests/api/cpi/test_metadata.py 1970-01-01 00:00:00 +0000 | |||
358 | +++ tests/api/cpi/test_metadata.py 2016-07-07 18:53:29 +0000 | |||
359 | @@ -0,0 +1,176 @@ | |||
360 | 1 | import json | ||
361 | 2 | |||
362 | 3 | import requests | ||
363 | 4 | |||
364 | 5 | from .helpers import ( | ||
365 | 6 | CLICK_PACKAGE_NAME, | ||
366 | 7 | CPI_ROOT_URL, | ||
367 | 8 | SNAP_PACKAGE_SNAP_ID, | ||
368 | 9 | APITestCase, | ||
369 | 10 | ) | ||
370 | 11 | |||
371 | 12 | |||
372 | 13 | def get_click_metadata(names=None, method='get'): | ||
373 | 14 | url = '{}/api/v1/click-metadata'.format(CPI_ROOT_URL) | ||
374 | 15 | if method == 'get': | ||
375 | 16 | if names is not None: | ||
376 | 17 | url += '?' + '&'.join('name=' + name for name in names) | ||
377 | 18 | response = requests.get(url) | ||
378 | 19 | elif method == 'post': | ||
379 | 20 | data = {} | ||
380 | 21 | if names is not None: | ||
381 | 22 | data['name'] = names | ||
382 | 23 | response = requests.post(url, data=json.dumps(data), | ||
383 | 24 | headers={'Content-Type': 'application/json'}) | ||
384 | 25 | else: | ||
385 | 26 | raise ValueError('invalid method: %s' % method) | ||
386 | 27 | return response | ||
387 | 28 | |||
388 | 29 | |||
389 | 30 | class ClickMetadataTestCase(APITestCase): | ||
390 | 31 | |||
391 | 32 | def assert_click_metadata(self, response, name): | ||
392 | 33 | body = response.json() | ||
393 | 34 | self.assertEqual(len(body), 1) | ||
394 | 35 | # assert result data structure | ||
395 | 36 | self._assert_package_data(body[0], name=name) | ||
396 | 37 | |||
397 | 38 | def _assert_package_data(self, data, name): | ||
398 | 39 | self.assertIn('anon_download_url', data) | ||
399 | 40 | self.assertIn('binary_filesize', data) | ||
400 | 41 | self.assertIn('changelog', data) | ||
401 | 42 | self.assertIn('channel', data) | ||
402 | 43 | self.assertIn('content', data) | ||
403 | 44 | self.assertIn('department', data) | ||
404 | 45 | self.assertIn('download_sha512', data) | ||
405 | 46 | self.assertIn('download_url', data) | ||
406 | 47 | self.assertIn('icon_url', data) | ||
407 | 48 | self.assertIn('name', data) | ||
408 | 49 | self.assertIn('origin', data) | ||
409 | 50 | self.assertIn('revision', data) | ||
410 | 51 | self.assertIn('status', data) | ||
411 | 52 | self.assertIn('summary', data) | ||
412 | 53 | self.assertIn('title', data) | ||
413 | 54 | self.assertIn('version', data) | ||
414 | 55 | self.assertEqual(data['name'], name) | ||
415 | 56 | |||
416 | 57 | def test_get_click_metadata(self): | ||
417 | 58 | response = get_click_metadata(names=[CLICK_PACKAGE_NAME]) | ||
418 | 59 | self.assert_success(response, content_type='application/json') | ||
419 | 60 | self.assert_click_metadata(response, name=CLICK_PACKAGE_NAME) | ||
420 | 61 | |||
421 | 62 | def test_get_click_metadata_no_packages(self): | ||
422 | 63 | response = get_click_metadata() | ||
423 | 64 | self.assert_bad_request( | ||
424 | 65 | response, | ||
425 | 66 | message='Package names must be specified in parameter "name".') | ||
426 | 67 | |||
427 | 68 | def test_get_click_metadata_via_post(self): | ||
428 | 69 | response = get_click_metadata( | ||
429 | 70 | names=[CLICK_PACKAGE_NAME], method='post') | ||
430 | 71 | self.assert_success(response, content_type='application/json') | ||
431 | 72 | self.assert_click_metadata(response, name=CLICK_PACKAGE_NAME) | ||
432 | 73 | |||
433 | 74 | def test_get_click_metadata_no_packages_via_post(self): | ||
434 | 75 | response = get_click_metadata(method='post') | ||
435 | 76 | self.assert_bad_request( | ||
436 | 77 | response, | ||
437 | 78 | message='Package names must be specified in parameter "name".') | ||
438 | 79 | |||
439 | 80 | def test_get_click_metadata_bad_request_via_post(self): | ||
440 | 81 | url = '{}/api/v1/click-metadata'.format(CPI_ROOT_URL) | ||
441 | 82 | response = requests.post(url, data='invalid data', | ||
442 | 83 | headers={'Content-Type': 'application/json'}) | ||
443 | 84 | self.assert_bad_request( | ||
444 | 85 | response, message='No JSON object could be decoded') | ||
445 | 86 | |||
446 | 87 | |||
447 | 88 | def get_metadata(snaps=None, fields=None): | ||
448 | 89 | url = '{}/api/v1/metadata'.format(CPI_ROOT_URL) | ||
449 | 90 | data = {} | ||
450 | 91 | if snaps is not None: | ||
451 | 92 | data['snaps'] = snaps | ||
452 | 93 | if fields is not None: | ||
453 | 94 | data['fields'] = fields | ||
454 | 95 | response = requests.post(url, data=json.dumps(data), | ||
455 | 96 | headers={'Content-Type': 'application/json'}) | ||
456 | 97 | return response | ||
457 | 98 | |||
458 | 99 | |||
459 | 100 | class MetadataTestCase(APITestCase): | ||
460 | 101 | |||
461 | 102 | def assert_metadata(self, response, fields=None, snap_id=None): | ||
462 | 103 | body = response.json() | ||
463 | 104 | # assert generic embedded data structure | ||
464 | 105 | self.assertIn('_embedded', body) | ||
465 | 106 | self.assertIn('clickindex:package', body['_embedded']) | ||
466 | 107 | # assert there is at least one result | ||
467 | 108 | self.assertGreater(len(body['_embedded']['clickindex:package']), 0) | ||
468 | 109 | # assert result data structure | ||
469 | 110 | item = body['_embedded']['clickindex:package'][0] | ||
470 | 111 | self._assert_package_data(item, fields=fields, snap_id=snap_id) | ||
471 | 112 | |||
472 | 113 | def _assert_package_data(self, data, fields=None, snap_id=None): | ||
473 | 114 | if fields is None: | ||
474 | 115 | fields = [ | ||
475 | 116 | 'anon_download_url', | ||
476 | 117 | 'binary_filesize', | ||
477 | 118 | 'changelog', | ||
478 | 119 | 'channel', | ||
479 | 120 | 'content', | ||
480 | 121 | 'department', | ||
481 | 122 | 'download_sha512', | ||
482 | 123 | 'download_url', | ||
483 | 124 | 'icon_url', | ||
484 | 125 | 'name', | ||
485 | 126 | 'origin', | ||
486 | 127 | 'package_name', | ||
487 | 128 | 'plugs', | ||
488 | 129 | 'revision', | ||
489 | 130 | 'slots', | ||
490 | 131 | 'snap_id', | ||
491 | 132 | 'status', | ||
492 | 133 | 'summary', | ||
493 | 134 | 'title', | ||
494 | 135 | 'version', | ||
495 | 136 | ] | ||
496 | 137 | expected = fields + ['_links'] | ||
497 | 138 | self.assertEqual(set(data.keys()), set(expected)) | ||
498 | 139 | if snap_id is not None: | ||
499 | 140 | self.assertEqual(data['snap_id'], snap_id) | ||
500 | 141 | |||
501 | 142 | def assert_no_results(self, response): | ||
502 | 143 | body = response.json() | ||
503 | 144 | self._assert_package_data(body, fields=[]) | ||
504 | 145 | |||
505 | 146 | def test_get_metadata(self): | ||
506 | 147 | response = get_metadata( | ||
507 | 148 | snaps=[{'snap_id': SNAP_PACKAGE_SNAP_ID}]) | ||
508 | 149 | self.assert_success(response) | ||
509 | 150 | self.assert_metadata(response, snap_id=SNAP_PACKAGE_SNAP_ID) | ||
510 | 151 | |||
511 | 152 | def test_get_metadata_no_snaps(self): | ||
512 | 153 | response = get_metadata() | ||
513 | 154 | self.assert_bad_request( | ||
514 | 155 | response, | ||
515 | 156 | message='Packages must be specified in parameter "snaps".') | ||
516 | 157 | |||
517 | 158 | def test_get_metadata_missing_snap_id(self): | ||
518 | 159 | response = get_metadata(snaps=[{}]) | ||
519 | 160 | self.assert_bad_request( | ||
520 | 161 | response, message='Missing snap_id for snap.') | ||
521 | 162 | |||
522 | 163 | def test_get_metadata_filter_fields(self): | ||
523 | 164 | fields = ['channel', 'revision', 'snap_id'] | ||
524 | 165 | response = get_metadata( | ||
525 | 166 | snaps=[{'snap_id': SNAP_PACKAGE_SNAP_ID}], | ||
526 | 167 | fields=fields) | ||
527 | 168 | self.assert_success(response) | ||
528 | 169 | self.assert_metadata( | ||
529 | 170 | response, fields=fields, snap_id=SNAP_PACKAGE_SNAP_ID) | ||
530 | 171 | |||
531 | 172 | def test_get_metadata_invalid_package(self): | ||
532 | 173 | response = get_metadata( | ||
533 | 174 | snaps=[{'snap_id': 'invalid'}]) | ||
534 | 175 | self.assert_success(response) | ||
535 | 176 | self.assert_no_results(response) | ||
536 | 0 | 177 | ||
537 | === added file 'tests/api/cpi/test_package.py' | |||
538 | --- tests/api/cpi/test_package.py 1970-01-01 00:00:00 +0000 | |||
539 | +++ tests/api/cpi/test_package.py 2016-07-07 18:53:29 +0000 | |||
540 | @@ -0,0 +1,225 @@ | |||
541 | 1 | import json | ||
542 | 2 | |||
543 | 3 | import requests | ||
544 | 4 | import requests_oauthlib | ||
545 | 5 | |||
546 | 6 | from .helpers import ( | ||
547 | 7 | CPI_ROOT_URL, | ||
548 | 8 | PRIVATE_SNAP_PACKAGE_CHANNEL, | ||
549 | 9 | PRIVATE_SNAP_PACKAGE_NAME, | ||
550 | 10 | PRIVATE_SNAP_PACKAGE_REVISION, | ||
551 | 11 | PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
552 | 12 | SNAP_PACKAGE_CHANNEL, | ||
553 | 13 | SNAP_PACKAGE_NAME, | ||
554 | 14 | SNAP_PACKAGE_REVISION, | ||
555 | 15 | SSO_ROOT_URL, | ||
556 | 16 | TEST_USER_EMAIL, | ||
557 | 17 | TEST_USER_PASSWORD, | ||
558 | 18 | APITestCase, | ||
559 | 19 | ) | ||
560 | 20 | |||
561 | 21 | |||
562 | 22 | def get_package(package_id, channel=None, revision=None, auth=False): | ||
563 | 23 | url = '{}/api/v1/package/{}'.format(CPI_ROOT_URL, package_id) | ||
564 | 24 | if channel is not None: | ||
565 | 25 | url += '/{}'.format(channel) | ||
566 | 26 | elif revision is not None: | ||
567 | 27 | url += '/{}'.format(revision) | ||
568 | 28 | if auth: | ||
569 | 29 | # use client as contextmanager to ensure ssl socket is closed | ||
570 | 30 | # afterwards | ||
571 | 31 | with get_oauth_client() as client: | ||
572 | 32 | response = client.get(url) | ||
573 | 33 | else: | ||
574 | 34 | response = requests.get(url) | ||
575 | 35 | return response | ||
576 | 36 | |||
577 | 37 | |||
578 | 38 | def get_oauth_token_data(email, password): | ||
579 | 39 | url = '{}/api/v2/tokens/oauth'.format(SSO_ROOT_URL) | ||
580 | 40 | data = { | ||
581 | 41 | 'email': email, | ||
582 | 42 | 'password': password, | ||
583 | 43 | 'token_name': 'store-acceptance-tests', | ||
584 | 44 | } | ||
585 | 45 | headers = { | ||
586 | 46 | 'Content-Type': 'application/json', | ||
587 | 47 | 'Accept': 'application/json', | ||
588 | 48 | } | ||
589 | 49 | response = requests.post(url, data=json.dumps(data), headers=headers) | ||
590 | 50 | assert response.ok, 'Failed to get oauth token: %r' % response.content | ||
591 | 51 | |||
592 | 52 | data = response.json() | ||
593 | 53 | return { | ||
594 | 54 | 'client_key': data['consumer_key'], | ||
595 | 55 | 'client_secret': data['consumer_secret'], | ||
596 | 56 | 'resource_owner_key': data['token_key'], | ||
597 | 57 | 'resource_owner_secret': data['token_secret'], | ||
598 | 58 | } | ||
599 | 59 | |||
600 | 60 | |||
601 | 61 | def get_oauth_client(): | ||
602 | 62 | credentials = get_oauth_token_data(TEST_USER_EMAIL, TEST_USER_PASSWORD) | ||
603 | 63 | client = requests_oauthlib.OAuth1Session(**credentials) | ||
604 | 64 | client.headers.update({ | ||
605 | 65 | 'Content-Type': 'application/json', | ||
606 | 66 | 'Cache-Control': 'no-cache', | ||
607 | 67 | }) | ||
608 | 68 | return client | ||
609 | 69 | |||
610 | 70 | |||
611 | 71 | class PackageBaseTestCase(APITestCase): | ||
612 | 72 | |||
613 | 73 | def assert_package(self, response, snap_id=None, name=None, | ||
614 | 74 | channel=None, revision=None, exclude_fields=None): | ||
615 | 75 | body = response.json() | ||
616 | 76 | fields = [ | ||
617 | 77 | 'alias', | ||
618 | 78 | 'allow_unauthenticated', | ||
619 | 79 | 'anon_download_url', | ||
620 | 80 | 'architecture', | ||
621 | 81 | 'binary_filesize', | ||
622 | 82 | 'blacklist_country_codes', | ||
623 | 83 | 'changelog', | ||
624 | 84 | 'channel', | ||
625 | 85 | 'channels', | ||
626 | 86 | 'click_framework', | ||
627 | 87 | 'click_version', | ||
628 | 88 | 'company_name', | ||
629 | 89 | 'confinement', | ||
630 | 90 | 'content', | ||
631 | 91 | 'date_published', | ||
632 | 92 | 'department', | ||
633 | 93 | 'description', | ||
634 | 94 | 'developer_id', | ||
635 | 95 | 'developer_name', | ||
636 | 96 | 'download_sha512', | ||
637 | 97 | 'download_url', | ||
638 | 98 | 'epoch', | ||
639 | 99 | 'framework', | ||
640 | 100 | 'icon_url', | ||
641 | 101 | 'icon_urls', | ||
642 | 102 | 'id', | ||
643 | 103 | 'is_published', | ||
644 | 104 | 'keywords', | ||
645 | 105 | 'last_updated', | ||
646 | 106 | 'license', | ||
647 | 107 | 'name', | ||
648 | 108 | 'origin', | ||
649 | 109 | 'package_name', | ||
650 | 110 | 'plugs', | ||
651 | 111 | 'price', | ||
652 | 112 | 'prices', | ||
653 | 113 | 'promoted', | ||
654 | 114 | 'publisher', | ||
655 | 115 | 'ratings_average', | ||
656 | 116 | 'release', | ||
657 | 117 | 'revision', | ||
658 | 118 | 'screenshot_url', | ||
659 | 119 | 'screenshot_urls', | ||
660 | 120 | 'slots', | ||
661 | 121 | 'snap_id', | ||
662 | 122 | 'status', | ||
663 | 123 | 'summary', | ||
664 | 124 | 'support_url', | ||
665 | 125 | 'terms_of_service', | ||
666 | 126 | 'title', | ||
667 | 127 | 'version', | ||
668 | 128 | 'video_urls', | ||
669 | 129 | 'website', | ||
670 | 130 | 'whitelist_country_codes', | ||
671 | 131 | ] | ||
672 | 132 | # assert result data structure | ||
673 | 133 | expected = set(fields + ['_links']) | ||
674 | 134 | if exclude_fields: | ||
675 | 135 | expected = expected - set(exclude_fields) | ||
676 | 136 | self.assertEqual(set(body.keys()), expected) | ||
677 | 137 | if snap_id is not None: | ||
678 | 138 | self.assertEqual(body['snap_id'], snap_id) | ||
679 | 139 | if name is not None: | ||
680 | 140 | self.assertEqual(body['package_name'], name) | ||
681 | 141 | if channel is not None: | ||
682 | 142 | self.assertEqual(body['channel'], channel) | ||
683 | 143 | self.assertIn(channel, body['channels']) | ||
684 | 144 | if revision is not None: | ||
685 | 145 | self.assertEqual(body['revision'], revision) | ||
686 | 146 | |||
687 | 147 | |||
688 | 148 | class PackageTestCase(PackageBaseTestCase): | ||
689 | 149 | |||
690 | 150 | def test_get_package(self): | ||
691 | 151 | response = get_package(SNAP_PACKAGE_NAME) | ||
692 | 152 | self.assert_success(response) | ||
693 | 153 | self.assert_package(response, name=SNAP_PACKAGE_NAME) | ||
694 | 154 | |||
695 | 155 | def test_get_private_package_when_not_authorized(self): | ||
696 | 156 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID) | ||
697 | 157 | self.assert_not_found(response, message='No such package') | ||
698 | 158 | |||
699 | 159 | def test_get_private_package_when_authorized(self): | ||
700 | 160 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID, auth=True) | ||
701 | 161 | self.assert_success(response) | ||
702 | 162 | self.assert_package( | ||
703 | 163 | response, snap_id=PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
704 | 164 | name=PRIVATE_SNAP_PACKAGE_NAME) | ||
705 | 165 | |||
706 | 166 | def test_get_package_not_found(self): | ||
707 | 167 | response = get_package('missing') | ||
708 | 168 | self.assert_not_found(response, message='No such package') | ||
709 | 169 | |||
710 | 170 | |||
711 | 171 | class ChannelPackageTestCase(PackageBaseTestCase): | ||
712 | 172 | |||
713 | 173 | def test_get_channel_package(self): | ||
714 | 174 | response = get_package(SNAP_PACKAGE_NAME, channel=SNAP_PACKAGE_CHANNEL) | ||
715 | 175 | self.assert_success(response) | ||
716 | 176 | self.assert_package(response, name=SNAP_PACKAGE_NAME, | ||
717 | 177 | channel=SNAP_PACKAGE_CHANNEL) | ||
718 | 178 | |||
719 | 179 | def test_get_private_channel_package_when_not_authorized(self): | ||
720 | 180 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
721 | 181 | channel=PRIVATE_SNAP_PACKAGE_CHANNEL) | ||
722 | 182 | self.assert_not_found(response, message='No such package') | ||
723 | 183 | |||
724 | 184 | def test_get_private_channel_package_when_authorized(self): | ||
725 | 185 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
726 | 186 | channel=PRIVATE_SNAP_PACKAGE_CHANNEL, auth=True) | ||
727 | 187 | self.assert_success(response) | ||
728 | 188 | self.assert_package( | ||
729 | 189 | response, snap_id=PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
730 | 190 | name=PRIVATE_SNAP_PACKAGE_NAME, channel=PRIVATE_SNAP_PACKAGE_CHANNEL) | ||
731 | 191 | |||
732 | 192 | def test_get_channel_package_not_found(self): | ||
733 | 193 | response = get_package('missing', channel='stable') | ||
734 | 194 | self.assert_not_found(response, message='No such package') | ||
735 | 195 | |||
736 | 196 | def test_get_channel_package_wrong_channel(self): | ||
737 | 197 | response = get_package(SNAP_PACKAGE_NAME, channel='missing') | ||
738 | 198 | self.assert_not_found(response, message='No such package') | ||
739 | 199 | |||
740 | 200 | |||
741 | 201 | class PackageRevisionTestCase(PackageBaseTestCase): | ||
742 | 202 | |||
743 | 203 | def test_get_package_revision(self): | ||
744 | 204 | response = get_package(SNAP_PACKAGE_NAME, revision=SNAP_PACKAGE_REVISION) | ||
745 | 205 | self.assert_success(response) | ||
746 | 206 | self.assert_package( | ||
747 | 207 | response, name=SNAP_PACKAGE_NAME, revision=SNAP_PACKAGE_REVISION, | ||
748 | 208 | exclude_fields=['channel']) | ||
749 | 209 | |||
750 | 210 | def test_get_private_package_revision_when_not_authorized(self): | ||
751 | 211 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
752 | 212 | revision=PRIVATE_SNAP_PACKAGE_REVISION) | ||
753 | 213 | self.assert_not_found(response, message='No such package') | ||
754 | 214 | |||
755 | 215 | def test_get_private_package_when_authorized(self): | ||
756 | 216 | response = get_package(PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
757 | 217 | revision=PRIVATE_SNAP_PACKAGE_REVISION, auth=True) | ||
758 | 218 | self.assert_success(response) | ||
759 | 219 | self.assert_package( | ||
760 | 220 | response, snap_id=PRIVATE_SNAP_PACKAGE_SNAP_ID, | ||
761 | 221 | name=PRIVATE_SNAP_PACKAGE_NAME, exclude_fields=['channel']) | ||
762 | 222 | |||
763 | 223 | def test_get_package_revision_not_found(self): | ||
764 | 224 | response = get_package(SNAP_PACKAGE_NAME, revision=-1) | ||
765 | 225 | self.assert_not_found(response, message='No such package') |
Looking good, added a few comments/questions.