FossilRepo

fossilrepo / tests / test_ticket_fields.py
Blame History Raw 238 lines
1
import pytest
2
from django.contrib.auth.models import User
3
from django.test import Client
4
5
from fossil.models import FossilRepository
6
from fossil.ticket_fields import TicketFieldDefinition
7
from organization.models import Team
8
from projects.models import ProjectTeam
9
10
11
@pytest.fixture
12
def fossil_repo_obj(sample_project):
13
"""Return the auto-created FossilRepository for sample_project."""
14
return FossilRepository.objects.get(project=sample_project, deleted_at__isnull=True)
15
16
17
@pytest.fixture
18
def text_field(fossil_repo_obj, admin_user):
19
return TicketFieldDefinition.objects.create(
20
repository=fossil_repo_obj,
21
name="component",
22
label="Component",
23
field_type="text",
24
is_required=False,
25
sort_order=1,
26
created_by=admin_user,
27
)
28
29
30
@pytest.fixture
31
def select_field(fossil_repo_obj, admin_user):
32
return TicketFieldDefinition.objects.create(
33
repository=fossil_repo_obj,
34
name="platform",
35
label="Platform",
36
field_type="select",
37
choices="Linux\nWindows\nmacOS",
38
is_required=True,
39
sort_order=2,
40
created_by=admin_user,
41
)
42
43
44
@pytest.fixture
45
def writer_user(db, admin_user, sample_project):
46
"""User with write access but not admin."""
47
writer = User.objects.create_user(username="writer", password="testpass123")
48
team = Team.objects.create(name="Writers", organization=sample_project.organization, created_by=admin_user)
49
team.members.add(writer)
50
ProjectTeam.objects.create(project=sample_project, team=team, role="write", created_by=admin_user)
51
return writer
52
53
54
@pytest.fixture
55
def writer_client(writer_user):
56
client = Client()
57
client.login(username="writer", password="testpass123")
58
return client
59
60
61
# --- Model Tests ---
62
63
64
@pytest.mark.django_db
65
class TestTicketFieldDefinitionModel:
66
def test_create_field(self, text_field):
67
assert text_field.pk is not None
68
assert str(text_field) == "Component (component)"
69
70
def test_choices_list(self, select_field):
71
assert select_field.choices_list == ["Linux", "Windows", "macOS"]
72
73
def test_choices_list_empty(self, text_field):
74
assert text_field.choices_list == []
75
76
def test_soft_delete(self, text_field, admin_user):
77
text_field.soft_delete(user=admin_user)
78
assert text_field.is_deleted
79
assert TicketFieldDefinition.objects.filter(pk=text_field.pk).count() == 0
80
assert TicketFieldDefinition.all_objects.filter(pk=text_field.pk).count() == 1
81
82
def test_ordering(self, text_field, select_field):
83
fields = list(TicketFieldDefinition.objects.filter(repository=text_field.repository))
84
assert fields[0] == text_field # sort_order=1
85
assert fields[1] == select_field # sort_order=2
86
87
def test_unique_name_per_repo(self, fossil_repo_obj, admin_user, text_field):
88
from django.db import IntegrityError
89
90
with pytest.raises(IntegrityError):
91
TicketFieldDefinition.objects.create(
92
repository=fossil_repo_obj,
93
name="component",
94
label="Duplicate Component",
95
created_by=admin_user,
96
)
97
98
99
# --- List View Tests ---
100
101
102
@pytest.mark.django_db
103
class TestTicketFieldsListView:
104
def test_list_fields(self, admin_client, sample_project, text_field, select_field):
105
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/")
106
assert response.status_code == 200
107
content = response.content.decode()
108
assert "Component" in content
109
assert "Platform" in content
110
111
def test_list_empty(self, admin_client, sample_project, fossil_repo_obj):
112
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/")
113
assert response.status_code == 200
114
assert "No custom ticket fields defined" in response.content.decode()
115
116
def test_list_denied_for_writer(self, writer_client, sample_project, text_field):
117
"""Custom field management requires admin."""
118
response = writer_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/")
119
assert response.status_code == 403
120
121
def test_list_denied_for_anon(self, client, sample_project):
122
response = client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/")
123
assert response.status_code == 302 # redirect to login
124
125
126
# --- Create View Tests ---
127
128
129
@pytest.mark.django_db
130
class TestTicketFieldCreateView:
131
def test_get_form(self, admin_client, sample_project, fossil_repo_obj):
132
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/create/")
133
assert response.status_code == 200
134
assert "Add Custom Ticket Field" in response.content.decode()
135
136
def test_create_field(self, admin_client, sample_project, fossil_repo_obj):
137
response = admin_client.post(
138
f"/projects/{sample_project.slug}/fossil/tickets/fields/create/",
139
{
140
"name": "affected_version",
141
"label": "Affected Version",
142
"field_type": "text",
143
"sort_order": "5",
144
},
145
)
146
assert response.status_code == 302
147
field = TicketFieldDefinition.objects.get(name="affected_version")
148
assert field.label == "Affected Version"
149
assert field.field_type == "text"
150
assert field.sort_order == 5
151
assert field.is_required is False
152
153
def test_create_select_field_with_choices(self, admin_client, sample_project, fossil_repo_obj):
154
response = admin_client.post(
155
f"/projects/{sample_project.slug}/fossil/tickets/fields/create/",
156
{
157
"name": "env",
158
"label": "Environment",
159
"field_type": "select",
160
"choices": "dev\nstaging\nprod",
161
"is_required": "on",
162
"sort_order": "0",
163
},
164
)
165
assert response.status_code == 302
166
field = TicketFieldDefinition.objects.get(name="env")
167
assert field.choices_list == ["dev", "staging", "prod"]
168
assert field.is_required is True
169
170
def test_create_duplicate_name_rejected(self, admin_client, sample_project, text_field):
171
response = admin_client.post(
172
f"/projects/{sample_project.slug}/fossil/tickets/fields/create/",
173
{"name": "component", "label": "Another Component", "field_type": "text", "sort_order": "0"},
174
)
175
assert response.status_code == 200 # re-renders form
176
assert TicketFieldDefinition.objects.filter(name="component").count() == 1
177
178
def test_create_denied_for_writer(self, writer_client, sample_project):
179
response = writer_client.post(
180
f"/projects/{sample_project.slug}/fossil/tickets/fields/create/",
181
{"name": "evil", "label": "Evil", "field_type": "text", "sort_order": "0"},
182
)
183
assert response.status_code == 403
184
185
186
# --- Edit View Tests ---
187
188
189
@pytest.mark.django_db
190
class TestTicketFieldEditView:
191
def test_get_edit_form(self, admin_client, sample_project, text_field):
192
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/edit/")
193
assert response.status_code == 200
194
content = response.content.decode()
195
assert "component" in content
196
assert "Component" in content
197
198
def test_edit_field(self, admin_client, sample_project, text_field):
199
response = admin_client.post(
200
f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/edit/",
201
{"name": "component", "label": "SW Component", "field_type": "text", "sort_order": "10"},
202
)
203
assert response.status_code == 302
204
text_field.refresh_from_db()
205
assert text_field.label == "SW Component"
206
assert text_field.sort_order == 10
207
208
def test_edit_denied_for_writer(self, writer_client, sample_project, text_field):
209
response = writer_client.post(
210
f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/edit/",
211
{"name": "component", "label": "Hacked", "field_type": "text", "sort_order": "0"},
212
)
213
assert response.status_code == 403
214
215
def test_edit_nonexistent(self, admin_client, sample_project, fossil_repo_obj):
216
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/99999/edit/")
217
assert response.status_code == 404
218
219
220
# --- Delete View Tests ---
221
222
223
@pytest.mark.django_db
224
class TestTicketFieldDeleteView:
225
def test_delete_field(self, admin_client, sample_project, text_field):
226
response = admin_client.post(f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/delete/")
227
assert response.status_code == 302
228
text_field.refresh_from_db()
229
assert text_field.is_deleted
230
231
def test_delete_get_redirects(self, admin_client, sample_project, text_field):
232
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/delete/")
233
assert response.status_code == 302
234
235
def test_delete_denied_for_writer(self, writer_client, sample_project, text_field):
236
response = writer_client.post(f"/projects/{sample_project.slug}/fossil/tickets/fields/{text_field.pk}/delete/")
237
assert response.status_code == 403
238

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button