comparison test/rest_common.py @ 5639:f576957cbb1f

Add support for prev/next/self links when returning paginated results. To do this: 1) change "data" envelope from an array to a dict 2) move the "data" array to the "collection" property, which is an array of elements in the collection. 3) add @links dict keyed by link relation: self, next, prev. Each relation is an array of dicts with uri and rel keys. In this case there is only one element, but there is nothing preventing a relation from having multiple url's. So this follows the formatting needed for the general case. Relations are present only if it makes sense. So first page has no prev and last page has no next. 4) add @total_size with number of element selected if they were not paginated. Replicates data in X-Count-Total header. Changed index to start at 1. So the first page is page_index 1 and not page_index 0. (So I am no longer surprised when I set page_index to 1 and am missing a bunch of records 8-)). Also a small fixup, json response ends with a newline so printing the data, or using curl makes sure that anything printing after the json output (like shell prompts) is on a new line. Tests added for all cases.
author John Rouillard <rouilj@ieee.org>
date Sat, 09 Mar 2019 11:06:10 -0500
parents 07abc8d36940
children a60cbbcc9309
comparison
equal deleted inserted replaced
5638:7e3cceec3f4f 5639:f576957cbb1f
90 obtain data for 'joe' 90 obtain data for 'joe'
91 """ 91 """
92 # Retrieve all three users. 92 # Retrieve all three users.
93 results = self.server.get_collection('user', self.empty_form) 93 results = self.server.get_collection('user', self.empty_form)
94 self.assertEqual(self.dummy_client.response_code, 200) 94 self.assertEqual(self.dummy_client.response_code, 200)
95 self.assertEqual(len(results['data']), 3) 95 self.assertEqual(len(results['data']['collection']), 3)
96 self.assertEqual(results['data']['@total_size'], 3)
97 print self.dummy_client.additional_headers["X-Count-Total"]
98 self.assertEqual(
99 self.dummy_client.additional_headers["X-Count-Total"],
100 "3"
101 )
96 102
97 # Obtain data for 'joe'. 103 # Obtain data for 'joe'.
98 results = self.server.get_element('user', self.joeid, self.empty_form) 104 results = self.server.get_element('user', self.joeid, self.empty_form)
99 results = results['data'] 105 results = results['data']
100 self.assertEqual(self.dummy_client.response_code, 200) 106 self.assertEqual(self.dummy_client.response_code, 200)
167 form.list = [ 173 form.list = [
168 cgi.MiniFieldStorage('where_status', 'open') 174 cgi.MiniFieldStorage('where_status', 'open')
169 ] 175 ]
170 results = self.server.get_collection('issue', form) 176 results = self.server.get_collection('issue', form)
171 self.assertEqual(self.dummy_client.response_code, 200) 177 self.assertEqual(self.dummy_client.response_code, 200)
172 self.assertIn(get_obj(base_path, issue_open_norm), results['data']) 178 self.assertIn(get_obj(base_path, issue_open_norm),
173 self.assertIn(get_obj(base_path, issue_open_crit), results['data']) 179 results['data']['collection'])
180 self.assertIn(get_obj(base_path, issue_open_crit),
181 results['data']['collection'])
174 self.assertNotIn( 182 self.assertNotIn(
175 get_obj(base_path, issue_closed_norm), results['data'] 183 get_obj(base_path, issue_closed_norm),
184 results['data']['collection']
176 ) 185 )
177 186
178 # Retrieve all issue status=closed and priority=critical 187 # Retrieve all issue status=closed and priority=critical
179 form = cgi.FieldStorage() 188 form = cgi.FieldStorage()
180 form.list = [ 189 form.list = [
181 cgi.MiniFieldStorage('where_status', 'closed'), 190 cgi.MiniFieldStorage('where_status', 'closed'),
182 cgi.MiniFieldStorage('where_priority', 'critical') 191 cgi.MiniFieldStorage('where_priority', 'critical')
183 ] 192 ]
184 results = self.server.get_collection('issue', form) 193 results = self.server.get_collection('issue', form)
185 self.assertEqual(self.dummy_client.response_code, 200) 194 self.assertEqual(self.dummy_client.response_code, 200)
186 self.assertIn(get_obj(base_path, issue_closed_crit), results['data']) 195 self.assertIn(get_obj(base_path, issue_closed_crit),
187 self.assertNotIn(get_obj(base_path, issue_open_crit), results['data']) 196 results['data']['collection'])
197 self.assertNotIn(get_obj(base_path, issue_open_crit),
198 results['data']['collection'])
188 self.assertNotIn( 199 self.assertNotIn(
189 get_obj(base_path, issue_closed_norm), results['data'] 200 get_obj(base_path, issue_closed_norm),
201 results['data']['collection']
190 ) 202 )
191 203
192 # Retrieve all issue status=closed and priority=normal,critical 204 # Retrieve all issue status=closed and priority=normal,critical
193 form = cgi.FieldStorage() 205 form = cgi.FieldStorage()
194 form.list = [ 206 form.list = [
195 cgi.MiniFieldStorage('where_status', 'closed'), 207 cgi.MiniFieldStorage('where_status', 'closed'),
196 cgi.MiniFieldStorage('where_priority', 'normal,critical') 208 cgi.MiniFieldStorage('where_priority', 'normal,critical')
197 ] 209 ]
198 results = self.server.get_collection('issue', form) 210 results = self.server.get_collection('issue', form)
199 self.assertEqual(self.dummy_client.response_code, 200) 211 self.assertEqual(self.dummy_client.response_code, 200)
200 self.assertIn(get_obj(base_path, issue_closed_crit), results['data']) 212 self.assertIn(get_obj(base_path, issue_closed_crit),
201 self.assertIn(get_obj(base_path, issue_closed_norm), results['data']) 213 results['data']['collection'])
202 self.assertNotIn(get_obj(base_path, issue_open_crit), results['data']) 214 self.assertIn(get_obj(base_path, issue_closed_norm),
203 self.assertNotIn(get_obj(base_path, issue_open_norm), results['data']) 215 results['data']['collection'])
216 self.assertNotIn(get_obj(base_path, issue_open_crit),
217 results['data']['collection'])
218 self.assertNotIn(get_obj(base_path, issue_open_norm),
219 results['data']['collection'])
204 220
205 def testPagination(self): 221 def testPagination(self):
206 """ 222 """
207 Retrieve all three users 223 Test pagination. page_size is required and is an integer
208 obtain data for 'joe' 224 starting at 1. page_index is optional and is an integer
225 starting at 1. Verify that pagination links are present
226 if paging, @total_size and X-Count-Total header match
227 number of items.
209 """ 228 """
210 # create sample data 229 # create sample data
211 for i in range(0, random.randint(5, 10)): 230 for i in range(0, random.randint(8,15)):
212 self.db.issue.create(title='foo' + str(i)) 231 self.db.issue.create(title='foo' + str(i))
213 232
214 # Retrieving all the issues 233 # Retrieving all the issues
215 results = self.server.get_collection('issue', self.empty_form) 234 results = self.server.get_collection('issue', self.empty_form)
216 self.assertEqual(self.dummy_client.response_code, 200) 235 self.assertEqual(self.dummy_client.response_code, 200)
217 total_length = len(results['data']) 236 total_length = len(results['data']['collection'])
218 237 # Verify no pagination links if paging not used
219 # Pagination will be 70% of the total result 238 self.assertFalse('@links' in results['data'])
220 page_size = total_length * 70 // 100 239 self.assertEqual(results['data']['@total_size'], total_length)
221 page_zero_expected = page_size 240 self.assertEqual(
222 page_one_expected = total_length - page_zero_expected 241 self.dummy_client.additional_headers["X-Count-Total"],
223 242 str(total_length)
224 # Retrieve page 0 243 )
225 form = cgi.FieldStorage() 244
226 form.list = [ 245
227 cgi.MiniFieldStorage('page_size', page_size), 246 # Pagination will be 45% of the total result
228 cgi.MiniFieldStorage('page_index', 0) 247 # So 2 full pages and 1 partial page.
229 ] 248 page_size = total_length * 45 // 100
230 results = self.server.get_collection('issue', form) 249 page_one_expected = page_size
231 self.assertEqual(self.dummy_client.response_code, 200) 250 page_two_expected = page_size
232 self.assertEqual(len(results['data']), page_zero_expected) 251 page_three_expected = total_length - (2*page_one_expected)
252 base_url="http://tracker.example/cgi-bin/roundup.cgi/" \
253 "bugs/rest/data/issue"
233 254
234 # Retrieve page 1 255 # Retrieve page 1
235 form = cgi.FieldStorage() 256 form = cgi.FieldStorage()
236 form.list = [ 257 form.list = [
237 cgi.MiniFieldStorage('page_size', page_size), 258 cgi.MiniFieldStorage('page_size', page_size),
238 cgi.MiniFieldStorage('page_index', 1) 259 cgi.MiniFieldStorage('page_index', 1)
239 ] 260 ]
240 results = self.server.get_collection('issue', form) 261 results = self.server.get_collection('issue', form)
241 self.assertEqual(self.dummy_client.response_code, 200) 262 self.assertEqual(self.dummy_client.response_code, 200)
242 self.assertEqual(len(results['data']), page_one_expected) 263 self.assertEqual(len(results['data']['collection']),
264 page_one_expected)
265 self.assertTrue('@links' in results['data'])
266 self.assertTrue('self' in results['data']['@links'])
267 self.assertTrue('next' in results['data']['@links'])
268 self.assertFalse('prev' in results['data']['@links'])
269 self.assertEqual(results['data']['@links']['self'][0]['uri'],
270 "%s?page_index=1&page_size=%s"%(base_url,page_size))
271 self.assertEqual(results['data']['@links']['next'][0]['uri'],
272 "%s?page_index=2&page_size=%s"%(base_url,page_size))
273
274 page_one_results = results # save this for later
243 275
244 # Retrieve page 2 276 # Retrieve page 2
245 form = cgi.FieldStorage() 277 form = cgi.FieldStorage()
246 form.list = [ 278 form.list = [
247 cgi.MiniFieldStorage('page_size', page_size), 279 cgi.MiniFieldStorage('page_size', page_size),
248 cgi.MiniFieldStorage('page_index', 2) 280 cgi.MiniFieldStorage('page_index', 2)
249 ] 281 ]
250 results = self.server.get_collection('issue', form) 282 results = self.server.get_collection('issue', form)
251 self.assertEqual(self.dummy_client.response_code, 200) 283 self.assertEqual(self.dummy_client.response_code, 200)
252 self.assertEqual(len(results['data']), 0) 284 self.assertEqual(len(results['data']['collection']), page_two_expected)
253 285 self.assertTrue('@links' in results['data'])
286 self.assertTrue('self' in results['data']['@links'])
287 self.assertTrue('next' in results['data']['@links'])
288 self.assertTrue('prev' in results['data']['@links'])
289 self.assertEqual(results['data']['@links']['self'][0]['uri'],
290 "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue?page_index=2&page_size=%s"%page_size)
291 self.assertEqual(results['data']['@links']['next'][0]['uri'],
292 "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue?page_index=3&page_size=%s"%page_size)
293 self.assertEqual(results['data']['@links']['prev'][0]['uri'],
294 "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue?page_index=1&page_size=%s"%page_size)
295 self.assertEqual(results['data']['@links']['self'][0]['rel'],
296 'self')
297 self.assertEqual(results['data']['@links']['next'][0]['rel'],
298 'next')
299 self.assertEqual(results['data']['@links']['prev'][0]['rel'],
300 'prev')
301
302 # Retrieve page 3
303 form = cgi.FieldStorage()
304 form.list = [
305 cgi.MiniFieldStorage('page_size', page_size),
306 cgi.MiniFieldStorage('page_index', 3)
307 ]
308 results = self.server.get_collection('issue', form)
309 self.assertEqual(self.dummy_client.response_code, 200)
310 self.assertEqual(len(results['data']['collection']), page_three_expected)
311 self.assertTrue('@links' in results['data'])
312 self.assertTrue('self' in results['data']['@links'])
313 self.assertFalse('next' in results['data']['@links'])
314 self.assertTrue('prev' in results['data']['@links'])
315 self.assertEqual(results['data']['@links']['self'][0]['uri'],
316 "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue?page_index=3&page_size=%s"%page_size)
317 self.assertEqual(results['data']['@links']['prev'][0]['uri'],
318 "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue?page_index=2&page_size=%s"%page_size)
319
320 # Verify that page_index is optional
321 # Should start at page 1
322 form = cgi.FieldStorage()
323 form.list = [
324 cgi.MiniFieldStorage('page_size', page_size),
325 ]
326 results = self.server.get_collection('issue', form)
327 self.assertEqual(self.dummy_client.response_code, 200)
328 self.assertEqual(len(results['data']['collection']), page_size)
329 self.assertTrue('@links' in results['data'])
330 self.assertTrue('self' in results['data']['@links'])
331 self.assertTrue('next' in results['data']['@links'])
332 self.assertFalse('prev' in results['data']['@links'])
333 self.assertEqual(page_one_results, results)
334
335 # FIXME add tests for out of range once we decide what response
336 # is needed to:
337 # page_size < 0
338 # page_index < 0
254 339
255 def testEtagProcessing(self): 340 def testEtagProcessing(self):
256 ''' 341 '''
257 Etags can come from two places: 342 Etags can come from two places:
258 ETag http header 343 ETag http header

Roundup Issue Tracker: http://roundup-tracker.org/