senior's post-demo changes: parent report in the student's own account, Jalali birth date, invoices, math-only tutor, priced quotas
- remove the family app from the root (separate parent accounts, invite codes, parental settings); progress.0006 drops its tables, permissions and migration history, and Profile.parent_phone, the two family notification types and ?userId= on /api/progress/nodes go with it - GET /api/user/weekly-report: last 7 days next to the 7 before (estimated minutes, questions, accuracy, islands, tutor questions) for the "parent report" page - profile birthDate in Jalali (accounts/jalali.py), ages 5-99, birthYear derived from it - GET /api/payment/invoices; orders now snapshot list price and plan title - tutor answers only math: server pre-filter, scope rule in the prompt (tutor-v11), refusal marker replaced with fixed text; self-harm gets a help line instead - streamed answers now record token usage (it was always 0) - daily tutor quota: AI_DAILY_QUOTA_FREE (default 3) vs Plan.ai_daily_quota - delete_users management command for wiping test accounts across every app - docs/ai-cost-and-guardrails.md: measured cost per question and guardrail results Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
53
ai_tutor/tests_quota.py
Normal file
53
ai_tutor/tests_quota.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
سقفِ روزانهی دستیار: رایگان در برابرِ اشتراکی (۲۰۲۶-۱۰-۰۱). تا آن روز همه ۱۵ سوال
|
||||
داشتند، یعنی گرانترین بخشِ اپ برای کاربرِ بیاشتراک هم کامل باز بود.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils import timezone
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from accounts.authentication import issue_access_token
|
||||
from accounts.models import User
|
||||
from subscription.models import Plan, Subscription
|
||||
from subscription.services import ai_daily_quota
|
||||
|
||||
|
||||
@override_settings(AI_DAILY_QUOTA_FREE=2)
|
||||
class DailyQuotaTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.user = User.objects.create(phone='09120006001', is_guest=False)
|
||||
self.client = APIClient()
|
||||
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
|
||||
|
||||
def ask(self):
|
||||
return self.client.post('/api/ai/ask', {'userQuestion': '۲ + ۲ چند میشه؟'}, format='json')
|
||||
|
||||
def test_free_user_gets_the_free_quota_and_a_hint_to_subscribe(self):
|
||||
self.assertEqual([self.ask().status_code for _ in range(2)], [200, 200])
|
||||
blocked = self.ask()
|
||||
self.assertEqual(blocked.status_code, 429)
|
||||
self.assertIn('اشتراک', blocked.json()['detail'])
|
||||
|
||||
def test_remaining_quota_follows_the_users_own_limit(self):
|
||||
self.assertEqual(self.ask().json()['remainingQuota'], 1)
|
||||
|
||||
def test_subscriber_gets_the_plans_quota(self):
|
||||
plan = Plan.objects.create(title='ماهانه', price=99000, duration_days=30, ai_daily_quota=4)
|
||||
Subscription.objects.create(user_id=self.user.id, plan=plan, status='active',
|
||||
expires_at=timezone.now() + timedelta(days=30))
|
||||
self.assertEqual(ai_daily_quota(self.user.id), 4)
|
||||
self.assertEqual([self.ask().status_code for _ in range(5)], [200, 200, 200, 200, 429])
|
||||
|
||||
def test_expired_subscription_falls_back_to_free(self):
|
||||
plan = Plan.objects.create(title='ماهانه', price=99000, duration_days=30, ai_daily_quota=40)
|
||||
Subscription.objects.create(user_id=self.user.id, plan=plan, status='active',
|
||||
expires_at=timezone.now() - timedelta(days=1))
|
||||
self.assertEqual(ai_daily_quota(self.user.id), 2)
|
||||
|
||||
def test_gifted_subscription_without_plan_gets_the_default(self):
|
||||
Subscription.objects.create(user_id=self.user.id, plan=None, status='active', expires_at=None)
|
||||
self.assertEqual(ai_daily_quota(self.user.id), 15)
|
||||
Reference in New Issue
Block a user