Files
chunyu 37cad3947d feat(C-06):关注feed+课程聚合修复
文章列表新增author_user_id/is_following与feed=following过滤;课程students_count改真实在学人数去重统计、新增total_minutes(不可解析不伪造);author_name走sync_to_async防ASGI同步阻塞。
2026-09-15 15:20:23 +08:00

92 lines
4.2 KiB
Python

"""C-06 签到幂等 + 学习聚合去重回归。"""
from django.test import TestCase
from rest_framework.test import APIClient
from learn.models import Chapter, ChapterRead, Course
from user.models import DailyCheckin, FUser
class CheckinIdempotentTest(TestCase):
def setUp(self):
self.client = APIClient()
self.u = FUser.objects.create_user(username='c06sa', password='x12345678')
def test_double_checkin_second_400_no_double_points(self):
self.client.force_authenticate(user=self.u)
r1 = self.client.post('/user/wallet/checkin/')
self.assertEqual(r1.status_code, 200)
earned = r1.json()['data']['earned']
self.u.refresh_from_db()
p1 = self.u.points
self.assertGreaterEqual(p1, earned)
r2 = self.client.post('/user/wallet/checkin/')
self.assertEqual(r2.status_code, 400)
self.u.refresh_from_db()
self.assertEqual(self.u.points, p1)
self.assertEqual(DailyCheckin.objects.filter(user=self.u).count(), 1)
def test_checkin_status_reflects_signed(self):
self.client.force_authenticate(user=self.u)
self.client.post('/user/wallet/checkin/')
r = self.client.get('/user/wallet/checkin/status/')
self.assertEqual(r.status_code, 200)
self.assertTrue(r.json()['data']['signed_today'])
def test_anonymous_checkin_401(self):
r = self.client.post('/user/wallet/checkin/')
self.assertEqual(r.status_code, 401)
class LearnersCountTest(TestCase):
def setUp(self):
self.client = APIClient()
self.u1 = FUser.objects.create_user(username='c06la', password='x12345678')
self.u2 = FUser.objects.create_user(username='c06lb', password='x12345678')
self.course = Course.objects.create(title='C06聚合课', author=self.u1, status='published')
self.ch1 = Chapter.objects.create(course=self.course, title='第一章')
self.ch2 = Chapter.objects.create(course=self.course, title='第二章')
def test_cross_read_dedup_two_users(self):
ChapterRead.objects.create(user=self.u1, chapter=self.ch1, course=self.course)
ChapterRead.objects.create(user=self.u1, chapter=self.ch2, course=self.course)
ChapterRead.objects.create(user=self.u2, chapter=self.ch1, course=self.course)
r = self.client.get('/learn/courses/')
self.assertEqual(r.status_code, 200)
items = {x['title']: x for x in r.json()['data']}
self.assertEqual(items['C06聚合课']['students_count'], 2)
def test_no_read_zero(self):
r = self.client.get('/learn/courses/')
self.assertEqual(r.status_code, 200)
items = {x['title']: x for x in r.json()['data']}
self.assertEqual(items['C06聚合课']['students_count'], 0)
def test_detail_students_count_matches(self):
ChapterRead.objects.create(user=self.u1, chapter=self.ch1, course=self.course)
r = self.client.get(f'/learn/courses/{self.course.id}/')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['data']['students_count'], 1)
class TotalMinutesTest(TestCase):
def setUp(self):
self.client = APIClient()
self.u = FUser.objects.create_user(username='c06ma', password='x12345678')
def test_sums_parsable_durations(self):
course = Course.objects.create(title='C06时长课', author=self.u, status='published')
Chapter.objects.create(course=course, title='C1', duration='30分钟')
Chapter.objects.create(course=course, title='C2', duration='45分钟')
r = self.client.get('/learn/courses/')
self.assertEqual(r.status_code, 200)
items = {x['title']: x for x in r.json()['data']}
self.assertEqual(items['C06时长课']['total_minutes'], 75)
def test_unparsable_returns_none(self):
course = Course.objects.create(title='C06无时长课', author=self.u, status='published')
Chapter.objects.create(course=course, title='C1', duration='')
Chapter.objects.create(course=course, title='C2', duration='未知')
r = self.client.get(f'/learn/courses/{course.id}/')
self.assertEqual(r.status_code, 200)
self.assertIsNone(r.json()['data']['total_minutes'])