stop an existing .env from cancelling the defaults meant to replace it

پیش‌فرض‌های دیروز روی یک سرورِ تازه کار می‌کردند ولی روی سرورِ واقعی نه، و
تستِ با .env واقعی نشانش داد: هر دو حالتِ شایع خنثی‌شان می‌کرد.

مقدارِ موجود ولی ناقص: .env سرور از قبل
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 دارد. چون مقدار خالی نبود، کاملاً
جایگزینِ پیش‌فرض می‌شد و دامنه‌ی api.math.tadrisino2.ir باز هم غایب می‌ماند —
یعنی همان «Disallowed Host» قبلی، و باز هم ویرایشِ دستیِ فایل. حالا
دامنه‌های خودمان به مقدارِ محیطی اضافه می‌شوند نه جایگزینش؛ همین برای
CSRF_TRUSTED_ORIGINS و CORS_ALLOWED_ORIGINS هم اعمال شد.

مقدارِ موجود ولی خالی: PUBLIC_BASE_URL در .env هست و خالی است، و
os.environ.get فقط وقتی کلید *نباشد* پیش‌فرض می‌دهد. نتیجه‌اش paymentUrl
نسبی و callbackِ خالیِ زرین‌پال بود — خرابی‌ای که فقط موقعِ اولین پرداختِ
واقعی معلوم می‌شود. حالا مقدارِ خالی هم به پیش‌فرض می‌افتد.

این سه‌تا همان‌هایی‌اند که تا امروز مسئولِ سرور باید دستی به .env اضافه
می‌کرد و چون در .env.example هم نبودند، هر بار از نو کشف می‌شدند. حالا در
همان فایل مستند شده‌اند و تست نگهبانشان است.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 00:00:35 +03:30
parent 3dacf3a50f
commit a3a47b459d
3 changed files with 109 additions and 21 deletions

View File

@@ -4,6 +4,7 @@ import logging
import os
from unittest.mock import patch
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.test import RequestFactory, SimpleTestCase, TestCase
from rest_framework.test import APIClient
@@ -306,3 +307,52 @@ class JSONLogFormatterTests(SimpleTestCase):
self.assertEqual(payload['message'], 'something failed')
self.assertIn('exception', payload)
self.assertIn('ValueError: boom', payload['exception'])
class ServerEnvDefaultsTests(SimpleTestCase):
"""
متغیرهایی که قبلاً مسئولِ سرور مجبور بود دستی به .env اضافه کند.
هر سه‌تا یک الگو داشتند: نبودشان یا سرویس را می‌خواباند یا یک مسیر را
بی‌سروصدا می‌شکست، و چون در .env.example هم نبودند، هر بار از نو کشف
می‌شدند. این تست نگهبانِ همان است — نه اینکه مقدارها «قشنگ»اند، بلکه
اینکه بدونِ هیچ متغیرِ محیطی‌ای مقدارِ کارآمد دارند.
"""
HOST = 'api.math.tadrisino2.ir'
def test_the_api_domain_is_allowed_without_any_env_var(self):
self.assertIn(self.HOST, settings.DEFAULT_ALLOWED_HOSTS)
def test_an_incomplete_env_value_does_not_drop_our_domains(self):
"""
.env سرور از قبل DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 دارد.
اگر مقدارِ موجود پیش‌فرض را کنار بزند، همان «Disallowed Host» قدیمی
برمی‌گردد و باز کسی باید دستی فایل را ویرایش کند.
"""
# settings فقط نام‌های بزرگ را نشان می‌دهد؛ تابع از خودِ ماژول می‌آید.
from config.settings import _with_defaults
with patch.dict(os.environ, {'DJANGO_ALLOWED_HOSTS': 'localhost,127.0.0.1'}):
merged = _with_defaults('DJANGO_ALLOWED_HOSTS', settings.DEFAULT_ALLOWED_HOSTS)
self.assertIn(self.HOST, merged)
self.assertIn('localhost', merged, 'مقدارِ موجود نباید حذف شود')
def test_a_present_but_empty_value_still_falls_back(self):
"""کلید در .env هست ولی خالی — os.environ.get پیش‌فرض نمی‌دهد."""
with patch.dict(os.environ, {'PUBLIC_BASE_URL': ''}):
value = (os.environ.get('PUBLIC_BASE_URL', '').strip()
or 'https://api.math.tadrisino2.ir')
self.assertTrue(value.startswith('https://'))
def test_admin_login_origin_is_trusted_without_any_env_var(self):
self.assertIn(f'https://{self.HOST}', settings.CSRF_TRUSTED_ORIGINS)
def test_the_deployed_frontend_may_call_the_api(self):
self.assertIn('https://math.tadrisino2.ir', settings.CORS_ALLOWED_ORIGINS)
def test_payment_urls_are_absolute_without_any_env_var(self):
"""خالی بودنِ PUBLIC_BASE_URL مسیرِ پرداخت را بی‌صدا می‌شکست."""
self.assertTrue(settings.PUBLIC_BASE_URL.startswith('https://'))
self.assertTrue(settings.ZARINPAL_CALLBACK_URL.startswith('https://'))
self.assertIn('/api/payment/zarinpal/callback', settings.ZARINPAL_CALLBACK_URL)