import base64 from collections import OrderedDict from datetime import datetime from django.contrib.auth.models import User from django.core.files.uploadedfile import SimpleUploadedFile from freezegun import freeze_time from helpdesk.models import CustomField, Queue, Ticket from rest_framework import HTTP_HEADER_ENCODING from rest_framework.exceptions import ErrorDetail from rest_framework.status import ( HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, ) from rest_framework.test import APITestCase class TicketTest(APITestCase): due_date = datetime(2022, 4, 10, 15, 6) @classmethod def setUpTestData(cls): cls.queue = Queue.objects.create( title="Test Queue", slug="test-queue", ) def test_create_api_ticket_not_authenticated_user(self): response = self.client.post("/api/tickets/") self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create_api_ticket_authenticated_non_staff_user(self): non_staff_user = User.objects.create_user(username="test") self.client.force_authenticate(non_staff_user) response = self.client.post("/api/tickets/") self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) def test_create_api_ticket_no_data(self): staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) response = self.client.post("/api/tickets/") self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) self.assertEqual( response.data, { "queue": [ ErrorDetail(string="This field is required.", code="required") ], "title": [ ErrorDetail(string="This field is required.", code="required") ], }, ) self.assertFalse(Ticket.objects.exists()) def test_create_api_ticket_wrong_date_format(self): staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Test title", "due_date": "monday, 1st of may 2022", }, ) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) self.assertEqual( response.data, { "due_date": [ ErrorDetail( string="Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].", code="invalid", ) ] }, ) self.assertFalse(Ticket.objects.exists()) def test_create_api_ticket_authenticated_staff_user(self): staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Test title", "description": "Test description\nMulti lines", "submitter_email": "test@mail.com", "priority": 4, }, ) self.assertEqual(response.status_code, HTTP_201_CREATED) created_ticket = Ticket.objects.get() self.assertEqual(created_ticket.title, "Test title") self.assertEqual(created_ticket.description, "Test description\nMulti lines") self.assertEqual(created_ticket.submitter_email, "test@mail.com") self.assertEqual(created_ticket.priority, 4) self.assertEqual(created_ticket.followup_set.count(), 1) def test_create_api_ticket_with_basic_auth(self): username = "admin" password = "admin" User.objects.create_user(username=username, password=password, is_staff=True) test_user = User.objects.create_user(username="test") merge_ticket = Ticket.objects.create(queue=self.queue, title="merge ticket") # Generate base64 credentials string credentials = f"{username}:{password}" base64_credentials = base64.b64encode( credentials.encode(HTTP_HEADER_ENCODING) ).decode(HTTP_HEADER_ENCODING) self.client.credentials(HTTP_AUTHORIZATION=f"Basic {base64_credentials}") response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Title", "description": "Description", "resolution": "Resolution", "assigned_to": test_user.id, "submitter_email": "test@mail.com", "status": Ticket.RESOLVED_STATUS, "priority": 1, "on_hold": True, "due_date": self.due_date, "merged_to": merge_ticket.id, }, ) self.assertEqual(response.status_code, HTTP_201_CREATED) created_ticket = Ticket.objects.last() self.assertEqual(created_ticket.title, "Title") self.assertEqual(created_ticket.description, "Description") # resolution can not be set on creation self.assertIsNone(created_ticket.resolution) self.assertEqual(created_ticket.assigned_to, test_user) self.assertEqual(created_ticket.submitter_email, "test@mail.com") self.assertEqual(created_ticket.priority, 1) # on_hold is False on creation self.assertFalse(created_ticket.on_hold) # status is always open on creation self.assertEqual(created_ticket.status, Ticket.OPEN_STATUS) self.assertEqual(created_ticket.due_date, self.due_date) # merged_to can not be set on creation self.assertIsNone(created_ticket.merged_to) def test_edit_api_ticket(self): staff_user = User.objects.create_user(username="admin", is_staff=True) test_ticket = Ticket.objects.create(queue=self.queue, title="Test ticket") test_user = User.objects.create_user(username="test") merge_ticket = Ticket.objects.create(queue=self.queue, title="merge ticket") self.client.force_authenticate(staff_user) response = self.client.put( "/api/tickets/%d/" % test_ticket.id, { "queue": self.queue.id, "title": "Title", "description": "Description", "resolution": "Resolution", "assigned_to": test_user.id, "submitter_email": "test@mail.com", "status": Ticket.RESOLVED_STATUS, "priority": 1, "on_hold": True, "due_date": self.due_date, "merged_to": merge_ticket.id, }, ) self.assertEqual(response.status_code, HTTP_200_OK) test_ticket.refresh_from_db() self.assertEqual(test_ticket.title, "Title") self.assertEqual(test_ticket.description, "Description") self.assertEqual(test_ticket.resolution, "Resolution") self.assertEqual(test_ticket.assigned_to, test_user) self.assertEqual(test_ticket.submitter_email, "test@mail.com") self.assertEqual(test_ticket.priority, 1) self.assertTrue(test_ticket.on_hold) self.assertEqual(test_ticket.status, Ticket.RESOLVED_STATUS) self.assertEqual(test_ticket.due_date, self.due_date) self.assertEqual(test_ticket.merged_to, merge_ticket) def test_partial_edit_api_ticket(self): staff_user = User.objects.create_user(username="admin", is_staff=True) test_ticket = Ticket.objects.create(queue=self.queue, title="Test ticket") self.client.force_authenticate(staff_user) response = self.client.patch( "/api/tickets/%d/" % test_ticket.id, { "description": "New description", }, ) self.assertEqual(response.status_code, HTTP_200_OK) test_ticket.refresh_from_db() self.assertEqual(test_ticket.description, "New description") def test_delete_api_ticket(self): staff_user = User.objects.create_user(username="admin", is_staff=True) test_ticket = Ticket.objects.create(queue=self.queue, title="Test ticket") self.client.force_authenticate(staff_user) response = self.client.delete("/api/tickets/%d/" % test_ticket.id) self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) self.assertFalse(Ticket.objects.exists()) @freeze_time("2022-06-30 23:09:44") def test_create_api_ticket_with_custom_fields(self): # Create custom fields for field_type, field_display in CustomField.DATA_TYPE_CHOICES: extra_data = {} if field_type in ("varchar", "text"): extra_data["max_length"] = 10 if field_type == "integer": # Set one field as required to test error if not provided extra_data["required"] = True if field_type == "decimal": extra_data["max_length"] = 7 extra_data["decimal_places"] = 3 if field_type == "list": extra_data["list_values"] = """Green Blue Red Yellow""" CustomField.objects.create( name=field_type, label=field_display, data_type=field_type, **extra_data ) staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) # Test creation without providing required field response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Test title", "description": "Test description\nMulti lines", "submitter_email": "test@mail.com", "priority": 4, }, ) self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST) self.assertEqual( response.data, { "custom_integer": [ ErrorDetail(string="This field is required.", code="required") ] }, ) # Test creation with custom field values response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Test title", "description": "Test description\nMulti lines", "submitter_email": "test@mail.com", "priority": 4, "custom_varchar": "test", "custom_text": "multi\nline", "custom_integer": "1", "custom_decimal": "42.987", "custom_list": "Red", "custom_boolean": True, "custom_date": "2022-4-11", "custom_time": "23:59:59", "custom_datetime": "2022-4-10 18:27", "custom_email": "email@test.com", "custom_url": "http://django-helpdesk.readthedocs.org/", "custom_ipaddress": "127.0.0.1", "custom_slug": "test-slug", }, ) self.assertEqual(response.status_code, HTTP_201_CREATED) # Check all fields with data returned from the response self.assertEqual( response.data, { "id": 1, "queue": 1, "title": "Test title", "description": "Test description\nMulti lines", "resolution": None, "submitter_email": "test@mail.com", "assigned_to": None, "status": 1, "on_hold": False, "priority": 4, "due_date": None, "merged_to": None, "followup_set": [ OrderedDict( [ ("id", 1), ("ticket", 1), ("user", 1), ("title", "Ticket Opened"), ("comment", "Test description\nMulti lines"), ("public", True), ("new_status", None), ("time_spent", None), ("followupattachment_set", []), ("date", "2022-06-30T23:09:44"), ("message_id", None), ] ) ], "custom_varchar": "test", "custom_text": "multi\nline", "custom_integer": 1, "custom_decimal": "42.987", "custom_list": "Red", "custom_boolean": True, "custom_date": "2022-04-11", "custom_time": "23:59:59", "custom_datetime": "2022-04-10T18:27", "custom_email": "email@test.com", "custom_url": "http://django-helpdesk.readthedocs.org/", "custom_ipaddress": "127.0.0.1", "custom_slug": "test-slug", }, ) def test_create_api_ticket_with_attachment(self): staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) test_file = SimpleUploadedFile( "file.jpg", b"file_content", content_type="image/jpg" ) response = self.client.post( "/api/tickets/", { "queue": self.queue.id, "title": "Test title", "description": "Test description\nMulti lines", "submitter_email": "test@mail.com", "priority": 4, "attachment": test_file, }, ) self.assertEqual(response.status_code, HTTP_201_CREATED) created_ticket = Ticket.objects.get() self.assertEqual(created_ticket.title, "Test title") self.assertEqual(created_ticket.description, "Test description\nMulti lines") self.assertEqual(created_ticket.submitter_email, "test@mail.com") self.assertEqual(created_ticket.priority, 4) self.assertEqual(created_ticket.followup_set.count(), 1) self.assertEqual( created_ticket.followup_set.get().followupattachment_set.count(), 1 ) attachment = created_ticket.followup_set.get().followupattachment_set.get() self.assertEqual( attachment.file.name, f"helpdesk/attachments/test-queue-1-{created_ticket.secret_key}/1/file.jpg", ) def test_create_follow_up_with_attachments(self): staff_user = User.objects.create_user(username="test", is_staff=True) self.client.force_authenticate(staff_user) ticket = Ticket.objects.create(queue=self.queue, title="Test") test_file_1 = SimpleUploadedFile( "file.jpg", b"file_content", content_type="image/jpg" ) test_file_2 = SimpleUploadedFile( "doc.pdf", b"Doc content", content_type="application/pdf" ) response = self.client.post( "/api/followups/", { "ticket": ticket.id, "title": "Test", "comment": "Test answer\nMulti lines", "attachments": [test_file_1, test_file_2], }, ) self.assertEqual(response.status_code, HTTP_201_CREATED) created_followup = ticket.followup_set.last() self.assertEqual(created_followup.title, "Test") self.assertEqual(created_followup.comment, "Test answer\nMulti lines") self.assertEqual(created_followup.followupattachment_set.count(), 2) self.assertEqual( created_followup.followupattachment_set.first().filename, "doc.pdf" ) self.assertEqual( created_followup.followupattachment_set.first().mime_type, "application/pdf" ) self.assertEqual( created_followup.followupattachment_set.last().filename, "file.jpg" ) self.assertEqual( created_followup.followupattachment_set.last().mime_type, "image/jpg" ) class UserTicketTest(APITestCase): def setUp(self): self.queue = Queue.objects.create(title="Test queue") self.user = User.objects.create_user(username="test") self.client.force_authenticate(self.user) def test_get_user_tickets(self): user = User.objects.create_user(username="test2", email="foo@example.com") ticket_1 = Ticket.objects.create( queue=self.queue, title="Test 1", submitter_email="foo@example.com" ) Ticket.objects.create( queue=self.queue, title="Test 2", submitter_email="bar@example.com" ) ticket_3 = Ticket.objects.create( queue=self.queue, title="Test 3", submitter_email="foo@example.com" ) self.client.force_authenticate(user) response = self.client.get("/api/user_tickets/") self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(len(response.data["results"]), 2) self.assertEqual(response.data["results"][0]["id"], ticket_3.id) self.assertEqual(response.data["results"][1]["id"], ticket_1.id) def test_staff_user(self): staff_user = User.objects.create_user( username="test2", is_staff=True, email="staff@example.com" ) Ticket.objects.create( queue=self.queue, title="Test 1", submitter_email="staff@example.com" ) Ticket.objects.create( queue=self.queue, title="Test 2", submitter_email="foo@example.com" ) self.client.force_authenticate(staff_user) response = self.client.get("/api/user_tickets/") self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(len(response.data["results"]), 1) def test_not_logged_in_user(self): Ticket.objects.create( queue=self.queue, title="Test 1", submitter_email="ex@example.com" ) self.client.logout() response = self.client.get("/api/user_tickets/") self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)