FossilRepo

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

Keyboard Shortcuts

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