import pytest import importlib import yaml from app.config import EmbeddingProvider, TTSProvider, Settings @pytest.fixture def tmp_config_file(tmp_path): """ Creates a temporary config.yaml file and returns its path. Corrected the 'provider' value to be lowercase 'mock' to match the Enum. Added database and TTS settings for testing. """ config_content = { "application": { "project_name": "Test Project from YAML", "log_level": "WARNING" }, "llm_providers": {"deepseek_model_name": "deepseek-from-yaml"}, "embedding_provider": { # This value must be lowercase to match the Pydantic Enum member "provider": "mock", "model_name": "embedding-model-from-yaml", "api_key": "embedding-api-from-yaml" }, "database": { "mode": "sqlite", "local_path": "custom_folder/test_ai_hub.db", "url": "postgresql://user:pass@host/dbname" # Should be ignored for sqlite mode }, "tts_provider": { "provider": "google_genai", "voice_name": "Laomedeia", "model_name": "tts-model-from-yaml", # Added configurable model name "api_key": "tts-api-from-yaml" } } config_path = tmp_path / "test_config.yaml" with open(config_path, 'w') as f: yaml.dump(config_content, f) return str(config_path) @pytest.fixture def clear_all_env(monkeypatch): """ A fixture to clear all relevant environment variables for test isolation. """ monkeypatch.delenv("CONFIG_PATH", raising=False) monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False) monkeypatch.delenv("GEMINI_API_KEY", raising=False) monkeypatch.delenv("EMBEDDING_API_KEY", raising=False) monkeypatch.delenv("TTS_PROVIDER", raising=False) monkeypatch.delenv("TTS_VOICE_NAME", raising=False) monkeypatch.delenv("TTS_MODEL_NAME", raising=False) # Added for the new setting monkeypatch.delenv("TTS_API_KEY", raising=False) monkeypatch.delenv("DB_MODE", raising=False) monkeypatch.delenv("LOCAL_DB_PATH", raising=False) monkeypatch.delenv("DATABASE_URL", raising=False) @pytest.fixture def mock_all_keys(monkeypatch): """ A fixture to set all mock API keys. """ monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_deepseek_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_gemini_key") monkeypatch.setenv("EMBEDDING_API_KEY", "mock_embedding_key") monkeypatch.setenv("TTS_API_KEY", "mock_tts_key") def test_sqlite_db_url_from_yaml(monkeypatch, tmp_config_file, clear_all_env): """Tests DATABASE_URL is constructed correctly from YAML sqlite local_path.""" monkeypatch.setenv("CONFIG_PATH", tmp_config_file) monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() expected_path = "sqlite:///./custom_folder/test_ai_hub.db" assert settings.DB_MODE == "sqlite" assert settings.DATABASE_URL == expected_path def test_sqlite_db_url_from_env_local_path(monkeypatch, tmp_path, clear_all_env): """Tests that LOCAL_DB_PATH env var overrides YAML for sqlite DATABASE_URL.""" monkeypatch.setenv("DB_MODE", "sqlite") monkeypatch.setenv("LOCAL_DB_PATH", "env_folder/env_ai_hub.db") monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() expected_path = "sqlite:///./env_folder/env_ai_hub.db" assert settings.DB_MODE == "sqlite" assert settings.DATABASE_URL == expected_path def test_external_db_url_used_when_not_sqlite(monkeypatch, tmp_config_file, clear_all_env): """Tests DATABASE_URL uses external URL when DB_MODE is not sqlite.""" monkeypatch.setenv("CONFIG_PATH", tmp_config_file) monkeypatch.setenv("DB_MODE", "postgresql") monkeypatch.setenv("DATABASE_URL", "postgresql://env_user:env_pass@env_host/env_db") monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() assert settings.DB_MODE == "postgresql" assert settings.DATABASE_URL == "postgresql://env_user:env_pass@env_host/env_db" def test_external_db_url_from_yaml_when_not_sqlite(monkeypatch, tmp_path, clear_all_env): """Tests DATABASE_URL uses YAML url when DB_MODE != sqlite and no env DATABASE_URL.""" # Write YAML with postgresql mode and url config_content = { "database": { "mode": "postgresql", "url": "postgresql://yaml_user:yaml_pass@yaml_host/yaml_db", "local_path": "ignored_path_for_postgresql.db" }, "llm_providers": {"deepseek_model_name": "deepseek-from-yaml"} } config_path = tmp_path / "test_config_pg.yaml" with open(config_path, 'w') as f: yaml.dump(config_content, f) monkeypatch.setenv("CONFIG_PATH", str(config_path)) monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() assert settings.DB_MODE == "postgresql" assert settings.DATABASE_URL == "postgresql://yaml_user:yaml_pass@yaml_host/yaml_db" def test_sqlite_db_url_defaults(monkeypatch, clear_all_env): """Tests DATABASE_URL defaults to sqlite path if no env or YAML.""" monkeypatch.setenv("DB_MODE", "sqlite") monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() assert settings.DB_MODE == "sqlite" assert settings.DATABASE_URL == "sqlite:///./data/ai_hub.db" # --- Tests for TTS Configuration --- def test_tts_settings_from_yaml(monkeypatch, tmp_config_file, clear_all_env): """Tests that TTS settings are loaded correctly from a YAML file.""" monkeypatch.setenv("CONFIG_PATH", tmp_config_file) monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() assert settings.TTS_PROVIDER == TTSProvider.GOOGLE_GENAI assert settings.TTS_VOICE_NAME == "Laomedeia" assert settings.TTS_MODEL_NAME == "tts-model-from-yaml" # Test for new model name assert settings.TTS_API_KEY == "tts-api-from-yaml" def test_tts_settings_from_env(monkeypatch, tmp_config_file, clear_all_env): """Tests that TTS environment variables override the YAML file.""" monkeypatch.setenv("CONFIG_PATH", tmp_config_file) # Explicitly set all TTS env vars for this test monkeypatch.setenv("TTS_PROVIDER", "google_genai") monkeypatch.setenv("TTS_VOICE_NAME", "Zephyr") monkeypatch.setenv("TTS_MODEL_NAME", "env-tts-model") # Added for the new setting monkeypatch.setenv("TTS_API_KEY", "env_tts_key") monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") monkeypatch.setenv("GEMINI_API_KEY", "mock_key") settings = Settings() assert settings.TTS_PROVIDER == TTSProvider.GOOGLE_GENAI assert settings.TTS_VOICE_NAME == "Zephyr" assert settings.TTS_MODEL_NAME == "env-tts-model" # Assert new setting is loaded assert settings.TTS_API_KEY == "env_tts_key" def test_tts_settings_defaults(monkeypatch, clear_all_env): """Tests that TTS settings fall back to Pydantic defaults if no env or YAML are present.""" monkeypatch.setenv("DEEPSEEK_API_KEY", "mock_key") # We remove the line below that sets GEMINI_API_KEY. # The clear_all_env fixture already ensures no env vars are set initially. # settings = Settings() will be able to fall back to None for the API key. settings = Settings() assert settings.TTS_PROVIDER == TTSProvider.GOOGLE_GENAI assert settings.TTS_VOICE_NAME == "Kore" assert settings.TTS_MODEL_NAME == "gemini-2.5-flash-preview-tts" # Assert default value assert settings.TTS_API_KEY is None