From b10b5839c7d9c152a7aa16a4eedea82d4f826b8f Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 6 Feb 2026 12:56:13 -0500 Subject: [PATCH] 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. --- app/lib_config_v3.py | 84 +++++++++++++++++------- tests/e2e/test_e2e_email_send.py | 48 ++++++++++++++ tests/integration/test_int_email_live.py | 29 -------- 3 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 tests/e2e/test_e2e_email_send.py delete mode 100644 tests/integration/test_int_email_live.py diff --git a/app/lib_config_v3.py b/app/lib_config_v3.py index 7d871e8..a6580e7 100644 --- a/app/lib_config_v3.py +++ b/app/lib_config_v3.py @@ -9,13 +9,13 @@ def validate_critical_config(settings: Any): Logs warnings or errors for missing critical infrastructure. """ log.info("Checking critical system configuration...") - + # 1. Database Check db = getattr(settings, 'DB', {}) if not db.get('server') or db.get('server') == 'mariadb': # 'mariadb' is the default in .env, usually fine, but worth noting log.info(f"Database server: {db.get('server')}") - + # 2. SMTP Check smtp = getattr(settings, 'SMTP', {}) 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': 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: """ @@ -36,11 +36,13 @@ def bootstrap_db_config(settings: Any) -> bool: Uses deferred import of sql_select to avoid circular dependencies. """ # CRITICAL: Deferred import to prevent boot-time circular dependencies - from app.db_sql import sql_select - - cfg_id = settings.AETHER_CFG.get('id', '0') - log.info(f"Bootstrapping system configuration from DB (cfg_id={cfg_id})...") - + from app.db_sql import sql_select + + log.setLevel(logging.DEBUG) + + cfg_id = settings.AETHER_CFG.get('id', 0) + log.info(f"Bootstrapping Aether system configuration from DB (cfg_id={cfg_id})...") + try: # Fetch the config record aether_cfg_sql = sql_select( @@ -49,7 +51,8 @@ def bootstrap_db_config(settings: Any) -> bool: as_list=False, 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 if isinstance(aether_cfg_sql, list): if len(aether_cfg_sql) > 0: @@ -62,25 +65,62 @@ def bootstrap_db_config(settings: Any) -> bool: return False # --- Update Database settings --- - # Safety: Only update if the values are provided in the DB record - if aether_cfg_sql.get('db_server'): settings.DB_SERVER = aether_cfg_sql.get('db_server') - if aether_cfg_sql.get('db_port'): settings.DB_PORT = str(aether_cfg_sql.get('db_port')) - if aether_cfg_sql.get('db_name'): settings.DB_NAME = aether_cfg_sql.get('db_name') - if aether_cfg_sql.get('db_username'): settings.DB_USER = aether_cfg_sql.get('db_username') - if aether_cfg_sql.get('db_password'): settings.DB_PASS = aether_cfg_sql.get('db_password') - + # ID Vision: Prioritize Environment Variables for core infrastructure. + # We only overwrite if the DB value is present AND the environment value is empty OR changed. + db_smtp_server = aether_cfg_sql.get('db_server') + if db_smtp_server and (not settings.DB_SERVER or settings.DB_SERVER != db_smtp_server): + settings.DB_SERVER = db_smtp_server + + 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 --- - if aether_cfg_sql.get('smtp_server'): settings.SMTP['server'] = aether_cfg_sql.get('smtp_server') - if aether_cfg_sql.get('smtp_port'): settings.SMTP['port'] = str(aether_cfg_sql.get('smtp_port')) - if aether_cfg_sql.get('smtp_username'): settings.SMTP['username'] = aether_cfg_sql.get('smtp_username') - if aether_cfg_sql.get('smtp_password'): settings.SMTP['password'] = aether_cfg_sql.get('smtp_password') + # ID Vision: Prioritize Environment Variables for core infrastructure. + # We overwrite ONLY if: + # 1. The environment value is a known placeholder ('set-in-ae-sql-db-cnf-tbl') + # 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 --- # 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_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 except Exception as e: diff --git a/tests/e2e/test_e2e_email_send.py b/tests/e2e/test_e2e_email_send.py new file mode 100644 index 0000000..3f530a0 --- /dev/null +++ b/tests/e2e/test_e2e_email_send.py @@ -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": "

Aether API Status Check

This email confirms that the SMTP subsystem is operational and prioritizing correctly.

Environment: Development

", + "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() diff --git a/tests/integration/test_int_email_live.py b/tests/integration/test_int_email_live.py deleted file mode 100644 index 89f7108..0000000 --- a/tests/integration/test_int_email_live.py +++ /dev/null @@ -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="

Test

", - test=True - ) - - print(f"Result: {result}") - -except Exception as e: - print(f"Error during email test: {e}") - import traceback - traceback.print_exc()