File size: 10,188 Bytes
a5784e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
"""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)