API output without Persian diacritics or written ezafe ی (company decision); tutor answers too
plain_persian() strips kasra/fatha/damma/shadda/sukun/hamza-above and the written «هی» ezafe, keeping tanwin. Applied at output, not in data: PlainPersianJSONRenderer runs one byte-level pass over every JSON response (~5 ms per MB locally), and to_plain() cleans tutor answers including the stream. Rewriting titles in the DB/seed would have broken question binding by title path and SeedRecord keys. Tests updated to expect the cleaned text; config/tests_plain_persian.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
تستهای قراردادِ API با صفحههای فرانت.
|
||||
تستهای قرارداد API با صفحههای فرانت.
|
||||
|
||||
اینها از روی خودِ کدِ فرانت نوشته شدهاند، نه از روی حدس: هر صفحه دادهی
|
||||
مشخصی هاردکد داشت و اینجا بررسی میشود که API همان شکل را میدهد. بدونِ
|
||||
اینها، هر بارِ بعدی که سریالایزری عوض شود، شکستن فقط در مرورگر معلوم میشد.
|
||||
اینها از روی خود کد فرانت نوشته شدهاند، نه از روی حدس: هر صفحه داده
|
||||
مشخصی هاردکد داشت و اینجا بررسی میشود که API همان شکل را میدهد. بدون
|
||||
اینها، هر بار بعدی که سریالایزری عوض شود، شکستن فقط در مرورگر معلوم میشد.
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
@@ -16,7 +16,7 @@ from content.models import (
|
||||
|
||||
|
||||
class GradePickerTests(TestCase):
|
||||
"""صفحهی انتخابِ پایه نباید پایهی خالی را مثلِ پایهی پر نشان دهد."""
|
||||
"""صفحه انتخاب پایه نباید پایه خالی را مثل پایه پر نشان دهد."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
@@ -34,7 +34,7 @@ class GradePickerTests(TestCase):
|
||||
|
||||
|
||||
class SkillMapTests(TestCase):
|
||||
"""نقشهی مهارت برای هر قدم عنوان، نوع، زیرنویس و «انجام شد» میخواهد."""
|
||||
"""نقشه مهارت برای هر قدم عنوان، نوع، زیرنویس و «انجام شد» میخواهد."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
@@ -86,7 +86,7 @@ class SkillMapTests(TestCase):
|
||||
|
||||
|
||||
class LessonContentTests(TestCase):
|
||||
"""صفحهی درس سه تکه میخواهد: توضیح، مثال، حلِ گامبهگام."""
|
||||
"""صفحه درس سه تکه میخواهد: توضیح، مثال، حل گامبهگام."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
@@ -117,16 +117,16 @@ class LessonContentTests(TestCase):
|
||||
|
||||
class HomeGradeParamTests(TestCase):
|
||||
"""
|
||||
کلاینتِ فرانت `?gradeId=` میفرستد ولی نسخهی اولِ این اندپوینت `?grade=`
|
||||
میخواند — و پارامترِ ناشناخته بیصدا نادیده گرفته میشد، یعنی کاربر نقشهی
|
||||
پایهی دیگری را میدید بدونِ هیچ خطایی.
|
||||
کلاینت فرانت `?gradeId=` میفرستد ولی نسخه اول این اندپوینت `?grade=`
|
||||
میخواند — و پارامتر ناشناخته بیصدا نادیده گرفته میشد، یعنی کاربر نقشه
|
||||
پایه دیگری را میدید بدون هیچ خطایی.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.a = Grade.objects.create(title='پایه هفتم', icon='', color='')
|
||||
self.b = Grade.objects.create(title='پایه نهم', icon='', color='')
|
||||
for grade, unit_title in ((self.a, 'فصلِ هفتم'), (self.b, 'فصلِ نهم')):
|
||||
for grade, unit_title in ((self.a, 'فصل هفتم'), (self.b, 'فصل نهم')):
|
||||
unit = Unit.objects.create(grade=grade, title=unit_title, order=1)
|
||||
node = Node.objects.create(unit=unit, title='درس', order=1)
|
||||
Lesson.objects.create(node=node, title='نوع', intro_text='x')
|
||||
@@ -140,13 +140,13 @@ class HomeGradeParamTests(TestCase):
|
||||
return [u['title'] for u in data['units']]
|
||||
|
||||
def test_grade_id_selects_that_grade(self):
|
||||
self.assertEqual(self._titles(f'?gradeId={self.b.id}'), ['فصلِ نهم'])
|
||||
self.assertEqual(self._titles(f'?gradeId={self.b.id}'), ['فصل نهم'])
|
||||
|
||||
def test_the_removed_grade_name_is_a_400_not_a_silent_fallback(self):
|
||||
self.assertEqual(self.client.get(f'/api/home?grade={self.b.id}', **self.auth).status_code, 400)
|
||||
|
||||
def test_without_a_parameter_the_users_own_grade_is_used(self):
|
||||
self.assertEqual(self._titles(), ['فصلِ هفتم'])
|
||||
self.assertEqual(self._titles(), ['فصل هفتم'])
|
||||
|
||||
def test_an_unknown_grade_is_a_clean_404_not_a_silent_fallback(self):
|
||||
self.assertEqual(self.client.get('/api/home?gradeId=999999', **self.auth).status_code, 404)
|
||||
@@ -154,9 +154,9 @@ class HomeGradeParamTests(TestCase):
|
||||
|
||||
class NodeVisualDefaultsTests(TestCase):
|
||||
"""
|
||||
تا وقتی کسی از پنلِ ادمین رنگ نگذاشته، کارتِ مبحث نباید بیرنگ بماند.
|
||||
تا وقتی کسی از پنل ادمین رنگ نگذاشته، کارت مبحث نباید بیرنگ بماند.
|
||||
|
||||
پیشفرض باید *پایدار* باشد نه تصادفی: رنگی که بینِ دو بارگذاری عوض شود،
|
||||
پیشفرض باید *پایدار* باشد نه تصادفی: رنگی که بین دو بارگذاری عوض شود،
|
||||
بدتر از بیرنگ بودن است.
|
||||
"""
|
||||
|
||||
@@ -205,8 +205,8 @@ class NodeVisualDefaultsTests(TestCase):
|
||||
class HomeMatchesItsSerializerTests(TestCase):
|
||||
"""
|
||||
/api/home برای سرعت dict را مستقیم برمیگرداند (content/views.py، HomeView)، ولی HomeResponseSerializer
|
||||
هنوز قراردادِ Swagger است. اگر کسی کلیدی به پاسخ اضافه کند یا نوعی را عوض کند و سریالساز را نه،
|
||||
این تست میافتد: خروجیِ سریالساز روی همان پاسخ باید عینِ خودش باشد.
|
||||
هنوز قرارداد Swagger است. اگر کسی کلیدی به پاسخ اضافه کند یا نوعی را عوض کند و سریالساز را نه،
|
||||
این تست میافتد: خروجی سریالساز روی همان پاسخ باید عین خودش باشد.
|
||||
"""
|
||||
|
||||
def test_view_output_is_exactly_the_documented_shape(self):
|
||||
|
||||
Reference in New Issue
Block a user