Free and open source ticket system written in python
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))