feat: add community contribution ranks

This commit is contained in:
2026-09-29 22:18:57 +03:30
parent 0f6220f72c
commit 77567178ad
46 changed files with 2206 additions and 23 deletions

View File

@@ -0,0 +1,129 @@
import pytest
from django.contrib import admin
from types import SimpleNamespace
from apps.blog.admin import CommentAdmin, PostAdmin
from apps.blog.models import Comment, Like, Post, SavedPost
from apps.gamification.models import ContributionLedger
from apps.gamification.services import summary_for_user
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
def test_publish_coauthor_like_and_save_award_expected_contributions(user_factory):
author = user_factory()
coauthor = user_factory()
actor = user_factory()
post = Post.objects.create(
title="Community story",
content="A real story",
author=author,
status=Post.StatusChoices.PUBLISHED,
)
post.writers.add(coauthor)
Like.objects.create(post=post, user=actor)
SavedPost.objects.create(post=post, user=actor)
assert summary_for_user(author)["points_total"] == 22
assert summary_for_user(coauthor)["points_total"] == 22
assert summary_for_user(actor)["points_total"] == 2
assert summary_for_user(actor)["activity_counts"]["likes"] == 1
assert summary_for_user(coauthor)["activity_counts"]["likes"] == 1
post.writers.remove(coauthor)
post.writers.remove(author)
assert summary_for_user(author)["points_total"] == 22
assert summary_for_user(coauthor)["points_total"] == 0
assert summary_for_user(actor)["points_total"] == 2
def test_self_like_and_save_count_once_each_and_only_like_advances_badge_ladder(user_factory):
member = user_factory()
post = Post.objects.create(
title="My post",
content="My content",
author=member,
status=Post.StatusChoices.PUBLISHED,
)
Like.objects.create(post=post, user=member)
SavedPost.objects.create(post=post, user=member)
summary = summary_for_user(member)
assert summary["points_total"] == 22
assert summary["activity_counts"]["likes"] == 1
assert not any(badge["category"] == "save" for badge in summary["badges"])
def test_approved_comments_and_replies_score_and_hiding_reverses_them(user_factory):
author = user_factory()
commenter = user_factory()
post = Post.objects.create(
title="Discussion",
content="Discuss this",
author=author,
status=Post.StatusChoices.PUBLISHED,
)
comment = Comment.objects.create(post=post, author=commenter, content="A comment")
reply = Comment.objects.create(post=post, author=author, content="A reply", parent=comment)
assert summary_for_user(commenter)["points_total"] == 5
assert summary_for_user(author)["points_total"] == 25
comment.hide(user=author, note="moderated")
assert summary_for_user(commenter)["points_total"] == 0
assert summary_for_user(author)["points_total"] == 20
assert summary_for_user(commenter)["activity_counts"]["comments"] == 0
assert ContributionLedger.objects.filter(user=commenter).count() == 2
assert ContributionLedger.objects.filter(user=author, source_type="blog_comment", source_id=str(reply.pk)).count() == 2
def test_removing_like_and_unpublishing_post_reverse_only_valid_points(user_factory):
author = user_factory()
actor = user_factory()
post = Post.objects.create(
title="Published then withdrawn",
content="Content",
author=author,
status=Post.StatusChoices.PUBLISHED,
)
like = Like.objects.create(post=post, user=actor)
SavedPost.objects.create(post=post, user=actor)
assert summary_for_user(actor)["points_total"] == 2
like.delete()
assert summary_for_user(actor)["points_total"] == 1
assert summary_for_user(author)["points_total"] == 21
post.status = Post.StatusChoices.ARCHIVED
post.save(update_fields=["status", "updated_at"])
assert summary_for_user(author)["points_total"] == 0
assert summary_for_user(actor)["points_total"] == 0
def test_blog_admin_publish_and_approve_actions_flow_through_scoring(user_factory, mocker):
editor = user_factory(is_staff=True)
author = user_factory()
commenter = user_factory()
draft = Post.objects.create(title="Draft", content="Content", author=author)
post_admin = PostAdmin(Post, admin.site)
comment_admin = CommentAdmin(Comment, admin.site)
mocker.patch.object(PostAdmin, "message_user")
mocker.patch.object(CommentAdmin, "message_user")
post_admin.make_published(SimpleNamespace(user=editor), Post.objects.filter(pk=draft.pk))
draft.refresh_from_db()
pending_comment = Comment.objects.create(
post=draft,
author=commenter,
content="Awaiting approval",
is_approved=False,
)
assert summary_for_user(author)["points_total"] == 20
assert summary_for_user(commenter)["points_total"] == 0
comment_admin.approve_comments(SimpleNamespace(user=editor), Comment.objects.filter(pk=pending_comment.pk))
assert summary_for_user(commenter)["points_total"] == 5