Spaces:
Sleeping
Sleeping
| """Tests for src/config/settings.py and src/config/logging.py""" | |
| import os | |
| import tempfile | |
| from pathlib import Path | |
| import pytest | |
| from config.settings import FrameworkSettings, load_env_file, load_settings | |
| # βββββββββββββββββββββββββββ config/logging.py βββββββββββββββββββββββββββββββ | |
| class TestConfigLogging: | |
| def test_as_bool_truthy_values(self): | |
| from config.logging import _as_bool | |
| assert _as_bool("1") is True | |
| assert _as_bool("true") is True | |
| assert _as_bool("True") is True | |
| assert _as_bool("TRUE") is True | |
| assert _as_bool("yes") is True | |
| assert _as_bool("on") is True | |
| def test_as_bool_falsy_values(self): | |
| from config.logging import _as_bool | |
| assert _as_bool("0") is False | |
| assert _as_bool("false") is False | |
| assert _as_bool("no") is False | |
| assert _as_bool("") is False | |
| assert _as_bool("random") is False | |
| def test_as_bool_none(self): | |
| from config.logging import _as_bool | |
| assert _as_bool(None) is False | |
| def test_setup_logging_basic(self): | |
| from config.logging import setup_logging | |
| # Should not raise | |
| setup_logging(level="INFO") | |
| def test_setup_logging_debug_level(self): | |
| from config.logging import setup_logging | |
| setup_logging(level="DEBUG") | |
| def test_setup_logging_with_format(self): | |
| from config.logging import setup_logging | |
| setup_logging(format_string="{time} | {message}") | |
| def test_setup_logging_with_backtrace(self): | |
| from config.logging import setup_logging | |
| setup_logging(backtrace=True) | |
| setup_logging(backtrace=False) | |
| def test_setup_logging_with_log_file(self, tmp_path): | |
| from config.logging import setup_logging | |
| log_file = str(tmp_path / "test.log") | |
| setup_logging(level="INFO", log_file=log_file) | |
| def test_setup_logging_from_env(self, monkeypatch): | |
| from config.logging import setup_logging | |
| monkeypatch.setenv("RWXF_LOG_LEVEL", "WARNING") | |
| monkeypatch.setenv("RWXF_LOG_BACKTRACE", "true") | |
| setup_logging() # Should read from env vars | |
| def test_setup_logging_log_file_from_env(self, monkeypatch, tmp_path): | |
| from config.logging import setup_logging | |
| log_file = str(tmp_path / "env.log") | |
| monkeypatch.setenv("RWXF_LOG_FILE", log_file) | |
| setup_logging() | |
| monkeypatch.delenv("RWXF_LOG_FILE", raising=False) | |
| def test_logger_is_available(self): | |
| from config.logging import logger | |
| assert logger is not None | |
| # βββββββββββββββββββββββββββ FrameworkSettings βββββββββββββββββββββββββββββββ | |
| class TestFrameworkSettings: | |
| def test_create_with_api_key(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "test-key-123") | |
| settings = FrameworkSettings() | |
| assert settings.resolved_api_key == "test-key-123" | |
| def test_default_values(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "test-key") | |
| settings = FrameworkSettings() | |
| assert settings.model_name == "gpt-4o-mini" | |
| assert settings.default_timeout == 60 | |
| assert settings.max_retries == 3 | |
| assert settings.log_level == "INFO" | |
| assert settings.embedding_normalize is True | |
| def test_missing_api_key_raises(self, monkeypatch): | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| monkeypatch.delenv("RWXF_API_KEY_FILE", raising=False) | |
| with pytest.raises(Exception): | |
| FrameworkSettings() | |
| def test_custom_model_name(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_MODEL_NAME", "gpt-4") | |
| settings = FrameworkSettings() | |
| assert settings.model_name == "gpt-4" | |
| def test_custom_timeout(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_DEFAULT_TIMEOUT", "120") | |
| settings = FrameworkSettings() | |
| assert settings.default_timeout == 120 | |
| def test_valid_hash_embedding_model(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_EMBEDDING_MODEL", "hash:768") | |
| settings = FrameworkSettings() | |
| assert settings.embedding_model == "hash:768" | |
| def test_valid_sentence_transformers_model(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2") | |
| settings = FrameworkSettings() | |
| assert "sentence-transformers" in settings.embedding_model | |
| def test_invalid_embedding_model(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_EMBEDDING_MODEL", "openai/text-embedding") | |
| with pytest.raises(Exception): | |
| FrameworkSettings() | |
| def test_api_key_file(self, monkeypatch, tmp_path): | |
| key_file = tmp_path / "api_key.txt" | |
| key_file.write_text("secret-key-from-file", encoding="utf-8") | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| monkeypatch.setenv("RWXF_API_KEY_FILE", str(key_file)) | |
| settings = FrameworkSettings() | |
| assert settings.resolved_api_key == "secret-key-from-file" | |
| def test_api_key_file_not_found(self, monkeypatch, tmp_path): | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| monkeypatch.setenv("RWXF_API_KEY_FILE", str(tmp_path / "nonexistent.txt")) | |
| with pytest.raises(Exception): | |
| FrameworkSettings() | |
| def test_resolved_api_key_none_raises(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| settings = FrameworkSettings() | |
| # Manually set api_key to None to test RuntimeError | |
| object.__setattr__(settings, "api_key", None) | |
| with pytest.raises(RuntimeError, match="API key is not configured"): | |
| _ = settings.resolved_api_key | |
| def test_empty_string_api_key_treated_as_none(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", " ") | |
| monkeypatch.delenv("RWXF_API_KEY_FILE", raising=False) | |
| # Empty string should be treated as None β raises | |
| with pytest.raises(Exception): | |
| FrameworkSettings() | |
| def test_log_level_custom(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "key") | |
| monkeypatch.setenv("RWXF_LOG_LEVEL", "DEBUG") | |
| settings = FrameworkSettings() | |
| assert settings.log_level == "DEBUG" | |
| # βββββββββββββββββββββββββββ load_env_file βββββββββββββββββββββββββββββββββββ | |
| class TestLoadEnvFile: | |
| def test_load_from_file(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text( | |
| "MY_TEST_VAR=hello\n" | |
| "ANOTHER_VAR=world\n" | |
| "# This is a comment\n" | |
| "\n", | |
| encoding="utf-8", | |
| ) | |
| # Remove env vars before loading | |
| monkeypatch.delenv("MY_TEST_VAR", raising=False) | |
| monkeypatch.delenv("ANOTHER_VAR", raising=False) | |
| load_env_file(env_file) | |
| assert os.environ.get("MY_TEST_VAR") == "hello" | |
| assert os.environ.get("ANOTHER_VAR") == "world" | |
| def test_load_nonexistent_file(self, tmp_path): | |
| # Should not raise | |
| load_env_file(tmp_path / "nonexistent.env") | |
| def test_does_not_overwrite_existing_env(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text("EXISTING_VAR=from_file\n", encoding="utf-8") | |
| monkeypatch.setenv("EXISTING_VAR", "original") | |
| load_env_file(env_file) | |
| assert os.environ.get("EXISTING_VAR") == "original" | |
| def test_handles_quoted_values(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text('QUOTED_VAR="quoted_value"\n', encoding="utf-8") | |
| monkeypatch.delenv("QUOTED_VAR", raising=False) | |
| load_env_file(env_file) | |
| assert os.environ.get("QUOTED_VAR") == "quoted_value" | |
| def test_handles_single_quoted_values(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text("SQ_VAR='single_quoted'\n", encoding="utf-8") | |
| monkeypatch.delenv("SQ_VAR", raising=False) | |
| load_env_file(env_file) | |
| assert os.environ.get("SQ_VAR") == "single_quoted" | |
| def test_skips_lines_without_equals(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text("NOEQUALS\nVALID=value\n", encoding="utf-8") | |
| monkeypatch.delenv("VALID", raising=False) | |
| monkeypatch.delenv("NOEQUALS", raising=False) | |
| load_env_file(env_file) | |
| assert os.environ.get("VALID") == "value" | |
| assert os.environ.get("NOEQUALS") is None | |
| def test_skips_lines_with_empty_key(self, tmp_path, monkeypatch): | |
| """Line 123: continue when key is empty after stripping (e.g. '=value').""" | |
| env_file = tmp_path / ".env" | |
| env_file.write_text("=value_with_no_key\nVALID2=ok\n", encoding="utf-8") | |
| monkeypatch.delenv("VALID2", raising=False) | |
| load_env_file(env_file) | |
| assert os.environ.get("VALID2") == "ok" | |
| # Empty key line should be skipped without error | |
| assert os.environ.get("") is None or True # no key named "" should be set | |
| # βββββββββββββββββββββββββββ load_settings βββββββββββββββββββββββββββββββββββ | |
| class TestLoadSettings: | |
| def test_load_settings_success(self, monkeypatch): | |
| monkeypatch.setenv("RWXF_API_KEY", "test-key") | |
| settings = load_settings() | |
| assert settings is not None | |
| assert settings.resolved_api_key == "test-key" | |
| def test_load_settings_with_env_file(self, tmp_path, monkeypatch): | |
| env_file = tmp_path / ".env" | |
| env_file.write_text("RWXF_API_KEY=key-from-file\n", encoding="utf-8") | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| settings = load_settings(env_file) | |
| assert settings.resolved_api_key == "key-from-file" | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| def test_load_settings_failure_raises_runtime_error(self, monkeypatch): | |
| monkeypatch.delenv("RWXF_API_KEY", raising=False) | |
| monkeypatch.delenv("RWXF_API_KEY_FILE", raising=False) | |
| with pytest.raises(RuntimeError): | |
| load_settings() | |