Spaces:
Paused
Paused
| """Tests for gui/config.py module.""" | |
| import re | |
| class TestConfigPaths: | |
| """Tests for path configurations.""" | |
| def test_project_root_exists(self): | |
| """PROJECT_ROOT should point to valid directory.""" | |
| from gui.config import PROJECT_ROOT | |
| assert PROJECT_ROOT.exists() | |
| assert PROJECT_ROOT.is_dir() | |
| def test_gui_dir_exists(self): | |
| """GUI_DIR should point to the gui module directory.""" | |
| from gui.config import GUI_DIR | |
| assert GUI_DIR.exists() | |
| assert GUI_DIR.is_dir() | |
| assert GUI_DIR.name == "gui" | |
| def test_auth_profiles_dir_structure(self): | |
| """AUTH_PROFILES_DIR should be under PROJECT_ROOT.""" | |
| from gui.config import AUTH_PROFILES_DIR, PROJECT_ROOT | |
| assert AUTH_PROFILES_DIR == PROJECT_ROOT / "auth_profiles" | |
| def test_saved_auth_dir_structure(self): | |
| """SAVED_AUTH_DIR should be under AUTH_PROFILES_DIR.""" | |
| from gui.config import AUTH_PROFILES_DIR, SAVED_AUTH_DIR | |
| assert SAVED_AUTH_DIR == AUTH_PROFILES_DIR / "saved" | |
| def test_active_auth_dir_structure(self): | |
| """ACTIVE_AUTH_DIR should be under AUTH_PROFILES_DIR.""" | |
| from gui.config import ACTIVE_AUTH_DIR, AUTH_PROFILES_DIR | |
| assert ACTIVE_AUTH_DIR == AUTH_PROFILES_DIR / "active" | |
| def test_launch_script_path(self): | |
| """LAUNCH_SCRIPT should point to launch_camoufox.py.""" | |
| from gui.config import LAUNCH_SCRIPT, PROJECT_ROOT | |
| assert LAUNCH_SCRIPT == PROJECT_ROOT / "launch_camoufox.py" | |
| assert LAUNCH_SCRIPT.exists() | |
| def test_config_file_path(self): | |
| """CONFIG_FILE should be in GUI_DIR.""" | |
| from gui.config import CONFIG_FILE, GUI_DIR | |
| assert CONFIG_FILE == GUI_DIR / "user_config.json" | |
| def test_log_file_path(self): | |
| """LOG_FILE should be in PROJECT_ROOT/logs.""" | |
| from gui.config import LOG_FILE, PROJECT_ROOT | |
| assert LOG_FILE == PROJECT_ROOT / "logs" / "gui_launcher.log" | |
| class TestConfigVersion: | |
| """Tests for version string.""" | |
| def test_version_format(self): | |
| """VERSION should be a valid semver string.""" | |
| from gui.config import VERSION | |
| assert isinstance(VERSION, str) | |
| parts = VERSION.split(".") | |
| assert len(parts) >= 2 | |
| assert all(p.isdigit() for p in parts[:2]) | |
| class TestDefaultConfig: | |
| """Tests for DEFAULT_CONFIG dictionary.""" | |
| def test_default_config_has_required_keys(self): | |
| """DEFAULT_CONFIG should have all required keys.""" | |
| from gui.config import DEFAULT_CONFIG | |
| required_keys = [ | |
| "fastapi_port", | |
| "camoufox_port", | |
| "stream_port", | |
| "proxy_address", | |
| "proxy_enabled", | |
| "last_account", | |
| "appearance_mode", | |
| "minimize_to_tray", | |
| "language", | |
| ] | |
| for key in required_keys: | |
| assert key in DEFAULT_CONFIG, f"Missing key: {key}" | |
| def test_default_ports_are_valid(self): | |
| """Default ports should be in valid range.""" | |
| from gui.config import DEFAULT_CONFIG | |
| assert 1 <= DEFAULT_CONFIG["fastapi_port"] <= 65535 | |
| assert 1 <= DEFAULT_CONFIG["camoufox_port"] <= 65535 | |
| assert 1 <= DEFAULT_CONFIG["stream_port"] <= 65535 | |
| def test_default_ports_are_different(self): | |
| """Default ports should not conflict.""" | |
| from gui.config import DEFAULT_CONFIG | |
| ports = [ | |
| DEFAULT_CONFIG["fastapi_port"], | |
| DEFAULT_CONFIG["camoufox_port"], | |
| DEFAULT_CONFIG["stream_port"], | |
| ] | |
| assert len(set(ports)) == 3, "Ports should be unique" | |
| def test_default_language_is_valid(self): | |
| """Default language should be 'en' or 'zh'.""" | |
| from gui.config import DEFAULT_CONFIG | |
| assert DEFAULT_CONFIG["language"] in ("en", "zh") | |
| def test_default_booleans(self): | |
| """Boolean defaults should be booleans.""" | |
| from gui.config import DEFAULT_CONFIG | |
| assert isinstance(DEFAULT_CONFIG["proxy_enabled"], bool) | |
| assert isinstance(DEFAULT_CONFIG["minimize_to_tray"], bool) | |
| # appearance_mode is a string, not a bool | |
| assert isinstance(DEFAULT_CONFIG["appearance_mode"], str) | |
| assert DEFAULT_CONFIG["appearance_mode"] in ("dark", "light", "system") | |
| class TestCustomTkinterSettings: | |
| """Tests for CustomTkinter theme settings.""" | |
| def test_ctk_appearance_mode(self): | |
| """CTK_APPEARANCE_MODE should be valid.""" | |
| from gui.config import CTK_APPEARANCE_MODE | |
| assert CTK_APPEARANCE_MODE in ("dark", "light", "system") | |
| def test_ctk_color_theme(self): | |
| """CTK_COLOR_THEME should be valid.""" | |
| from gui.config import CTK_COLOR_THEME | |
| valid_themes = ("blue", "dark-blue", "green") | |
| assert CTK_COLOR_THEME in valid_themes | |
| class TestColors: | |
| """Tests for COLORS dictionary.""" | |
| def test_colors_has_required_keys(self): | |
| """COLORS should have all required color keys.""" | |
| from gui.config import COLORS | |
| required_keys = [ | |
| "bg_dark", | |
| "bg_medium", | |
| "bg_light", | |
| "accent", | |
| "accent_hover", | |
| "success", | |
| "warning", | |
| "error", | |
| "text_primary", | |
| "text_secondary", | |
| "border", | |
| ] | |
| for key in required_keys: | |
| assert key in COLORS, f"Missing color: {key}" | |
| def test_colors_are_valid_hex_or_special(self): | |
| """Colors should be valid hex codes, tuples of hex codes, or special values.""" | |
| from gui.config import COLORS | |
| hex_pattern = re.compile(r"^#[0-9A-Fa-f]{6}$") | |
| special_values = {"transparent"} | |
| def is_valid_color(color): | |
| """Check if a single color value is valid.""" | |
| return hex_pattern.match(color) or color in special_values | |
| for name, color in COLORS.items(): | |
| if isinstance(color, tuple): | |
| # CustomTkinter tuple format: (light_color, dark_color) | |
| assert len(color) == 2, f"Color tuple {name} should have 2 values" | |
| for c in color: | |
| assert is_valid_color(c), f"Invalid color in tuple for {name}: {c}" | |
| else: | |
| # Single color value | |
| assert is_valid_color(color), f"Invalid color for {name}: {color}" | |
| def test_has_semantic_colors(self): | |
| """Should have semantic color variants.""" | |
| from gui.config import COLORS | |
| # Success colors | |
| assert "success" in COLORS | |
| assert "success_hover" in COLORS | |
| # Error colors | |
| assert "error" in COLORS | |
| assert "error_hover" in COLORS | |
| class TestFonts: | |
| """Tests for FONTS configuration.""" | |
| def test_fonts_has_required_keys(self): | |
| """FONTS should have all required keys.""" | |
| from gui.config import FONTS | |
| required_keys = [ | |
| "family", | |
| "family_mono", | |
| "size_small", | |
| "size_normal", | |
| "size_large", | |
| "size_header", | |
| ] | |
| for key in required_keys: | |
| assert key in FONTS, f"Missing font key: {key}" | |
| def test_font_sizes_are_positive(self): | |
| """Font sizes should be positive integers.""" | |
| from gui.config import FONTS | |
| size_keys = [k for k in FONTS if k.startswith("size_")] | |
| for key in size_keys: | |
| assert isinstance(FONTS[key], int) | |
| assert FONTS[key] > 0 | |
| class TestDimensions: | |
| """Tests for DIMENSIONS configuration.""" | |
| def test_dimensions_has_corner_radius(self): | |
| """DIMENSIONS should have corner radius values.""" | |
| from gui.config import DIMENSIONS | |
| assert "corner_radius" in DIMENSIONS | |
| assert "corner_radius_small" in DIMENSIONS | |
| def test_dimensions_are_positive(self): | |
| """Dimension values should be positive.""" | |
| from gui.config import DIMENSIONS | |
| for key, value in DIMENSIONS.items(): | |
| assert isinstance(value, int) | |
| assert value > 0 | |
| class TestUrls: | |
| """Tests for URL constants.""" | |
| def test_github_url_format(self): | |
| """GITHUB_URL should be a valid GitHub URL.""" | |
| from gui.config import GITHUB_URL | |
| assert GITHUB_URL.startswith("https://github.com/") | |
| def test_docs_url_references_github(self): | |
| """DOCS_URL should reference the GitHub README.""" | |
| from gui.config import DOCS_URL, GITHUB_URL | |
| assert DOCS_URL.startswith(GITHUB_URL) | |
| assert "#readme" in DOCS_URL | |
| class TestColorUtilityFunctions: | |
| """Tests for color utility functions.""" | |
| def test_get_color_returns_dark_mode_by_default(self): | |
| """get_color should return dark mode color by default.""" | |
| from gui.config import get_color | |
| # Test with accent color (same in both modes) | |
| assert get_color("accent") == "#e94560" | |
| def test_get_color_returns_light_mode(self): | |
| """get_color should return light mode color when specified.""" | |
| from gui.config import get_color | |
| # bg_dark is different in light vs dark mode | |
| light_color = get_color("bg_dark", "light") | |
| dark_color = get_color("bg_dark", "dark") | |
| assert light_color == "#e8e8e8" | |
| assert dark_color == "#1a1a2e" | |
| assert light_color != dark_color | |
| def test_get_color_with_plain_string(self): | |
| """get_color should handle plain string colors.""" | |
| from gui.config import get_color | |
| # text_on_color is a plain string, not a tuple | |
| result = get_color("text_on_color") | |
| assert result == "#ffffff" | |
| def test_get_color_with_unknown_key(self): | |
| """get_color should return default for unknown keys.""" | |
| from gui.config import get_color | |
| result = get_color("nonexistent_color") | |
| assert result == "#ffffff" # default fallback | |
| def test_get_current_color_function_exists(self): | |
| """get_current_color function should exist.""" | |
| from gui.config import get_current_color | |
| # Just verify it can be called (it uses ctk which may not be set up) | |
| assert callable(get_current_color) | |