Merge pull request #744 from nofusscomputing/test-ticket-comment-base
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,7 @@ __pycache__
|
|||||||
**.sqlite3
|
**.sqlite3
|
||||||
**.sqlite
|
**.sqlite
|
||||||
**.coverage
|
**.coverage
|
||||||
|
.coverage*
|
||||||
artifacts/
|
artifacts/
|
||||||
**.tmp.*
|
**.tmp.*
|
||||||
volumes/
|
volumes/
|
||||||
@ -19,3 +20,4 @@ package.json
|
|||||||
feature_flags.json
|
feature_flags.json
|
||||||
coverage_*.json
|
coverage_*.json
|
||||||
*-coverage.xml
|
*-coverage.xml
|
||||||
|
log/
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
## Version 1.17.0
|
||||||
|
|
||||||
|
- Added setting for log files.
|
||||||
|
|
||||||
|
Enables user to specify a default path for centurion's logging. Add the following to your settings file `/etc/itsm/settings.py`
|
||||||
|
|
||||||
|
``` py
|
||||||
|
LOG_FILES = {
|
||||||
|
"centurion": "/var/log/centurion.log", # Normal Centurion Operations
|
||||||
|
"weblog": "/var/log/weblog.log", # All web requests made to Centurion
|
||||||
|
"rest_api": "/var/log/rest_api.log", # Rest API
|
||||||
|
"catch_all":"/var/log/catch-all.log" # A catch all log. Note: does not log anything that has already been logged.
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
With this new setting, the previous setting `LOGGING` will no longer function.
|
||||||
|
|
||||||
|
|
||||||
## Version 1.16.0
|
## Version 1.16.0
|
||||||
|
|
||||||
- Employees model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
- Employees model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||||
|
@ -130,7 +130,7 @@ class OrganizationMixin:
|
|||||||
parent_model (Model): with PK from kwargs['pk']
|
parent_model (Model): with PK from kwargs['pk']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
return self.get_parent_model().objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# from django.conf import settings
|
import logging
|
||||||
from django.db import models
|
from django.db import models
|
||||||
# from django.contrib.auth.models import User, Group
|
# from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
@ -193,6 +193,16 @@ class TenancyObject(SaveHistory):
|
|||||||
only be used when there is model inheritence.
|
only be used when there is model inheritence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_log: logging.Logger = None
|
||||||
|
|
||||||
|
def get_log(self):
|
||||||
|
|
||||||
|
if self._log is None:
|
||||||
|
|
||||||
|
self._log = logging.getLogger('centurion.' + self._meta.app_label)
|
||||||
|
|
||||||
|
return self._log
|
||||||
|
|
||||||
page_layout: list = None
|
page_layout: list = None
|
||||||
|
|
||||||
note_basename: str = None
|
note_basename: str = None
|
||||||
|
@ -14,6 +14,25 @@ from app.tests.common import DoesNotExist
|
|||||||
|
|
||||||
|
|
||||||
class APIFieldsTestCases:
|
class APIFieldsTestCases:
|
||||||
|
""" API field Rendering Test Suite
|
||||||
|
|
||||||
|
This test suite tests the rendering of API fieilds.
|
||||||
|
|
||||||
|
## Additional Items
|
||||||
|
|
||||||
|
You may find a scenario where you are unable to have all fileds available
|
||||||
|
within a single request. to overcome this this test suite has the features
|
||||||
|
available wherein you can prepare an additional item for an additional
|
||||||
|
check. the following is required before the API request is made
|
||||||
|
(setup_post fixture):
|
||||||
|
|
||||||
|
- additional item created and stored in attribute `self.item_two`
|
||||||
|
- additional url as a string and stored in attribute `self.url_two`
|
||||||
|
|
||||||
|
Once you have these two objects, an additional check will be done and each
|
||||||
|
test will check both API requests. if the field is found in either api
|
||||||
|
request the test will pass
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parameterized_test_data(self) -> dict:
|
def parameterized_test_data(self) -> dict:
|
||||||
@ -135,6 +154,8 @@ class APIFieldsTestCases:
|
|||||||
organization = request.cls.organization,
|
organization = request.cls.organization,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
request.cls.view_team = view_team
|
||||||
|
|
||||||
view_team.permissions.set([view_permissions])
|
view_team.permissions.set([view_permissions])
|
||||||
|
|
||||||
|
|
||||||
@ -176,10 +197,25 @@ class APIFieldsTestCases:
|
|||||||
|
|
||||||
request.cls.api_data = response.data
|
request.cls.api_data = response.data
|
||||||
|
|
||||||
|
item_two = getattr(request.cls, 'url_two', None)
|
||||||
|
|
||||||
|
if item_two:
|
||||||
|
|
||||||
|
response_two = client.get(request.cls.url_two)
|
||||||
|
|
||||||
|
request.cls.api_data_two = response_two.data
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
request.cls.api_data_two = {}
|
||||||
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
del request.cls.url_view_kwargs['pk']
|
del request.cls.url_view_kwargs['pk']
|
||||||
|
|
||||||
|
del request.cls.api_data_two
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -201,13 +237,21 @@ class APIFieldsTestCases:
|
|||||||
|
|
||||||
api_data = recursearray(self.api_data, param_value)
|
api_data = recursearray(self.api_data, param_value)
|
||||||
|
|
||||||
|
api_data_two = recursearray(self.api_data_two, param_value)
|
||||||
|
|
||||||
if param_expected is DoesNotExist:
|
if param_expected is DoesNotExist:
|
||||||
|
|
||||||
assert api_data['key'] not in api_data['obj']
|
assert(
|
||||||
|
api_data['key'] not in api_data['obj']
|
||||||
|
and api_data_two['key'] not in api_data_two['obj']
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
assert api_data['key'] in api_data['obj']
|
assert(
|
||||||
|
api_data['key'] in api_data['obj']
|
||||||
|
or api_data_two['key'] in api_data_two['obj']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -219,13 +263,21 @@ class APIFieldsTestCases:
|
|||||||
|
|
||||||
api_data = recursearray(self.api_data, param_value)
|
api_data = recursearray(self.api_data, param_value)
|
||||||
|
|
||||||
|
api_data_two = recursearray(self.api_data_two, param_value)
|
||||||
|
|
||||||
if param_expected is DoesNotExist:
|
if param_expected is DoesNotExist:
|
||||||
|
|
||||||
assert api_data['key'] not in api_data['obj']
|
assert(
|
||||||
|
api_data['key'] not in api_data['obj']
|
||||||
|
and api_data_two['key'] not in api_data_two['obj']
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
assert type( api_data['value'] ) is param_expected
|
assert(
|
||||||
|
type( api_data['value'] ) is param_expected
|
||||||
|
or type( api_data_two.get('value', 'is empty') ) is param_expected
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import importlib
|
import importlib
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from rest_framework import viewsets, pagination
|
from rest_framework import viewsets, pagination
|
||||||
@ -242,6 +244,8 @@ class Retrieve(
|
|||||||
status = 501
|
status = 501
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.get_log().exception(e)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
@ -315,6 +319,8 @@ class Update(
|
|||||||
status = 501
|
status = 501
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.get_log().exception(e)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
@ -382,6 +388,8 @@ class Update(
|
|||||||
status = 501
|
status = 501
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.get_log().exception(e)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
@ -431,6 +439,16 @@ class CommonViewSet(
|
|||||||
_Optional_, if specified will be add to list view metadata
|
_Optional_, if specified will be add to list view metadata
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_log: logging.Logger = None
|
||||||
|
|
||||||
|
def get_log(self):
|
||||||
|
|
||||||
|
if self._log is None:
|
||||||
|
|
||||||
|
self._log = logging.getLogger('centurion.' + self.model._meta.app_label)
|
||||||
|
|
||||||
|
return self._log
|
||||||
|
|
||||||
|
|
||||||
metadata_class = ReactUIMetadata
|
metadata_class = ReactUIMetadata
|
||||||
""" Metadata Class
|
""" Metadata Class
|
||||||
|
@ -80,6 +80,95 @@ FEATURE_FLAG_OVERRIDES = None # Feature Flags to override fetched feature flags
|
|||||||
# PROMETHEUS_METRICS_EXPORT_PORT = 8010
|
# PROMETHEUS_METRICS_EXPORT_PORT = 8010
|
||||||
# PROMETHEUS_METRICS_EXPORT_ADDRESS = ''
|
# PROMETHEUS_METRICS_EXPORT_ADDRESS = ''
|
||||||
|
|
||||||
|
|
||||||
|
LOG_FILES = { # defaults for devopment. docker includes settings has correct locations
|
||||||
|
"centurion": "../log/centurion.log",
|
||||||
|
"weblog": "../log/weblog.log",
|
||||||
|
"rest_api": "../log/rest_api.log",
|
||||||
|
"catch_all":"../log/catch-all.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
CENTURION_LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"console": {
|
||||||
|
"format": "{asctime} {levelname} {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
"verbose": {
|
||||||
|
"format": "{asctime} {levelname} {name} {module} {process:d} {thread:d} {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
"simple": {
|
||||||
|
"format": "{levelname} {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
"web_log": {
|
||||||
|
"format": "{asctime} {levelname} {name} {module} {process:d} {thread:d} {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
'console': {
|
||||||
|
'level': 'INFO',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'console',
|
||||||
|
},
|
||||||
|
"file_centurion": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": "centurion.log",
|
||||||
|
'formatter': 'verbose',
|
||||||
|
},
|
||||||
|
"file_weblog": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": "weblog.log",
|
||||||
|
'formatter': 'web_log',
|
||||||
|
},
|
||||||
|
"file_rest_api": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": "rest_api.log",
|
||||||
|
'formatter': 'verbose',
|
||||||
|
},
|
||||||
|
"file_catch_all": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": "catch-all.log",
|
||||||
|
'formatter': 'verbose',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"centurion": {
|
||||||
|
"handlers": ['console', 'file_centurion'],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
|
"django.server": {
|
||||||
|
"handlers": ["file_weblog", 'console'],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
|
"django": {
|
||||||
|
"handlers": ['console', 'file_catch_all'],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
|
'rest_framework': {
|
||||||
|
'handlers': ['file_rest_api', 'console'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
'': {
|
||||||
|
'handlers': ['file_catch_all'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
METRICS_ENABLED = False # Enable Metrics
|
METRICS_ENABLED = False # Enable Metrics
|
||||||
METRICS_EXPORT_PORT = 8080 # Port to serve metrics on
|
METRICS_EXPORT_PORT = 8080 # Port to serve metrics on
|
||||||
METRICS_MULTIPROC_DIR = '/tmp/prometheus' # path the metrics from multiple-process' save to
|
METRICS_MULTIPROC_DIR = '/tmp/prometheus' # path the metrics from multiple-process' save to
|
||||||
@ -393,7 +482,22 @@ CSRF_TRUSTED_ORIGINS = [
|
|||||||
*TRUSTED_ORIGINS
|
*TRUSTED_ORIGINS
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Add the user specified log files
|
||||||
|
CENTURION_LOGGING['handlers']['file_centurion']['filename'] = LOG_FILES['centurion']
|
||||||
|
CENTURION_LOGGING['handlers']['file_weblog']['filename'] = LOG_FILES['weblog']
|
||||||
|
CENTURION_LOGGING['handlers']['file_rest_api']['filename'] = LOG_FILES['rest_api']
|
||||||
|
CENTURION_LOGGING['handlers']['file_catch_all']['filename'] = LOG_FILES['catch_all']
|
||||||
|
|
||||||
|
|
||||||
|
if str(CENTURION_LOGGING['handlers']['file_centurion']['filename']).startswith('../log'):
|
||||||
|
|
||||||
|
if not os.path.exists('../log'): # Create log dir
|
||||||
|
|
||||||
|
os.makedirs('../log')
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
|
||||||
INSTALLED_APPS += [
|
INSTALLED_APPS += [
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
]
|
]
|
||||||
@ -407,6 +511,10 @@ if DEBUG:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Setup Logging
|
||||||
|
LOGGING = CENTURION_LOGGING
|
||||||
|
|
||||||
|
|
||||||
if METRICS_ENABLED:
|
if METRICS_ENABLED:
|
||||||
|
|
||||||
INSTALLED_APPS += [ 'django_prometheus', ]
|
INSTALLED_APPS += [ 'django_prometheus', ]
|
||||||
|
@ -67,7 +67,10 @@ class SlashCommands(
|
|||||||
|
|
||||||
returned_line = ''
|
returned_line = ''
|
||||||
|
|
||||||
if command == 'spend':
|
if(
|
||||||
|
command == 'spend'
|
||||||
|
or command == 'spent'
|
||||||
|
):
|
||||||
|
|
||||||
returned_line = re.sub(self.time_spent, self.command_duration, line)
|
returned_line = re.sub(self.time_spent, self.command_duration, line)
|
||||||
|
|
||||||
|
@ -82,7 +82,10 @@ For this command to process the following conditions must be met:
|
|||||||
user = self.opened_by,
|
user = self.opened_by,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif str(self._meta.verbose_name).lower().replace(' ', '_') == 'ticket_comment':
|
elif(
|
||||||
|
str(self._meta.verbose_name).lower().replace(' ', '_') == 'ticket_comment'
|
||||||
|
or str(self.__class__.__name__).lower().startswith('ticketcomment')
|
||||||
|
):
|
||||||
|
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
|
||||||
|
@ -819,5 +819,18 @@ class TicketBase(
|
|||||||
|
|
||||||
self.date_closed = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0).isoformat()
|
self.date_closed = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
if(
|
||||||
|
self.description != ''
|
||||||
|
and self.description is not None
|
||||||
|
):
|
||||||
|
|
||||||
|
description = self.slash_command(self.description)
|
||||||
|
|
||||||
|
if description != self.description:
|
||||||
|
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
|
||||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||||
|
|
||||||
|
@ -45,6 +45,19 @@ class TicketCommentBase(
|
|||||||
verbose_name_plural = "Ticket Comments"
|
verbose_name_plural = "Ticket Comments"
|
||||||
|
|
||||||
|
|
||||||
|
def field_validation_not_empty(value):
|
||||||
|
|
||||||
|
if value == '' or value is None:
|
||||||
|
|
||||||
|
raise centurion_exception.ValidationError(
|
||||||
|
detail = {
|
||||||
|
'comment_type': 'Comment Type requires a value.'
|
||||||
|
},
|
||||||
|
code = 'comment_type_empty_or_null'
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
model_notes = None
|
model_notes = None
|
||||||
|
|
||||||
@ -62,7 +75,6 @@ class TicketCommentBase(
|
|||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
'self',
|
'self',
|
||||||
blank = True,
|
blank = True,
|
||||||
default = None,
|
|
||||||
help_text = 'Parent ID for creating discussion threads',
|
help_text = 'Parent ID for creating discussion threads',
|
||||||
null = True,
|
null = True,
|
||||||
on_delete = models.PROTECT,
|
on_delete = models.PROTECT,
|
||||||
@ -80,7 +92,6 @@ class TicketCommentBase(
|
|||||||
|
|
||||||
external_ref = models.IntegerField(
|
external_ref = models.IntegerField(
|
||||||
blank = True,
|
blank = True,
|
||||||
default = None,
|
|
||||||
help_text = 'External System reference',
|
help_text = 'External System reference',
|
||||||
null = True,
|
null = True,
|
||||||
verbose_name = 'Reference Number',
|
verbose_name = 'Reference Number',
|
||||||
@ -89,7 +100,6 @@ class TicketCommentBase(
|
|||||||
external_system = models.IntegerField(
|
external_system = models.IntegerField(
|
||||||
blank = True,
|
blank = True,
|
||||||
choices=TicketBase.Ticket_ExternalSystem,
|
choices=TicketBase.Ticket_ExternalSystem,
|
||||||
default=None,
|
|
||||||
help_text = 'External system this item derives',
|
help_text = 'External system this item derives',
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name = 'External System',
|
verbose_name = 'External System',
|
||||||
@ -98,7 +108,7 @@ class TicketCommentBase(
|
|||||||
@property
|
@property
|
||||||
def get_comment_type(self):
|
def get_comment_type(self):
|
||||||
|
|
||||||
comment_type = str(self.Meta.sub_model_type).lower().replace(
|
comment_type = str(self._meta.sub_model_type).lower().replace(
|
||||||
' ', '_'
|
' ', '_'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -128,13 +138,15 @@ class TicketCommentBase(
|
|||||||
help_text = 'Type this comment is. derived from Meta.verbose_name',
|
help_text = 'Type this comment is. derived from Meta.verbose_name',
|
||||||
max_length = 30,
|
max_length = 30,
|
||||||
null = False,
|
null = False,
|
||||||
|
validators = [
|
||||||
|
field_validation_not_empty
|
||||||
|
],
|
||||||
verbose_name = 'Type',
|
verbose_name = 'Type',
|
||||||
)
|
)
|
||||||
|
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
TicketCommentCategory,
|
TicketCommentCategory,
|
||||||
blank = True,
|
blank = True,
|
||||||
default = None,
|
|
||||||
help_text = 'Category of the comment',
|
help_text = 'Category of the comment',
|
||||||
null = True,
|
null = True,
|
||||||
on_delete = models.PROTECT,
|
on_delete = models.PROTECT,
|
||||||
@ -411,10 +423,3 @@ class TicketCommentBase(
|
|||||||
if hasattr(self.ticket, '_ticket_comments'):
|
if hasattr(self.ticket, '_ticket_comments'):
|
||||||
|
|
||||||
del 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)
|
|
||||||
# update_ticket.status = int(TicketBase.TicketStatus.All.SOLVED.value)
|
|
||||||
|
|
||||||
# update_ticket.save()
|
|
@ -62,6 +62,8 @@ class TicketCommentSolution(
|
|||||||
|
|
||||||
self.date_closed = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0).isoformat()
|
self.date_closed = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0).isoformat()
|
||||||
|
|
||||||
|
super().save(force_insert = force_insert, force_update = force_update, using = using, update_fields = update_fields)
|
||||||
|
|
||||||
self.ticket.is_solved = self.is_closed
|
self.ticket.is_solved = self.is_closed
|
||||||
|
|
||||||
self.ticket.date_solved = self.date_closed
|
self.ticket.date_solved = self.date_closed
|
||||||
@ -70,8 +72,6 @@ class TicketCommentSolution(
|
|||||||
|
|
||||||
self.ticket.save()
|
self.ticket.save()
|
||||||
|
|
||||||
super().save(force_insert = force_insert, force_update = force_update, using = using, update_fields = update_fields)
|
|
||||||
|
|
||||||
# clear comment cache
|
# clear comment cache
|
||||||
if hasattr(self.ticket, '_ticket_comments'):
|
if hasattr(self.ticket, '_ticket_comments'):
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.tests.functional.slash_commands.test_slash_command_related import SlashCommandsTicketInheritedTestCases
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketBaseModelTestCases(
|
||||||
|
SlashCommandsTicketInheritedTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticket(self, request, django_db_blocker, model):
|
||||||
|
""" Ticket that requires body
|
||||||
|
|
||||||
|
when using this fixture, set the `description` then call ticket.save()
|
||||||
|
before use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket = model()
|
||||||
|
|
||||||
|
ticket.organization = request.cls.organization
|
||||||
|
ticket.title = 'A ticket for slash commands'
|
||||||
|
ticket.opened_by = request.cls.ticket_user
|
||||||
|
|
||||||
|
# ticket = TicketBase.objects.create(
|
||||||
|
# organization = request.cls.organization,
|
||||||
|
# title = 'A ticket for slash commands',
|
||||||
|
# opened_by = request.cls.ticket_user,
|
||||||
|
# )
|
||||||
|
|
||||||
|
yield ticket
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class TicketBaseModelInheritedTestCases(
|
||||||
|
TicketBaseModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketBaseModelPyTest(
|
||||||
|
TicketBaseModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
14
app/core/tests/functional/ticket_comment_base/conftest.py
Normal file
14
app/core/tests/functional/ticket_comment_base/conftest.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.models.ticket_comment_base import TicketCommentBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def model(request):
|
||||||
|
|
||||||
|
request.cls.model = TicketCommentBase
|
||||||
|
|
||||||
|
yield request.cls.model
|
||||||
|
|
||||||
|
del request.cls.model
|
@ -0,0 +1,85 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.models.ticket.ticket import Ticket
|
||||||
|
from core.tests.functional.slash_commands.test_slash_command_related import SlashCommandsTicketCommentInheritedTestCases
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseModelTestCases(
|
||||||
|
SlashCommandsTicketCommentInheritedTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticket(self, request, django_db_blocker):
|
||||||
|
""" Ticket that requires body
|
||||||
|
|
||||||
|
when using this fixture, set the `description` then call ticket.save()
|
||||||
|
before use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from core.models.ticket_comment_base import TicketBase
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket = TicketBase()
|
||||||
|
|
||||||
|
ticket.organization = request.cls.organization
|
||||||
|
ticket.title = 'A ticket for slash commands'
|
||||||
|
ticket.opened_by = request.cls.ticket_user
|
||||||
|
|
||||||
|
ticket = TicketBase.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
title = 'A ticket for slash commands',
|
||||||
|
opened_by = request.cls.ticket_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield ticket
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticket_comment(self, request, django_db_blocker, ticket, model):
|
||||||
|
""" Ticket Comment that requires body
|
||||||
|
|
||||||
|
when using this fixture, set the `body` then call ticket_comment.save()
|
||||||
|
before use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket.title = 'slash command ticket with comment'
|
||||||
|
|
||||||
|
ticket.save()
|
||||||
|
|
||||||
|
ticket_comment = model()
|
||||||
|
|
||||||
|
ticket_comment.user = request.cls.entity_user
|
||||||
|
|
||||||
|
ticket_comment.ticket = ticket
|
||||||
|
|
||||||
|
ticket_comment.comment_type = model._meta.sub_model_type
|
||||||
|
|
||||||
|
yield ticket_comment
|
||||||
|
|
||||||
|
ticket_comment.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseModelInheritedTestCases(
|
||||||
|
TicketCommentBaseModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseModelPyTest(
|
||||||
|
TicketCommentBaseModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.models.ticket_comment_solution import TicketCommentSolution
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def model(request):
|
||||||
|
|
||||||
|
request.cls.model = TicketCommentSolution
|
||||||
|
|
||||||
|
yield request.cls.model
|
||||||
|
|
||||||
|
del request.cls.model
|
@ -0,0 +1,28 @@
|
|||||||
|
from core.tests.functional.ticket_comment_base.test_functional_ticket_comment_base_model import TicketCommentBaseModelInheritedTestCases
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionModelTestCases(
|
||||||
|
TicketCommentBaseModelInheritedTestCases
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# check closes ticket
|
||||||
|
|
||||||
|
# check ticket status changes to solved
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionModelInheritedTestCases(
|
||||||
|
TicketCommentSolutionModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionModelPyTest(
|
||||||
|
TicketCommentSolutionModelTestCases
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
@ -850,6 +850,71 @@ class TicketBaseModelTestCases(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_called_clean_ticketcommentbase(self, model, mocker):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(TicketBase, 'clean')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['title'] = 'was clean called'
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
|
||||||
|
model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert spy.assert_called_once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_called_save_ticketcommentbase(self, model, mocker):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketBase.save` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(TicketBase, 'save')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['title'] = 'was save called'
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
|
||||||
|
model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert spy.assert_called_once
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_save_called_slash_command(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(self.model, 'slash_command')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['title'] = 'was save called'
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
|
||||||
|
item = model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
spy.assert_called_with(item, valid_data['description'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TicketBaseModelInheritedCases(
|
class TicketBaseModelInheritedCases(
|
||||||
TicketBaseModelTestCases,
|
TicketBaseModelTestCases,
|
||||||
):
|
):
|
||||||
@ -900,3 +965,27 @@ class TicketBaseModelPyTest(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
assert type(self.model().get_related_model()) is type(None)
|
assert type(self.model().get_related_model()) is type(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_save_called_slash_command(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
This test case is a duplicate of a test with the same name. This
|
||||||
|
test is required so that the base class `save()` function can be tested.
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(self.model, 'slash_command')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['title'] = 'was save called'
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
|
||||||
|
item = model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
spy.assert_called_with(item, valid_data['description'])
|
||||||
|
0
app/core/tests/unit/ticket_comment_base/__init__.py
Normal file
0
app/core/tests/unit/ticket_comment_base/__init__.py
Normal file
14
app/core/tests/unit/ticket_comment_base/conftest.py
Normal file
14
app/core/tests/unit/ticket_comment_base/conftest.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.models.ticket_comment_base import TicketCommentBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def model(request):
|
||||||
|
|
||||||
|
request.cls.model = TicketCommentBase
|
||||||
|
|
||||||
|
yield request.cls.model
|
||||||
|
|
||||||
|
del request.cls.model
|
@ -0,0 +1,374 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from django.contrib.auth.models import ContentType, Permission, User
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
|
from rest_framework.relations import Hyperlink
|
||||||
|
|
||||||
|
from app.tests.common import DoesNotExist
|
||||||
|
|
||||||
|
from api.tests.unit.test_unit_api_fields import (
|
||||||
|
APIFieldsInheritedCases,
|
||||||
|
)
|
||||||
|
|
||||||
|
from core.models.ticket_comment_base import (
|
||||||
|
Entity,
|
||||||
|
TicketBase,
|
||||||
|
TicketCommentBase,
|
||||||
|
TicketCommentCategory
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseAPITestCases(
|
||||||
|
APIFieldsInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
base_model = TicketCommentBase
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def setup_model(self, request, django_db_blocker,
|
||||||
|
model,
|
||||||
|
):
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
|
||||||
|
ticket_view_permission = Permission.objects.get(
|
||||||
|
codename = 'view_' + TicketBase._meta.model_name,
|
||||||
|
content_type = ContentType.objects.get(
|
||||||
|
app_label = TicketBase._meta.app_label,
|
||||||
|
model = TicketBase._meta.model_name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
request.cls.view_team.permissions.add( ticket_view_permission )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
category = TicketCommentCategory.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
name = 'comment category'
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket_user = User.objects.create_user(username="ticket_user", password="password")
|
||||||
|
|
||||||
|
ticket = TicketBase.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
title = 'ticket comment title',
|
||||||
|
opened_by = ticket_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
comment_user = Entity.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
)
|
||||||
|
|
||||||
|
request.cls.comment_user = comment_user
|
||||||
|
|
||||||
|
|
||||||
|
valid_data = request.cls.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['body'] = 'the template comment'
|
||||||
|
|
||||||
|
del valid_data['external_ref']
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['category']
|
||||||
|
del valid_data['template']
|
||||||
|
del valid_data['parent']
|
||||||
|
|
||||||
|
valid_data['comment_type'] = TicketCommentBase._meta.sub_model_type
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
valid_data['user'] = request.cls.comment_user
|
||||||
|
|
||||||
|
|
||||||
|
template_comment = TicketCommentBase.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
request.cls.kwargs_create_item.update({
|
||||||
|
'category': category,
|
||||||
|
'ticket': ticket,
|
||||||
|
'user': comment_user,
|
||||||
|
'parent': None,
|
||||||
|
'template': template_comment,
|
||||||
|
'comment_type': model._meta.sub_model_type
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
template_comment.delete()
|
||||||
|
|
||||||
|
category.delete()
|
||||||
|
|
||||||
|
del request.cls.comment_user
|
||||||
|
|
||||||
|
for comment in ticket.ticketcommentbase_set.all():
|
||||||
|
|
||||||
|
comment.delete()
|
||||||
|
|
||||||
|
ticket.delete()
|
||||||
|
|
||||||
|
ticket_user.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def post_model(self, request, model, django_db_blocker ):
|
||||||
|
|
||||||
|
request.cls.url_view_kwargs.update({
|
||||||
|
'ticket_id': request.cls.item.ticket.id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
model != self.base_model
|
||||||
|
or self.item.parent
|
||||||
|
):
|
||||||
|
|
||||||
|
request.cls.url_view_kwargs.update({
|
||||||
|
'ticket_comment_model': model._meta.sub_model_type
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
valid_data = request.cls.kwargs_create_item.copy()
|
||||||
|
valid_data['body'] = 'the child comment'
|
||||||
|
|
||||||
|
valid_data['comment_type'] = TicketCommentBase._meta.sub_model_type
|
||||||
|
valid_data['parent'] = request.cls.item
|
||||||
|
valid_data['ticket'] = request.cls.item.ticket
|
||||||
|
valid_data['user'] = request.cls.comment_user
|
||||||
|
|
||||||
|
del valid_data['external_ref']
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['category']
|
||||||
|
del valid_data['template']
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
request.cls.item.ticket.is_closed = False
|
||||||
|
request.cls.item.ticket.date_closed = None
|
||||||
|
request.cls.item.ticket.is_solved = False
|
||||||
|
request.cls.item.ticket.date_solved = None
|
||||||
|
request.cls.item.ticket.status = TicketBase.TicketStatus.NEW
|
||||||
|
request.cls.item.ticket.save()
|
||||||
|
|
||||||
|
request.cls.item_two = model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
url_ns_name = '_api_v2_ticket_comment_base_sub_thread'
|
||||||
|
|
||||||
|
request.cls.url_two = reverse(
|
||||||
|
'v2:' + url_ns_name + '-detail',
|
||||||
|
kwargs = {
|
||||||
|
**request.cls.url_view_kwargs,
|
||||||
|
'pk': request.cls.item_two.id,
|
||||||
|
'parent_id': request.cls.item.id,
|
||||||
|
'ticket_comment_model': model._meta.sub_model_type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
request.cls.item_two.delete(keep_parents = False)
|
||||||
|
|
||||||
|
del request.cls.item_two
|
||||||
|
|
||||||
|
del request.cls.url_two
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class', autouse = True)
|
||||||
|
def class_setup(self, request, django_db_blocker,
|
||||||
|
setup_pre,
|
||||||
|
setup_model,
|
||||||
|
create_model,
|
||||||
|
post_model,
|
||||||
|
setup_post,
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parameterized_test_data(self):
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
'parent': {
|
||||||
|
'expected': dict
|
||||||
|
},
|
||||||
|
'parent.id': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'parent.display_name': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'parent.url': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'ticket': {
|
||||||
|
'expected': dict
|
||||||
|
},
|
||||||
|
'ticket.id': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'ticket.display_name': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'ticket.url': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
|
||||||
|
'external_ref': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'external_system': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'comment_type': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'category': {
|
||||||
|
'expected': dict
|
||||||
|
},
|
||||||
|
'category.id': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'category.display_name': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'category.url': {
|
||||||
|
'expected': Hyperlink
|
||||||
|
},
|
||||||
|
|
||||||
|
'body': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'private': {
|
||||||
|
'expected': bool
|
||||||
|
},
|
||||||
|
'duration': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'estimation': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'template': {
|
||||||
|
'expected': dict
|
||||||
|
},
|
||||||
|
'template.id': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'template.display_name': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'template.url': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
|
||||||
|
'is_template': {
|
||||||
|
'expected': bool
|
||||||
|
},
|
||||||
|
'source': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'user': {
|
||||||
|
'expected': dict
|
||||||
|
},
|
||||||
|
'user.id': {
|
||||||
|
'expected': int
|
||||||
|
},
|
||||||
|
'user.display_name': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
'user.url': {
|
||||||
|
'expected': Hyperlink
|
||||||
|
},
|
||||||
|
|
||||||
|
'is_closed': {
|
||||||
|
'expected': bool
|
||||||
|
},
|
||||||
|
'date_closed': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
|
||||||
|
'_urls.threads': {
|
||||||
|
'expected': str
|
||||||
|
},
|
||||||
|
# Below fields dont exist.
|
||||||
|
|
||||||
|
'display_name': {
|
||||||
|
'expected': DoesNotExist
|
||||||
|
},
|
||||||
|
'model_notes': {
|
||||||
|
'expected': DoesNotExist
|
||||||
|
},
|
||||||
|
'_urls.notes': {
|
||||||
|
'expected': DoesNotExist
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {
|
||||||
|
'parent': '',
|
||||||
|
'ticket': '',
|
||||||
|
'external_ref': 123,
|
||||||
|
'external_system': TicketBase.Ticket_ExternalSystem.CUSTOM_1,
|
||||||
|
'comment_type': '',
|
||||||
|
'category': '',
|
||||||
|
'body': 'the ticket comment',
|
||||||
|
'private': False,
|
||||||
|
'duration': 1,
|
||||||
|
'estimation': 2,
|
||||||
|
'template': '',
|
||||||
|
'is_template': True,
|
||||||
|
'source': TicketBase.TicketSource.HELPDESK,
|
||||||
|
'user': '',
|
||||||
|
'is_closed': True,
|
||||||
|
'date_closed': '2025-05-09T19:32Z',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
url_ns_name = '_api_v2_ticket_comment_base'
|
||||||
|
"""Url namespace (optional, if not required) and url name"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseAPIInheritedCases(
|
||||||
|
TicketCommentBaseAPITestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
kwargs_create_item: dict = None
|
||||||
|
|
||||||
|
model = None
|
||||||
|
|
||||||
|
url_ns_name = '_api_v2_ticket_comment_base_sub'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseAPIPyTest(
|
||||||
|
TicketCommentBaseAPITestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
@ -0,0 +1,576 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from access.models.person import Person
|
||||||
|
|
||||||
|
from app.tests.unit.test_unit_models import (
|
||||||
|
PyTestTenancyObjectInheritedCases,
|
||||||
|
)
|
||||||
|
|
||||||
|
from core.models.ticket_comment_base import TicketBase, TicketCommentBase, TicketCommentCategory
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseModelTestCases(
|
||||||
|
PyTestTenancyObjectInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
base_model = TicketCommentBase
|
||||||
|
|
||||||
|
sub_model_type = 'comment'
|
||||||
|
"""Sub Model Type
|
||||||
|
|
||||||
|
sub-models must have this attribute defined in `ModelName.Meta.sub_model_type`
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {
|
||||||
|
'parent': None,
|
||||||
|
'ticket': '',
|
||||||
|
'external_ref': 0,
|
||||||
|
'external_system': TicketBase.Ticket_ExternalSystem.CUSTOM_1,
|
||||||
|
'comment_type': sub_model_type,
|
||||||
|
'category': '',
|
||||||
|
'body': 'asdasdas',
|
||||||
|
'private': False,
|
||||||
|
'template': None,
|
||||||
|
'source': TicketBase.TicketSource.HELPDESK,
|
||||||
|
'user': '',
|
||||||
|
'is_closed': True,
|
||||||
|
'date_closed': '2025-05-08T17:10Z',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parameterized_fields: dict = {
|
||||||
|
"is_global": {
|
||||||
|
'field_type': None,
|
||||||
|
'field_parameter_default_exists': None,
|
||||||
|
'field_parameter_default_value': None,
|
||||||
|
'field_parameter_verbose_name_type': None
|
||||||
|
},
|
||||||
|
"model_notes": {
|
||||||
|
'field_type': None,
|
||||||
|
'field_parameter_default_exists': None,
|
||||||
|
'field_parameter_default_value': None,
|
||||||
|
'field_parameter_verbose_name_type': None
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
'field_type': models.ForeignKey,
|
||||||
|
'field_parameter_default_exists': False,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"ticket": {
|
||||||
|
'field_type': models.ForeignKey,
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
"external_system": {
|
||||||
|
'field_type': models.fields.IntegerField,
|
||||||
|
'field_parameter_default_exists': False,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"comment_type": {
|
||||||
|
'field_type': models.fields.CharField,
|
||||||
|
'field_parameter_default_exists': False,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
'field_type': models.ForeignKey,
|
||||||
|
'field_parameter_default_exists': False,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
'field_type': models.fields.IntegerField,
|
||||||
|
'field_parameter_default_exists': True,
|
||||||
|
'field_parameter_default_value': 0,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"estimation": {
|
||||||
|
'field_type': models.fields.IntegerField,
|
||||||
|
'field_parameter_default_exists': True,
|
||||||
|
'field_parameter_default_value': 0,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
'field_type': models.ForeignKey,
|
||||||
|
'field_parameter_default_exists': True,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
'field_type': models.fields.IntegerField,
|
||||||
|
'field_parameter_default_exists': True,
|
||||||
|
'field_parameter_default_value': TicketBase.TicketSource.HELPDESK,
|
||||||
|
'field_parameter_verbose_name_type': str,
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
'field_type': models.ForeignKey,
|
||||||
|
'field_parameter_default_exists': False,
|
||||||
|
'field_parameter_default_value': None,
|
||||||
|
'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_model(self,
|
||||||
|
request,
|
||||||
|
model,
|
||||||
|
django_db_blocker,
|
||||||
|
organization_one,
|
||||||
|
organization_two
|
||||||
|
):
|
||||||
|
|
||||||
|
request.cls.model = model
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view", password="password")
|
||||||
|
|
||||||
|
comment_category = TicketCommentCategory.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
name = 'test cat comment'
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket = TicketBase.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
title = 'tester comment ticket',
|
||||||
|
description = 'aa',
|
||||||
|
opened_by = request.cls.view_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
user = Person.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
f_name = 'ip',
|
||||||
|
l_name = 'funny'
|
||||||
|
)
|
||||||
|
|
||||||
|
request.cls.kwargs_create_item.update({
|
||||||
|
'category': comment_category,
|
||||||
|
'ticket': ticket,
|
||||||
|
'user': user,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if 'organization' not in request.cls.kwargs_create_item:
|
||||||
|
|
||||||
|
request.cls.kwargs_create_item.update({
|
||||||
|
'organization': request.cls.organization
|
||||||
|
})
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
del request.cls.kwargs_create_item
|
||||||
|
|
||||||
|
comment_category.delete()
|
||||||
|
|
||||||
|
ticket.delete()
|
||||||
|
|
||||||
|
user.delete()
|
||||||
|
|
||||||
|
request.cls.view_user.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class', autouse = True)
|
||||||
|
def class_setup(self,
|
||||||
|
setup_model,
|
||||||
|
create_model,
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticket(self, request, django_db_blocker):
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
ticket = TicketBase.objects.create(
|
||||||
|
organization = request.cls.organization,
|
||||||
|
title = 'per function_ticket',
|
||||||
|
opened_by = request.cls.view_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield ticket
|
||||||
|
|
||||||
|
|
||||||
|
with django_db_blocker.unblock():
|
||||||
|
|
||||||
|
for comment in ticket.ticketcommentbase_set.all():
|
||||||
|
|
||||||
|
comment.delete()
|
||||||
|
|
||||||
|
ticket.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_validation_exception_no_organization(self):
|
||||||
|
""" Tenancy objects must have an organization
|
||||||
|
|
||||||
|
This test case is an over-ride of a test with the same name. this test
|
||||||
|
is not required as the organization is derived from the ticket.
|
||||||
|
|
||||||
|
Must not be able to create an item without an organization
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_class_inherits_ticketcommentbase(self):
|
||||||
|
""" Class inheritence
|
||||||
|
|
||||||
|
TenancyObject must inherit SaveHistory
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert issubclass(self.model, TicketCommentBase)
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_meta_exists_permissions(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` exists
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert hasattr(self.model._meta, 'permissions')
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_meta_not_none_permissions(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` does not have a value of none
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.model._meta.permissions is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_meta_type_permissions(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` value is of type list
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert type(self.model._meta.permissions) is list
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_value_permissions_has_import(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` value contains permission
|
||||||
|
`import`
|
||||||
|
"""
|
||||||
|
|
||||||
|
permission_found = False
|
||||||
|
|
||||||
|
for permission, description in self.model._meta.permissions:
|
||||||
|
|
||||||
|
if permission == 'import_' + self.model._meta.model_name:
|
||||||
|
|
||||||
|
permission_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
assert permission_found
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_value_permissions_has_triage(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` value contains permission
|
||||||
|
`triage`
|
||||||
|
"""
|
||||||
|
|
||||||
|
permission_found = False
|
||||||
|
|
||||||
|
for permission, description in self.model._meta.permissions:
|
||||||
|
|
||||||
|
if permission == 'triage_' + self.model._meta.model_name:
|
||||||
|
|
||||||
|
permission_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
assert permission_found
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_value_permissions_has_purge(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.permissions` value contains permission
|
||||||
|
`purge`
|
||||||
|
"""
|
||||||
|
|
||||||
|
permission_found = False
|
||||||
|
|
||||||
|
for permission, description in self.model._meta.permissions:
|
||||||
|
|
||||||
|
if permission == 'purge_' + self.model._meta.model_name:
|
||||||
|
|
||||||
|
permission_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
assert permission_found
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_meta_type_sub_model_type(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.sub_model_type` value is of type str
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert type(self.model._meta.sub_model_type) is str
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_meta_value_sub_model_type(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `Meta.sub_model_type` value is correct
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.model._meta.sub_model_type == self.sub_model_type
|
||||||
|
|
||||||
|
|
||||||
|
def test_attribute_type_get_comment_type(self):
|
||||||
|
"""Attribute Check
|
||||||
|
|
||||||
|
Ensure attribute `get_comment_type` value is correct
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.item.get_comment_type == self.item._meta.sub_model_type
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_get_related_model(self):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Confirm function `get_related_model` returns `None` for self
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.item.get_related_model() == None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_get_related_field_name(self):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Confirm function `get_related_field_name` returns an empty string
|
||||||
|
for self
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.item.get_related_field_name() == ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_get_url(self):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Confirm function `get_url` returns the correct url
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.item.parent:
|
||||||
|
|
||||||
|
expected_value = '/core/ticket/' + str(self.item.ticket.id) + '/' + self.sub_model_type + '/' + str(
|
||||||
|
self.item.parent.id) + '/threads/' + str(self.item.id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
expected_value = '/core/ticket/' + str( self.item.ticket.id) + '/' + self.sub_model_type + '/' + str(self.item.id)
|
||||||
|
|
||||||
|
assert self.item.get_url() == '/api/v2' + expected_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_parent_object(self):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Confirm function `parent_object` returns the ticket
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert self.item.parent_object == self.item.ticket
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_clean_validation_mismatch_comment_type_raises_exception(self):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `clean` does validation
|
||||||
|
"""
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['comment_type'] = 'Nope'
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError) as err:
|
||||||
|
|
||||||
|
self.model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert err.value.get_codes()['comment_type'] == 'comment_type_wrong_endpoint'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_called_clean_ticketcommentbase(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(TicketCommentBase, 'clean')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['external_ref']
|
||||||
|
|
||||||
|
model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert spy.assert_called_once
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_save_called_slash_command(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(self.model, 'slash_command')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['external_ref']
|
||||||
|
|
||||||
|
item = model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
spy.assert_called_with(item, valid_data['body'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseModelInheritedCases(
|
||||||
|
TicketCommentBaseModelTestCases,
|
||||||
|
):
|
||||||
|
"""Sub-Ticket Test Cases
|
||||||
|
|
||||||
|
Test Cases for Ticket models that inherit from model TicketCommentBase
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {}
|
||||||
|
|
||||||
|
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 TicketCommentBaseModelPyTest(
|
||||||
|
TicketCommentBaseModelTestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_clean_validation_close_raises_exception(self, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `clean` does validation
|
||||||
|
"""
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
|
||||||
|
del valid_data['date_closed']
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError) as err:
|
||||||
|
|
||||||
|
self.model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert err.value.get_codes()['date_closed'] == 'ticket_closed_no_date'
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_save_called_slash_command(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
This test case is a duplicate of a test with the same name. This
|
||||||
|
test is required so that the base class `save()` function can be tested.
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(self.model, 'slash_command')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['external_ref']
|
||||||
|
|
||||||
|
item = model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
spy.assert_called_with(item, valid_data['body'])
|
@ -0,0 +1,121 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
from api.tests.unit.test_unit_common_viewset import SubModelViewSetInheritedCases
|
||||||
|
|
||||||
|
from core.viewsets.ticket_comment import (
|
||||||
|
NoDocsViewSet,
|
||||||
|
TicketBase,
|
||||||
|
TicketCommentBase,
|
||||||
|
ViewSet
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseViewsetTestCases(
|
||||||
|
SubModelViewSetInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
model = None
|
||||||
|
|
||||||
|
viewset = ViewSet
|
||||||
|
|
||||||
|
base_model = TicketCommentBase
|
||||||
|
|
||||||
|
route_name = None
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(self):
|
||||||
|
|
||||||
|
|
||||||
|
self.viewset = ViewSet
|
||||||
|
|
||||||
|
|
||||||
|
if self.model is None:
|
||||||
|
|
||||||
|
self.model = TicketCommentBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
|
self.ticket = TicketBase.objects.create(
|
||||||
|
organization = self.organization,
|
||||||
|
title = 'ticket comment test',
|
||||||
|
opened_by = self.view_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.kwargs = {
|
||||||
|
'ticket_id': self.ticket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.model is not TicketCommentBase:
|
||||||
|
|
||||||
|
self.kwargs = {
|
||||||
|
**self.kwargs,
|
||||||
|
'ticket_comment_model': self.model._meta.sub_model_type
|
||||||
|
}
|
||||||
|
|
||||||
|
self.viewset.kwargs = self.kwargs
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
|
||||||
|
cls.ticket.delete()
|
||||||
|
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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_comment_model'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseViewsetInheritedCases(
|
||||||
|
TicketCommentBaseViewsetTestCases,
|
||||||
|
):
|
||||||
|
"""Test Suite for Sub-Models of TicketCommentBase
|
||||||
|
|
||||||
|
Use this Test suit if your sub-model inherits directly from TicketCommentBase.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model: str = None
|
||||||
|
"""name of the model to test"""
|
||||||
|
|
||||||
|
route_name = 'v2:_api_v2_ticket_comment_base_sub'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentBaseViewsetTest(
|
||||||
|
TicketCommentBaseViewsetTestCases,
|
||||||
|
TestCase,
|
||||||
|
):
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
route_name = 'v2:_api_v2_ticket_comment_base'
|
||||||
|
|
||||||
|
viewset = NoDocsViewSet
|
14
app/core/tests/unit/ticket_comment_solution/conftest.py
Normal file
14
app/core/tests/unit/ticket_comment_solution/conftest.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.models.ticket_comment_solution import TicketCommentSolution
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture( scope = 'class')
|
||||||
|
def model(request):
|
||||||
|
|
||||||
|
request.cls.model = TicketCommentSolution
|
||||||
|
|
||||||
|
yield request.cls.model
|
||||||
|
|
||||||
|
del request.cls.model
|
@ -0,0 +1,84 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from core.models.ticket_comment_solution import TicketCommentSolution
|
||||||
|
from core.tests.unit.ticket_comment_base.test_unit_ticket_comment_base_model import (
|
||||||
|
TicketCommentBaseModelInheritedCases
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionModelTestCases(
|
||||||
|
TicketCommentBaseModelInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
sub_model_type = 'solution'
|
||||||
|
"""Sub Model Type
|
||||||
|
|
||||||
|
sub-models must have this attribute defined in `ModelName.Meta.sub_model_type`
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {
|
||||||
|
'comment_type': sub_model_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_class_inherits_ticketcommentsolution(self):
|
||||||
|
""" Class inheritence
|
||||||
|
|
||||||
|
TenancyObject must inherit SaveHistory
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert issubclass(self.model, TicketCommentSolution)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_function_called_clean_ticketcommentsolution(self, model, mocker, ticket):
|
||||||
|
"""Function Check
|
||||||
|
|
||||||
|
Ensure function `TicketCommentBase.clean` is called
|
||||||
|
"""
|
||||||
|
|
||||||
|
spy = mocker.spy(TicketCommentSolution, 'clean')
|
||||||
|
|
||||||
|
valid_data = self.kwargs_create_item.copy()
|
||||||
|
|
||||||
|
valid_data['ticket'] = ticket
|
||||||
|
|
||||||
|
del valid_data['external_system']
|
||||||
|
del valid_data['external_ref']
|
||||||
|
|
||||||
|
model.objects.create(
|
||||||
|
**valid_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert spy.assert_called_once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionModelInheritedCases(
|
||||||
|
TicketCommentSolutionModelTestCases,
|
||||||
|
):
|
||||||
|
"""Sub-Ticket Test Cases
|
||||||
|
|
||||||
|
Test Cases for Ticket models that inherit from model TicketCommentSolution
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {}
|
||||||
|
|
||||||
|
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 TicketCommentSolutionModelPyTest(
|
||||||
|
TicketCommentSolutionModelTestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
@ -0,0 +1,39 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from core.models.ticket_comment_solution import TicketCommentSolution
|
||||||
|
from core.tests.unit.ticket_comment_base.test_unit_ticket_comment_base_viewset import TicketCommentBaseViewsetInheritedCases
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionViewsetTestCases(
|
||||||
|
TicketCommentBaseViewsetInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(self):
|
||||||
|
|
||||||
|
self.model = TicketCommentSolution
|
||||||
|
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionViewsetInheritedCases(
|
||||||
|
TicketCommentSolutionViewsetTestCases,
|
||||||
|
):
|
||||||
|
"""Test Suite for Sub-Models of TicketBase
|
||||||
|
|
||||||
|
Use this Test suit if your sub-model inherits directly from TicketCommentSolution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model: str = None
|
||||||
|
"""name of the model to test"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionViewsetTest(
|
||||||
|
TicketCommentSolutionViewsetTestCases,
|
||||||
|
TestCase,
|
||||||
|
):
|
||||||
|
pass
|
@ -0,0 +1,31 @@
|
|||||||
|
from core.tests.unit.ticket_comment_base.test_unit_ticket_comment_base_api_fields import (
|
||||||
|
TicketCommentBaseAPIInheritedCases
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionAPITestCases(
|
||||||
|
TicketCommentBaseAPIInheritedCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
parameterized_test_data = {}
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionAPIInheritedCases(
|
||||||
|
TicketCommentSolutionAPITestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
kwargs_create_item: dict = {None}
|
||||||
|
|
||||||
|
model = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TicketCommentSolutionAPIPyTest(
|
||||||
|
TicketCommentSolutionAPITestCases,
|
||||||
|
):
|
||||||
|
|
||||||
|
pass
|
@ -40,4 +40,6 @@ As with any other object within Centurion, the addition of a feature requires it
|
|||||||
|
|
||||||
- API Permissions `core.tests.functional.ticket_base.test_functional_ticket_base_permission.TicketBasePermissionsAPIInheritedCases`
|
- API Permissions `core.tests.functional.ticket_base.test_functional_ticket_base_permission.TicketBasePermissionsAPIInheritedCases`
|
||||||
|
|
||||||
|
- Model `app.core.tests.functional.ticket_base.test_functional_ticket_base_model.TicketBaseModelInheritedTestCases` _(if inheriting from `TicketBase`)_ Test cases for sub-models
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: Ticket Comment
|
||||||
|
description: Centurion ERP Base Model Ticket Comment development documentation
|
||||||
|
date: 2025-04-16
|
||||||
|
template: project.html
|
||||||
|
about: https://github.com/nofusscomputing/centurion_erp
|
||||||
|
---
|
||||||
|
|
||||||
|
Ticket Comments is a base model within Centurion ERP. This base provides the core features for all subsequent sub-ticket_comment models. As such extending Centurion ERP with a new ticket comment type is a simple process. The adding of a ticket comment type only requires that you extend an existing ticket model containing only the changes for your new ticket type.
|
||||||
|
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
- ...
|
||||||
|
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
Ticketing does not use the standard history model of Centurion ERP. History for a ticket is kept in the form of action comments. As each change to a ticket occurs, an action comment is created denoting the from and to in relation to a change.
|
||||||
|
|
||||||
|
|
||||||
|
## Model
|
||||||
|
|
||||||
|
When creating your sub-model, do not re-define any field that is already specified within the model you are inheriting from. This is however, with the exception of the code docs specifying otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
As with any other object within Centurion, the addition of a feature requires it be tested. The following Test Suites are available:
|
||||||
|
|
||||||
|
- `Unit` Test Cases
|
||||||
|
|
||||||
|
- `core.tests.unit.ticket_comment_base.<*>.<Inherited class name>InheritedCases` _(if inheriting from `TicketCommentBase`)_ Test cases for sub-models
|
||||||
|
|
||||||
|
- ViewSet `core.tests.unit.ticket_comment_base.test_unit_ticket_comment_base_viewset.TicketCommentBaseViewsetInheritedCases`
|
||||||
|
|
||||||
|
- `Functional` Test Cases
|
||||||
|
|
||||||
|
- `core.tests.functional.ticket_comment_base.<*>.<Inherited class name>InheritedCases` _(if inheriting from `TicketCommentBase`)_ Test cases for sub-models
|
||||||
|
|
||||||
|
- API Permissions `core.tests.functional.ticket_comment_base.test_functional_ticket_comment_base_permission.TicketCommentBasePermissionsAPIInheritedCases`
|
||||||
|
|
||||||
|
- Model `app.core.tests.functional.ticket_comment_base.test_functional_ticket_comment_base_model.TicketCommentBaseModelInheritedTestCases` _(if inheriting from `TicketCommentBase`)_ Test cases for sub-models
|
||||||
|
|
||||||
|
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.
|
@ -96,7 +96,7 @@ We do have some core sub-models available. There intended purpose is to serve as
|
|||||||
|
|
||||||
- [Ticket](./core/ticket.md)
|
- [Ticket](./core/ticket.md)
|
||||||
|
|
||||||
- Ticket Comment
|
- [Ticket Comment](./core/ticket_comment.md)
|
||||||
|
|
||||||
All sub-models are intended to be extended and contain the core features for ALL models. This aids in extensibility and reduces the work required to add a model.
|
All sub-models are intended to be extended and contain the core features for ALL models. This aids in extensibility and reduces the work required to add a model.
|
||||||
|
|
||||||
|
@ -33,6 +33,15 @@ FEATURE_FLAGGING_ENABLED = True # Turn Feature Flagging on/off
|
|||||||
|
|
||||||
FEATURE_FLAG_OVERRIDES = [] # Feature Flag Overrides. Takes preceedence over downloaded feature flags.
|
FEATURE_FLAG_OVERRIDES = [] # Feature Flag Overrides. Takes preceedence over downloaded feature flags.
|
||||||
|
|
||||||
|
|
||||||
|
LOG_FILES = { # Location where log files will be created
|
||||||
|
"centurion": "/var/log/centurion.log",
|
||||||
|
"weblog": "/var/log/weblog.log",
|
||||||
|
"rest_api": "/var/log/rest_api.log",
|
||||||
|
"catch_all":"/var/log/catch-all.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
SECURE_SSL_REDIRECT = True
|
SECURE_SSL_REDIRECT = True
|
||||||
|
@ -153,6 +153,8 @@ nav:
|
|||||||
|
|
||||||
- projects/centurion_erp/development/core/ticket.md
|
- projects/centurion_erp/development/core/ticket.md
|
||||||
|
|
||||||
|
- projects/centurion_erp/development/core/ticket_comment.md
|
||||||
|
|
||||||
- projects/centurion_erp/development/views.md
|
- projects/centurion_erp/development/views.md
|
||||||
|
|
||||||
- User:
|
- User:
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
|
|
||||||
Django==5.1.8
|
django==5.1.9
|
||||||
django-cors-headers==4.4.0
|
django-cors-headers==4.4.0
|
||||||
|
|
||||||
django-debug-toolbar==5.1.0
|
django-debug-toolbar==5.1.0
|
||||||
social-auth-app-django==5.4.1
|
social-auth-app-django==5.4.1
|
||||||
|
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.16.0
|
||||||
djangorestframework-jsonapi==7.0.2
|
djangorestframework-jsonapi==7.1.0
|
||||||
|
|
||||||
# DRF
|
# DRF
|
||||||
pyyaml>=6.0.1
|
pyyaml>=6.0.2
|
||||||
django-filter==24.2
|
django-filter==24.2
|
||||||
|
|
||||||
# OpenAPI Schema
|
# OpenAPI Schema
|
||||||
uritemplate==4.1.1
|
uritemplate==4.1.1
|
||||||
coreapi==2.3.3
|
coreapi==2.3.3
|
||||||
|
|
||||||
drf-spectacular==0.27.2
|
drf-spectacular==0.28.0
|
||||||
drf-spectacular[sidecar]==0.27.2
|
drf-spectacular[sidecar]==0.28.0
|
||||||
|
|
||||||
django_split_settings==1.3.1
|
django_split_settings==1.3.1
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pytest==8.3.5
|
pytest==8.3.5
|
||||||
pytest-django==4.11.1
|
pytest-django==4.11.1
|
||||||
|
pytest-mock==3.14.0
|
||||||
coverage==7.8.0
|
coverage==7.8.0
|
||||||
pytest-cov==6.1.1
|
pytest-cov==6.1.1
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user