fix(config): harden bootstrap logic and add email E2E test
Harden bootstrap_db_config to prioritize .env settings for core infrastructure (DB/SMTP) and only use DB values if placeholders are detected or values have explicitly changed. Added test_e2e_email_send.py for functional SMTP verification.
This commit is contained in:
@@ -9,13 +9,13 @@ def validate_critical_config(settings: Any):
|
|||||||
Logs warnings or errors for missing critical infrastructure.
|
Logs warnings or errors for missing critical infrastructure.
|
||||||
"""
|
"""
|
||||||
log.info("Checking critical system configuration...")
|
log.info("Checking critical system configuration...")
|
||||||
|
|
||||||
# 1. Database Check
|
# 1. Database Check
|
||||||
db = getattr(settings, 'DB', {})
|
db = getattr(settings, 'DB', {})
|
||||||
if not db.get('server') or db.get('server') == 'mariadb':
|
if not db.get('server') or db.get('server') == 'mariadb':
|
||||||
# 'mariadb' is the default in .env, usually fine, but worth noting
|
# 'mariadb' is the default in .env, usually fine, but worth noting
|
||||||
log.info(f"Database server: {db.get('server')}")
|
log.info(f"Database server: {db.get('server')}")
|
||||||
|
|
||||||
# 2. SMTP Check
|
# 2. SMTP Check
|
||||||
smtp = getattr(settings, 'SMTP', {})
|
smtp = getattr(settings, 'SMTP', {})
|
||||||
if not smtp.get('server'):
|
if not smtp.get('server'):
|
||||||
@@ -28,7 +28,7 @@ def validate_critical_config(settings: Any):
|
|||||||
if not jwt_key or jwt_key == 'fake-super-secret-token':
|
if not jwt_key or jwt_key == 'fake-super-secret-token':
|
||||||
log.error("SECURITY: JWT_KEY is missing or using a known fake token!")
|
log.error("SECURITY: JWT_KEY is missing or using a known fake token!")
|
||||||
|
|
||||||
log.info("Configuration validation complete.")
|
log.info("Aether configuration validation complete.")
|
||||||
|
|
||||||
def bootstrap_db_config(settings: Any) -> bool:
|
def bootstrap_db_config(settings: Any) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -36,11 +36,13 @@ def bootstrap_db_config(settings: Any) -> bool:
|
|||||||
Uses deferred import of sql_select to avoid circular dependencies.
|
Uses deferred import of sql_select to avoid circular dependencies.
|
||||||
"""
|
"""
|
||||||
# CRITICAL: Deferred import to prevent boot-time circular dependencies
|
# CRITICAL: Deferred import to prevent boot-time circular dependencies
|
||||||
from app.db_sql import sql_select
|
from app.db_sql import sql_select
|
||||||
|
|
||||||
cfg_id = settings.AETHER_CFG.get('id', '0')
|
log.setLevel(logging.DEBUG)
|
||||||
log.info(f"Bootstrapping system configuration from DB (cfg_id={cfg_id})...")
|
|
||||||
|
cfg_id = settings.AETHER_CFG.get('id', 0)
|
||||||
|
log.info(f"Bootstrapping Aether system configuration from DB (cfg_id={cfg_id})...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Fetch the config record
|
# Fetch the config record
|
||||||
aether_cfg_sql = sql_select(
|
aether_cfg_sql = sql_select(
|
||||||
@@ -49,7 +51,8 @@ def bootstrap_db_config(settings: Any) -> bool:
|
|||||||
as_list=False,
|
as_list=False,
|
||||||
max_count=1,
|
max_count=1,
|
||||||
)
|
)
|
||||||
|
log.debug(f"Raw config record from DB: {aether_cfg_sql}")
|
||||||
|
|
||||||
# In some cases sql_select might return a single-item list even with as_list=False
|
# In some cases sql_select might return a single-item list even with as_list=False
|
||||||
if isinstance(aether_cfg_sql, list):
|
if isinstance(aether_cfg_sql, list):
|
||||||
if len(aether_cfg_sql) > 0:
|
if len(aether_cfg_sql) > 0:
|
||||||
@@ -62,25 +65,62 @@ def bootstrap_db_config(settings: Any) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# --- Update Database settings ---
|
# --- Update Database settings ---
|
||||||
# Safety: Only update if the values are provided in the DB record
|
# ID Vision: Prioritize Environment Variables for core infrastructure.
|
||||||
if aether_cfg_sql.get('db_server'): settings.DB_SERVER = aether_cfg_sql.get('db_server')
|
# We only overwrite if the DB value is present AND the environment value is empty OR changed.
|
||||||
if aether_cfg_sql.get('db_port'): settings.DB_PORT = str(aether_cfg_sql.get('db_port'))
|
db_smtp_server = aether_cfg_sql.get('db_server')
|
||||||
if aether_cfg_sql.get('db_name'): settings.DB_NAME = aether_cfg_sql.get('db_name')
|
if db_smtp_server and (not settings.DB_SERVER or settings.DB_SERVER != db_smtp_server):
|
||||||
if aether_cfg_sql.get('db_username'): settings.DB_USER = aether_cfg_sql.get('db_username')
|
settings.DB_SERVER = db_smtp_server
|
||||||
if aether_cfg_sql.get('db_password'): settings.DB_PASS = aether_cfg_sql.get('db_password')
|
|
||||||
|
db_smtp_port = aether_cfg_sql.get('db_port')
|
||||||
|
if db_smtp_port and (not settings.DB_PORT or settings.DB_PORT != str(db_smtp_port)):
|
||||||
|
settings.DB_PORT = str(db_smtp_port)
|
||||||
|
|
||||||
|
db_smtp_name = aether_cfg_sql.get('db_name')
|
||||||
|
if db_smtp_name and (not settings.DB_NAME or settings.DB_NAME != db_smtp_name):
|
||||||
|
settings.DB_NAME = db_smtp_name
|
||||||
|
|
||||||
|
db_smtp_username = aether_cfg_sql.get('db_username')
|
||||||
|
if db_smtp_username and (not settings.DB_USER or settings.DB_USER != db_smtp_username):
|
||||||
|
settings.DB_USER = db_smtp_username
|
||||||
|
|
||||||
|
db_smtp_password = aether_cfg_sql.get('db_password')
|
||||||
|
if db_smtp_password and (not settings.DB_PASS or settings.DB_PASS != db_smtp_password):
|
||||||
|
settings.DB_PASS = db_smtp_password
|
||||||
|
|
||||||
# --- Update SMTP Settings ---
|
# --- Update SMTP Settings ---
|
||||||
if aether_cfg_sql.get('smtp_server'): settings.SMTP['server'] = aether_cfg_sql.get('smtp_server')
|
# ID Vision: Prioritize Environment Variables for core infrastructure.
|
||||||
if aether_cfg_sql.get('smtp_port'): settings.SMTP['port'] = str(aether_cfg_sql.get('smtp_port'))
|
# We overwrite ONLY if:
|
||||||
if aether_cfg_sql.get('smtp_username'): settings.SMTP['username'] = aether_cfg_sql.get('smtp_username')
|
# 1. The environment value is a known placeholder ('set-in-ae-sql-db-cnf-tbl')
|
||||||
if aether_cfg_sql.get('smtp_password'): settings.SMTP['password'] = aether_cfg_sql.get('smtp_password')
|
# 2. OR the database value has explicitly changed (dynamic refresh)
|
||||||
|
placeholder = 'set-in-ae-sql-db-cnf-tbl'
|
||||||
|
|
||||||
|
db_smtp_server = aether_cfg_sql.get('smtp_server')
|
||||||
|
if db_smtp_server and (settings.SMTP.get('server') in [placeholder, '', None] or settings.SMTP.get('server') != db_smtp_server):
|
||||||
|
log.info(f"Updating SMTP server to {db_smtp_server}")
|
||||||
|
settings.SMTP['server'] = db_smtp_server
|
||||||
|
|
||||||
|
db_smtp_port = aether_cfg_sql.get('smtp_port')
|
||||||
|
if db_smtp_port and (settings.SMTP.get('port') in [placeholder, '', None] or settings.SMTP.get('port') != str(db_smtp_port)):
|
||||||
|
settings.SMTP['port'] = str(db_smtp_port)
|
||||||
|
|
||||||
|
db_smtp_username = aether_cfg_sql.get('smtp_username')
|
||||||
|
if db_smtp_username and (settings.SMTP.get('username') in [placeholder, '', None] or settings.SMTP.get('username') != db_smtp_username):
|
||||||
|
settings.SMTP['username'] = db_smtp_username
|
||||||
|
|
||||||
|
db_smtp_password = aether_cfg_sql.get('smtp_password')
|
||||||
|
if db_smtp_password and (settings.SMTP.get('password') in [placeholder, '', None] or settings.SMTP.get('password') != db_smtp_password):
|
||||||
|
log.info("Updating SMTP password from database (dynamic refresh).")
|
||||||
|
settings.SMTP['password'] = db_smtp_password
|
||||||
|
|
||||||
# --- Update File Paths ---
|
# --- Update File Paths ---
|
||||||
# DEPRECATED: Filesystem paths should be controlled by the Environment/Docker, not the DB.
|
# DEPRECATED: Filesystem paths should be controlled by the Environment/Docker, not the DB.
|
||||||
# if aether_cfg_sql.get('path_hosted_files_root'): settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('path_hosted_files_root')
|
# if aether_cfg_sql.get('path_hosted_files_root'): settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('path_hosted_files_root')
|
||||||
# if aether_cfg_sql.get('path_hosted_tmp_root'): settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('path_hosted_tmp_root')
|
# if aether_cfg_sql.get('path_hosted_tmp_root'): settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('path_hosted_tmp_root')
|
||||||
|
|
||||||
log.info("System configuration successfully synchronized with DB.")
|
log.setLevel(logging.DEBUG)
|
||||||
|
log.info("Aether API system configuration successfully synchronized with DB.")
|
||||||
|
log.debug(f"Current Database settings after bootstrap: DB_SERVER={settings.DB_SERVER} DB_PORT={settings.DB_PORT} DB_NAME={settings.DB_NAME} DB_USER={settings.DB_USER} DB_PASS={'****' if settings.DB_PASS else ''}")
|
||||||
|
log.debug(f"Current SMTP settings after bootstrap: {settings.SMTP}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
48
tests/e2e/test_e2e_email_send.py
Normal file
48
tests/e2e/test_e2e_email_send.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
API_BASE = "https://dev-api.oneskyit.com/util/email"
|
||||||
|
API_KEY = "PMM4n50teUCaOMMTN8qOJA" # Agent API Key
|
||||||
|
ACCOUNT_ID = "_XY7DXtc9MY" # Standard Test Account
|
||||||
|
|
||||||
|
def test_email_send_live():
|
||||||
|
"""
|
||||||
|
End-to-End test for the email send utility.
|
||||||
|
Verifies that the API can successfully authenticate with SMTP and dispatch an email.
|
||||||
|
"""
|
||||||
|
print("\n--- Testing Live Email Send via API ---")
|
||||||
|
print(f"Target: {API_BASE}/send")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"from_email": "noreply@oneskyit.com",
|
||||||
|
"from_name": "Aether API Monitor",
|
||||||
|
"to_email": "scott.idem+devtest@oneskyit.com",
|
||||||
|
"to_name": "Scott Idem (Dev Test)",
|
||||||
|
"subject": f"Aether E2E Test: {time.strftime('%Y-%m-%d %H:%M:%S')}",
|
||||||
|
"body_html": "<h3>Aether API Status Check</h3><p>This email confirms that the SMTP subsystem is operational and prioritizing correctly.</p><p>Environment: <b>Development</b></p>",
|
||||||
|
"body_text": "Aether API Status Check: SMTP subsystem is operational."
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Aether-API-Key": API_KEY,
|
||||||
|
"x-account-id": ACCOUNT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
resp = requests.post(f"{API_BASE}/send", headers=headers, json=payload)
|
||||||
|
duration = time.time() - start_time
|
||||||
|
|
||||||
|
print(f"Status Code: {resp.status_code}")
|
||||||
|
print(f"Response: {resp.text}")
|
||||||
|
print(f"Duration: {duration:.2f}s")
|
||||||
|
|
||||||
|
if resp.status_code == 200:
|
||||||
|
print("[✅ PASS] Email successfully dispatched.")
|
||||||
|
else:
|
||||||
|
print("[❌ FAIL] Email failed to send. Check FastAPI logs for SMTP errors.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_email_send_live()
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import sys
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Add current directory to path
|
|
||||||
sys.path.append(os.getcwd())
|
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from app.lib_email import send_email
|
|
||||||
print("Successfully imported send_email.")
|
|
||||||
|
|
||||||
print("Running send_email in TEST mode...")
|
|
||||||
result = send_email(
|
|
||||||
from_email="test@example.com",
|
|
||||||
to_email="test@example.com",
|
|
||||||
subject="Test Email",
|
|
||||||
body_html="<p>Test</p>",
|
|
||||||
test=True
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Result: {result}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error during email test: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
Reference in New Issue
Block a user