Add product model and api tests
- added product model
- adds basic api and api tests for product
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 @@
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 @@
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 @@
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 @@
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/api/__init__.py b/trackers/api/__init__.py
new file mode 100644
index 0000000..084b296
--- /dev/null
+++ b/trackers/api/__init__.py
@@ -0,0 +1,16 @@
+# 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.
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 @@
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/api/tests/__init__.py b/trackers/api/tests/__init__.py
new file mode 100644
index 0000000..084b296
--- /dev/null
+++ b/trackers/api/tests/__init__.py
@@ -0,0 +1,16 @@
+# 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.
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/api/urls.py b/trackers/api/urls.py
new file mode 100644
index 0000000..c9ccf2f
--- /dev/null
+++ b/trackers/api/urls.py
@@ -0,0 +1,38 @@
+# 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 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('products', views.ProductViewSet)
+router.register('tickets', views.TicketViewSet)
+
+ticket_router = routers.DefaultRouter()
+ticket_router.register('ticketevents', views.ChangeEventViewSet)
+
+urlpatterns = [
+ 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/api/views.py b/trackers/api/views.py
new file mode 100644
index 0000000..34f0d57
--- /dev/null
+++ b/trackers/api/views.py
@@ -0,0 +1,64 @@
+# 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, Group
+from drf_yasg.views import get_schema_view
+from drf_yasg import openapi
+from rest_framework import permissions, viewsets
+from . import serializers
+from ..models import Product
+from trackers import models
+
+
+schema_view = get_schema_view(
+ openapi.Info(
+ title='Bloodhound Core API',
+ default_version='v1',
+ ),
+ public=True,
+ permission_classes=(permissions.AllowAny,),
+)
+
+
+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 ProductViewSet(viewsets.ModelViewSet):
+ queryset = Product.objects.all()
+ serializer_class = serializers.ProductSerializer
+
+
+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
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 @@
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 @@
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
--- /dev/null
+++ b/trackers/tests/__init__.py
diff --git a/trackers/tests/test_models.py b/trackers/tests/test_models.py
new file mode 100644
index 0000000..13191fc
--- /dev/null
+++ b/trackers/tests/test_models.py
@@ -0,0 +1,41 @@
+# 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.test import TestCase
+from ..models import Product
+
+
+class ProductTest(TestCase):
+ """Test modules for Product model"""
+ def setUp(self):
+ 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 @@
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