tutor answers step by step in plain text, islands finish, and the audit's money and login holes are closed
Tutor (prompt tutor-v3-stepwise-plain): plain text instead of LaTeX/markdown, with rag/services/mathtext.py converting whatever still slips through; full step-by-step solutions with a check before the final answer; three tones by grade; the last 6 messages of the conversation go to the model and follow-ups are no longer served from another user's cache; book solutions the checker disputes are kept out of the context; a questionId gives the tutor the practice question and the student's wrong answer. /api/health reports when the AI credit runs out. rag_model_benchmark compares chat models on verified book problems (run on the server; credit ran out mid-run). Map: only lessons that have questions count toward finishing an island, lessonIds[0] / nextLessonId is always the next lesson to play, and a finished island stays finished. The Seyar characters (sigma/delta/pi) are seeded. Audit fixes, each reproduced before fixing: - the plan is stored on the order, so paying a monthly order after starting a yearly checkout no longer grants a year - opening checkout no longer demotes an active subscription; renewals extend from the current expiry; expired subscriptions are not reported active - one discount code per order and only before the gateway; a 100% code completes the purchase; lost Zarinpal confirmations are re-verified - the unused /api/payment/webhook "mark paid" endpoint is removed - XP counts only answers recorded on the server - 6-digit OTP, each code dies after 5 wrong guesses; POST /api/auth/logout - a guest who signs up keeps their account and progress (test phones excluded) - affiliate commission on the first real payment; guests excluded - parents with several children no longer get a 500 - SQLite runs transactions IMMEDIATE, since it ignores select_for_update - admin login is rate-limited; display names drop phone numbers and links; userQuestion is capped at 2000 characters - server-side heart enforcement exists behind HEARTS_ENFORCED (off until the frontend handles 403 no_hearts) Not done on purpose: rotating the server's secret key and moving to Postgres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,13 +13,13 @@ from accounts.models import User
|
||||
from . import bazaar, zarinpal
|
||||
from .bazaar import BazaarPurchaseResult
|
||||
from .models import Order, Transaction
|
||||
from .permissions import HasInternalServiceKey, HasWebhookSecret
|
||||
from .permissions import HasInternalServiceKey
|
||||
from .services import call_payment_gateway
|
||||
|
||||
|
||||
class SecretDefaultsFailClosedTests(TestCase):
|
||||
"""
|
||||
تستهای متمرکز روی این فیکس: INTERNAL_SERVICE_KEY و PAYMENT_WEBHOOK_SECRET
|
||||
تستهای متمرکز روی این فیکس: INTERNAL_SERVICE_KEY
|
||||
دیگه در پروداکشن (DEBUG=False) دیفالتِ قابلحدسزدن ندارن. اگه env تنظیم
|
||||
نشده باشه، مقدار خالی میمونه و پرمیشنهای مربوطه صراحتاً رد میکنن
|
||||
(fail closed)، نه اینکه با یک مقدارِ شناختهشده قبول کنن.
|
||||
@@ -35,15 +35,9 @@ class SecretDefaultsFailClosedTests(TestCase):
|
||||
# settings.INTERNAL_SERVICE_KEY خودش خالیه، درخواست باید رد بشه.
|
||||
self.assertFalse(HasInternalServiceKey().has_permission(request, None))
|
||||
|
||||
@override_settings(DEBUG=False, PAYMENT_WEBHOOK_SECRET='')
|
||||
def test_production_missing_webhook_secret_rejects(self):
|
||||
request = self.factory.post('/api/payment/webhook')
|
||||
self.assertFalse(HasWebhookSecret().has_permission(request, None))
|
||||
|
||||
@override_settings(
|
||||
DEBUG=False,
|
||||
INTERNAL_SERVICE_KEY='prod-internal-key-xyz',
|
||||
PAYMENT_WEBHOOK_SECRET='prod-webhook-secret-xyz',
|
||||
)
|
||||
def test_production_configured_secrets_accepts(self):
|
||||
internal_request = self.factory.post(
|
||||
@@ -51,11 +45,6 @@ class SecretDefaultsFailClosedTests(TestCase):
|
||||
)
|
||||
self.assertTrue(HasInternalServiceKey().has_permission(internal_request, None))
|
||||
|
||||
webhook_request = self.factory.post(
|
||||
'/api/payment/webhook', HTTP_X_WEBHOOK_SECRET='prod-webhook-secret-xyz',
|
||||
)
|
||||
self.assertTrue(HasWebhookSecret().has_permission(webhook_request, None))
|
||||
|
||||
def test_debug_mode_local_fallback_still_valid(self):
|
||||
# settings.py این مقادیر رو یکبار موقعِ import محاسبه میکنه، نه
|
||||
# چیزی که override_settings بعداً toggle کنه؛ پس برای تستِ این
|
||||
@@ -63,7 +52,7 @@ class SecretDefaultsFailClosedTests(TestCase):
|
||||
from config import settings as settings_module
|
||||
|
||||
env_overrides = {'DJANGO_DEBUG': '1'}
|
||||
env_removed = {'INTERNAL_SERVICE_KEY', 'PAYMENT_WEBHOOK_SECRET'}
|
||||
env_removed = {'INTERNAL_SERVICE_KEY'}
|
||||
clean_env = {
|
||||
k: v for k, v in os.environ.items() if k not in env_removed
|
||||
}
|
||||
@@ -77,10 +66,6 @@ class SecretDefaultsFailClosedTests(TestCase):
|
||||
settings_module.INTERNAL_SERVICE_KEY,
|
||||
'dev-internal-service-key-change-me',
|
||||
)
|
||||
self.assertEqual(
|
||||
settings_module.PAYMENT_WEBHOOK_SECRET,
|
||||
'dev-webhook-secret-change-me',
|
||||
)
|
||||
finally:
|
||||
# همیشه ماژول رو به حالتِ واقعیِ محیطِ تست برمیگردونیم تا روی
|
||||
# بقیهی تستها اثر نذاره.
|
||||
|
||||
Reference in New Issue
Block a user