[PATCH v2 2/5] tests: add tests for patch comments detail endpoint

Stephen Finucane stephen at that.guru
Thu Aug 12 21:41:31 AEST 2021


On Wed, 2021-08-11 at 09:39 +1000, Daniel Axtens wrote:
> Raxel Gutierrez <raxel at google.com> writes:
> 
> > Add tests for the new api/.../comments/<comment_id> endpoint that takes
> > GET, PATCH, and PUT requests. The tests cover retrieval and update
> > requests and handle calls from the various API versions. Also, they
> > handle permissions for update requests on the new `addressed` field and
> > invalid update values for the `addressed` field.
> > 
> > Add `addressed` field to create_patch_comment helper in api tests
> > utils.py.
> > 
> > Signed-off-by: Raxel Gutierrez <raxel at google.com>
> > ---
> >  patchwork/tests/api/test_comment.py | 199 +++++++++++++++++++++++++---
> >  patchwork/tests/utils.py            |   1 +
> >  2 files changed, 183 insertions(+), 17 deletions(-)
> > 
> > diff --git a/patchwork/tests/api/test_comment.py b/patchwork/tests/api/test_comment.py
> > index 59450d8..f43d1c7 100644
> > --- a/patchwork/tests/api/test_comment.py
> > +++ b/patchwork/tests/api/test_comment.py
> > @@ -9,11 +9,16 @@ from django.conf import settings
> >  from django.urls import NoReverseMatch
> >  from django.urls import reverse
> >  
> > +from patchwork.models import PatchComment
> >  from patchwork.tests.api import utils
> >  from patchwork.tests.utils import create_cover
> >  from patchwork.tests.utils import create_cover_comment
> >  from patchwork.tests.utils import create_patch
> >  from patchwork.tests.utils import create_patch_comment
> > +from patchwork.tests.utils import create_maintainer
> > +from patchwork.tests.utils import create_project
> > +from patchwork.tests.utils import create_person
> > +from patchwork.tests.utils import create_user
> >  from patchwork.tests.utils import SAMPLE_CONTENT
> >  
> >  if settings.ENABLE_REST_API:
> > @@ -86,34 +91,40 @@ class TestCoverComments(utils.APITestCase):
> >  @unittest.skipUnless(settings.ENABLE_REST_API, 'requires ENABLE_REST_API')
> >  class TestPatchComments(utils.APITestCase):
> >      @staticmethod
> > -    def api_url(patch, version=None):
> > -        kwargs = {}
> > +    def api_url(patch, version=None, item=None):
> > +        kwargs = {'patch_id': patch.id}
> >          if version:
> >              kwargs['version'] = version
> > -        kwargs['patch_id'] = patch.id
> > +        if item is None:
> > +            return reverse('api-patch-comment-list', kwargs=kwargs)
> > +        kwargs['comment_id'] = item.id
> > +        return reverse('api-patch-comment-detail', kwargs=kwargs)
> >  
> > -        return reverse('api-patch-comment-list', kwargs=kwargs)
> > +    def setUp(self):
> > +        super(TestPatchComments, self).setUp()
> > +        self.project = create_project()
> > +        self.user = create_maintainer(self.project)
> > +        self.patch = create_patch(project=self.project)
> >  
> >      def assertSerialized(self, comment_obj, comment_json):
> >          self.assertEqual(comment_obj.id, comment_json['id'])
> >          self.assertEqual(comment_obj.submitter.id,
> >                           comment_json['submitter']['id'])
> > +        self.assertEqual(comment_obj.addressed, comment_json['addressed'])
> >          self.assertIn(SAMPLE_CONTENT, comment_json['content'])
> >  
> >      def test_list_empty(self):
> >          """List patch comments when none are present."""
> > -        patch = create_patch()
> > -        resp = self.client.get(self.api_url(patch))
> > +        resp = self.client.get(self.api_url(self.patch))
> >          self.assertEqual(status.HTTP_200_OK, resp.status_code)
> >          self.assertEqual(0, len(resp.data))
> >  
> >      @utils.store_samples('patch-comment-list')
> >      def test_list(self):
> >          """List patch comments."""
> > -        patch = create_patch()
> > -        comment = create_patch_comment(patch=patch)
> > +        comment = create_patch_comment(patch=self.patch)
> >  
> > -        resp = self.client.get(self.api_url(patch))
> > +        resp = self.client.get(self.api_url(self.patch))
> >          self.assertEqual(status.HTTP_200_OK, resp.status_code)
> >          self.assertEqual(1, len(resp.data))
> >          self.assertSerialized(comment, resp.data[0])
> > @@ -121,26 +132,180 @@ class TestPatchComments(utils.APITestCase):
> >  
> >      def test_list_version_1_1(self):
> >          """List patch comments using API v1.1."""
> > -        patch = create_patch()
> > -        comment = create_patch_comment(patch=patch)
> > +        comment = create_patch_comment(patch=self.patch)
> >  
> > -        resp = self.client.get(self.api_url(patch, version='1.1'))
> > +        resp = self.client.get(self.api_url(self.patch, version='1.1'))
> >          self.assertEqual(status.HTTP_200_OK, resp.status_code)
> >          self.assertEqual(1, len(resp.data))
> >          self.assertSerialized(comment, resp.data[0])
> >          self.assertNotIn('list_archive_url', resp.data[0])
> >  
> >      def test_list_version_1_0(self):
> > -        """List patch comments using API v1.0."""
> > -        patch = create_patch()
> > -        create_patch_comment(patch=patch)
> > +        """List patch comments using API v1.0.
> >  
> > -        # check we can't access comments using the old version of the API
> > +        Ensure we can't access comments using the old version of the API.
> > +        """
> >          with self.assertRaises(NoReverseMatch):
> > -            self.client.get(self.api_url(patch, version='1.0'))
> > +            self.client.get(self.api_url(self.patch, version='1.0'))
> >  
> >      def test_list_invalid_patch(self):
> >          """Ensure we get a 404 for a non-existent patch."""
> >          resp = self.client.get(
> >              reverse('api-patch-comment-list', kwargs={'patch_id': '99999'}))
> >          self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
> > +
> > +    @utils.store_samples('patch-comment-detail')
> > +    def test_detail(self):
> > +        """Show a patch comment."""
> > +        comment = create_patch_comment(patch=self.patch)
> > +
> > +        resp = self.client.get(self.api_url(self.patch, item=comment))
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertSerialized(comment, resp.data)
> > +
> > +    def test_detail_version_1_3(self):
> > +        """Show a patch comment using API v1.3."""
> > +        comment = create_patch_comment(patch=self.patch)
> > +
> > +        resp = self.client.get(
> > +            self.api_url(self.patch, version='1.3', item=comment))
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertSerialized(comment, resp.data)
> > +
> > +    def test_detail_version_1_2(self):
> > +        """Show a patch comment using API v1.2."""
> > +        comment = create_patch_comment(patch=self.patch)
> > +
> > +        with self.assertRaises(NoReverseMatch):
> > +            self.client.get(
> > +                self.api_url(self.patch, version='1.2', item=comment))
> > +
> > +    def test_detail_version_1_1(self):
> > +        """Show a patch comment using API v1.1."""
> > +        comment = create_patch_comment(patch=self.patch)
> > +
> > +        with self.assertRaises(NoReverseMatch):
> > +            self.client.get(
> > +                self.api_url(self.patch, version='1.1', item=comment))
> > +
> > +    def test_detail_version_1_0(self):
> > +        """Show a patch comment using API v1.0."""
> > +        comment = create_patch_comment(patch=self.patch)
> > +
> > +        with self.assertRaises(NoReverseMatch):
> > +            self.client.get(
> > +                self.api_url(self.patch, version='1.0', item=comment))
> > +
> > +    @utils.store_samples('patch-comment-detail-error-not-found')
> > +    def test_detail_invalid_patch(self):
> > +        """Ensure we handle non-existent patches."""
> > +        comment = create_patch_comment()
> > +        resp = self.client.get(
> > +            reverse('api-patch-comment-detail', kwargs={
> > +                'patch_id': '99999',
> > +                'comment_id': comment.id}
> > +            ),
> > +        )
> > +        self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
> > +
> > +    def _test_update(self, person, **kwargs):
> > +        submitter = kwargs.get('submitter', person)
> > +        patch = kwargs.get('patch', self.patch)
> > +        comment = create_patch_comment(submitter=submitter, patch=patch)
> > +
> > +        if kwargs.get('authenticate', True):
> > +            self.client.force_authenticate(user=person.user)
> > +        return self.client.patch(
> > +            self.api_url(patch, item=comment),
> > +            {'addressed': kwargs.get('addressed', True)},
> > +            validate_request=kwargs.get('validate_request', True)
> > +        )
> > +
> > +    @utils.store_samples('patch-comment-detail-update-authorized')
> > +    def test_update_authorized(self):
> > +        """Update an existing patch comment as an authorized user.
> > +
> > +        To be authorized users must meet at least one of the following:
> > +        - project maintainer, patch submitter, patch delegate, or
> > +          patch comment submitter
> > +
> > +        Ensure updates can only be performed by authorized users.
> > +        """
> > +        # Update as maintainer
> > +        person = create_person(user=self.user)
> > +        resp = self._test_update(person=person)
> > +        self.assertEqual(1, PatchComment.objects.all().count())
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertTrue(resp.data['addressed'])
> > +
> > +        # Update as patch submitter
> > +        person = create_person(name='patch-submitter', user=create_user())
> > +        patch = create_patch(submitter=person)
> > +        resp = self._test_update(person=person, patch=patch)
> > +        self.assertEqual(2, PatchComment.objects.all().count())
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertTrue(resp.data['addressed'])
> > +
> > +        # Update as patch delegate
> > +        person = create_person(name='patch-delegate', user=create_user())
> > +        patch = create_patch(delegate=person.user)
> > +        resp = self._test_update(person=person, patch=patch)
> > +        self.assertEqual(3, PatchComment.objects.all().count())
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertTrue(resp.data['addressed'])
> > +
> > +        # Update as patch comment submitter
> > +        person = create_person(name='comment-submitter', user=create_user())
> > +        patch = create_patch()
> > +        resp = self._test_update(person=person, patch=patch, submitter=person)
> > +        self.assertEqual(4, PatchComment.objects.all().count())
> > +        self.assertEqual(status.HTTP_200_OK, resp.status_code)
> > +        self.assertTrue(resp.data['addressed'])
> > +
> > +    @utils.store_samples('patch-comment-detail-update-not-authorized')
> Stephen would be better able to speak to which methods should get
> store_samples and which shouldn't. I was initially worried that because
> you're doing multiple requests in the test case you'd get a really
> complex sample file but it seems that only one request is stored.

Yeah, they're all stored in a separate file and we maintain a list of stored
files to prevent duplicates. We don't actually use this information currently
but I plan to start including it in documentation as API samples. The only
reason I haven't done so is because the test data isn't very "complete" so these
examples are currently worse than what our Sphinx extension currently generates
for us.

> 
> Anyway you don't need to do anything here, just flagging that sfin might
> know more about this area.
> 
> > +    def test_update_not_authorized(self):
> > +        """Update an existing patch comment when not signed in and not authorized.
> > +
> > +        To be authorized users must meet at least one of the following:
> > +        - project maintainer, patch submitter, patch delegate, or
> > +          patch comment submitter
> > +
> > +        Ensure updates can only be performed by authorized users.
> > +        """
> > +        person = create_person(user=self.user)
> > +        resp = self._test_update(person=person, authenticate=False)
> > +        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
> > +
> > +        person = create_person()  # normal user without edit permissions
> > +        resp = self._test_update(person=person)  # signed-in
> > +        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
> > +
> > +    @utils.store_samples('patch-comment-detail-update-error-bad-request')
> > +    def test_update_invalid_addressed(self):
> > +        """Update an existing patch comment using invalid values.
> > +
> > +        Ensure we handle invalid patch comment addressed values.
> > +        """
> > +        person = create_person(name='patch-submitter', user=create_user())
> > +        patch = create_patch(submitter=person)
> > +        resp = self._test_update(person=person,
> > +                                 patch=patch,
> > +                                 addressed='not-valid',
> > +                                 validate_request=False)
> > +        self.assertEqual(status.HTTP_400_BAD_REQUEST, resp.status_code)
> > +        self.assertFalse(
> > +            getattr(PatchComment.objects.all().first(), 'addressed')
> > +        )
> > +
> > +    def test_create_delete(self):
> > +        """Ensure creates and deletes aren't allowed"""
> > +        comment = create_patch_comment(patch=self.patch)
> > +        self.user.is_superuser = True
> > +        self.user.save()
> > +        self.client.force_authenticate(user=self.user)
> > +
> > +        resp = self.client.post(self.api_url(self.patch, item=comment))
> > +        self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code)
> > +
> > +        resp = self.client.delete(self.api_url(self.patch, item=comment))
> > +        self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code)
> > diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py
> > index cc09e84..2d5bd4d 100644
> > --- a/patchwork/tests/utils.py
> > +++ b/patchwork/tests/utils.py
> > @@ -260,6 +260,7 @@ def create_patch_comment(**kwargs):
> >          'patch': create_patch() if 'patch' not in kwargs else None,
> >          'msgid': make_msgid(),
> >          'content': SAMPLE_CONTENT,
> > +        'addressed': False,
> 
> Addressed is false by default so you don't need to set it explictly.
> 
> Apart from that I think this looks pretty good. I will be interested to
> see if Stephen wants to flatten the comments endpoint (that is, go from
> /patch/NN/comment/MM/ to /comment/XX). I think I prefer it as-is, fwiw.

Nope, I'm good with nesting. If anything we could do with more of it (I'd like
to nest patches under projects in API v2.0).

Stephen

> 
> I test db query load on the next revision.
> 
> Kind regards,
> Daniel
> 
> >      }
> >      values.update(kwargs)
> >  
> > -- 
> > 2.32.0.554.ge1b32706d8-goog
> > 
> > _______________________________________________
> > Patchwork mailing list
> > Patchwork at lists.ozlabs.org
> > https://lists.ozlabs.org/listinfo/patchwork
> _______________________________________________
> Patchwork mailing list
> Patchwork at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/patchwork




More information about the Patchwork mailing list