FossilRepo

fossilrepo / tests / test_user_management.py
Blame History Raw 340 lines
1
import pytest
2
from django.contrib.auth.models import User
3
from django.test import Client
4
from django.urls import reverse
5
6
from organization.models import OrganizationMember
7
8
9
@pytest.fixture
10
def org_admin_user(db, org):
11
"""A non-superuser who has ORGANIZATION_CHANGE permission via group."""
12
from django.contrib.auth.models import Group, Permission
13
14
user = User.objects.create_user(username="orgadmin", email="[email protected]", password="testpass123")
15
group, _ = Group.objects.get_or_create(name="OrgAdmins")
16
change_perm = Permission.objects.get(content_type__app_label="organization", codename="change_organization")
17
view_perm = Permission.objects.get(content_type__app_label="organization", codename="view_organizationmember")
18
group.permissions.add(change_perm, view_perm)
19
user.groups.add(group)
20
OrganizationMember.objects.create(member=user, organization=org)
21
return user
22
23
24
@pytest.fixture
25
def org_admin_client(org_admin_user):
26
c = Client()
27
c.login(username="orgadmin", password="testpass123")
28
return c
29
30
31
@pytest.fixture
32
def target_user(db, org, admin_user):
33
"""A regular user who is an org member, to be the target of management actions."""
34
user = User.objects.create_user(
35
username="targetuser", email="[email protected]", password="testpass123", first_name="Target", last_name="User"
36
)
37
OrganizationMember.objects.create(member=user, organization=org, created_by=admin_user)
38
return user
39
40
41
# --- user_create ---
42
43
44
@pytest.mark.django_db
45
class TestUserCreate:
46
def test_get_form(self, admin_client):
47
response = admin_client.get(reverse("organization:user_create"))
48
assert response.status_code == 200
49
assert "New User" in response.content.decode()
50
51
def test_create_user(self, admin_client, org):
52
response = admin_client.post(
53
reverse("organization:user_create"),
54
{
55
"username": "newuser",
56
"email": "[email protected]",
57
"first_name": "New",
58
"last_name": "User",
59
"password1": "Str0ng!Pass99",
60
"password2": "Str0ng!Pass99",
61
},
62
)
63
assert response.status_code == 302
64
user = User.objects.get(username="newuser")
65
assert user.email == "[email protected]"
66
assert user.first_name == "New"
67
assert user.check_password("Str0ng!Pass99")
68
# Verify auto-added as org member
69
assert OrganizationMember.objects.filter(member=user, organization=org, deleted_at__isnull=True).exists()
70
71
def test_create_password_mismatch(self, admin_client):
72
response = admin_client.post(
73
reverse("organization:user_create"),
74
{
75
"username": "baduser",
76
"email": "[email protected]",
77
"password1": "Str0ng!Pass99",
78
"password2": "differentpass",
79
},
80
)
81
assert response.status_code == 200
82
assert not User.objects.filter(username="baduser").exists()
83
84
def test_create_duplicate_username(self, admin_client, target_user):
85
response = admin_client.post(
86
reverse("organization:user_create"),
87
{
88
"username": "targetuser",
89
"email": "[email protected]",
90
"password1": "Str0ng!Pass99",
91
"password2": "Str0ng!Pass99",
92
},
93
)
94
assert response.status_code == 200 # Form re-rendered with errors
95
96
def test_create_denied_for_viewer(self, viewer_client):
97
response = viewer_client.get(reverse("organization:user_create"))
98
assert response.status_code == 403
99
100
def test_create_denied_for_anon(self, client):
101
response = client.get(reverse("organization:user_create"))
102
assert response.status_code == 302 # Redirect to login
103
104
def test_create_allowed_for_org_admin(self, org_admin_client, org):
105
response = org_admin_client.post(
106
reverse("organization:user_create"),
107
{
108
"username": "orgcreated",
109
"email": "[email protected]",
110
"password1": "Str0ng!Pass99",
111
"password2": "Str0ng!Pass99",
112
},
113
)
114
assert response.status_code == 302
115
assert User.objects.filter(username="orgcreated").exists()
116
117
118
# --- user_detail ---
119
120
121
@pytest.mark.django_db
122
class TestUserDetail:
123
def test_view_user(self, admin_client, target_user):
124
response = admin_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"}))
125
assert response.status_code == 200
126
content = response.content.decode()
127
assert "targetuser" in content
128
assert "[email protected]" in content
129
assert "Target User" in content
130
131
def test_view_shows_teams(self, admin_client, target_user, sample_team):
132
sample_team.members.add(target_user)
133
response = admin_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"}))
134
assert response.status_code == 200
135
assert "Core Devs" in response.content.decode()
136
137
def test_view_denied_for_no_perm(self, no_perm_client, target_user):
138
response = no_perm_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"}))
139
assert response.status_code == 403
140
141
def test_view_allowed_for_viewer(self, viewer_client, target_user):
142
response = viewer_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"}))
143
assert response.status_code == 200
144
145
def test_view_404_for_missing_user(self, admin_client):
146
response = admin_client.get(reverse("organization:user_detail", kwargs={"username": "nonexistent"}))
147
assert response.status_code == 404
148
149
150
# --- user_edit ---
151
152
153
@pytest.mark.django_db
154
class TestUserEdit:
155
def test_get_edit_form(self, admin_client, target_user):
156
response = admin_client.get(reverse("organization:user_edit", kwargs={"username": "targetuser"}))
157
assert response.status_code == 200
158
content = response.content.decode()
159
assert "Edit targetuser" in content
160
161
def test_edit_user(self, admin_client, target_user):
162
response = admin_client.post(
163
reverse("organization:user_edit", kwargs={"username": "targetuser"}),
164
{
165
"email": "[email protected]",
166
"first_name": "Updated",
167
"last_name": "Name",
168
"is_active": "on",
169
},
170
)
171
assert response.status_code == 302
172
target_user.refresh_from_db()
173
assert target_user.email == "[email protected]"
174
assert target_user.first_name == "Updated"
175
176
def test_edit_deactivate_user(self, admin_client, target_user):
177
response = admin_client.post(
178
reverse("organization:user_edit", kwargs={"username": "targetuser"}),
179
{
180
"email": "[email protected]",
181
"first_name": "Target",
182
"last_name": "User",
183
# is_active omitted = False for checkbox
184
},
185
)
186
assert response.status_code == 302
187
target_user.refresh_from_db()
188
assert target_user.is_active is False
189
190
def test_edit_self_cannot_deactivate(self, admin_client, admin_user):
191
"""Superuser editing themselves should not be able to toggle is_active."""
192
response = admin_client.post(
193
reverse("organization:user_edit", kwargs={"username": "admin"}),
194
{
195
"email": "[email protected]",
196
"first_name": "Admin",
197
"last_name": "",
198
# is_active omitted -- but field is disabled so value comes from instance
199
},
200
)
201
assert response.status_code == 302
202
admin_user.refresh_from_db()
203
# Should still be active because the field was disabled
204
assert admin_user.is_active is True
205
206
def test_edit_denied_for_viewer(self, viewer_client, target_user):
207
response = viewer_client.get(reverse("organization:user_edit", kwargs={"username": "targetuser"}))
208
assert response.status_code == 403
209
210
def test_edit_denied_for_no_perm(self, no_perm_client, target_user):
211
response = no_perm_client.get(reverse("organization:user_edit", kwargs={"username": "targetuser"}))
212
assert response.status_code == 403
213
214
def test_edit_allowed_for_org_admin(self, org_admin_client, target_user):
215
response = org_admin_client.post(
216
reverse("organization:user_edit", kwargs={"username": "targetuser"}),
217
{
218
"email": "[email protected]",
219
"first_name": "Org",
220
"last_name": "Edited",
221
"is_active": "on",
222
},
223
)
224
assert response.status_code == 302
225
target_user.refresh_from_db()
226
assert target_user.email == "[email protected]"
227
228
229
# --- user_password ---
230
231
232
@pytest.mark.django_db
233
class TestUserPassword:
234
def test_get_password_form(self, admin_client, target_user):
235
response = admin_client.get(reverse("organization:user_password", kwargs={"username": "targetuser"}))
236
assert response.status_code == 200
237
assert "Change Password" in response.content.decode()
238
239
def test_change_password(self, admin_client, target_user):
240
response = admin_client.post(
241
reverse("organization:user_password", kwargs={"username": "targetuser"}),
242
{
243
"new_password1": "NewStr0ng!Pass99",
244
"new_password2": "NewStr0ng!Pass99",
245
},
246
)
247
assert response.status_code == 302
248
target_user.refresh_from_db()
249
assert target_user.check_password("NewStr0ng!Pass99")
250
251
def test_change_password_mismatch(self, admin_client, target_user):
252
response = admin_client.post(
253
reverse("organization:user_password", kwargs={"username": "targetuser"}),
254
{
255
"new_password1": "NewStr0ng!Pass99",
256
"new_password2": "different",
257
},
258
)
259
assert response.status_code == 200 # Form re-rendered
260
target_user.refresh_from_db()
261
assert target_user.check_password("testpass123") # Unchanged
262
263
def test_change_own_password(self, target_user):
264
"""A regular user (no special perms) can change their own password."""
265
c = Client()
266
c.login(username="targetuser", password="testpass123")
267
response = c.post(
268
reverse("organization:user_password", kwargs={"username": "targetuser"}),
269
{
270
"new_password1": "MyNewStr0ng!Pass99",
271
"new_password2": "MyNewStr0ng!Pass99",
272
},
273
)
274
assert response.status_code == 302
275
target_user.refresh_from_db()
276
assert target_user.check_password("MyNewStr0ng!Pass99")
277
278
def test_change_other_password_denied_for_no_perm(self, no_perm_client, target_user):
279
response = no_perm_client.post(
280
reverse("organization:user_password", kwargs={"username": "targetuser"}),
281
{
282
"new_password1": "HackedStr0ng!Pass99",
283
"new_password2": "HackedStr0ng!Pass99",
284
},
285
)
286
assert response.status_code == 403
287
target_user.refresh_from_db()
288
assert target_user.check_password("testpass123") # Unchanged
289
290
def test_change_other_password_denied_for_viewer(self, viewer_client, target_user):
291
response = viewer_client.post(
292
reverse("organization:user_password", kwargs={"username": "targetuser"}),
293
{
294
"new_password1": "HackedStr0ng!Pass99",
295
"new_password2": "HackedStr0ng!Pass99",
296
},
297
)
298
assert response.status_code == 403
299
300
def test_change_password_denied_for_anon(self, client, target_user):
301
response = client.get(reverse("organization:user_password", kwargs={"username": "targetuser"}))
302
assert response.status_code == 302 # Redirect to login
303
304
def test_change_other_password_allowed_for_org_admin(self, org_admin_client, target_user):
305
response = org_admin_client.post(
306
reverse("organization:user_password", kwargs={"username": "targetuser"}),
307
{
308
"new_password1": "OrgAdminSet!Pass99",
309
"new_password2": "OrgAdminSet!Pass99",
310
},
311
)
312
assert response.status_code == 302
313
target_user.refresh_from_db()
314
assert target_user.check_password("OrgAdminSet!Pass99")
315
316
317
# --- member_list updates ---
318
319
320
@pytest.mark.django_db
321
class TestMemberListUpdates:
322
def test_usernames_are_clickable_links(self, admin_client, org, admin_user):
323
response = admin_client.get(reverse("organization:members"))
324
assert response.status_code == 200
325
content = response.content.decode()
326
expected_url = reverse("organization:user_detail", kwargs={"username": admin_user.username})
327
assert expected_url in content
328
329
def test_create_user_button_visible_for_superuser(self, admin_client, org):
330
response = admin_client.get(reverse("organization:members"))
331
assert response.status_code == 200
332
content = response.content.decode()
333
assert "Create User" in content
334
335
def test_create_user_button_hidden_for_viewer(self, viewer_client, org):
336
response = viewer_client.get(reverse("organization:members"))
337
assert response.status_code == 200
338
content = response.content.decode()
339
assert "Create User" not in content
340

Keyboard Shortcuts

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