Merge pull request #733 from nofusscomputing/test-ticket-models

This commit is contained in:
Jon
2025-05-01 01:42:47 +09:30
committed by GitHub
25 changed files with 2547 additions and 171 deletions

4
.vscode/launch.json vendored
View File

@ -84,7 +84,9 @@
"request": "launch",
"module": "pytest",
"args": [
"--collect-only"
"--override-ini", "addopts=",
"--collect-only",
"app",
],
"console": "integratedTerminal",
"justMyCode": false

View File

@ -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

View File

@ -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,
):

View File

@ -728,13 +728,7 @@ class ModelViewSet(
class SubModelViewSet(
ModelViewSetBase,
Create,
Retrieve,
Update,
Destroy,
List,
viewsets.ModelViewSet,
ModelViewSet,
):
base_model = None

View File

@ -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,
)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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
},
}

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -9,9 +9,12 @@ class TicketSLMAPITestCases(
):
parametrized_test_data = {
'tto': int,
'ttr': int,
'tto': {
'expected': int
},
'ttr': {
'expected': int
}
}
kwargs_create_item: dict = {

View File

@ -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.

View File

@ -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:

View File

@ -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