Skip to content

Commit 9b17e4b

Browse files
tests: add tests for series versioning
Signed-off-by: Victor Accarini <[email protected]>
1 parent 2240516 commit 9b17e4b

File tree

3 files changed

+361
-7
lines changed

3 files changed

+361
-7
lines changed

patchwork/api/series.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ def update(self, instance, validated_data, *args, **kwargs):
6161
if 'supersedes' in validated_data:
6262
supersedes = validated_data.pop('supersedes', [])
6363

64+
if instance in supersedes:
65+
raise ValidationError(
66+
{'detail': 'A series cannot be linked to itself.'}
67+
)
68+
69+
if any(
70+
series.project != instance.project for series in supersedes
71+
):
72+
raise ValidationError(
73+
{'detail': 'Series must belong to the same project.'}
74+
)
75+
6476
try:
6577
instance.supersedes.set(supersedes)
6678
except Series.DoesNotExist:

patchwork/tests/api/test_series.py

Lines changed: 325 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)