Free and open source ticket system written in python
at main 246 lines 11 kB view raw
1from django.test import TestCase 2 3from django.contrib.auth import get_user_model 4from django.core.files.uploadedfile import SimpleUploadedFile 5from django.urls import reverse 6from ticketing.models import Ticket, Team, Category, Template, Comment 7 8User = get_user_model() 9 10def make_user(username="u", is_superuser=False, **kwargs): 11 defaults = {"email": f"{username}@example.com", "password": "pass-1234567890"} 12 defaults.update(kwargs) 13 user = User.objects.create_user(username=username, **defaults) 14 user.is_superuser = is_superuser 15 user.save(update_fields=["is_superuser"]) 16 return user 17 18def make_team(name="Team A", **kwargs): 19 return Team.objects.create(name=name, **kwargs) 20 21def make_category(name="GeneralCat", team=None): 22 return Category.objects.create(name=name, team=team) 23 24def make_ticket(user, title="T", desc="D", status=Ticket.Status.OPEN, category=None, priority=Ticket.Priority.MEDIUM, **kwargs): 25 return Ticket.objects.create( 26 title=title, user=user, description=desc, status=status, category=category, priority=priority, **kwargs 27 ) 28 29def fake_upload(name="file.pdf", content=b"%PDF-1.4", content_type="application/pdf"): 30 return SimpleUploadedFile(name=name, content=content, content_type=content_type) 31 32class TicketListViewsTest(TestCase): 33 def setUp(self): 34 self.user = make_user("alice") 35 self.other = make_user("bob") 36 37 def test_requires_login(self): 38 resp = self.client.get(reverse("all_tickets")) 39 self.assertEqual(resp.status_code, 302) # redirect to login 40 41 def test_open_tickets_list_shows_user_related(self): 42 self.client.force_login(self.user) 43 make_ticket(self.user, title="Mine Open", status=Ticket.Status.OPEN) 44 make_ticket(self.user, title="Mine Closed", status=Ticket.Status.CLOSED) 45 make_ticket(self.other, title="Other Open", status=Ticket.Status.OPEN) 46 47 resp = self.client.get(reverse("all_tickets")) 48 self.assertEqual(resp.status_code, 200) 49 body = resp.content.decode() 50 self.assertIn("Mine Open", body) 51 self.assertNotIn("Mine Closed", body) 52 53 # Depending on your _get_tickets rules, other user’s tickets might be hidden unless team assignment applies. 54 self.assertNotIn("Other Open", body) 55 56 def test_closed_tickets_list(self): 57 self.client.force_login(self.user) 58 make_ticket(self.user, title="Closed 1", status=Ticket.Status.CLOSED) 59 make_ticket(self.user, title="Open 1", status=Ticket.Status.OPEN) 60 61 resp = self.client.get(reverse("tickets_history")) 62 self.assertEqual(resp.status_code, 200) 63 body = resp.content.decode() 64 self.assertIn("Closed 1", body) 65 self.assertNotIn("Open 1", body) 66 67 68class TicketDetailViewTest(TestCase): 69 def setUp(self): 70 self.owner = make_user("owner") 71 self.agent = make_user("agent") 72 self.team = make_team("Support", readonly_access=False, access_non_category_tickets=True) 73 self.team.members.add(self.agent) 74 75 def test_cannot_open_without_permission(self): 76 t = make_ticket(self.owner, title="Secret") 77 stranger = make_user("stranger") 78 self.client.force_login(stranger) 79 resp = self.client.get(reverse("ticket_detail", args=[t.pk])) 80 # View redirects to all_tickets when cannot open 81 self.assertEqual(resp.status_code, 302) 82 self.assertEqual(resp.url, reverse("all_tickets")) 83 84 def test_owner_can_view_and_post_comment(self): 85 t = make_ticket(self.owner, title="Mine") 86 self.client.force_login(self.owner) 87 # GET 88 resp = self.client.get(reverse("ticket_detail", args=[t.pk])) 89 self.assertEqual(resp.status_code, 200) 90 # POST comment 91 resp = self.client.post( 92 reverse("ticket_detail", args=[t.pk]), 93 {"text": "Hello", "hidden_from_client": False} 94 ) 95 self.assertEqual(resp.status_code, 302) 96 self.assertEqual(Comment.objects.filter(ticket=t).count(), 1) 97 t.refresh_from_db() 98 self.assertEqual(t.status, Ticket.Status.IN_PROGRESS) 99 100 def test_assign_self_when_can_edit(self): 101 # Assignable because agent is in team with write access and ticket unassigned 102 t = make_ticket(self.owner, title="Assignable", assigned_team=self.team) 103 self.client.force_login(self.agent) 104 resp = self.client.post(reverse("ticket_detail", args=[t.pk]), {"assign_self": "1"}) 105 self.assertEqual(resp.status_code, 200 if resp.status_code == 200 else 302) # allow either 106 t.refresh_from_db() 107 self.assertEqual(t.assigned_to, self.agent) 108 109 def test_apply_template_prefills_comment(self): 110 Template.objects.create(name="Tmpl", content="prefilled", category=None) 111 t = make_ticket(self.owner, title="T") 112 self.client.force_login(self.agent) 113 # Grant edit permission: add team and assign ticket 114 t.assigned_team = self.team 115 t.save() 116 117 resp = self.client.post(reverse("ticket_detail", args=[t.pk]), {"apply_template": "1", "template_select": Template.objects.first().pk}) 118 self.assertEqual(resp.status_code, 200) 119 self.assertContains(resp, "prefilled") 120 121 def test_assign_to_team(self): 122 t = make_ticket(self.owner) 123 self.client.force_login(self.agent) 124 resp = self.client.post(reverse("ticket_detail", args=[t.pk]), {"assign_to_team": "1", "team_select": self.team.pk}) 125 self.assertIn(resp.status_code, (200, 302)) 126 t.refresh_from_db() 127 self.assertEqual(t.assigned_team, self.team) 128 129 def test_assign_to_category(self): 130 t = make_ticket(self.owner) 131 cat = make_category("Cat", team=self.team) 132 self.client.force_login(self.agent) 133 # Give edit permission: assign agent's team 134 t.assigned_team = self.team 135 t.save() 136 resp = self.client.post(reverse("ticket_detail", args=[t.pk]), {"assign_to_category": "1", "category_select": cat.pk}) 137 self.assertIn(resp.status_code, (200, 302)) 138 t.refresh_from_db() 139 self.assertEqual(t.category, cat) 140 141 def test_reopen_ticket(self): 142 t = make_ticket(self.owner, status=Ticket.Status.CLOSED) 143 # Give edit permission 144 t.assigned_team = self.team 145 t.save() 146 self.client.force_login(self.agent) 147 resp = self.client.post(reverse("ticket_detail", args=[t.pk]), {"reopen_ticket": "1"}) 148 self.assertIn(resp.status_code, (200, 302)) 149 t.refresh_from_db() 150 self.assertEqual(t.status, Ticket.Status.IN_PROGRESS) 151 152class TicketCreateViewTest(TestCase): 153 def setUp(self): 154 self.user = make_user("creator") 155 156 def test_requires_login(self): 157 resp = self.client.get(reverse("create_ticket")) 158 self.assertEqual(resp.status_code, 302) 159 160 def test_create_ticket_basic(self): 161 self.client.force_login(self.user) 162 resp = self.client.post(reverse("create_ticket"), { 163 "title": "Issue", 164 "description": "Something broken", 165 "category": "", # General 166 "follow_up_to": "", # No Follow-up 167 }) 168 self.assertEqual(resp.status_code, 302) 169 t = Ticket.objects.get() 170 self.assertEqual(t.title, "Issue") 171 self.assertEqual(t.user, self.user) 172 173 def test_create_with_attachments(self): 174 self.client.force_login(self.user) 175 f1 = fake_upload("a.pdf", b"%PDF-1.4", "application/pdf") 176 resp = self.client.post(reverse("create_ticket"), { 177 "title": "With files", 178 "description": "desc", 179 "category": "", 180 "follow_up_to": "", 181 "attachments": [f1], 182 }) 183 self.assertEqual(resp.status_code, 302) 184 t = Ticket.objects.get(title="With files") 185 self.assertEqual(t.fileattachment_set.count(), 1) 186 187 def test_follow_up_queryset_only_closed_by_user(self): 188 self.client.force_login(self.user) 189 closed = make_ticket(self.user, title="Closed", status=Ticket.Status.CLOSED) 190 other_open = make_ticket(self.user, title="Open", status=Ticket.Status.OPEN) 191 resp = self.client.get(reverse("create_ticket")) 192 self.assertEqual(resp.status_code, 200) 193 form = resp.context["form"] 194 qs = form.fields["follow_up_to"].queryset 195 self.assertIn(closed, qs) 196 self.assertNotIn(other_open, qs) 197 198# class DashboardViewTest(TestCase): 199# def setUp(self): 200# self.user = make_user("viewer") 201# self.client.force_login(self.user) 202 203# def test_groups_by_status(self): 204# make_ticket(self.user, title="O1", status=Ticket.Status.OPEN) 205# make_ticket(self.user, title="P1", status=Ticket.Status.IN_PROGRESS) 206# make_ticket(self.user, title="C1", status=Ticket.Status.CLOSED) 207 208# resp = self.client.get(reverse("dashboard")) 209# self.assertEqual(resp.status_code, 200) 210# ctx = resp.context 211# self.assertEqual(ctx["open_tickets"].count(), 1) 212# self.assertEqual(ctx["in_progress_tickets"].count(), 1) 213# self.assertEqual(ctx["closed_tickets"].count(), 1) 214 215class TicketAccessTest(TestCase): 216 def setUp(self): 217 self.owner = make_user("owner") 218 self.agent = make_user("agent") 219 self.viewer = make_user("viewer") 220 self.team_rw = make_team("RW", access_non_category_tickets=True, readonly_access=False) 221 self.team_ro = make_team("RO", access_non_category_tickets=True, readonly_access=True) 222 self.team_rw.members.add(self.agent) 223 self.team_ro.members.add(self.viewer) 224 225 def test_can_open_rules(self): 226 t_general = make_ticket(self.owner, category=None, assigned_team=None) 227 t_assigned_team = make_ticket(self.owner, category=None, assigned_team=self.team_rw) 228 t_assigned_user = make_ticket(self.owner, category=None, assigned_to=self.agent) 229 230 self.assertTrue(t_general.can_open(self.agent)) # has access_non_category_tickets via team_rw 231 self.assertTrue(t_assigned_team.can_open(self.agent)) 232 self.assertTrue(t_assigned_user.can_open(self.agent)) 233 234 stranger = make_user("stranger") 235 self.assertFalse(t_assigned_team.can_open(stranger)) 236 237 def test_can_edit_requires_write_access(self): 238 t = make_ticket(self.owner, assigned_team=self.team_rw) 239 self.assertTrue(t.can_edit(self.agent)) # RW team member 240 241 t2 = make_ticket(self.owner, assigned_team=self.team_ro) 242 self.assertFalse(t2.can_edit(self.viewer)) # RO team member 243 244 # Unassigned general with access_non_category_tickets but viewer is read-only, so cannot edit 245 t3 = make_ticket(self.owner, assigned_team=None, category=None) 246 self.assertFalse(t3.can_edit(self.viewer))