"""C-02 工具热榜聚合用例:总榜/周榜窗口/匿名归因/recent 权限/兼容参数。""" from datetime import timedelta from django.test import TestCase from django.utils import timezone from rest_framework.test import APIClient from tool.models import Tool, ToolUsageRecord from user.models import FUser class ToolTopTest(TestCase): def setUp(self): self.client = APIClient() self.a = Tool.objects.create(name='A', url_path='/utility/a-tool', usage_count=10) self.b = Tool.objects.create(name='B', url_path='/utility/b-tool', usage_count=5) self.c = Tool.objects.create(name='C', url_path='/utility/c-tool', usage_count=1) self.u = FUser.objects.create_user(username='c02u', password='x12345678') def test_total_rank_order(self): r = self.client.get('/tool/top/', {'range': 'all', 'limit': 3}) self.assertEqual(r.status_code, 200) data = r.json()['data'] self.assertEqual([x['id'] for x in data], [self.a.id, self.b.id, self.c.id]) self.assertEqual([x['rank'] for x in data], [1, 2, 3]) for x in data: self.assertIn('usage_count', x) self.assertIn('weekly_count', x) def test_period_alias_total(self): r = self.client.get('/tool/top/', {'period': 'total', 'limit': 3}) self.assertEqual(r.status_code, 200) self.assertEqual([x['rank'] for x in r.json()['data']], [1, 2, 3]) def test_weekly_window_aggregation(self): now = timezone.now() for _ in range(3): ToolUsageRecord.objects.create(tool=self.c, user=self.u) old = ToolUsageRecord.objects.create(tool=self.a, user=self.u) old.created_at = now - timedelta(days=8) old.save(update_fields=['created_at']) r = self.client.get('/tool/top/', {'range': 'week', 'limit': 3}) self.assertEqual(r.status_code, 200) data = r.json()['data'] # C 近7天3次应排第一 self.assertEqual(data[0]['id'], self.c.id) self.assertEqual(data[0]['weekly_count'], 3) def test_weekly_boundary_7days(self): now = timezone.now() edge = ToolUsageRecord.objects.create(tool=self.b) edge.created_at = now - timedelta(days=7) + timedelta(seconds=60) edge.save(update_fields=['created_at']) out = ToolUsageRecord.objects.create(tool=self.b) out.created_at = now - timedelta(days=7, seconds=60) out.save(update_fields=['created_at']) r = self.client.get('/tool/top/', {'range': 'week', 'limit': 10}) data = {x['id']: x['weekly_count'] for x in r.json()['data']} self.assertEqual(data[self.b.id], 1) def test_weekly_empty_fallback_to_total(self): r = self.client.get('/tool/top/', {'range': 'week', 'limit': 3}) self.assertEqual(r.status_code, 200) self.assertEqual([x['id'] for x in r.json()['data']], [self.a.id, self.b.id, self.c.id]) def test_increment_writes_record_anonymous(self): r = self.client.post(f'/tool/usage/{self.a.id}/increment/') self.assertEqual(r.status_code, 200) self.assertEqual(ToolUsageRecord.objects.filter(tool=self.a).count(), 1) self.a.refresh_from_db() self.assertEqual(self.a.usage_count, 11) def test_increment_writes_record_login(self): self.client.force_authenticate(user=self.u) r = self.client.post(f'/tool/usage/{self.b.id}/increment/') self.assertEqual(r.status_code, 200) rec = ToolUsageRecord.objects.filter(tool=self.b).order_by('-id').first() self.assertEqual(rec.user_id, self.u.id) def test_recent_requires_login(self): r = self.client.get('/tool/recent/') self.assertIn(r.status_code, (401, 403)) def test_recent_dedup_order(self): ToolUsageRecord.objects.create(tool=self.a, user=self.u) ToolUsageRecord.objects.create(tool=self.b, user=self.u) ToolUsageRecord.objects.create(tool=self.a, user=self.u) self.client.force_authenticate(user=self.u) r = self.client.get('/tool/recent/', {'limit': 10}) self.assertEqual(r.status_code, 200) ids = [x['id'] for x in r.json()['data']] self.assertEqual(ids, [self.a.id, self.b.id])