A domain change is coming (the Zarinpal terminal rejects the current callback domain). ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, CORS defaults, the Bazaar redirect URI and deploy.sh's public health URL each spelled the domain out. They now derive from PUBLIC_BASE_URL and FRONTEND_URL, which keep today's domain as their default; env values for the three lists are still added on top, so the old domain can stay allowed while DNS moves. ZARINPAL_CALLBACK_URL used os.environ.get(key, default), so an empty key in .env produced an empty callback and every payment would fail closed. It now uses the same `.strip() or` fallback as its neighbours; the new test caught it. config/tests_domains.py loads settings in a subprocess with a new domain and fails if any derived value still mentions the old one. docs/operations.md gains the step-by-step domain move (DNS, .env, Zarinpal/Bazaar panels, frontend config.js, verification). CLAUDE.md records that the frontend is now ours and where the rebuild stands. 624 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
3.0 KiB
Python
63 lines
3.0 KiB
Python
"""
|
|
دامنه فقط از PUBLIC_BASE_URL و FRONTEND_URL میآید.
|
|
|
|
تنظیمات در زمانِ import ساخته میشوند، پس هر حالت در یک پروسهی جدا با env
|
|
خودش بار میشود. اگر کسی دوباره دامنهای را مستقیم در settings بنویسد، حالتِ
|
|
«دامنهی تازه» اینجا قرمز میشود.
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from django.test import SimpleTestCase
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
PROBE = """
|
|
import json, os
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'config.settings'
|
|
import django; django.setup()
|
|
from django.conf import settings as s
|
|
print(json.dumps({
|
|
'hosts': s.ALLOWED_HOSTS, 'csrf': s.CSRF_TRUSTED_ORIGINS, 'cors': s.CORS_ALLOWED_ORIGINS,
|
|
'callback': s.ZARINPAL_CALLBACK_URL, 'redirect': s.ZARINPAL_REDIRECT_URL, 'bazaar': s.BAZAAR_REDIRECT_URI,
|
|
}))
|
|
"""
|
|
|
|
|
|
def load_settings(**env):
|
|
clean = {k: v for k, v in os.environ.items() if not k.startswith(('DJANGO_ALLOWED', 'CSRF_', 'CORS_', 'ZARINPAL_', 'BAZAAR_REDIRECT', 'PUBLIC_BASE', 'FRONTEND_URL'))}
|
|
clean.update(env)
|
|
# .env محلی نباید مقدارهای آزمون را بپوشاند؛ python-dotenv متغیرِ موجود را عوض نمیکند،
|
|
# پس کلیدهای خالی را هم صریح میگذاریم.
|
|
for key in ('DJANGO_ALLOWED_HOSTS', 'CSRF_TRUSTED_ORIGINS', 'CORS_ALLOWED_ORIGINS', 'ZARINPAL_CALLBACK_URL',
|
|
'ZARINPAL_REDIRECT_URL', 'BAZAAR_REDIRECT_URI'):
|
|
clean.setdefault(key, '')
|
|
out = subprocess.run([sys.executable, '-c', PROBE], cwd=BASE_DIR, env=clean, capture_output=True, text=True, check=True)
|
|
return json.loads(out.stdout.strip().splitlines()[-1])
|
|
|
|
|
|
class DomainSettingsTests(SimpleTestCase):
|
|
def test_new_domain_moves_everything(self):
|
|
s = load_settings(PUBLIC_BASE_URL='https://api.new-domain.ir', FRONTEND_URL='https://app.new-domain.ir/')
|
|
self.assertIn('api.new-domain.ir', s['hosts'])
|
|
self.assertIn('app.new-domain.ir', s['hosts'])
|
|
self.assertIn('https://app.new-domain.ir', s['cors'])
|
|
self.assertIn('https://api.new-domain.ir', s['csrf'])
|
|
self.assertEqual(s['callback'], 'https://api.new-domain.ir/api/payment/zarinpal/callback')
|
|
self.assertEqual(s['redirect'], 'https://app.new-domain.ir/')
|
|
self.assertEqual(s['bazaar'], 'https://api.new-domain.ir/api/payment/bazaar/verify')
|
|
self.assertNotIn('tadrisino2', json.dumps(s))
|
|
|
|
def test_extra_domain_is_added_not_replacing(self):
|
|
s = load_settings(
|
|
PUBLIC_BASE_URL='https://api.new-domain.ir', FRONTEND_URL='https://app.new-domain.ir',
|
|
DJANGO_ALLOWED_HOSTS='old.example.ir', CORS_ALLOWED_ORIGINS='https://old.example.ir',
|
|
)
|
|
self.assertIn('old.example.ir', s['hosts'])
|
|
self.assertIn('api.new-domain.ir', s['hosts'])
|
|
self.assertIn('https://old.example.ir', s['cors'])
|
|
self.assertIn('https://app.new-domain.ir', s['cors'])
|