@@ -44,6 +44,7 @@ def assertSerialized(self, series_obj, series_json):
4444 self .assertIn (series_obj .get_mbox_url (), series_json ['mbox' ])
4545 self .assertIn (series_obj .get_absolute_url (), series_json ['web_url' ])
4646
47+ # dependencies
4748 for dep , item in zip (
4849 series_obj .dependencies .all (), series_json ['dependencies' ]
4950 ):
@@ -58,6 +59,21 @@ def assertSerialized(self, series_obj, series_json):
5859 reverse ('api-series-detail' , kwargs = {'pk' : dep .id }), item
5960 )
6061
62+ # versioning
63+ for ver , item in zip (
64+ series_obj .supersedes .all (), series_json ['supersedes' ]
65+ ):
66+ self .assertIn (
67+ reverse ('api-series-detail' , kwargs = {'pk' : ver .id }), item
68+ )
69+
70+ for ver , item in zip (
71+ series_obj .superseded .all (), series_json ['superseded' ]
72+ ):
73+ self .assertIn (
74+ reverse ('api-series-detail' , kwargs = {'pk' : ver .id }), item
75+ )
76+
6177 # nested fields
6278
6379 self .assertEqual (series_obj .project .id , series_json ['project' ]['id' ])
@@ -79,7 +95,9 @@ def test_list_empty(self):
7995
8096 def _create_series (self ):
8197 project_obj = create_project (
82- linkname = 'myproject' , show_dependencies = True
98+ linkname = 'myproject' ,
99+ show_dependencies = True ,
100+ show_series_versions = True ,
83101 )
84102 person_obj = create_person (
email = '[email protected] ' )
85103 series_obj = create_series (project = project_obj , submitter = person_obj )
@@ -197,7 +215,7 @@ def test_list_bug_335(self):
197215 create_cover (series = series_obj )
198216 create_patch (series = series_obj )
199217
200- with self .assertNumQueries (8 ):
218+ with self .assertNumQueries (10 ):
201219 self .client .get (self .api_url ())
202220
203221 @utils .store_samples ('series-detail' )
@@ -225,6 +243,8 @@ def test_detail_version_1_3(self):
225243 self .assertIn ('web_url' , resp .data ['patches' ][0 ])
226244 self .assertNotIn ('dependents' , resp .data )
227245 self .assertNotIn ('dependencies' , resp .data )
246+ self .assertNotIn ('superseded' , resp .data )
247+ self .assertNotIn ('supersedes' , resp .data )
228248
229249 @utils .store_samples ('series-detail-1-0' )
230250 def test_detail_version_1_0 (self ):
@@ -251,8 +271,8 @@ def test_detail_invalid(self):
251271 with self .assertRaises (NoReverseMatch ):
252272 self .client .get (self .api_url ('foo' ))
253273
254- def test_create_update_delete (self ):
255- """Ensure creates, updates and deletes aren't allowed"""
274+ def test_create_delete (self ):
275+ """Ensure creates and deletes aren't allowed"""
256276 user = create_maintainer ()
257277 user .is_superuser = True
258278 user .save ()
@@ -263,8 +283,306 @@ def test_create_update_delete(self):
263283
264284 series = create_series ()
265285
266- resp = self .client .patch (self .api_url (series .id ), {'name' : 'Test' })
267- self .assertEqual (status .HTTP_405_METHOD_NOT_ALLOWED , resp .status_code )
268-
269286 resp = self .client .delete (self .api_url (series .id ))
270287 self .assertEqual (status .HTTP_405_METHOD_NOT_ALLOWED , resp .status_code )
288+
289+ def test_series_versioning (self ):
290+ """Test toggling versioning on and off."""
291+ project = create_project (
292+ show_dependencies = True ,
293+ show_series_versions = True ,
294+ )
295+ submitter = create_person (
email = '[email protected] ' )
296+ series_a = create_series (project = project , submitter = submitter )
297+ create_cover (series = series_a )
298+ create_patch (series = series_a )
299+ series_b = create_series (project = project , submitter = submitter )
300+ create_cover (series = series_b )
301+ create_patch (series = series_b )
302+ series_a .supersedes .set ([series_b ])
303+
304+ resp = self .client .get (self .api_url ())
305+ self .assertEqual (2 , len (resp .data ))
306+ for series_data in resp .data :
307+ self .assertIn ('supersedes' , series_data )
308+ self .assertIn ('superseded' , series_data )
309+
310+ project .show_series_versions = False
311+ project .save ()
312+
313+ resp = self .client .get (self .api_url ())
314+ self .assertEqual (2 , len (resp .data ))
315+ for series_data in resp .data :
316+ self .assertNotIn ('supersedes' , series_data )
317+ self .assertNotIn ('superseded' , series_data )
318+
319+
320+ @override_settings (PATCHWORK_API_ENABLED = True )
321+ class TestSeriesDetailUpdate (utils .APITestCase ):
322+ @staticmethod
323+ def api_url (item , version = None ):
324+ kwargs = {}
325+ if version :
326+ kwargs ['version' ] = version
327+
328+ kwargs ['pk' ] = item
329+ return reverse ('api-series-detail' , kwargs = kwargs )
330+
331+ def _get_series_url (self , series , request = None ):
332+ url = reverse (
333+ 'api-series-detail' ,
334+ kwargs = {'pk' : series .id },
335+ )
336+ # Build absolute uri for spec validation
337+ if request is not None :
338+ return request .build_absolute_uri (url )
339+
340+ return url
341+
342+ def _assert_contains_series_url (self , response , key , series ):
343+ self .assertEqual (response .status_code , status .HTTP_200_OK )
344+ container = response .json ().get (key )
345+ for item in container :
346+ if item .endswith (
347+ self ._get_series_url (series , response .wsgi_request )
348+ ):
349+ return True
350+ raise AssertionError (
351+ f'No item in { container } ends with { self ._get_series_url (series )} '
352+ )
353+
354+ def setUp (self ):
355+ super ().setUp ()
356+ self .client .defaults .update (
357+ {'HTTP_HOST' : 'example.com' , 'SERVER_NAME' : 'example.com' }
358+ )
359+ self .project = create_project (
360+ linkname = 'myproject' ,
361+ show_dependencies = True ,
362+ show_series_versions = True ,
363+ )
364+ user = create_user ()
365+ self .
submitter = create_person (
email = '[email protected] ' ,
user = user )
366+
367+ self .superseded = create_series (
368+ project = self .project , submitter = self .submitter
369+ )
370+ create_cover (series = self .superseded )
371+ create_patch (series = self .superseded )
372+
373+ self .series = create_series (
374+ project = self .project , submitter = self .submitter
375+ )
376+ create_cover (series = self .series )
377+ create_patch (series = self .series )
378+
379+ self .url = self ._get_series_url (self .series )
380+
381+ def authenticate_as_submitter (self ):
382+ self .client .authenticate (user = self .submitter .user )
383+
384+ def authenticate_as_maintainer (self ):
385+ user = create_maintainer (self .project )
386+ self .client .authenticate (user = user )
387+
388+ def authenticate_as_superuser (self ):
389+ user = create_user ()
390+ user .is_superuser = True
391+ user .save ()
392+ self .client .authenticate (user = user )
393+
394+ def authenticate_as_unrelated_user (self ):
395+ user = create_user ()
396+ self .client .authenticate (user = user )
397+
398+ # PATCH tests
399+ def test_patch_series_as_submitter (self ):
400+ series_b = create_series (
401+ project = self .project , submitter = self .submitter
402+ )
403+ self .authenticate_as_submitter ()
404+ response = self .client .patch (
405+ self .url ,
406+ {'supersedes' : [self ._get_series_url (self .superseded )]},
407+ format = 'json' ,
408+ )
409+ self ._assert_contains_series_url (
410+ response , 'supersedes' , self .superseded
411+ )
412+
413+ # Add series_b and remove superseded
414+ response = self .client .patch (
415+ self .url ,
416+ {'supersedes' : [self ._get_series_url (series_b )]},
417+ format = 'json' ,
418+ )
419+ self ._assert_contains_series_url (response , 'supersedes' , series_b )
420+ with self .assertRaises (AssertionError ):
421+ self ._assert_contains_series_url (
422+ response , 'supersedes' , self .superseded
423+ )
424+
425+ def test_patch_series_as_maintainer (self ):
426+ self .authenticate_as_maintainer ()
427+ response = self .client .patch (
428+ self .url ,
429+ {'supersedes' : [self ._get_series_url (self .superseded )]},
430+ format = 'json' ,
431+ )
432+ self ._assert_contains_series_url (
433+ response , 'supersedes' , self .superseded
434+ )
435+
436+ def test_patch_series_as_superuser (self ):
437+ self .authenticate_as_superuser ()
438+ response = self .client .patch (
439+ self .url ,
440+ {'supersedes' : [self ._get_series_url (self .superseded )]},
441+ format = 'json' ,
442+ )
443+ self ._assert_contains_series_url (
444+ response , 'supersedes' , self .superseded
445+ )
446+
447+ def test_patch_series_as_unrelated_user_forbidden (self ):
448+ self .authenticate_as_unrelated_user ()
449+ response = self .client .patch (
450+ self .url ,
451+ {'supersedes' : [self ._get_series_url (self .superseded )]},
452+ format = 'json' ,
453+ )
454+ self .assertEqual (response .status_code , status .HTTP_403_FORBIDDEN )
455+
456+ def test_patch_series_unauthenticated_forbidden (self ):
457+ response = self .client .patch (
458+ self .url ,
459+ {'supersedes' : [self ._get_series_url (self .superseded )]},
460+ format = 'json' ,
461+ )
462+ self .assertEqual (response .status_code , status .HTTP_403_FORBIDDEN )
463+
464+ # PUT tests
465+ def test_put_series_as_submitter (self ):
466+ series_b = create_series (
467+ project = self .project , submitter = self .submitter
468+ )
469+ self .authenticate_as_submitter ()
470+ response = self .client .put (
471+ self .url ,
472+ {'supersedes' : [self ._get_series_url (self .superseded )]},
473+ format = 'json' ,
474+ )
475+ self ._assert_contains_series_url (
476+ response , 'supersedes' , self .superseded
477+ )
478+
479+ # Rewrite the whole supersedes attribute
480+ response = self .client .put (
481+ self .url ,
482+ {'supersedes' : [self ._get_series_url (series_b )]},
483+ format = 'json' ,
484+ )
485+ self ._assert_contains_series_url (response , 'supersedes' , series_b )
486+ with self .assertRaises (AssertionError ):
487+ self ._assert_contains_series_url (
488+ response , 'supersedes' , self .superseded
489+ )
490+
491+ def test_put_series_as_maintainer (self ):
492+ self .authenticate_as_maintainer ()
493+ response = self .client .put (
494+ self .url ,
495+ {'supersedes' : [self ._get_series_url (self .superseded )]},
496+ format = 'json' ,
497+ )
498+ self ._assert_contains_series_url (
499+ response , 'supersedes' , self .superseded
500+ )
501+
502+ def test_put_series_as_superuser (self ):
503+ self .authenticate_as_superuser ()
504+ response = self .client .put (
505+ self .url ,
506+ {'supersedes' : [self ._get_series_url (self .superseded )]},
507+ format = 'json' ,
508+ )
509+ self ._assert_contains_series_url (
510+ response , 'supersedes' , self .superseded
511+ )
512+
513+ def test_put_series_as_unrelated_user_forbidden (self ):
514+ self .authenticate_as_unrelated_user ()
515+ response = self .client .put (
516+ self .url ,
517+ {'supersedes' : [self ._get_series_url (self .superseded )]},
518+ format = 'json' ,
519+ )
520+ self .assertEqual (response .status_code , status .HTTP_403_FORBIDDEN )
521+
522+ def test_put_series_unauthenticated_forbidden (self ):
523+ response = self .client .put (
524+ self .url ,
525+ {'supersedes' : [self ._get_series_url (self .superseded )]},
526+ format = 'json' ,
527+ )
528+ self .assertEqual (response .status_code , status .HTTP_403_FORBIDDEN )
529+
530+ # Invalid input tests
531+ def test_patch_invalid_input (self ):
532+ self .authenticate_as_maintainer ()
533+ response = self .client .patch (self .url , {'name' : 'name' }, format = 'json' )
534+ self .assertEqual (response .status_code , status .HTTP_400_BAD_REQUEST )
535+
536+ def test_put_invalid_input (self ):
537+ self .authenticate_as_maintainer ()
538+ response = self .client .put (self .url , {'name' : 'name' }, format = 'json' )
539+ self .assertEqual (response .status_code , status .HTTP_400_BAD_REQUEST )
540+
541+ def test_patch_invalid_series_id (self ):
542+ self .authenticate_as_maintainer ()
543+ response = self .client .patch (
544+ self .url ,
545+ {'supersedes' : ['/api/series/99999999/' ]},
546+ format = 'json' ,
547+ )
548+ self .assertEqual (response .status_code , status .HTTP_400_BAD_REQUEST )
549+
550+ def test_put_invalid_series_id (self ):
551+ self .authenticate_as_maintainer ()
552+ response = self .client .put (
553+ self .url ,
554+ {'supersedes' : ['/api/series/99999999/' ]},
555+ format = 'json' ,
556+ )
557+ self .assertEqual (response .status_code , status .HTTP_400_BAD_REQUEST )
558+
559+ def test_self_link_validation (self ):
560+ self .authenticate_as_submitter ()
561+
562+ response = self .client .put (
563+ self .url ,
564+ {'supersedes' : [self ._get_series_url (self .series )]},
565+ format = 'json' ,
566+ )
567+
568+ self .assertContains (
569+ response ,
570+ 'A series cannot be linked to itself.' ,
571+ status_code = status .HTTP_400_BAD_REQUEST ,
572+ )
573+
574+ def test_cross_project_validation (self ):
575+ self .authenticate_as_submitter ()
576+ series_x = create_series ()
577+
578+ response = self .client .put (
579+ self .url ,
580+ {'supersedes' : [self ._get_series_url (series_x )]},
581+ format = 'json' ,
582+ )
583+
584+ self .assertContains (
585+ response ,
586+ 'Series must belong to the same project.' ,
587+ status_code = status .HTTP_400_BAD_REQUEST ,
588+ )
0 commit comments