test(core): Incomplete Model Unit Tests for TicketBase

ref: #734 #723
This commit is contained in:
2025-05-03 22:53:52 +09:30
parent 393c8ce0ce
commit 70a2d502ef
4 changed files with 717 additions and 6 deletions

View File

@ -139,7 +139,6 @@ class TicketBase(
external_system = models.IntegerField(
blank = True,
choices = Ticket_ExternalSystem,
default = None,
help_text = 'External system this item derives',
null = True,
verbose_name = 'External System',
@ -147,7 +146,6 @@ class TicketBase(
external_ref = models.IntegerField(
blank = True,
default = None,
help_text = 'External System reference',
null = True,
verbose_name = 'Reference Number',
@ -156,7 +154,6 @@ class TicketBase(
parent_ticket = models.ForeignKey(
'self',
blank = True,
default = None,
help_text = 'Parent of this ticket',
null = True,
on_delete = models.PROTECT,
@ -179,7 +176,7 @@ class TicketBase(
None: The ticket is for the Base class. Used to prevent creating a base ticket.
"""
ticket_type = str(self.Meta.sub_model_type).lower().replace(' ', '_')
ticket_type = str(self._meta.sub_model_type).lower().replace(' ', '_')
if ticket_type == 'ticket':
@ -292,7 +289,7 @@ class TicketBase(
duration = 0
return str(duration)
return int(duration)
@property
@ -306,7 +303,7 @@ class TicketBase(
estimation = 0
return str(estimation)
return int(estimation)
project = models.ForeignKey(

View File

@ -421,6 +421,11 @@ class TicketCommentBase(
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
# clear ticket comment cache
if hasattr(self.ticket, '_ticket_comments'):
del self.ticket._ticket_comments
# if self.comment_type == self.CommentType.SOLUTION:
# update_ticket = self.ticket.__class__.objects.get(pk=self.ticket.id)

View File

@ -58,4 +58,9 @@ class TicketCommentSolution(
super().save(force_insert = force_insert, force_update = force_update, using = using, update_fields = update_fields)
# clear comment cache
if hasattr(self.ticket, '_ticket_comments'):
del self.ticket._ticket_comments

View File

@ -0,0 +1,704 @@
import pytest
import datetime
from django.contrib.auth.models import User
from django.db.models.query import QuerySet
from django.db import models
from django.test import TestCase
from access.models.entity import Entity
from app.tests.unit.test_unit_models import (
PyTestTenancyObjectInheritedCases,
)
from core import exceptions as centurion_exceptions
from core.classes.badge import Badge
from core.lib.feature_not_used import FeatureNotUsed
from core.models.ticket.ticket_category import TicketCategory
from core.models.ticket_base import TicketBase
from core.models.ticket_comment_base import TicketCommentBase
from project_management.models.project_milestone import Project, ProjectMilestone
class TicketBaseModelTestCases(
PyTestTenancyObjectInheritedCases,
):
base_model = TicketBase
kwargs_create_item: dict = {
'title': 'ticket title',
'description': 'the ticket description',
}
sub_model_type = 'ticket'
"""Ticket Sub Model Type
Ticket sub-models must have this attribute defined in `ModelName.Meta.sub_model_type`
"""
parameterized_fields: dict = {
"model_notes": {
'field_type': None,
'field_parameter_verbose_name_type': None
},
"is_global": {
'field_type': None,
'field_parameter_verbose_name_type': None
},
"external_system": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str,
},
"external_ref": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"parent_ticket": {
'field_type': models.ForeignKey,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"ticket_type": {
'field_type': models.fields.CharField,
'field_parameter_default_exists': True,
'field_parameter_default_value': 'ticket',
'field_parameter_verbose_name_type': str
},
"status": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': True,
'field_parameter_default_value': TicketBase.TicketStatus.NEW,
'field_parameter_verbose_name_type': str
},
"category": {
'field_type': models.ForeignKey,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"title": {
'field_type': models.fields.CharField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"description": {
'field_type': models.fields.TextField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"private": {
'field_type': models.fields.BooleanField,
'field_parameter_default_exists': True,
'field_parameter_default_value': False,
'field_parameter_verbose_name_type': str
},
"project": {
'field_type': models.ForeignKey,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"milestone": {
'field_type': models.ForeignKey,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"urgency": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': True,
'field_parameter_default_value': TicketBase.TicketUrgency.VERY_LOW,
'field_parameter_verbose_name_type': str
},
"impact": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': True,
'field_parameter_default_value': TicketBase.TicketImpact.VERY_LOW,
'field_parameter_verbose_name_type': str
},
"priority": {
'field_type': models.fields.IntegerField,
'field_parameter_default_exists': True,
'field_parameter_default_value': TicketBase.TicketPriority.VERY_LOW,
'field_parameter_verbose_name_type': str
},
"opened_by": {
'field_type': models.ForeignKey,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"subscribed_to": {
'field_type': models.ManyToManyField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"assigned_to": {
'field_type': models.ManyToManyField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"planned_start_date": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"planned_finish_date": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"real_start_date": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"real_finish_date": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"is_deleted": {
'field_type': models.fields.BooleanField,
'field_parameter_default_exists': True,
'field_parameter_default_value': False,
'field_parameter_verbose_name_type': str
},
"is_solved": {
'field_type': models.fields.BooleanField,
'field_parameter_default_exists': True,
'field_parameter_default_value': False,
'field_parameter_verbose_name_type': str
},
"date_solved": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
"is_closed": {
'field_type': models.fields.BooleanField,
'field_parameter_default_exists': True,
'field_parameter_default_value': False,
'field_parameter_verbose_name_type': str
},
"date_closed": {
'field_type': models.fields.DateTimeField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str
},
}
@pytest.fixture( scope = 'class')
def setup_pre(self,
request,
model,
django_db_blocker,
organization_one,
organization_two
):
with django_db_blocker.unblock():
request.cls.organization = organization_one
request.cls.different_organization = organization_two
kwargs_create_item = {}
for base in reversed(request.cls.__mro__):
if hasattr(base, 'kwargs_create_item'):
if base.kwargs_create_item is None:
continue
kwargs_create_item.update(**base.kwargs_create_item)
if len(kwargs_create_item) > 0:
request.cls.kwargs_create_item = kwargs_create_item
if 'organization' not in request.cls.kwargs_create_item:
request.cls.kwargs_create_item.update({
'organization': request.cls.organization
})
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.kwargs_create_item
@pytest.fixture( scope = 'class')
def setup_model(self, request, django_db_blocker,
model,
):
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'
)
request.cls.project_one = project
request.cls.project_two = Project.objects.create(
organization = request.cls.organization,
name = 'project_two'
)
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.milestone_two = ProjectMilestone.objects.create(
organization = request.cls.organization,
name = 'project milestone two',
project = request.cls.project_two
)
request.cls.kwargs_create_item.update({
'category': TicketCategory.objects.create(
organization = request.cls.organization,
name = 'a category'
),
'opened_by': request.cls.view_user,
'project': project,
'milestone': project_milestone,
'parent_ticket': parent_ticket,
'external_system': int(request.cls.model.Ticket_ExternalSystem.CUSTOM_1),
'impact': int(request.cls.model.TicketImpact.MEDIUM),
'priority': int(request.cls.model.TicketPriority.HIGH),
})
yield
with django_db_blocker.unblock():
request.cls.entity_user.delete()
parent_ticket.delete()
project_milestone.delete()
request.cls.milestone_two.delete()
request.cls.project_two.delete()
project.delete()
request.cls.kwargs_create_item['category'].delete()
@pytest.fixture( scope = 'class')
def post_model_create(self, request, django_db_blocker):
with django_db_blocker.unblock():
request.cls.item.assigned_to.add(request.cls.entity_user.id)
request.cls.item.subscribed_to.add(request.cls.entity_user.id)
@pytest.fixture( scope = 'class', autouse = True)
def class_setup(self,
setup_pre,
setup_model,
create_model,
post_model_create,
):
pass
def test_class_inherits_ticketbase(self):
""" Class inheritence
TenancyObject must inherit SaveHistory
"""
assert issubclass(self.model, TicketBase)
def test_milestone_different_project_raises_validationerror(self):
kwargs = self.kwargs_create_item.copy()
kwargs['title'] = kwargs['title'] + 'a'
ticket = self.model.objects.create( **kwargs )
with pytest.raises(centurion_exceptions.ValidationError) as err:
ticket.project = self.project_one
ticket.milestone = self.milestone_two
ticket.save()
assert err.value.get_codes()['milestone'] == 'milestone_different_project'
def test_attribute_type_get_url_kwargs_notes(self):
"""Test attribute
This test cases is a overwrite of a test with the same name. This Model
and it's children must not use the notes model as it has been deemed as
not required by design.
Attribute `get_url_kwargs_notes` must be FeatureNotUsed
"""
assert self.item.get_url_kwargs_notes() is FeatureNotUsed
def test_meta_attribute_exists_sub_model_type(self):
"""Test for existance of field in `<model>.Meta`
Attribute `Meta.sub_model_type` must be defined in `Meta` class.
"""
assert 'sub_model_type' in self.model._meta.original_attrs
def test_meta_attribute_type_sub_model_type(self):
"""Test for existance of field in `<model>.Meta`
Attribute `Meta.sub_model_type` must be of type str.
"""
assert type(self.model._meta.original_attrs['sub_model_type']) is str
def test_meta_attribute_value_sub_model_type(self):
"""Test for existance of field in `<model>.Meta`
Attribute `Meta.sub_model_type` must be the correct value (self.sub_model_type).
"""
assert self.model._meta.original_attrs['sub_model_type'] == self.sub_model_type
def test_function_validate_not_null_is_true(self):
"""Function test
Ensure that function `validate_not_null` returns true when the value is
not null.
"""
assert self.model.validate_not_null(55) == True
def test_function_validate_not_null_is_false(self):
"""Function test
Ensure that function `validate_not_null` returns false when the value
is null.
"""
assert self.model.validate_not_null(None) == False
def test_function_get_ticket_type(self):
"""Function test
As this model is not intended to be used alone.
Ensure that function `get_ticket_type` returns None for model
`TicketBase`
"""
assert self.model().get_ticket_type == None
def test_function_get_ticket_type_choices(self):
"""Function test
Ensure that function `get_ticket_type_choices` returns a tuple of
the ticket type ( `Model.Meta.sub_ticket_type`, `Model.Meta.verbose_name` )
"""
assert (self.model()._meta.sub_model_type, self.model()._meta.verbose_name) in self.model.get_ticket_type_choices()
def test_function_status_badge_type(self):
"""Function test
Ensure that function `status_badge` returns a value of type `Badge`
"""
assert type(self.model().status_badge) is Badge
def test_function_ticket_duration_type(self):
"""Function test
Ensure that function `ticket_duration` returns a value of type `int`
"""
assert type(self.model().ticket_duration) is int
def test_function_ticket_duration_value_not_none(self):
"""Function test
Ensure that function `ticket_duration` returns a value that is not None
"""
assert self.model().ticket_duration is not None
def test_function_ticket_estimation_type(self):
"""Function test
Ensure that function `ticket_estimation` returns a value of type `int`
"""
assert type(self.model().ticket_estimation) is int
def test_function_ticket_estimation_value_not_none(self):
"""Function test
Ensure that function `ticket_estimation` returns a value that is not None
"""
assert self.model().ticket_estimation is not None
@pytest.mark.skip( reason = 'write test')
def test_function_get_milestone_choices(self):
"""Function test
Ensure that function `get_ticket_type_choices` returns a tuple of
each projects milestones
"""
assert ('project_name', (self.model()._meta.sub_model_type, self.model()._meta.verbose_name)) in self.model.get_milestone_choices()
def test_function_urgency_badge_type(self):
"""Function test
Ensure that function `urgency_badge` returns a value of type `Badge`
"""
assert type(self.model().urgency_badge) is Badge
def test_function_impact_badge_type(self):
"""Function test
Ensure that function `impact_badge` returns a value of type `Badge`
"""
assert type(self.model().impact_badge) is Badge
def test_function_priority_badge_type(self):
"""Function test
Ensure that function `priority_badge` returns a value of type `Badge`
"""
assert type(self.model().priority_badge) is Badge
def test_function_get_can_close_type(self):
"""Function test
Ensure that function `get_can_close` returns a value of type `bool`
"""
assert type(self.model().get_can_close()) is bool
def test_function_get_can_close_value_false(self):
"""Function test
Ensure that function `get_can_close` returns a value of `False` when
the ticket can not be closed
"""
assert self.model().get_can_close() == False
def test_function_get_can_resolve_type(self):
"""Function test
Ensure that function `get_can_resolve` returns a value of type `bool`
"""
assert type(self.model().get_can_resolve()) is bool
@pytest.mark.skip( reason = 'write test')
def test_function_get_can_resolve_value_false(self):
"""Function test
Ensure that function `get_can_resolve` returns a value of `False` when
the ticket can not be closed
"""
assert self.model().get_can_resolve() == False
def test_function_get_can_resolve_value_true(self):
"""Function test
Ensure that function `get_can_resolve` returns a value of `True` when
the ticket can be closed
"""
assert self.model().get_can_resolve() == True
def test_function_get_comments_type(self):
"""Function test
Ensure that function `get_comments` returns a value of type QuerySet
"""
assert type(self.model().get_comments()) is QuerySet
def test_function_get_related_field_name_type(self):
"""Function test
Ensure that function `get_related_field_name` returns a value that
is of type `str`.
"""
ticket = self.base_model.objects.get(
pk = self.item.pk
)
assert type(ticket.get_related_field_name()) is str
def test_function_get_related_field_name_value(self):
"""Function test
Ensure that function `get_related_field_name` returns a string that is
model the attribute the model exists under.
"""
ticket = self.base_model.objects.get(
pk = self.item.pk
)
assert(
ticket.get_related_field_name() != None
and ticket.get_related_field_name() != ''
)
def test_function_get_related_model_type(self):
"""Function test
Ensure that function `get_related_model` returns a value that
is of type `QuerySet`.
"""
ticket = self.base_model.objects.get(
pk = self.item.pk
)
assert type(ticket.get_related_model()) is self.model
def test_meta_attribute_sub_model_type_length(self):
"""Meta Attribute Check
Ensure that attribute `Meta.sub_model_type` is not longer than the
field that stores the value.
"""
assert len(self.model._meta.sub_model_type) <= int(self.model._meta.get_field('ticket_type').max_length)
class TicketBaseModelInheritedCases(
TicketBaseModelTestCases,
):
"""Sub-Ticket Test Cases
Test Cases for Ticket models that inherit from model TicketBase
"""
kwargs_create_item: dict = None
model = None
sub_model_type = None
"""Ticket Sub Model Type
Ticket sub-models must have this attribute defined in `ModelNam.Meta.sub_model_type`
"""
class TicketBaseModelPyTest(
TicketBaseModelTestCases,
):
def test_function_get_related_field_name_value(self):
"""Function test
This test case overwrites a test of the same name. This model should
return an empty string as it's the base model.
Ensure that function `get_related_field_name` returns a string that is
model the attribute the model exists under.
"""
assert self.model().get_related_field_name() == ''
def test_function_get_related_model_type(self):
"""Function test
This test case overwrites a test of the same name. This model should
return `None` as it's the base model.
Ensure that function `get_related_model` returns a value that
is of type `QuerySet`.
"""
assert type(self.model().get_related_model()) is type(None)