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
commit dc439b7433ae52367099f2f4c4ab7a7ba47ec4f0 Author: Gary Martin <g...@apache.org> AuthorDate: Sat May 15 16:38:53 2021 +0100 Add product model and api tests - added product model - adds basic api and api tests for product --- bh_core/settings.py | 3 +- bh_core/urls.py | 1 + functional_tests.py | 18 +++- trackers/{apps.py => api/__init__.py} | 6 -- trackers/{ => api}/serializers.py | 7 ++ trackers/{apps.py => api/tests/__init__.py} | 6 -- trackers/api/tests/test_product_api.py | 112 +++++++++++++++++++++ trackers/{ => api}/urls.py | 7 +- trackers/{ => api}/views.py | 13 +-- trackers/apps.py | 1 + trackers/models.py | 32 ++++++ trackers/tests/__init__.py | 0 .../tests/test_models.py | 40 ++++---- trackers/{ => tests}/tests.py | 4 +- trackers/urls.py | 16 +-- trackers/views.py | 41 -------- 16 files changed, 204 insertions(+), 103 deletions(-) diff --git a/bh_core/settings.py b/bh_core/settings.py index f0db01a..37ac64b 100644 --- a/bh_core/settings.py +++ b/bh_core/settings.py @@ -145,6 +145,5 @@ STATIC_URL = '/static/' REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', - ] + ], } diff --git a/bh_core/urls.py b/bh_core/urls.py index 408612b..a285126 100644 --- a/bh_core/urls.py +++ b/bh_core/urls.py @@ -36,5 +36,6 @@ from django.urls import include, path urlpatterns = [ path('', include('trackers.urls')), + path('api-auth/', include('rest_framework.urls')), path('admin/', admin.site.urls), ] diff --git a/functional_tests.py b/functional_tests.py index df49437..50e22fb 100644 --- a/functional_tests.py +++ b/functional_tests.py @@ -21,7 +21,7 @@ from selenium import webdriver import unittest -class TicketViewTest(unittest.TestCase): +class HomePageViewTest(unittest.TestCase): def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(3) @@ -29,11 +29,25 @@ class TicketViewTest(unittest.TestCase): def tearDown(self): self.browser.quit() - def test_user_can_add_view_and_delete_ticket(self): + def test_user_can_see_homepage(self): self.browser.get('http://localhost:8000') self.assertIn('Bloodhound', self.browser.title) +class ApiHomePageViewTest(unittest.TestCase): + def setUp(self): + self.browser = webdriver.Firefox() + self.browser.implicitly_wait(3) + + def tearDown(self): + self.browser.quit() + + def test_user_can_see_api_homepage(self): + self.browser.get('http://localhost:8000/api') + + self.assertIn('Api Root', self.browser.title) + + if __name__ == '__main__': unittest.main(warnings='ignore') diff --git a/trackers/apps.py b/trackers/api/__init__.py similarity index 89% copy from trackers/apps.py copy to trackers/api/__init__.py index 7b9d013..084b296 100644 --- a/trackers/apps.py +++ b/trackers/api/__init__.py @@ -14,9 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - -from django.apps import AppConfig - - -class TrackersConfig(AppConfig): - name = 'trackers' diff --git a/trackers/serializers.py b/trackers/api/serializers.py similarity index 91% rename from trackers/serializers.py rename to trackers/api/serializers.py index 2616ff1..92db9c6 100644 --- a/trackers/serializers.py +++ b/trackers/api/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User, Group from rest_framework import serializers from trackers import models +from ..models import Product class UserSerializer(serializers.HyperlinkedModelSerializer): @@ -15,6 +16,12 @@ class GroupSerializer(serializers.HyperlinkedModelSerializer): fields = ('url', 'name') +class ProductSerializer(serializers.ModelSerializer): + class Meta: + model = Product + fields = '__all__' + + class TicketSerializer(serializers.ModelSerializer): api_url = serializers.SerializerMethodField() api_events_url = serializers.SerializerMethodField() diff --git a/trackers/apps.py b/trackers/api/tests/__init__.py similarity index 89% copy from trackers/apps.py copy to trackers/api/tests/__init__.py index 7b9d013..084b296 100644 --- a/trackers/apps.py +++ b/trackers/api/tests/__init__.py @@ -14,9 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - -from django.apps import AppConfig - - -class TrackersConfig(AppConfig): - name = 'trackers' diff --git a/trackers/api/tests/test_product_api.py b/trackers/api/tests/test_product_api.py new file mode 100644 index 0000000..39b9243 --- /dev/null +++ b/trackers/api/tests/test_product_api.py @@ -0,0 +1,112 @@ +# 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.contrib.auth.models import User +from django.urls import reverse +from rest_framework.test import APITestCase +from rest_framework import status + +from ...models import Product +from ..serializers import ProductSerializer + + +class ProductsApiTest(APITestCase): + """Test for GET all products API """ + def setUp(self): + self.ally = Product.objects.create(prefix='ALY', name='Project Alice') + self.bob = Product.objects.create(prefix='BOB', name='Project Robert') + + self.new_product_data = { + 'prefix': 'CAR', + 'name': 'Project Caroline', + } + + self.product_data = { + 'prefix': self.ally.prefix, + 'name': 'Project Alan', + } + + self.bad_product_data = { + 'prefix': self.bob.prefix, + 'name': '', + } + + def test_get_all_products(self): + response = self.client.get(reverse('product-list')) + products = Product.objects.all() + serializer = ProductSerializer(products, many=True) + self.assertEqual(response.data, serializer.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_product(self): + response = self.client.get( + reverse('product-detail', args=[self.ally.prefix]) + ) + product = Product.objects.get(prefix=self.ally.prefix) + serializer = ProductSerializer(product) + self.assertEqual(response.data, serializer.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_invalid_product(self): + response = self.client.get( + reverse('product-detail', args=['randomnonsense']) + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_create_product(self): + response = self.client.post( + reverse('product-list'), + self.new_product_data, + ) + product = Product.objects.get(prefix=self.new_product_data['prefix']) + serializer = ProductSerializer(product) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data, serializer.data) + + def test_create_bad_product(self): + response = self.client.post( + reverse('product-list'), + self.bad_product_data, + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_product(self): + response = self.client.put( + reverse('product-detail', args=[self.ally.prefix]), + self.product_data, + ) + product = Product.objects.get(prefix=self.product_data['prefix']) + serializer = ProductSerializer(product) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, serializer.data) + + def test_update_product_bad_data(self): + response = self.client.put( + reverse('product-detail', args=[self.bob.prefix]), + self.bad_product_data, + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete_product(self): + response = self.client.delete( + reverse('product-detail', args=[self.ally.prefix]), + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/trackers/urls.py b/trackers/api/urls.py similarity index 88% copy from trackers/urls.py copy to trackers/api/urls.py index ffcc408..c9ccf2f 100644 --- a/trackers/urls.py +++ b/trackers/api/urls.py @@ -23,16 +23,15 @@ from . import views router = routers.DefaultRouter() router.register('users', views.UserViewSet) router.register('groups', views.GroupViewSet) +router.register('products', views.ProductViewSet) router.register('tickets', views.TicketViewSet) -router.register('ticketfields', views.TicketFieldViewSet) ticket_router = routers.DefaultRouter() ticket_router.register('ticketevents', views.ChangeEventViewSet) urlpatterns = [ - path('', views.home, name='home'), - path('api/', include(router.urls)), - path('api/tickets/<uuid:id>/', include(ticket_router.urls)), + path('', include(router.urls)), + path('tickets/<uuid:id>/', include(ticket_router.urls)), path('swagger<str:format>', views.schema_view.without_ui(cache_timeout=0), name='schema-json'), path('swagger/', views.schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('redoc/', views.schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), diff --git a/trackers/views.py b/trackers/api/views.py similarity index 90% copy from trackers/views.py copy to trackers/api/views.py index f1a6999..34f0d57 100644 --- a/trackers/views.py +++ b/trackers/api/views.py @@ -16,12 +16,12 @@ # under the License. from django.contrib.auth.models import User, Group -from django.http import HttpResponse from drf_yasg.views import get_schema_view from drf_yasg import openapi from rest_framework import permissions, viewsets from . import serializers -from . import models +from ..models import Product +from trackers import models schema_view = get_schema_view( @@ -34,10 +34,6 @@ schema_view = get_schema_view( ) -def home(request): - return HttpResponse('<html><title>Bloodhound Trackers</title></html>') - - class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = serializers.UserSerializer @@ -48,6 +44,11 @@ class GroupViewSet(viewsets.ModelViewSet): serializer_class = serializers.GroupSerializer +class ProductViewSet(viewsets.ModelViewSet): + queryset = Product.objects.all() + serializer_class = serializers.ProductSerializer + + class TicketFieldViewSet(viewsets.ModelViewSet): queryset = models.TicketField.objects.all() serializer_class = serializers.TicketFieldSerializer diff --git a/trackers/apps.py b/trackers/apps.py index 7b9d013..9615c59 100644 --- a/trackers/apps.py +++ b/trackers/apps.py @@ -19,4 +19,5 @@ from django.apps import AppConfig class TrackersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' name = 'trackers' diff --git a/trackers/models.py b/trackers/models.py index 7baab71..f7fb321 100644 --- a/trackers/models.py +++ b/trackers/models.py @@ -26,6 +26,38 @@ from django.urls import reverse logger = logging.getLogger(__name__) +class Product(models.Model): + prefix = models.TextField(primary_key=True) + name = models.TextField() + description = models.TextField(blank=True, null=True) + owner = models.TextField(blank=True, null=True) + + class Meta: + db_table = 'bloodhound_product' + + +class ProductConfig(models.Model): + """Possibly legacy table - keeping for now""" + product = models.ForeignKey(Product, on_delete=models.CASCADE) + section = models.TextField() + option = models.TextField() + value = models.TextField(blank=True, null=True) + + class Meta: + db_table = 'bloodhound_productconfig' + unique_together = (('product', 'section', 'option'),) + + +class ProductResourceMap(models.Model): + """Possibly legacy model - keeping for now""" + product_id = models.ForeignKey(Product, on_delete=models.CASCADE) + resource_type = models.TextField(blank=True, null=True) + resource_id = models.TextField(blank=True, null=True) + + class Meta: + db_table = 'bloodhound_productresourcemap' + + class ModelCommon(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) created = models.DateTimeField(auto_now_add=True, editable=False) diff --git a/trackers/tests/__init__.py b/trackers/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/functional_tests.py b/trackers/tests/test_models.py similarity index 54% copy from functional_tests.py copy to trackers/tests/test_models.py index df49437..13191fc 100644 --- a/functional_tests.py +++ b/trackers/tests/test_models.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - # 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 @@ -17,23 +15,27 @@ # specific language governing permissions and limitations # under the License. -from selenium import webdriver -import unittest +from django.test import TestCase +from ..models import Product -class TicketViewTest(unittest.TestCase): +class ProductTest(TestCase): + """Test modules for Product model""" def setUp(self): - self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) - - def tearDown(self): - self.browser.quit() - - def test_user_can_add_view_and_delete_ticket(self): - self.browser.get('http://localhost:8000') - - self.assertIn('Bloodhound', self.browser.title) - - -if __name__ == '__main__': - unittest.main(warnings='ignore') + Product.objects.create( + prefix='BHD', + name='Bloodhound Legacy', + description='The original Apache Bloodhound', + ) + Product.objects.create( + prefix='BH', + name='Bloodhound', + description='The future of Apache Bloodhound', + ) + + def test_product_name(self): + bhd = Product.objects.get(prefix='BHD') + bh = Product.objects.get(prefix='BH') + + self.assertEqual(bhd.name, "Bloodhound Legacy") + self.assertEqual(bh.name, "Bloodhound") diff --git a/trackers/tests.py b/trackers/tests/tests.py similarity index 97% rename from trackers/tests.py rename to trackers/tests/tests.py index cbc666b..ef6bcb4 100644 --- a/trackers/tests.py +++ b/trackers/tests/tests.py @@ -18,7 +18,7 @@ from django.http import HttpRequest from django.test import TestCase from django.urls import resolve -from trackers.views import home +from ..views import home class HomePageTest(TestCase): @@ -35,7 +35,7 @@ class HomePageTest(TestCase): self.assertTrue(response.content.endswith(b'</html>')) -from trackers.models import Ticket +from ..models import Ticket class TicketModelTest(TestCase): def test_last_update_on_create_returns_created_date(self): diff --git a/trackers/urls.py b/trackers/urls.py index ffcc408..c62892f 100644 --- a/trackers/urls.py +++ b/trackers/urls.py @@ -17,23 +17,9 @@ from django.urls import path from django.conf.urls import include -from rest_framework import routers from . import views -router = routers.DefaultRouter() -router.register('users', views.UserViewSet) -router.register('groups', views.GroupViewSet) -router.register('tickets', views.TicketViewSet) -router.register('ticketfields', views.TicketFieldViewSet) - -ticket_router = routers.DefaultRouter() -ticket_router.register('ticketevents', views.ChangeEventViewSet) - urlpatterns = [ path('', views.home, name='home'), - path('api/', include(router.urls)), - path('api/tickets/<uuid:id>/', include(ticket_router.urls)), - path('swagger<str:format>', views.schema_view.without_ui(cache_timeout=0), name='schema-json'), - path('swagger/', views.schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), - path('redoc/', views.schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + path('api/', include('trackers.api.urls')), ] diff --git a/trackers/views.py b/trackers/views.py index f1a6999..0861c17 100644 --- a/trackers/views.py +++ b/trackers/views.py @@ -15,49 +15,8 @@ # specific language governing permissions and limitations # under the License. -from django.contrib.auth.models import User, Group from django.http import HttpResponse -from drf_yasg.views import get_schema_view -from drf_yasg import openapi -from rest_framework import permissions, viewsets -from . import serializers -from . import models - - -schema_view = get_schema_view( - openapi.Info( - title='Bloodhound Core API', - default_version='v1', - ), - public=True, - permission_classes=(permissions.AllowAny,), -) def home(request): return HttpResponse('<html><title>Bloodhound Trackers</title></html>') - - -class UserViewSet(viewsets.ModelViewSet): - queryset = User.objects.all() - serializer_class = serializers.UserSerializer - - -class GroupViewSet(viewsets.ModelViewSet): - queryset = Group.objects.all() - serializer_class = serializers.GroupSerializer - - -class TicketFieldViewSet(viewsets.ModelViewSet): - queryset = models.TicketField.objects.all() - serializer_class = serializers.TicketFieldSerializer - - -class TicketViewSet(viewsets.ModelViewSet): - queryset = models.Ticket.objects.all() - serializer_class = serializers.TicketSerializer - - -class ChangeEventViewSet(viewsets.ModelViewSet): - queryset = models.ChangeEvent.objects.all() - serializer_class = serializers.ChangeEventSerializer