FossilRepo

fossilrepo / tests / test_forum.py
Blame History Raw 248 lines
1
import pytest
2
from django.contrib.auth.models import User
3
from django.test import Client
4
5
from fossil.forum import ForumPost
6
from fossil.models import FossilRepository
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 forum_thread(fossil_repo_obj, admin_user):
19
"""Create a root forum post (thread starter)."""
20
post = ForumPost.objects.create(
21
repository=fossil_repo_obj,
22
title="Test Thread",
23
body="This is a test thread body.",
24
created_by=admin_user,
25
)
26
post.thread_root = post
27
post.save(update_fields=["thread_root", "updated_at", "version"])
28
return post
29
30
31
@pytest.fixture
32
def forum_reply_post(forum_thread, admin_user):
33
"""Create a reply to the thread."""
34
return ForumPost.objects.create(
35
repository=forum_thread.repository,
36
title="",
37
body="This is a reply.",
38
parent=forum_thread,
39
thread_root=forum_thread,
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
# --- ForumPost Model Tests ---
62
63
64
@pytest.mark.django_db
65
class TestForumPostModel:
66
def test_create_thread(self, forum_thread):
67
assert forum_thread.pk is not None
68
assert str(forum_thread) == "Test Thread"
69
assert forum_thread.is_reply is False
70
assert forum_thread.thread_root == forum_thread
71
72
def test_create_reply(self, forum_reply_post, forum_thread):
73
assert forum_reply_post.pk is not None
74
assert forum_reply_post.is_reply is True
75
assert forum_reply_post.parent == forum_thread
76
assert forum_reply_post.thread_root == forum_thread
77
78
def test_soft_delete(self, forum_thread, admin_user):
79
forum_thread.soft_delete(user=admin_user)
80
assert forum_thread.is_deleted
81
assert ForumPost.objects.filter(pk=forum_thread.pk).count() == 0
82
assert ForumPost.all_objects.filter(pk=forum_thread.pk).count() == 1
83
84
def test_ordering(self, fossil_repo_obj, admin_user):
85
"""Posts are ordered by created_at ascending."""
86
p1 = ForumPost.objects.create(repository=fossil_repo_obj, title="First", body="body", created_by=admin_user)
87
p2 = ForumPost.objects.create(repository=fossil_repo_obj, title="Second", body="body", created_by=admin_user)
88
posts = list(ForumPost.objects.filter(repository=fossil_repo_obj))
89
assert posts[0] == p1
90
assert posts[1] == p2
91
92
def test_reply_str_fallback(self, forum_reply_post):
93
"""Replies with no title use created_by in __str__."""
94
result = str(forum_reply_post)
95
assert "admin" in result
96
97
98
# --- Forum List View Tests ---
99
100
101
@pytest.mark.django_db
102
class TestForumListView:
103
def test_list_empty(self, admin_client, sample_project, fossil_repo_obj):
104
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/")
105
assert response.status_code == 200
106
assert "No forum posts" in response.content.decode()
107
108
def test_list_with_django_thread(self, admin_client, sample_project, forum_thread):
109
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/")
110
assert response.status_code == 200
111
content = response.content.decode()
112
assert "Test Thread" in content
113
assert "local" in content # Django posts show "local" badge
114
115
def test_new_thread_button_visible_for_writers(self, writer_client, sample_project, fossil_repo_obj):
116
response = writer_client.get(f"/projects/{sample_project.slug}/fossil/forum/")
117
assert response.status_code == 200
118
assert "New Thread" in response.content.decode()
119
120
def test_new_thread_button_hidden_for_no_perm(self, no_perm_client, sample_project, fossil_repo_obj):
121
# Make project public so no_perm can read it
122
sample_project.visibility = "public"
123
sample_project.save()
124
response = no_perm_client.get(f"/projects/{sample_project.slug}/fossil/forum/")
125
assert response.status_code == 200
126
assert "New Thread" not in response.content.decode()
127
128
def test_list_denied_for_private_project_no_perm(self, no_perm_client, sample_project):
129
response = no_perm_client.get(f"/projects/{sample_project.slug}/fossil/forum/")
130
assert response.status_code == 403
131
132
133
# --- Forum Create View Tests ---
134
135
136
@pytest.mark.django_db
137
class TestForumCreateView:
138
def test_get_form(self, admin_client, sample_project, fossil_repo_obj):
139
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/create/")
140
assert response.status_code == 200
141
assert "New Thread" in response.content.decode()
142
143
def test_create_thread(self, admin_client, sample_project, fossil_repo_obj):
144
response = admin_client.post(
145
f"/projects/{sample_project.slug}/fossil/forum/create/",
146
{"title": "My New Thread", "body": "Thread body content"},
147
)
148
assert response.status_code == 302
149
post = ForumPost.objects.get(title="My New Thread")
150
assert post.body == "Thread body content"
151
assert post.thread_root == post
152
assert post.parent is None
153
assert post.created_by.username == "admin"
154
155
def test_create_denied_for_no_perm(self, no_perm_client, sample_project):
156
response = no_perm_client.post(
157
f"/projects/{sample_project.slug}/fossil/forum/create/",
158
{"title": "Nope", "body": "Should fail"},
159
)
160
assert response.status_code == 403
161
162
def test_create_denied_for_anon(self, client, sample_project):
163
response = client.get(f"/projects/{sample_project.slug}/fossil/forum/create/")
164
assert response.status_code == 302 # redirect to login
165
166
def test_create_empty_fields_stays_on_form(self, admin_client, sample_project, fossil_repo_obj):
167
response = admin_client.post(
168
f"/projects/{sample_project.slug}/fossil/forum/create/",
169
{"title": "", "body": ""},
170
)
171
assert response.status_code == 200 # stays on form, no redirect
172
173
174
# --- Forum Reply View Tests ---
175
176
177
@pytest.mark.django_db
178
class TestForumReplyView:
179
def test_reply_form(self, admin_client, sample_project, forum_thread):
180
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/reply/")
181
assert response.status_code == 200
182
assert "Reply to:" in response.content.decode()
183
184
def test_post_reply(self, admin_client, sample_project, forum_thread):
185
response = admin_client.post(
186
f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/reply/",
187
{"body": "This is my reply"},
188
)
189
assert response.status_code == 302
190
reply = ForumPost.objects.filter(parent=forum_thread).first()
191
assert reply is not None
192
assert reply.body == "This is my reply"
193
assert reply.thread_root == forum_thread
194
assert reply.is_reply is True
195
196
def test_reply_denied_for_no_perm(self, no_perm_client, sample_project, forum_thread):
197
response = no_perm_client.post(
198
f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/reply/",
199
{"body": "Should fail"},
200
)
201
assert response.status_code == 403
202
203
def test_reply_denied_for_anon(self, client, sample_project, forum_thread):
204
response = client.post(
205
f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/reply/",
206
{"body": "Should redirect"},
207
)
208
assert response.status_code == 302 # redirect to login
209
210
def test_reply_to_nonexistent_post(self, admin_client, sample_project, fossil_repo_obj):
211
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/99999/reply/")
212
assert response.status_code == 404
213
214
215
# --- Forum Thread View Tests ---
216
217
218
@pytest.mark.django_db
219
class TestForumThreadView:
220
def test_django_thread_detail(self, admin_client, sample_project, forum_thread):
221
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/")
222
assert response.status_code == 200
223
content = response.content.decode()
224
assert "Test Thread" in content
225
assert "test thread body" in content.lower()
226
227
def test_django_thread_with_replies(self, admin_client, sample_project, forum_thread, forum_reply_post):
228
response = admin_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/")
229
assert response.status_code == 200
230
content = response.content.decode()
231
assert "This is a reply" in content
232
233
def test_thread_shows_reply_form_for_writers(self, writer_client, sample_project, forum_thread):
234
response = writer_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/")
235
assert response.status_code == 200
236
assert "Post Reply" in response.content.decode()
237
238
def test_thread_hides_reply_form_for_no_perm(self, no_perm_client, sample_project, forum_thread):
239
sample_project.visibility = "public"
240
sample_project.save()
241
response = no_perm_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/")
242
assert response.status_code == 200
243
assert "Post Reply" not in response.content.decode()
244
245
def test_thread_denied_for_private_no_perm(self, no_perm_client, sample_project, forum_thread):
246
response = no_perm_client.get(f"/projects/{sample_project.slug}/fossil/forum/{forum_thread.pk}/")
247
assert response.status_code == 403
248

Keyboard Shortcuts

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