diff --git a/tests/unit/test_deployment_config.py b/tests/unit/test_deployment_config.py new file mode 100644 index 0000000..090cf86 --- /dev/null +++ b/tests/unit/test_deployment_config.py @@ -0,0 +1,96 @@ +import os +import pytest +from tools.deployment_config import ( + get_deployment_mode, + is_dev_mode, + is_prod_mode, + get_data_path, + get_db_path, + should_preserve_dev_data, + log_api_key_warning, + get_deployment_mode_dict +) + + +def test_get_deployment_mode_default(): + """Test default deployment mode is PROD""" + # Clear env to test default + os.environ.pop("DEPLOYMENT_MODE", None) + assert get_deployment_mode() == "PROD" + + +def test_get_deployment_mode_dev(): + """Test DEV mode detection""" + os.environ["DEPLOYMENT_MODE"] = "DEV" + assert get_deployment_mode() == "DEV" + assert is_dev_mode() == True + assert is_prod_mode() == False + + +def test_get_deployment_mode_prod(): + """Test PROD mode detection""" + os.environ["DEPLOYMENT_MODE"] = "PROD" + assert get_deployment_mode() == "PROD" + assert is_dev_mode() == False + assert is_prod_mode() == True + + +def test_get_data_path_prod(): + """Test production data path""" + os.environ["DEPLOYMENT_MODE"] = "PROD" + assert get_data_path("./data/agent_data") == "./data/agent_data" + + +def test_get_data_path_dev(): + """Test dev data path substitution""" + os.environ["DEPLOYMENT_MODE"] = "DEV" + assert get_data_path("./data/agent_data") == "./data/dev_agent_data" + + +def test_get_db_path_prod(): + """Test production database path""" + os.environ["DEPLOYMENT_MODE"] = "PROD" + assert get_db_path("data/trading.db") == "data/trading.db" + + +def test_get_db_path_dev(): + """Test dev database path substitution""" + os.environ["DEPLOYMENT_MODE"] = "DEV" + assert get_db_path("data/trading.db") == "data/trading_dev.db" + assert get_db_path("data/jobs.db") == "data/jobs_dev.db" + + +def test_should_preserve_dev_data_default(): + """Test default preserve flag is False""" + os.environ.pop("PRESERVE_DEV_DATA", None) + assert should_preserve_dev_data() == False + + +def test_should_preserve_dev_data_true(): + """Test preserve flag can be enabled""" + os.environ["PRESERVE_DEV_DATA"] = "true" + assert should_preserve_dev_data() == True + + +def test_log_api_key_warning_in_dev(capsys): + """Test warning logged when API keys present in DEV mode""" + os.environ["DEPLOYMENT_MODE"] = "DEV" + os.environ["OPENAI_API_KEY"] = "sk-test123" + + log_api_key_warning() + + captured = capsys.readouterr() + assert "⚠️ WARNING: Production API keys detected in DEV mode" in captured.out + assert "OPENAI_API_KEY" in captured.out + + +def test_get_deployment_mode_dict(): + """Test deployment mode dictionary generation""" + os.environ["DEPLOYMENT_MODE"] = "DEV" + os.environ["PRESERVE_DEV_DATA"] = "true" + + result = get_deployment_mode_dict() + + assert result["deployment_mode"] == "DEV" + assert result["is_dev_mode"] == True + assert result["preserve_dev_data"] == True diff --git a/tools/deployment_config.py b/tools/deployment_config.py new file mode 100644 index 0000000..9e0297e --- /dev/null +++ b/tools/deployment_config.py @@ -0,0 +1,133 @@ +""" +Deployment mode configuration utilities + +Handles PROD vs DEV mode differentiation including: +- Data path isolation +- Database path isolation +- API key validation warnings +- Deployment mode detection +""" + +import os +from typing import Optional + + +def get_deployment_mode() -> str: + """ + Get current deployment mode + + Returns: + "PROD" or "DEV" (defaults to PROD if not set) + """ + mode = os.getenv("DEPLOYMENT_MODE", "PROD").upper() + if mode not in ["PROD", "DEV"]: + print(f"⚠️ Invalid DEPLOYMENT_MODE '{mode}', defaulting to PROD") + return "PROD" + return mode + + +def is_dev_mode() -> bool: + """Check if running in DEV mode""" + return get_deployment_mode() == "DEV" + + +def is_prod_mode() -> bool: + """Check if running in PROD mode""" + return get_deployment_mode() == "PROD" + + +def get_data_path(base_path: str) -> str: + """ + Get data path based on deployment mode + + Args: + base_path: Base data path (e.g., "./data/agent_data") + + Returns: + Modified path for DEV mode or original for PROD + + Example: + PROD: "./data/agent_data" -> "./data/agent_data" + DEV: "./data/agent_data" -> "./data/dev_agent_data" + """ + if is_dev_mode(): + # Replace agent_data with dev_agent_data + return base_path.replace("agent_data", "dev_agent_data") + return base_path + + +def get_db_path(base_db_path: str) -> str: + """ + Get database path based on deployment mode + + Args: + base_db_path: Base database path (e.g., "data/trading.db") + + Returns: + Modified path for DEV mode or original for PROD + + Example: + PROD: "data/trading.db" -> "data/trading.db" + DEV: "data/trading.db" -> "data/trading_dev.db" + """ + if is_dev_mode(): + # Insert _dev before .db extension + if base_db_path.endswith(".db"): + return base_db_path[:-3] + "_dev.db" + return base_db_path + "_dev" + return base_db_path + + +def should_preserve_dev_data() -> bool: + """ + Check if dev data should be preserved between runs + + Returns: + True if PRESERVE_DEV_DATA=true, False otherwise + """ + preserve = os.getenv("PRESERVE_DEV_DATA", "false").lower() + return preserve in ["true", "1", "yes"] + + +def log_api_key_warning() -> None: + """ + Log warning if production API keys are detected in DEV mode + + Checks for common API key environment variables and warns if found. + """ + if not is_dev_mode(): + return + + # List of API key environment variables to check + api_key_vars = [ + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "ALPHAADVANTAGE_API_KEY", + "JINA_API_KEY" + ] + + detected_keys = [] + for var in api_key_vars: + value = os.getenv(var) + if value and value != "" and "your_" not in value.lower(): + detected_keys.append(var) + + if detected_keys: + print("⚠️ WARNING: Production API keys detected in DEV mode") + print(f" Detected: {', '.join(detected_keys)}") + print(" These keys will NOT be used - mock AI responses will be returned") + print(" This is expected if you're testing dev mode with existing .env file") + + +def get_deployment_mode_dict() -> dict: + """ + Get deployment mode information as dictionary (for API responses) + + Returns: + Dictionary with deployment mode metadata + """ + return { + "deployment_mode": get_deployment_mode(), + "is_dev_mode": is_dev_mode(), + "preserve_dev_data": should_preserve_dev_data() if is_dev_mode() else None + }