Files
MathApp/accounts/tests_birth_date.py
Zareian 39247f6d53 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>
2026-10-02 02:38:43 +03:30

91 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
تاریخِ تولدِ شمسی در پروفایل (۲۰۲۶-۱۰-۰۱). اپ روز/ماه/سالِ شمسی می‌فرستد؛ سرور
میلادی ذخیره می‌کند و birthYear را از آن می‌سازد. راه‌های شکستن: تاریخِ ناموجود
(۳۰ اسفندِ غیرکبیسه)، ارقامِ فارسی، سنِ خارج از بازه، و جدا افتادنِ سال از تاریخ.
"""
from datetime import date
from django.core.cache import cache
from django.test import SimpleTestCase, TestCase
from rest_framework.test import APIClient
from accounts import jalali
from accounts.authentication import issue_access_token
from accounts.models import Profile, User
class JalaliConversionTests(SimpleTestCase):
KNOWN = [
((1405, 1, 1), date(2026, 3, 21)),
((1403, 12, 30), date(2025, 3, 20)), # اسفندِ کبیسه
((1404, 12, 29), date(2026, 3, 20)), # آخرین روزِ سالِ غیرکبیسه
((1385, 4, 12), date(2006, 7, 3)),
((1367, 10, 11), date(1989, 1, 1)),
]
def test_known_dates_both_ways(self):
for jalali_date, gregorian in self.KNOWN:
self.assertEqual(jalali.to_gregorian(*jalali_date), gregorian)
self.assertEqual(jalali.from_gregorian(gregorian), jalali_date)
def test_thirtieth_of_esfand_exists_only_in_leap_years(self):
self.assertTrue(jalali.is_leap(1403))
self.assertFalse(jalali.is_leap(1404))
with self.assertRaises(ValueError):
jalali.to_gregorian(1404, 12, 30)
def test_every_day_of_a_century_round_trips(self):
start = date(1940, 1, 1).toordinal()
for offset in range(0, 36600, 7):
day = date.fromordinal(start + offset)
self.assertEqual(jalali.to_gregorian(*jalali.from_gregorian(day)), day)
def test_persian_digits_and_slashes_are_accepted(self):
self.assertEqual(jalali.parse('۱۳۸۵/۰۴/۱۲'), date(2006, 7, 3))
class ProfileBirthDateApiTests(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create(device_id='birth-date-device', is_guest=True)
self.client = APIClient()
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
def patch(self, body):
return self.client.patch('/api/user/profile', body, format='json')
def test_date_is_stored_and_returned_in_jalali_with_its_year(self):
response = self.patch({'birthDate': '1385-04-12'})
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response.json()['birthDate'], '1385-04-12')
self.assertEqual(response.json()['birthYear'], 1385)
self.assertEqual(Profile.objects.get(user=self.user).birth_date, date(2006, 7, 3))
self.assertEqual(self.client.get('/api/user/profile').json()['birthDate'], '1385-04-12')
def test_adults_are_accepted(self):
self.assertEqual(self.patch({'birthDate': '1360-01-15'}).status_code, 200)
def test_impossible_dates_are_rejected(self):
for bad in ('1404-12-30', '1385-13-01', '1385-07-31', '1385-4', 'دیروز'):
self.assertEqual(self.patch({'birthDate': bad}).status_code, 400, bad)
def test_future_and_too_young_or_old_are_rejected(self):
for bad in ('1410-01-01', '1403-01-01', '1300-01-01'):
self.assertEqual(self.patch({'birthDate': bad}).status_code, 400, bad)
def test_date_and_year_together_are_ambiguous(self):
self.assertEqual(self.patch({'birthDate': '1385-04-12', 'birthYear': 1390}).status_code, 400)
def test_a_different_year_alone_clears_the_old_date(self):
self.patch({'birthDate': '1385-04-12'})
data = self.patch({'birthYear': 1388}).json()
self.assertEqual((data['birthYear'], data['birthDate']), (1388, None))
def test_null_clears_the_date(self):
self.patch({'birthDate': '1385-04-12'})
data = self.patch({'birthDate': None}).json()
self.assertEqual((data['birthYear'], data['birthDate']), (None, None))
def test_parent_phone_is_gone(self):
self.assertNotIn('parentPhone', self.client.get('/api/user/profile').json())