You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by gj...@apache.org on 2021/09/14 12:49:54 UTC

[bloodhound-core] branch main updated: Relax api regex for some models allowng . in names

This is an automated email from the ASF dual-hosted git repository.

gjm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bloodhound-core.git


The following commit(s) were added to refs/heads/main by this push:
     new 4228632  Relax api regex for some models allowng . in names
4228632 is described below

commit 4228632ce8c124e2bec1f76d83e1252ab5af064a
Author: Gary Martin <gj...@apache.org>
AuthorDate: Tue Sep 14 13:36:50 2021 +0100

    Relax api regex for some models allowng . in names
    
    Default api urls do not allow '.' characters when matching the names,
    presumably to ensure '.<format>' specifications are definitely matched
    properly. This patch relaxes this as we are definitely going to want to
    allow for '.' for things like version names.
    
     - change the lookup_value_regex for Component, Milestone and Version
       models
     - add hypothesis tests for testing the values that the api is allowed
       to use for names when creating objects from each list view
---
 pyproject.toml                            |  2 +
 trackers/api/serializers.py               |  2 -
 trackers/api/tests/test_ticket_api.py     | 10 +---
 trackers/api/tests/test_ticket_api_hyp.py | 84 +++++++++++++++++++++++++++++++
 trackers/api/urls.py                      |  1 -
 trackers/api/views.py                     |  3 ++
 6 files changed, 91 insertions(+), 11 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 1eec374..025e0d5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,8 @@ psycopg2-binary = { version = "^2.9", optional = true }
 
 [tool.poetry.dev-dependencies]
 selenium = "^3.141.0"
+django-extensions = "^3.1.3"
+hypothesis = "^6.21.0"
 
 [tool.poetry.extras]
 postgres = ["psycopg2"]
diff --git a/trackers/api/serializers.py b/trackers/api/serializers.py
index 58d9248..c8e58cd 100644
--- a/trackers/api/serializers.py
+++ b/trackers/api/serializers.py
@@ -3,8 +3,6 @@ from django.shortcuts import get_object_or_404
 from rest_framework import serializers
 from rest_framework.reverse import reverse
 from ..models import Component, Milestone, Product, Ticket, Version
-from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer
-from rest_framework_nested.relations import NestedHyperlinkedIdentityField
 from functools import partial
 
 
diff --git a/trackers/api/tests/test_ticket_api.py b/trackers/api/tests/test_ticket_api.py
index b8afb0a..b4e30c0 100644
--- a/trackers/api/tests/test_ticket_api.py
+++ b/trackers/api/tests/test_ticket_api.py
@@ -15,7 +15,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-from django.contrib.auth.models import User
 from django.urls import reverse
 from rest_framework.test import APITestCase
 from rest_framework import status
@@ -27,12 +26,7 @@ from ...models import (
     Ticket,
     Version,
 )
-from ..serializers import (
-    ComponentSerializer,
-    MilestoneSerializer,
-    TicketSerializer,
-    VersionSerializer,
-)
+
 
 class TicketApiTest(APITestCase):
     """Tests for ticket API"""
@@ -313,7 +307,7 @@ class ComponentApiTest(APITestCase):
 
 
 class MilestoneApiTest(APITestCase):
-    """Tests for component API"""
+    """Tests for milestone API"""
 
     def setUp(self):
         self.product = Product.objects.create(prefix="BH", name="Bloodhound")
diff --git a/trackers/api/tests/test_ticket_api_hyp.py b/trackers/api/tests/test_ticket_api_hyp.py
new file mode 100644
index 0000000..f54fc50
--- /dev/null
+++ b/trackers/api/tests/test_ticket_api_hyp.py
@@ -0,0 +1,84 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from django.urls import reverse
+from hypothesis import example, given, strategies as st
+from hypothesis.extra.django import TestCase
+from rest_framework.test import APIClient, APIRequestFactory
+from rest_framework import status
+
+from ...models import Product
+
+name_st = st.text(
+    st.characters(
+        blacklist_characters="/",
+        max_codepoint=1000,
+        blacklist_categories=("Cc", "Cs")
+    ),
+    min_size=1
+).map(lambda x: x.strip()).filter(lambda s: len(s) > 0)
+
+
+class CommonAPIPropertiesTestCase(TestCase):
+    """Common tests for API"""
+
+    def setUp(self):
+        self.client = APIClient()
+        self.factory = APIRequestFactory()
+        self.product = Product.objects.create(prefix="BH", name="Bloodhound")
+        self.list_uri = reverse(
+            self.list_view_name,
+            kwargs={"product_prefix": "BH"}
+        )
+
+
+class NameTestsMixin:
+    @given(name=name_st)
+    @example("next 1.x")
+    def test_create(self, name):
+        data = {
+            "name": name,
+            "description": "Example Description",
+        }
+
+        response = self.client.post(self.list_uri, data)
+
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+
+class ComponentAPIPropertiesTest(CommonAPIPropertiesTestCase, NameTestsMixin):
+    """Hypothesis tests for component API"""
+
+    def setUp(self):
+        self.list_view_name = "product-components-list"
+        super().setUp()
+
+
+class MilestoneAPIPropertiesTest(CommonAPIPropertiesTestCase, NameTestsMixin):
+    """Hypothesis tests for milestone API"""
+
+    def setUp(self):
+        self.list_view_name = "product-milestones-list"
+        super().setUp()
+
+
+class VersionAPIPropertiesTest(CommonAPIPropertiesTestCase, NameTestsMixin):
+    """Hypothesis tests for version API"""
+
+    def setUp(self):
+        self.list_view_name = "product-versions-list"
+        super().setUp()
diff --git a/trackers/api/urls.py b/trackers/api/urls.py
index 7dad774..403ea05 100644
--- a/trackers/api/urls.py
+++ b/trackers/api/urls.py
@@ -18,7 +18,6 @@
 from django.urls import path
 from django.conf.urls import include
 from rest_framework.schemas import get_schema_view
-from rest_framework.renderers import JSONOpenAPIRenderer
 from rest_framework_nested import routers
 from . import views
 
diff --git a/trackers/api/views.py b/trackers/api/views.py
index 9bacff7..91ceabd 100644
--- a/trackers/api/views.py
+++ b/trackers/api/views.py
@@ -63,6 +63,7 @@ class ComponentViewSet(viewsets.ModelViewSet):
     queryset = models.Component.objects.all()
     serializer_class = serializers.ComponentSerializer
     lookup_field = 'name'
+    lookup_value_regex = '[^/]+'
 
     def get_queryset(self, *args, **kwargs):
         prefix = self.kwargs['product_prefix']
@@ -73,6 +74,7 @@ class MilestoneViewSet(viewsets.ModelViewSet):
     queryset = models.Milestone.objects.all()
     serializer_class = serializers.MilestoneSerializer
     lookup_field = 'name'
+    lookup_value_regex = '[^/]+'
 
     def get_queryset(self, *args, **kwargs):
         prefix = self.kwargs['product_prefix']
@@ -83,6 +85,7 @@ class VersionViewSet(viewsets.ModelViewSet):
     queryset = models.Version.objects.all()
     serializer_class = serializers.VersionSerializer
     lookup_field = 'name'
+    lookup_value_regex = '[^/]+'
 
     def get_queryset(self, *args, **kwargs):
         prefix = self.kwargs['product_prefix']