Merge pull request #733 from nofusscomputing/test-ticket-models
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -84,7 +84,9 @@
|
||||
"request": "launch",
|
||||
"module": "pytest",
|
||||
"args": [
|
||||
"--collect-only"
|
||||
"--override-ini", "addopts=",
|
||||
"--collect-only",
|
||||
"app",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
|
@ -16,24 +16,48 @@ from app.tests.common import DoesNotExist
|
||||
class APIFieldsTestCases:
|
||||
|
||||
api_fields_common = {
|
||||
'id': int,
|
||||
'display_name': str,
|
||||
'_urls': dict,
|
||||
'_urls._self': str,
|
||||
'_urls.notes': str
|
||||
'id': {
|
||||
'expected': int
|
||||
},
|
||||
'display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls': {
|
||||
'expected': dict
|
||||
},
|
||||
'_urls._self': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls.notes': {
|
||||
'expected': str
|
||||
},
|
||||
}
|
||||
|
||||
api_fields_model = {
|
||||
'model_notes': str,
|
||||
'created': str,
|
||||
'modified': str
|
||||
'model_notes': {
|
||||
'expected': str
|
||||
},
|
||||
'created': {
|
||||
'expected': str
|
||||
},
|
||||
'modified': {
|
||||
'expected': str
|
||||
},
|
||||
}
|
||||
|
||||
api_fields_tenancy = {
|
||||
'organization': dict,
|
||||
'organization.id': int,
|
||||
'organization.display_name': str,
|
||||
'organization.url': Hyperlink,
|
||||
'organization': {
|
||||
'expected': dict
|
||||
},
|
||||
'organization.id': {
|
||||
'expected': int
|
||||
},
|
||||
'organization.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'organization.url': {
|
||||
'expected': Hyperlink
|
||||
},
|
||||
}
|
||||
|
||||
parametrized_test_data = {
|
||||
@ -156,12 +180,15 @@ class APIFieldsTestCases:
|
||||
pass
|
||||
|
||||
|
||||
def test_api_field_exists(self, recursearray, test_name, test_value, expected):
|
||||
def test_api_field_exists(self, recursearray, parameterized, param_key_test_data,
|
||||
param_value,
|
||||
param_expected
|
||||
):
|
||||
"""Test for existance of API Field"""
|
||||
|
||||
api_data = recursearray(self.api_data, test_value)
|
||||
api_data = recursearray(self.api_data, param_value)
|
||||
|
||||
if expected is DoesNotExist:
|
||||
if param_expected is DoesNotExist:
|
||||
|
||||
assert api_data['key'] not in api_data['obj']
|
||||
|
||||
@ -171,18 +198,21 @@ class APIFieldsTestCases:
|
||||
|
||||
|
||||
|
||||
def test_api_field_type(self, recursearray, test_name, test_value, expected):
|
||||
def test_api_field_type(self, recursearray, parameterized, param_key_test_data,
|
||||
param_value,
|
||||
param_expected
|
||||
):
|
||||
"""Test for type for API Field"""
|
||||
|
||||
api_data = recursearray(self.api_data, test_value)
|
||||
api_data = recursearray(self.api_data, param_value)
|
||||
|
||||
if expected is DoesNotExist:
|
||||
if param_expected is DoesNotExist:
|
||||
|
||||
assert api_data['key'] not in api_data['obj']
|
||||
|
||||
else:
|
||||
|
||||
assert type( api_data['value'] ) is expected
|
||||
assert type( api_data['value'] ) is param_expected
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import ContentType, Permission, User
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
|
||||
from unittest.mock import patch, PropertyMock
|
||||
@ -30,6 +33,7 @@ from api.viewsets.common import (
|
||||
|
||||
CommonViewSet,
|
||||
ModelViewSet,
|
||||
SubModelViewSet,
|
||||
|
||||
ModelCreateViewSet,
|
||||
ModelListRetrieveDeleteViewSet,
|
||||
@ -788,7 +792,7 @@ class ModelViewSetBaseCases(
|
||||
):
|
||||
"""Test Suite for class ModelViewSetBase"""
|
||||
|
||||
kwargs: dict
|
||||
kwargs: dict = {}
|
||||
|
||||
organization: Organization
|
||||
|
||||
@ -805,8 +809,6 @@ class ModelViewSetBaseCases(
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
def test_class_inherits_modelviewsetbase(self):
|
||||
"""Class Inheritence check
|
||||
@ -1291,6 +1293,211 @@ class ModelViewSetTest(
|
||||
|
||||
|
||||
|
||||
class SubModelViewSetTestCases(
|
||||
ModelViewSetCases
|
||||
):
|
||||
|
||||
kwargs: dict
|
||||
|
||||
organization: Organization
|
||||
|
||||
view_user: User
|
||||
|
||||
viewset = SubModelViewSet
|
||||
|
||||
|
||||
def test_class_inherits_submodelviewsetbase(self):
|
||||
"""Class Inheritence check
|
||||
|
||||
Class must inherit from `SubModelViewSet`
|
||||
"""
|
||||
|
||||
assert issubclass(self.viewset, SubModelViewSet)
|
||||
|
||||
|
||||
def test_view_attr_exists_base_model(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `base_model` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'base_model')
|
||||
|
||||
|
||||
def test_view_attr_type_base_model(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `base_model` must be of type Django Model
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert issubclass(view_set.base_model, models.Model)
|
||||
|
||||
|
||||
|
||||
def test_view_attr_exists_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model_kwarg` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'model_kwarg')
|
||||
|
||||
|
||||
def test_view_attr_type_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model_kwarg` must be of type str
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert type(view_set.model_kwarg) is str
|
||||
|
||||
|
||||
|
||||
def test_view_attr_value_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model_kwarg` must be equal to model._meta.sub_model_type
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model_kwarg is not None
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'to be written')
|
||||
def test_view_func_related_objects(self):
|
||||
""" Function Test
|
||||
|
||||
Function `related_objects` must return the highest model in the chain
|
||||
of models
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'to be written')
|
||||
def test_view_func_get_serializer_class_view(self):
|
||||
""" Function Test
|
||||
|
||||
Function `get_serializer_class` must return the correct view serializer
|
||||
for the model in question
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'to be written')
|
||||
def test_view_func_get_serializer_class_create(self):
|
||||
""" Function Test
|
||||
|
||||
Function `get_serializer_class` must return the correct create
|
||||
serializer for the model in question.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SubModelViewSetTest(
|
||||
SubModelViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
def test_view_attr_model_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Attribute `model` must return a value that is not None
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model is None
|
||||
|
||||
|
||||
|
||||
def test_view_attr_type_base_model(self):
|
||||
"""Attribute Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Attribute `base_model` must be of type Django Model
|
||||
"""
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_view_attr_type_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Attribute `model_kwarg` must be of type str
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model_kwarg is None
|
||||
|
||||
|
||||
def test_view_attr_value_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Attribute `model_kwarg` must be equal to model._meta.sub_model_type
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model_kwarg is None
|
||||
|
||||
def test_view_func_get_queryset_cache_result(self):
|
||||
"""Viewset Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Ensure that the `get_queryset` function caches the result under
|
||||
attribute `<viewset>.queryset`
|
||||
"""
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_view_func_get_queryset_cache_result_used(self):
|
||||
"""Viewset Test
|
||||
|
||||
This test case overrides a test case of the same name. As this test is
|
||||
checking the base classes, it's return is different to a class that
|
||||
has inherited from this or parent classes.
|
||||
|
||||
Ensure that the `get_queryset` function caches the result under
|
||||
attribute `<viewset>.queryset`
|
||||
"""
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
|
||||
class ModelCreateViewSetCases(
|
||||
ModelViewSetBaseCases,
|
||||
CreateCases,
|
||||
@ -2382,6 +2589,82 @@ class ModelViewSetInheritedCases(
|
||||
|
||||
|
||||
|
||||
class SubModelViewSetInheritedCases(
|
||||
SubModelViewSetTestCases,
|
||||
CommonViewSetAPIRenderOptionsCases,
|
||||
):
|
||||
"""Test Suite for classes that inherit SubModelViewSet
|
||||
|
||||
Use this Test Suite for ViewSet classes that inherit from SubModelViewSet
|
||||
"""
|
||||
|
||||
http_options_response_list = None
|
||||
"""Inherited class must make and store here a HTTP/Options request"""
|
||||
|
||||
route_name = None
|
||||
"""Inherited class must define the url rout name with namespace"""
|
||||
|
||||
base_model = None
|
||||
"""The Sub Model that is returned from the model property"""
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. make list request
|
||||
"""
|
||||
|
||||
self.viewset.kwargs = {}
|
||||
|
||||
self.viewset.kwargs[self.viewset.model_kwarg] = self.model._meta.sub_model_type
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_values(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` only contains valid values
|
||||
"""
|
||||
|
||||
# Values valid for model views
|
||||
valid_values: list = [
|
||||
'DELETE',
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
]
|
||||
|
||||
all_valid: bool = True
|
||||
|
||||
for method in list(self.http_options_response_list.data['allowed_methods']):
|
||||
|
||||
if method not in valid_values:
|
||||
|
||||
all_valid = False
|
||||
|
||||
assert all_valid
|
||||
|
||||
|
||||
def test_view_attr_model_value(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model` must return the correct sub-model
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model == self.model
|
||||
|
||||
|
||||
|
||||
class PublicReadOnlyViewSetInheritedCases(
|
||||
PublicReadOnlyViewSetCases,
|
||||
):
|
||||
|
@ -728,13 +728,7 @@ class ModelViewSet(
|
||||
|
||||
|
||||
class SubModelViewSet(
|
||||
ModelViewSetBase,
|
||||
Create,
|
||||
Retrieve,
|
||||
Update,
|
||||
Destroy,
|
||||
List,
|
||||
viewsets.ModelViewSet,
|
||||
ModelViewSet,
|
||||
):
|
||||
|
||||
base_model = None
|
||||
|
172
app/conftest.py
172
app/conftest.py
@ -41,11 +41,48 @@ def enable_db_access_for_all_tests(db): # pylint: disable=W0613:unused-argume
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
|
||||
# test_no_value = {"test_name", "test_value", "expected"} <= set(metafunc.fixturenames)
|
||||
arg_values:list = None
|
||||
|
||||
# test_value = {"test_name", "test_value", "return_value", "expected"} <= set(metafunc.fixturenames)
|
||||
fixture_parameters: list = []
|
||||
|
||||
parameterized_test = False
|
||||
|
||||
parameterized_key: str = None
|
||||
|
||||
if {'parameterized'} <= set(metafunc.fixturenames):
|
||||
|
||||
all_fixture_parameters = metafunc.fixturenames
|
||||
|
||||
fixture_parameters += ['parameterized']
|
||||
|
||||
for i in range(0, len(metafunc.fixturenames)):
|
||||
|
||||
if (
|
||||
str(all_fixture_parameters[i]).startswith('param_')
|
||||
and not str(all_fixture_parameters[i]).startswith('param_key_')
|
||||
):
|
||||
|
||||
fixture_parameters += [ all_fixture_parameters[i] ]
|
||||
|
||||
|
||||
elif str(all_fixture_parameters[i]).startswith('param_key_'):
|
||||
|
||||
parameterized_key = str( all_fixture_parameters[i] ).replace('param_key_', '')
|
||||
|
||||
if len(fixture_parameters) == 1:
|
||||
|
||||
fixture_parameters += [ all_fixture_parameters[i] ]
|
||||
|
||||
else:
|
||||
|
||||
fixture_parameters[1] = all_fixture_parameters[i]
|
||||
|
||||
|
||||
parameterized_test = len(fixture_parameters) > 0
|
||||
|
||||
|
||||
if parameterized_test:
|
||||
|
||||
if {"test_name", "test_value", "expected"} <= set(metafunc.fixturenames):
|
||||
values = {}
|
||||
|
||||
|
||||
@ -55,26 +92,129 @@ def pytest_generate_tests(metafunc):
|
||||
|
||||
for base in reversed(cls.__mro__):
|
||||
|
||||
base_values = getattr(base, "parametrized_test_data", [])
|
||||
base_values = getattr(base, 'parametrized_' + parameterized_key, None)
|
||||
|
||||
if isinstance(base_values, dict):
|
||||
if not isinstance(base_values, dict):
|
||||
|
||||
continue
|
||||
|
||||
if len(values) == 0 and len(base_values) > 0:
|
||||
|
||||
values.update(base_values)
|
||||
|
||||
continue
|
||||
|
||||
for key, value in values.items():
|
||||
|
||||
if(
|
||||
type(value) is not dict
|
||||
or key not in base_values
|
||||
):
|
||||
|
||||
continue
|
||||
|
||||
if key not in values:
|
||||
|
||||
values.update({
|
||||
key: base_values[key]
|
||||
})
|
||||
|
||||
else:
|
||||
|
||||
values[key].update( base_values[key] )
|
||||
|
||||
|
||||
for key, value in base_values.items():
|
||||
|
||||
if key not in values:
|
||||
|
||||
values.update({
|
||||
key: base_values[key]
|
||||
})
|
||||
|
||||
|
||||
if values:
|
||||
|
||||
metafunc.parametrize(
|
||||
argnames = (
|
||||
"test_name", "test_value", "expected"
|
||||
),
|
||||
argvalues = [
|
||||
(field, field, expected) for field, expected in values.items()
|
||||
],
|
||||
ids = [
|
||||
str( field.replace('.', '_') + '_' + getattr(expected, '__name__', 'None').lower() ) for field, expected in values.items()
|
||||
],
|
||||
)
|
||||
ids = []
|
||||
|
||||
arg_values:list = []
|
||||
|
||||
for item in values.items():
|
||||
|
||||
ids_name = item[0]
|
||||
|
||||
item_values:tuple = ()
|
||||
|
||||
length = len(item)
|
||||
|
||||
is_key_value: bool = True
|
||||
|
||||
if type(item[1]) is not dict:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
item_values += ( None, None, item[0])
|
||||
|
||||
for key in fixture_parameters:
|
||||
|
||||
if key in [ fixture_parameters[0], fixture_parameters[1], fixture_parameters[2], ]:
|
||||
# these values are already defined in `item_values`
|
||||
# fixture_parameters[0] = parameterized.
|
||||
# fixture_parameters[1] = param_key
|
||||
# fixture_parameters[2] = the dict name
|
||||
|
||||
continue
|
||||
|
||||
if(
|
||||
str(key).startswith('param_')
|
||||
and not str(key).startswith('param_key_')
|
||||
):
|
||||
|
||||
key = str(key).replace('param_', '')
|
||||
|
||||
if (
|
||||
type(item[1]) is not dict
|
||||
or item[1].get(key, 'key-does_not-exist') == 'key-does_not-exist'
|
||||
):
|
||||
|
||||
item_values = ()
|
||||
|
||||
continue
|
||||
|
||||
|
||||
if key in item[1]:
|
||||
|
||||
item_values += ( item[1][key], )
|
||||
|
||||
if type(item[1][key]) is type:
|
||||
|
||||
ids_name += '_' + getattr(item[1][key], '__name__', 'None').lower()
|
||||
|
||||
else:
|
||||
|
||||
ids_name += '_' + str(item[1][key]).lower()
|
||||
|
||||
|
||||
if(
|
||||
len(item_values) > 0
|
||||
and len(fixture_parameters) == len(item_values)
|
||||
):
|
||||
|
||||
arg_values += [ item_values ]
|
||||
|
||||
ids += [ ids_name, ]
|
||||
|
||||
|
||||
if len(arg_values) > 0:
|
||||
|
||||
metafunc.parametrize(
|
||||
argnames = [
|
||||
*fixture_parameters
|
||||
],
|
||||
argvalues = arg_values,
|
||||
ids = ids,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
@ -185,64 +185,114 @@ class ModelSerializer(
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'external_system',
|
||||
'external_ref',
|
||||
# 'ticket_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate_field_milestone( self ) -> bool:
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
is_valid: bool = False
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.instance is not None:
|
||||
if self.context.get('view', None) is not None:
|
||||
|
||||
if self.instance.milestone is None:
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
return True
|
||||
if not self.context['view']._has_import:
|
||||
|
||||
else:
|
||||
read_only_fields += [
|
||||
'external_system',
|
||||
'external_ref',
|
||||
'ticket_type',
|
||||
]
|
||||
|
||||
if self.instance.project is None:
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
details = 'Milestones require a project',
|
||||
code = 'milestone_requires_project',
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
if self.instance.project.id == self.instance.milestone.project.id:
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = 'Milestone must be from the same project',
|
||||
code = 'milestone_same_project',
|
||||
)
|
||||
|
||||
return is_valid
|
||||
self.Meta.read_only_fields = read_only_fields
|
||||
|
||||
|
||||
def validate_field_milestone( self, attrs, raise_exception = False ) -> bool:
|
||||
|
||||
def validate(self, attrs):
|
||||
milestone = attrs.get('milestone', None)
|
||||
|
||||
attrs = super().validate(attrs)
|
||||
project = attrs.get('project', None)
|
||||
|
||||
if not self.validate_field_milestone:
|
||||
if milestone is not None:
|
||||
|
||||
del attrs['milestone']
|
||||
if project is None:
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = {
|
||||
'milestone': 'Milestones require a project'
|
||||
},
|
||||
code = 'milestone_requires_project',
|
||||
)
|
||||
|
||||
|
||||
elif project.id != milestone.project.id:
|
||||
|
||||
del attrs['milestone']
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = {
|
||||
'milestone': 'Milestone must be from the same project'
|
||||
},
|
||||
code = 'milestone_same_project',
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def validate_field_external_system( self, attrs, raise_exception = False ) -> bool:
|
||||
|
||||
external_system = attrs.get('external_system', None)
|
||||
|
||||
external_ref = attrs.get('external_ref', None)
|
||||
|
||||
if external_system is None and external_ref is not None:
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = {
|
||||
'external_system': 'External System is required when an External Ref is defined'
|
||||
},
|
||||
code = 'external_system_missing',
|
||||
)
|
||||
|
||||
elif external_system is not None and external_ref is None:
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = {
|
||||
'external_ref': 'External Ref is required when an External System is defined'
|
||||
},
|
||||
code = 'external_ref_missing',
|
||||
)
|
||||
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
attrs = super().validate( attrs )
|
||||
|
||||
attrs = self.validate_field_milestone( attrs )
|
||||
|
||||
attrs = self.validate_field_external_system( attrs )
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def is_valid(self, raise_exception = False):
|
||||
|
||||
is_valid = super().is_valid( raise_exception = raise_exception )
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TicketBaseViewSerializer')
|
||||
|
@ -12,3 +12,13 @@ def model(request):
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from core.serializers.ticket import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
@ -0,0 +1,361 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
|
||||
from core.models.ticket_base import TicketBase
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'title': 'ticket one',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = TicketBase
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'title': 'ticket two',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'title': 'ticket three',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
model = TicketBase
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.kwargs_create_item.update({
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update({
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_sanity_is_ticket_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `TicketBase`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class TicketBaseMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_ticket_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'ticket_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'ticket_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class TicketBaseMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
url_name = '_api_v2_ticket'
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_has_key_urls_back(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.back`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert 'back' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_key_urls_back_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.back` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert type(response.data['urls']['back']) is str
|
||||
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_has_key_urls_return_url(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.return_url`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert 'return_url' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_key_urls_return_url_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.return_url` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert type(response.data['urls']['return_url']) is str
|
||||
|
||||
|
@ -0,0 +1,465 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from rest_framework.exceptions import (
|
||||
ValidationError
|
||||
)
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.ticket.ticket_category import TicketCategory
|
||||
from core.models.ticket_base import TicketBase
|
||||
|
||||
from project_management.models.project_milestone import (
|
||||
Project,
|
||||
ProjectMilestone,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class MockView:
|
||||
|
||||
_has_import: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has import permission.
|
||||
"""
|
||||
|
||||
_has_purge: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has purge permission.
|
||||
"""
|
||||
|
||||
_has_triage: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has triage permission.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class TicketBaseSerializerTestCases:
|
||||
|
||||
|
||||
parametrized_test_data: dict = {
|
||||
"organization": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'required',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"external_system": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'external_system_missing',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': True,
|
||||
},
|
||||
"external_ref": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'external_ref_missing',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': True,
|
||||
},
|
||||
"parent_ticket": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
# "ticket_type": "request",
|
||||
"status": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"category": True,
|
||||
"title": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'required',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"description": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'required',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"project": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'milestone_requires_project',
|
||||
'exception_code_key': 'milestone',
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"milestone": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"urgency": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"impact": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"priority": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"opened_by": {
|
||||
'will_create': False,
|
||||
'exception_obj': ValidationError,
|
||||
'exception_code': 'required',
|
||||
'exception_code_key': None,
|
||||
'permission_import_required': True,
|
||||
},
|
||||
"subscribed_to": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"assigned_to": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"planned_start_date": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"planned_finish_date": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"real_start_date": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"real_finish_date": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"is_deleted": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"is_solved": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"date_solved": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"is_closed": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"date_closed": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'external_ref': 1,
|
||||
'title': 'ticket title',
|
||||
'description': 'the ticket description',
|
||||
'status': TicketBase.TicketStatus.NEW,
|
||||
'planned_start_date': '2025-04-16T00:00:01',
|
||||
'planned_finish_date': '2025-04-16T00:00:02',
|
||||
'real_start_date': '2025-04-16T00:00:03',
|
||||
'real_finish_date': '2025-04-16T00:00:04',
|
||||
'is_deleted': False,
|
||||
'is_solved': False,
|
||||
'date_solved': '2025-04-16T00:00:04',
|
||||
'is_closed': False,
|
||||
'date_closed': '2025-04-16T00:00:04',
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def setup_data(self,
|
||||
request,
|
||||
model,
|
||||
django_db_blocker,
|
||||
organization_one,
|
||||
):
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.organization = organization_one
|
||||
|
||||
valid_data = {}
|
||||
|
||||
for base in reversed(request.cls.__mro__):
|
||||
|
||||
if hasattr(base, 'valid_data'):
|
||||
|
||||
if base.valid_data is None:
|
||||
|
||||
continue
|
||||
|
||||
valid_data.update(**base.valid_data)
|
||||
|
||||
|
||||
if len(valid_data) > 0:
|
||||
|
||||
request.cls.valid_data = valid_data
|
||||
|
||||
|
||||
if 'organization' not in request.cls.valid_data:
|
||||
|
||||
request.cls.valid_data.update({
|
||||
'organization': request.cls.organization.pk
|
||||
})
|
||||
|
||||
|
||||
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view", password="password")
|
||||
|
||||
|
||||
yield
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.view_user.delete()
|
||||
|
||||
del request.cls.valid_data
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def setup_model_data(self, request, django_db_blocker):
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.entity_user = Entity.objects.create(
|
||||
organization = request.cls.organization,
|
||||
model_notes = 'asdas'
|
||||
)
|
||||
|
||||
project = Project.objects.create(
|
||||
organization = request.cls.organization,
|
||||
name = 'project'
|
||||
)
|
||||
|
||||
parent_ticket = request.cls.model.objects.create(
|
||||
organization = request.cls.organization,
|
||||
title = 'parent ticket',
|
||||
description = 'bla bla',
|
||||
opened_by = request.cls.view_user,
|
||||
)
|
||||
|
||||
project_milestone = ProjectMilestone.objects.create(
|
||||
organization = request.cls.organization,
|
||||
name = 'project milestone one',
|
||||
project = project
|
||||
)
|
||||
|
||||
request.cls.valid_data.update({
|
||||
'category': TicketCategory.objects.create(
|
||||
organization = request.cls.organization,
|
||||
name = 'a category'
|
||||
).pk,
|
||||
'opened_by': request.cls.view_user.pk,
|
||||
'project': project.pk,
|
||||
'milestone': project_milestone.pk,
|
||||
'parent_ticket': parent_ticket.pk,
|
||||
'external_system': int(request.cls.model.Ticket_ExternalSystem.CUSTOM_1),
|
||||
'impact': int(request.cls.model.TicketImpact.MEDIUM),
|
||||
'priority': int(request.cls.model.TicketPriority.HIGH),
|
||||
'urgency': TicketBase.TicketUrgency.LOW,
|
||||
'assigned_to': [
|
||||
request.cls.entity_user.id,
|
||||
],
|
||||
'subscribed_to': [
|
||||
request.cls.entity_user.id,
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
yield
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.entity_user.delete()
|
||||
|
||||
parent_ticket.delete()
|
||||
|
||||
project_milestone.delete()
|
||||
|
||||
project.delete()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class', autouse = True)
|
||||
def class_setup(self,
|
||||
setup_data,
|
||||
setup_model_data,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_serializer_valid_data(self, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs.
|
||||
"""
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
def test_serializer_valid_data_permission_import(self, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs. when the user has permission import.
|
||||
"""
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_raises_exception(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_exception_obj,
|
||||
param_exception_code_key,
|
||||
param_exception_code
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating and the milestone is not from the project
|
||||
assigned to the ticket that a validation error occurs.
|
||||
|
||||
Requires that all permissions be assigned so that test case can
|
||||
function.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
with pytest.raises(param_exception_obj) as err:
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data,
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
exception_code_key = param_value
|
||||
|
||||
if param_exception_code_key is not None:
|
||||
|
||||
exception_code_key = param_exception_code_key
|
||||
|
||||
|
||||
assert err.value.get_codes()[exception_code_key][0] == param_exception_code
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_is_valid_permission_import(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_will_create,
|
||||
param_permission_import_required
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
is_valid = serializer.is_valid(raise_exception = False)
|
||||
|
||||
assert (
|
||||
( # import permission
|
||||
param_permission_import_required
|
||||
and not param_will_create
|
||||
and param_will_create == is_valid
|
||||
)
|
||||
or
|
||||
( # does not require import permission
|
||||
not param_permission_import_required
|
||||
and param_will_create == is_valid
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TicketBaseSerializerInheritedCases(
|
||||
TicketBaseSerializerTestCases,
|
||||
):
|
||||
|
||||
parametrized_test_data: dict = None
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class TicketBaseSerializerPyTest(
|
||||
TicketBaseSerializerTestCases,
|
||||
):
|
||||
|
||||
parametrized_test_data: dict = None
|
@ -0,0 +1,280 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
from core.models.ticket_base import TicketBase
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data: dict = {
|
||||
'title': 'ticket one',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = TicketBase
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'title': 'ticket two',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'title': 'ticket three',
|
||||
'description': 'sadsa'
|
||||
}
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.kwargs_create_item.update({
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update({
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
'opened_by': self.view_user
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_sanity_is_ticket_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `TicketBase`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
):
|
||||
|
||||
model = TicketBase
|
||||
|
||||
|
||||
|
||||
class TicketBaseViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
# kwargs_create_item: dict = {}
|
||||
|
||||
# kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_ticket_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'ticket_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'ticket_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class TicketBaseViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
url_name = '_api_v2_ticket'
|
@ -114,93 +114,259 @@ class APITestCases(
|
||||
|
||||
|
||||
parametrized_test_data = {
|
||||
'model_notes': DoesNotExist,
|
||||
'_urls.notes': DoesNotExist,
|
||||
'external_system': int,
|
||||
'external_ref': int,
|
||||
'parent_ticket': dict,
|
||||
'parent_ticket.id': int,
|
||||
'parent_ticket.display_name': str,
|
||||
'parent_ticket.url': str,
|
||||
'ticket_type': str,
|
||||
'status': int,
|
||||
'status_badge': dict,
|
||||
'status_badge.icon': dict,
|
||||
'status_badge.icon.name': str,
|
||||
'status_badge.icon.style': str,
|
||||
'status_badge.text': str,
|
||||
'status_badge.text_style': str,
|
||||
'status_badge.url': type(None),
|
||||
'category': dict,
|
||||
'category.id': int,
|
||||
'category.display_name': str,
|
||||
'category.url': Hyperlink,
|
||||
'title': str,
|
||||
'description': str,
|
||||
'ticket_duration': int,
|
||||
'ticket_estimation': int,
|
||||
'project': dict,
|
||||
'project.id': int,
|
||||
'project.display_name': str,
|
||||
'project.url': Hyperlink,
|
||||
'milestone': dict,
|
||||
'milestone.id': int,
|
||||
'milestone.display_name': str,
|
||||
'milestone.url': str,
|
||||
'urgency': int,
|
||||
'urgency_badge': dict,
|
||||
'urgency_badge.icon': dict,
|
||||
'urgency_badge.icon.name': str,
|
||||
'urgency_badge.icon.style': str,
|
||||
'urgency_badge.text': str,
|
||||
'urgency_badge.text_style': str,
|
||||
'urgency_badge.url': type(None),
|
||||
'impact': int,
|
||||
'impact_badge': dict,
|
||||
'impact_badge.icon': dict,
|
||||
'impact_badge.icon.name': str,
|
||||
'impact_badge.icon.style': str,
|
||||
'impact_badge.text': str,
|
||||
'impact_badge.text_style': str,
|
||||
'impact_badge.url': type(None),
|
||||
'priority': int,
|
||||
'priority_badge': dict,
|
||||
'priority_badge.icon': dict,
|
||||
'priority_badge.icon.name': str,
|
||||
'priority_badge.icon.style': str,
|
||||
'priority_badge.text': str,
|
||||
'priority_badge.text_style': str,
|
||||
'priority_badge.url': type(None),
|
||||
'opened_by': dict,
|
||||
'opened_by.id': int,
|
||||
'opened_by.display_name': str,
|
||||
'opened_by.first_name': str,
|
||||
'opened_by.last_name': str,
|
||||
'opened_by.username': str,
|
||||
'opened_by.username': str,
|
||||
'opened_by.is_active': bool,
|
||||
'opened_by.url': Hyperlink,
|
||||
'model_notes': {
|
||||
'expected': DoesNotExist
|
||||
},
|
||||
'_urls.notes': {
|
||||
'expected': DoesNotExist
|
||||
},
|
||||
'external_system': {
|
||||
'expected': int
|
||||
},
|
||||
'external_ref': {
|
||||
'expected': int
|
||||
},
|
||||
'parent_ticket': {
|
||||
'expected': dict
|
||||
},
|
||||
'parent_ticket.id': {
|
||||
'expected': int
|
||||
},
|
||||
'parent_ticket.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'parent_ticket.url': {
|
||||
'expected': str
|
||||
},
|
||||
'ticket_type': {
|
||||
'expected': str
|
||||
},
|
||||
'status': {
|
||||
'expected': int
|
||||
},
|
||||
'status_badge': {
|
||||
'expected': dict
|
||||
},
|
||||
'status_badge.icon': {
|
||||
'expected': dict
|
||||
},
|
||||
'status_badge.icon.name': {
|
||||
'expected': str
|
||||
},
|
||||
'status_badge.icon.style': {
|
||||
'expected': str
|
||||
},
|
||||
'status_badge.text': {
|
||||
'expected': str
|
||||
},
|
||||
'status_badge.text_style': {
|
||||
'expected': str
|
||||
},
|
||||
'status_badge.url': {
|
||||
'expected': type(None)
|
||||
},
|
||||
'category': {
|
||||
'expected': dict
|
||||
},
|
||||
'category.id': {
|
||||
'expected': int
|
||||
},
|
||||
'category.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'category.url': {
|
||||
'expected': Hyperlink
|
||||
},
|
||||
'title': {
|
||||
'expected': str
|
||||
},
|
||||
'description': {
|
||||
'expected': str
|
||||
},
|
||||
'ticket_duration': {
|
||||
'expected': int
|
||||
},
|
||||
'ticket_estimation': {
|
||||
'expected': int
|
||||
},
|
||||
'project': {
|
||||
'expected': dict
|
||||
},
|
||||
'project.id': {
|
||||
'expected': int
|
||||
},
|
||||
'project.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'project.url': {
|
||||
'expected': Hyperlink
|
||||
},
|
||||
'milestone': {
|
||||
'expected': dict
|
||||
},
|
||||
'milestone.id': {
|
||||
'expected': int
|
||||
},
|
||||
'milestone.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'milestone.url': {
|
||||
'expected': str
|
||||
},
|
||||
'urgency': {
|
||||
'expected': int
|
||||
},
|
||||
'urgency_badge': {
|
||||
'expected': dict
|
||||
},
|
||||
'urgency_badge.icon': {
|
||||
'expected': dict
|
||||
},
|
||||
'urgency_badge.icon.name': {
|
||||
'expected': str
|
||||
},
|
||||
'urgency_badge.icon.style': {
|
||||
'expected': str
|
||||
},
|
||||
'urgency_badge.text': {
|
||||
'expected': str
|
||||
},
|
||||
'urgency_badge.text_style': {
|
||||
'expected': str
|
||||
},
|
||||
'urgency_badge.url': {
|
||||
'expected': type(None)
|
||||
},
|
||||
'impact': {
|
||||
'expected': int
|
||||
},
|
||||
'impact_badge': {
|
||||
'expected': dict
|
||||
},
|
||||
'impact_badge.icon': {
|
||||
'expected': dict
|
||||
},
|
||||
'impact_badge.icon.name': {
|
||||
'expected': str
|
||||
},
|
||||
'impact_badge.icon.style': {
|
||||
'expected': str
|
||||
},
|
||||
'impact_badge.text': {
|
||||
'expected': str
|
||||
},
|
||||
'impact_badge.text_style': {
|
||||
'expected': str
|
||||
},
|
||||
'impact_badge.url': {
|
||||
'expected': type(None)
|
||||
},
|
||||
'priority': {
|
||||
'expected': int
|
||||
},
|
||||
'priority_badge': {
|
||||
'expected': dict
|
||||
},
|
||||
'priority_badge.icon': {
|
||||
'expected': dict
|
||||
},
|
||||
'priority_badge.icon.name': {
|
||||
'expected': str
|
||||
},
|
||||
'priority_badge.icon.style': {
|
||||
'expected': str
|
||||
},
|
||||
'priority_badge.text': {
|
||||
'expected': str
|
||||
},
|
||||
'priority_badge.text_style': {
|
||||
'expected': str
|
||||
},
|
||||
'priority_badge.url': {
|
||||
'expected': type(None)
|
||||
},
|
||||
'opened_by': {
|
||||
'expected': dict
|
||||
},
|
||||
'opened_by.id': {
|
||||
'expected': int
|
||||
},
|
||||
'opened_by.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'opened_by.first_name': {
|
||||
'expected': str
|
||||
},
|
||||
'opened_by.last_name': {
|
||||
'expected': str
|
||||
},
|
||||
'opened_by.username': {
|
||||
'expected': str
|
||||
},
|
||||
'opened_by.username': {
|
||||
'expected': str
|
||||
},
|
||||
'opened_by.is_active': {
|
||||
'expected': bool
|
||||
},
|
||||
'opened_by.url': {
|
||||
'expected': Hyperlink
|
||||
},
|
||||
|
||||
'subscribed_to': list,
|
||||
'subscribed_to.0.id': int,
|
||||
'subscribed_to.0.display_name': str,
|
||||
'subscribed_to.0.url': str,
|
||||
'subscribed_to': {
|
||||
'expected': list
|
||||
},
|
||||
'subscribed_to.0.id': {
|
||||
'expected': int
|
||||
},
|
||||
'subscribed_to.0.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'subscribed_to.0.url': {
|
||||
'expected': str
|
||||
},
|
||||
|
||||
'assigned_to': list,
|
||||
'assigned_to.0.id': int,
|
||||
'assigned_to.0.display_name': str,
|
||||
'assigned_to.0.url': str,
|
||||
'assigned_to': {
|
||||
'expected': list
|
||||
},
|
||||
'assigned_to.0.id': {
|
||||
'expected': int
|
||||
},
|
||||
'assigned_to.0.display_name': {
|
||||
'expected': str
|
||||
},
|
||||
'assigned_to.0.url': {
|
||||
'expected': str
|
||||
},
|
||||
|
||||
'planned_start_date': str,
|
||||
'planned_finish_date': str,
|
||||
'real_start_date': str,
|
||||
'real_finish_date': str,
|
||||
'planned_start_date': {
|
||||
'expected': str
|
||||
},
|
||||
'planned_finish_date': {
|
||||
'expected': str
|
||||
},
|
||||
'real_start_date': {
|
||||
'expected': str
|
||||
},
|
||||
'real_finish_date': {
|
||||
'expected': str
|
||||
},
|
||||
|
||||
'is_deleted': bool,
|
||||
'is_solved': bool,
|
||||
'date_solved': str,
|
||||
'is_closed': bool,
|
||||
'date_closed': str,
|
||||
'is_deleted': {
|
||||
'expected': bool
|
||||
},
|
||||
'is_solved': {
|
||||
'expected': bool
|
||||
},
|
||||
'date_solved': {
|
||||
'expected': str
|
||||
},
|
||||
'is_closed': {
|
||||
'expected': bool
|
||||
},
|
||||
'date_closed': {
|
||||
'expected': str
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
102
app/core/tests/unit/ticket_base/test_unit_ticket_base_viewset.py
Normal file
102
app/core/tests/unit/ticket_base/test_unit_ticket_base_viewset.py
Normal file
@ -0,0 +1,102 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
from core.viewsets.ticket import (
|
||||
NoDocsViewSet,
|
||||
TicketBase,
|
||||
ViewSet,
|
||||
)
|
||||
|
||||
from api.tests.unit.test_unit_common_viewset import SubModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
SubModelViewSetInheritedCases,
|
||||
):
|
||||
|
||||
model = TicketBase
|
||||
|
||||
kwargs = None
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
base_model = TicketBase
|
||||
|
||||
route_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. make list request
|
||||
"""
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
url = reverse(
|
||||
self.route_name + '-list',
|
||||
kwargs = self.kwargs
|
||||
)
|
||||
|
||||
client.force_login(self.view_user)
|
||||
|
||||
self.http_options_response_list = client.options(url)
|
||||
|
||||
|
||||
|
||||
def test_view_attr_value_model_kwarg(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model_kwarg` must be equal to model._meta.sub_model_type
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model_kwarg == 'ticket_model'
|
||||
|
||||
|
||||
|
||||
class TicketBaseViewsetInheritedCases(
|
||||
ViewsetTestCases,
|
||||
):
|
||||
"""Test Suite for Sub-Models of TicketBase
|
||||
|
||||
Use this Test suit if your sub-model inherits directly from TicketBase.
|
||||
"""
|
||||
|
||||
model: str = None
|
||||
"""name of the model to test"""
|
||||
|
||||
route_name = 'v2:_api_v2_ticket_sub'
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs = {
|
||||
'ticket_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class TicketBaseViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs = {}
|
||||
|
||||
route_name = 'v2:_api_v2_ticket'
|
||||
|
||||
viewset = NoDocsViewSet
|
26
app/itim/tests/functional/ticket_request/conftest.py
Normal file
26
app/itim/tests/functional/ticket_request/conftest.py
Normal file
@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
|
||||
from itim.models.request_ticket import RequestTicket
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = RequestTicket
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_serializer():
|
||||
|
||||
from itim.serializers.ticket_request import ModelSerializer
|
||||
|
||||
serializer = ModelSerializer
|
||||
|
||||
yield serializer
|
||||
|
||||
del serializer
|
@ -0,0 +1,53 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# from core.tests.functional.ticket_base.test_functional_ticket_base_metadata import TicketBaseMetadataInheritedCases
|
||||
|
||||
from itim.models.request_ticket import RequestTicket
|
||||
from itim.tests.functional.ticket_slm.test_functional_ticket_slm_metadata import SLMTicketMetadataInheritedCases
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
SLMTicketMetadataInheritedCases,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = RequestTicket
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
|
||||
class RequestTicketInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class RequestTicketTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,29 @@
|
||||
from itim.tests.functional.ticket_slm.test_functional_ticket_slm_serializer import SLMTicketSerializerInheritedCases
|
||||
|
||||
|
||||
|
||||
class RequestTicketSerializerTestCases(
|
||||
SLMTicketSerializerInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class RequestTicketSerializerInheritedCases(
|
||||
RequestTicketSerializerTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class RequestTicketSerializerPyTest(
|
||||
RequestTicketSerializerTestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,50 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# from core.tests.functional.ticket_base.test_functional_ticket_base_viewset import TicketBaseViewSetInheritedCases
|
||||
|
||||
from itim.models.request_ticket import RequestTicket
|
||||
from itim.tests.functional.ticket_slm.test_functional_ticket_slm_viewset import SLMTicketViewSetInheritedCases
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
SLMTicketViewSetInheritedCases,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = RequestTicket
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class RequestTicketInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class RequestTicketTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
24
app/itim/tests/functional/ticket_slm/conftest.py
Normal file
24
app/itim/tests/functional/ticket_slm/conftest.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from itim.models.slm_ticket_base import SLMTicket
|
||||
from itim.serializers.ticket_slm import ModelSerializer
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = SLMTicket
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_serializer():
|
||||
|
||||
serializer = ModelSerializer
|
||||
|
||||
yield serializer
|
||||
|
||||
del serializer
|
@ -0,0 +1,56 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.functional.ticket_base.test_functional_ticket_base_metadata import TicketBaseMetadataInheritedCases
|
||||
|
||||
from itim.models.slm_ticket_base import SLMTicket
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
TicketBaseMetadataInheritedCases,
|
||||
):
|
||||
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = SLMTicket
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
'tto': 1,
|
||||
'ttr': 2,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
'tto': 1,
|
||||
'ttr': 2,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class SLMTicketMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
# class SLMTicketTest(
|
||||
# MetadataTestCases,
|
||||
# TestCase,
|
||||
|
||||
# ):
|
||||
|
||||
# pass
|
@ -0,0 +1,46 @@
|
||||
from core.tests.functional.ticket_base.test_functional_ticket_base_serializer import TicketBaseSerializerInheritedCases
|
||||
|
||||
|
||||
|
||||
class SLMTicketSerializerTestCases(
|
||||
TicketBaseSerializerInheritedCases,
|
||||
):
|
||||
|
||||
parametrized_test_data: dict = {
|
||||
"tto": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
"ttr": {
|
||||
'will_create': True,
|
||||
'permission_import_required': False,
|
||||
},
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'tto': 2,
|
||||
'ttr': 3,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SLMTicketSerializerInheritedCases(
|
||||
SLMTicketSerializerTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
parametrized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class SLMTicketSerializerPyTest(
|
||||
SLMTicketSerializerTestCases,
|
||||
):
|
||||
|
||||
parametrized_test_data: dict = None
|
||||
|
@ -0,0 +1,59 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.functional.ticket_base.test_functional_ticket_base_viewset import TicketBaseViewSetInheritedCases
|
||||
|
||||
from itim.models.slm_ticket_base import SLMTicket
|
||||
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
TicketBaseViewSetInheritedCases,
|
||||
):
|
||||
|
||||
kwargs_create_item = {}
|
||||
|
||||
kwargs_create_item_diff_org = {}
|
||||
|
||||
model = SLMTicket
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
'tto': 1,
|
||||
'ttr': 2,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
'tto': 1,
|
||||
'ttr': 2,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class SLMTicketViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
# kwargs_create_item: dict = None
|
||||
|
||||
# kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
|
||||
# class SLMTicketTest(
|
||||
# ViewSetTestCases,
|
||||
# TestCase,
|
||||
# ):
|
||||
|
||||
# pass
|
@ -0,0 +1,27 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.unit.ticket_base.test_unit_ticket_base_viewset import (
|
||||
TicketBaseViewsetInheritedCases,
|
||||
)
|
||||
|
||||
from itim.models.request_ticket import RequestTicket
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
TicketBaseViewsetInheritedCases,
|
||||
):
|
||||
|
||||
model = RequestTicket
|
||||
|
||||
|
||||
|
||||
class TicketRequestViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
@ -9,9 +9,12 @@ class TicketSLMAPITestCases(
|
||||
):
|
||||
|
||||
parametrized_test_data = {
|
||||
'tto': int,
|
||||
'ttr': int,
|
||||
|
||||
'tto': {
|
||||
'expected': int
|
||||
},
|
||||
'ttr': {
|
||||
'expected': int
|
||||
}
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
|
@ -32,8 +32,12 @@ As with any other object within Centurion, the addition of a feature requires it
|
||||
|
||||
- `core.tests.unit.ticket_base.<*>.<Inherited class name>InheritedCases` _(if inheriting from `TicketBase`)_ Test cases for sub-models
|
||||
|
||||
- ViewSet `core.tests.unit.ticket_base.test_unit_ticket_base_viewset.TicketBaseViewsetInheritedCases`
|
||||
|
||||
- `Functional` Test Cases
|
||||
|
||||
- `core.tests.functional.ticket_base.<*>.<Inherited class name>InheritedCases` _(if inheriting from `TicketBase`)_ Test cases for sub-models
|
||||
|
||||
- API Permissions `core.tests.functional.ticket_base.test_functional_ticket_base_permission.TicketBasePermissionsAPIInheritedCases`
|
||||
|
||||
The above listed test cases cover **all** tests for objects that are inherited from the base class. To complete the tests, you will need to add test cases for the differences your model introduces.
|
||||
|
@ -151,6 +151,122 @@ Due to how pytest and pytest-django works, there is no method available for clas
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
|
||||
## Parameterizing Tests
|
||||
|
||||
To be able to paramertize any test case, the test must be setup to use PyTest. Within the test class the test data is required to be stored in a dictionary prefixed with string `paramaterized_<data_name>`. Variable `<data_name>` is the data key that you will specify within the test method.
|
||||
|
||||
Our test setup allows for class inheritance which means you can within each class of the inheritance chain, add the `paramaterized_<data_name>` attribute. If you do this, starting from the lowest base class, each class that specifies the `paramaterized_<data_name>` attribute will be merged. The merge is an overwrite of the classes previous base class, meaning that the classes higher in the chain will overwrite the value of the lower class in the inheritance chain. You can not however remove a key from attribute `paramaterized_<data_name>`.
|
||||
|
||||
The test method must be called with parameters:
|
||||
|
||||
- 'parameterized'
|
||||
|
||||
Tells the test setup that this test case is a parameterized test.
|
||||
|
||||
- `param_key_<data_name>`
|
||||
|
||||
Tells the test setup the suffix to use to find the test data. The value of variable `data_name` can be any value you wish as long as it only contains chars `a-z` and/or `_` (underscore). This value is also used in class parameter `paramaterized_<data_name>`.
|
||||
|
||||
- `param_<name>`
|
||||
|
||||
Tells the test setup that this is data to be passed from the test. When test setup is run, these attributes will contain the test data. It's of paramount importance, that the dict You can have as many of these attributes you wish, as long as `<name>` is unique and `<name>` is always prefixed with `param_`. If you specify more than to parameters with the `param_` prefix, the value after the `param_` prefix, must match the dictionary key for the data you wish to be assigned to that parameter. what ever name you give the first `param_` key, will always receive the key name from the `parameterized_test_data` attribute in the test class.
|
||||
|
||||
The value of `<name>` for each and in the order specified is suffixed to the test case name
|
||||
|
||||
``` py
|
||||
|
||||
class MyTestClassTestCases:
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
'key_1': {
|
||||
'expected': 'key_1'
|
||||
},
|
||||
'key_2': {
|
||||
'random': 'key_2'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class MyTestClassPyTest(
|
||||
MyTestClassTestCases
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
'key_2': {
|
||||
'random': 'value'
|
||||
}
|
||||
'key_3': {
|
||||
'expected': 'key_3',
|
||||
'is_type': bool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
parameterized_second_dict: dict = {
|
||||
'key_1': {
|
||||
'expected': 'key_1'
|
||||
},
|
||||
}
|
||||
|
||||
def test_my_test_case_one(self, parameterized, param_key_test_data, param_value, param_expected):
|
||||
|
||||
assert param_value == param_expected
|
||||
|
||||
|
||||
def test_my_test_case_two(self, parameterized, param_key_test_data, param_value, param_random):
|
||||
|
||||
assert param_value == param_random
|
||||
|
||||
|
||||
def test_my_test_case_three(self, parameterized, param_key_test_data, param_value, param_is_type):
|
||||
|
||||
my_test_dict = self.adict
|
||||
|
||||
assert type(my_test_dict[param_value]) is param_is_type
|
||||
|
||||
|
||||
def test_my_test_case_four(self, parameterized, param_key_second_dict, param_arbitrary_name, param_expected):
|
||||
|
||||
my_test_dict = self.a_dict_that_is_defined_in_the_test_class
|
||||
|
||||
assert my_test_dict[param_arbitrary_name] == param_expected
|
||||
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- The test class in this case is `MyTestClassPyTest` which inherits from `MyTestClassTestCases`. there are two parameterized variables: `test_data` and `second_dict`. Although, the concrete class attribute `parameterized_test_data` overrides the base classes variable of the same name, the test setup logic does merge `MyTestClassPyTest.parameterized_test_data` with `MyTestClassTestCases.parameterized_test_data`. So in this case the value dictionary `MyTestClassPyTest.parameterized_test_data[key_2][random]`, `value` will overwrite dictionary of the same name in the base class. In the same token, as dictionary `MyTestClassTestCases.parameterized_test_data[key_3]` does not exist, it will be added to the dictionary during merge so it exists in `MyTestClassPyTest.parameterized_test_data`
|
||||
|
||||
- test suite `MyTestClassPyTest` will create a total of five parmeterized test cases for the following reasons:
|
||||
|
||||
- `test_my_test_case_one` will create two parameterized test cases.
|
||||
|
||||
- will use data in attribute `test_data` prefixed with `parameterized_` as this is the attribute prefixed with `param_key_`.
|
||||
|
||||
- `MyTestClassPyTest.parameterized_test_data['key_1']` is a dictionary, which contains key `expected` which is also one of the attributes specified with prefix `param_`
|
||||
|
||||
- `MyTestClassPyTest.parameterized_test_data['key_3']` is a dictionary, which contains key `expected` which is also one of the attributes specified with prefix `param_`
|
||||
|
||||
- `test_my_test_case_two` will create one parameterized test case.
|
||||
|
||||
- will use data in attribute `test_data` prefixed with `parameterized_` as this is the attribute prefixed with `param_key_`.
|
||||
|
||||
- `MyTestClassPyTest.parameterized_test_data['key_2']` is a dictionary, which contains key `random` which is also one of the attributes specified with prefix `param_`
|
||||
|
||||
- `test_my_test_case_three` will create one parameterized test case.
|
||||
|
||||
- will use data in attribute `test_data` prefixed with `parameterized_` as this is the attribute prefixed with `param_key_`.
|
||||
|
||||
- `MyTestClassPyTest.parameterized_test_data['key_3']` is a dictionary, which contains key `is_type` which is also one of the attributes specified with prefix `param_`
|
||||
|
||||
- `test_my_test_case_four` will create one parameterized test case.
|
||||
|
||||
- will use data in attribute `second_dict` prefixed with `parameterized_` as this is the attribute prefixed with `param_key_`.
|
||||
|
||||
- `MyTestClassPyTest.parameterized_second_dict['key_1']` is a dictionary, which contains key `expected` which is also one of the attributes specified with prefix `param_`
|
||||
|
||||
|
||||
## Running Tests
|
||||
|
||||
Test can be run by running the following:
|
||||
|
@ -1,6 +1,6 @@
|
||||
pytest==8.2.0
|
||||
pytest-django==4.8.0
|
||||
coverage==7.5.1
|
||||
pytest-cov==5.0.0
|
||||
pytest==8.3.5
|
||||
pytest-django==4.11.1
|
||||
coverage==7.8.0
|
||||
pytest-cov==6.1.1
|
||||
|
||||
# selenium==4.21.0
|
Reference in New Issue
Block a user