Mercurial > p > roundup > code
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 |
