mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add coverage for dashboard ahead of Path conversion (#10669)
This commit is contained in:
		
							
								
								
									
										203
									
								
								tests/dashboard/test_entries.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								tests/dashboard/test_entries.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | |||||||
|  | """Tests for dashboard entries Path-related functionality.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | from pathlib import Path | ||||||
|  | import tempfile | ||||||
|  | from unittest.mock import MagicMock | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  | import pytest_asyncio | ||||||
|  |  | ||||||
|  | from esphome.core import CORE | ||||||
|  | from esphome.dashboard.entries import DashboardEntries, DashboardEntry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_cache_key() -> tuple[int, int, float, int]: | ||||||
|  |     """Helper to create a valid DashboardCacheKeyType.""" | ||||||
|  |     return (0, 0, 0.0, 0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(autouse=True) | ||||||
|  | def setup_core(): | ||||||
|  |     """Set up CORE for testing.""" | ||||||
|  |     with tempfile.TemporaryDirectory() as tmpdir: | ||||||
|  |         CORE.config_path = str(Path(tmpdir) / "test.yaml") | ||||||
|  |         yield | ||||||
|  |         CORE.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def mock_settings() -> MagicMock: | ||||||
|  |     """Create mock dashboard settings.""" | ||||||
|  |     settings = MagicMock() | ||||||
|  |     settings.config_dir = "/test/config" | ||||||
|  |     settings.absolute_config_dir = Path("/test/config") | ||||||
|  |     return settings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest_asyncio.fixture | ||||||
|  | async def dashboard_entries(mock_settings: MagicMock) -> DashboardEntries: | ||||||
|  |     """Create a DashboardEntries instance for testing.""" | ||||||
|  |     return DashboardEntries(mock_settings) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_dashboard_entry_path_initialization() -> None: | ||||||
|  |     """Test DashboardEntry initializes with path correctly.""" | ||||||
|  |     test_path = "/test/config/device.yaml" | ||||||
|  |     cache_key = create_cache_key() | ||||||
|  |  | ||||||
|  |     entry = DashboardEntry(test_path, cache_key) | ||||||
|  |  | ||||||
|  |     assert entry.path == test_path | ||||||
|  |     assert entry.cache_key == cache_key | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_dashboard_entry_path_with_absolute_path() -> None: | ||||||
|  |     """Test DashboardEntry handles absolute paths.""" | ||||||
|  |     # Use a truly absolute path for the platform | ||||||
|  |     test_path = Path.cwd() / "absolute" / "path" / "to" / "config.yaml" | ||||||
|  |     cache_key = create_cache_key() | ||||||
|  |  | ||||||
|  |     entry = DashboardEntry(str(test_path), cache_key) | ||||||
|  |  | ||||||
|  |     assert entry.path == str(test_path) | ||||||
|  |     assert Path(entry.path).is_absolute() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_dashboard_entry_path_with_relative_path() -> None: | ||||||
|  |     """Test DashboardEntry handles relative paths.""" | ||||||
|  |     test_path = "configs/device.yaml" | ||||||
|  |     cache_key = create_cache_key() | ||||||
|  |  | ||||||
|  |     entry = DashboardEntry(test_path, cache_key) | ||||||
|  |  | ||||||
|  |     assert entry.path == test_path | ||||||
|  |     assert not Path(entry.path).is_absolute() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_get_by_path( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test getting entry by path.""" | ||||||
|  |     test_path = "/test/config/device.yaml" | ||||||
|  |     entry = DashboardEntry(test_path, create_cache_key()) | ||||||
|  |  | ||||||
|  |     dashboard_entries._entries[test_path] = entry | ||||||
|  |  | ||||||
|  |     result = dashboard_entries.get(test_path) | ||||||
|  |     assert result == entry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_get_nonexistent_path( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test getting non-existent entry returns None.""" | ||||||
|  |     result = dashboard_entries.get("/nonexistent/path.yaml") | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_path_normalization( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that paths are handled consistently.""" | ||||||
|  |     path1 = "/test/config/device.yaml" | ||||||
|  |  | ||||||
|  |     entry = DashboardEntry(path1, create_cache_key()) | ||||||
|  |     dashboard_entries._entries[path1] = entry | ||||||
|  |  | ||||||
|  |     result = dashboard_entries.get(path1) | ||||||
|  |     assert result == entry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_path_with_spaces( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test handling paths with spaces.""" | ||||||
|  |     test_path = "/test/config/my device.yaml" | ||||||
|  |     entry = DashboardEntry(test_path, create_cache_key()) | ||||||
|  |  | ||||||
|  |     dashboard_entries._entries[test_path] = entry | ||||||
|  |  | ||||||
|  |     result = dashboard_entries.get(test_path) | ||||||
|  |     assert result == entry | ||||||
|  |     assert result.path == test_path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_path_with_special_chars( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test handling paths with special characters.""" | ||||||
|  |     test_path = "/test/config/device-01_test.yaml" | ||||||
|  |     entry = DashboardEntry(test_path, create_cache_key()) | ||||||
|  |  | ||||||
|  |     dashboard_entries._entries[test_path] = entry | ||||||
|  |  | ||||||
|  |     result = dashboard_entries.get(test_path) | ||||||
|  |     assert result == entry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_dashboard_entries_windows_path() -> None: | ||||||
|  |     """Test handling Windows-style paths.""" | ||||||
|  |     test_path = r"C:\Users\test\esphome\device.yaml" | ||||||
|  |     cache_key = create_cache_key() | ||||||
|  |  | ||||||
|  |     entry = DashboardEntry(test_path, cache_key) | ||||||
|  |  | ||||||
|  |     assert entry.path == test_path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_path_to_cache_key_mapping( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test internal entries storage with paths and cache keys.""" | ||||||
|  |     path1 = "/test/config/device1.yaml" | ||||||
|  |     path2 = "/test/config/device2.yaml" | ||||||
|  |  | ||||||
|  |     entry1 = DashboardEntry(path1, create_cache_key()) | ||||||
|  |     entry2 = DashboardEntry(path2, (1, 1, 1.0, 1)) | ||||||
|  |  | ||||||
|  |     dashboard_entries._entries[path1] = entry1 | ||||||
|  |     dashboard_entries._entries[path2] = entry2 | ||||||
|  |  | ||||||
|  |     assert path1 in dashboard_entries._entries | ||||||
|  |     assert path2 in dashboard_entries._entries | ||||||
|  |     assert dashboard_entries._entries[path1].cache_key == create_cache_key() | ||||||
|  |     assert dashboard_entries._entries[path2].cache_key == (1, 1, 1.0, 1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_dashboard_entry_path_property() -> None: | ||||||
|  |     """Test that path property returns expected value.""" | ||||||
|  |     test_path = "/test/config/device.yaml" | ||||||
|  |     entry = DashboardEntry(test_path, create_cache_key()) | ||||||
|  |  | ||||||
|  |     assert entry.path == test_path | ||||||
|  |     assert isinstance(entry.path, str) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_dashboard_entries_all_returns_entries_with_paths( | ||||||
|  |     dashboard_entries: DashboardEntries, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that all() returns entries with their paths intact.""" | ||||||
|  |     paths = [ | ||||||
|  |         "/test/config/device1.yaml", | ||||||
|  |         "/test/config/device2.yaml", | ||||||
|  |         "/test/config/subfolder/device3.yaml", | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     for path in paths: | ||||||
|  |         entry = DashboardEntry(path, create_cache_key()) | ||||||
|  |         dashboard_entries._entries[path] = entry | ||||||
|  |  | ||||||
|  |     all_entries = dashboard_entries.async_all() | ||||||
|  |  | ||||||
|  |     assert len(all_entries) == len(paths) | ||||||
|  |     retrieved_paths = [entry.path for entry in all_entries] | ||||||
|  |     assert set(retrieved_paths) == set(paths) | ||||||
							
								
								
									
										168
									
								
								tests/dashboard/test_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								tests/dashboard/test_settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | |||||||
|  | """Tests for dashboard settings Path-related functionality.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | from pathlib import Path | ||||||
|  | import tempfile | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from esphome.dashboard.settings import DashboardSettings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def dashboard_settings(tmp_path: Path) -> DashboardSettings: | ||||||
|  |     """Create DashboardSettings instance with temp directory.""" | ||||||
|  |     settings = DashboardSettings() | ||||||
|  |     # Resolve symlinks to ensure paths match | ||||||
|  |     resolved_dir = tmp_path.resolve() | ||||||
|  |     settings.config_dir = str(resolved_dir) | ||||||
|  |     settings.absolute_config_dir = resolved_dir | ||||||
|  |     return settings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_simple(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path with simple relative path.""" | ||||||
|  |     result = dashboard_settings.rel_path("config.yaml") | ||||||
|  |  | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "config.yaml") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_multiple_components(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path with multiple path components.""" | ||||||
|  |     result = dashboard_settings.rel_path("subfolder", "device", "config.yaml") | ||||||
|  |  | ||||||
|  |     expected = str( | ||||||
|  |         Path(dashboard_settings.config_dir) / "subfolder" / "device" / "config.yaml" | ||||||
|  |     ) | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_with_dots(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path prevents directory traversal.""" | ||||||
|  |     # This should raise ValueError as it tries to go outside config_dir | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         dashboard_settings.rel_path("..", "outside.yaml") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_absolute_path_within_config( | ||||||
|  |     dashboard_settings: DashboardSettings, | ||||||
|  | ) -> None: | ||||||
|  |     """Test rel_path with absolute path that's within config dir.""" | ||||||
|  |     internal_path = dashboard_settings.absolute_config_dir / "internal.yaml" | ||||||
|  |  | ||||||
|  |     internal_path.touch() | ||||||
|  |     result = dashboard_settings.rel_path("internal.yaml") | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "internal.yaml") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_absolute_path_outside_config( | ||||||
|  |     dashboard_settings: DashboardSettings, | ||||||
|  | ) -> None: | ||||||
|  |     """Test rel_path with absolute path outside config dir raises error.""" | ||||||
|  |     outside_path = "/tmp/outside/config.yaml" | ||||||
|  |  | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         dashboard_settings.rel_path(outside_path) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_empty_args(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path with no arguments returns config_dir.""" | ||||||
|  |     result = dashboard_settings.rel_path() | ||||||
|  |     assert result == dashboard_settings.config_dir | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_with_pathlib_path(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path works with Path objects as arguments.""" | ||||||
|  |     path_obj = Path("subfolder") / "config.yaml" | ||||||
|  |     result = dashboard_settings.rel_path(path_obj) | ||||||
|  |  | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "subfolder" / "config.yaml") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_normalizes_slashes(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path normalizes path separators.""" | ||||||
|  |     # os.path.join normalizes slashes on Windows but preserves them on Unix | ||||||
|  |     # Test that providing components separately gives same result | ||||||
|  |     result1 = dashboard_settings.rel_path("folder", "subfolder", "file.yaml") | ||||||
|  |     result2 = dashboard_settings.rel_path("folder", "subfolder", "file.yaml") | ||||||
|  |     assert result1 == result2 | ||||||
|  |  | ||||||
|  |     # Also test that the result is as expected | ||||||
|  |     expected = os.path.join( | ||||||
|  |         dashboard_settings.config_dir, "folder", "subfolder", "file.yaml" | ||||||
|  |     ) | ||||||
|  |     assert result1 == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_handles_spaces(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path handles paths with spaces.""" | ||||||
|  |     result = dashboard_settings.rel_path("my folder", "my config.yaml") | ||||||
|  |  | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "my folder" / "my config.yaml") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_handles_special_chars(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path handles paths with special characters.""" | ||||||
|  |     result = dashboard_settings.rel_path("device-01_test", "config.yaml") | ||||||
|  |  | ||||||
|  |     expected = str( | ||||||
|  |         Path(dashboard_settings.config_dir) / "device-01_test" / "config.yaml" | ||||||
|  |     ) | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_config_dir_as_path_property(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test that config_dir can be accessed and used with Path operations.""" | ||||||
|  |     config_path = Path(dashboard_settings.config_dir) | ||||||
|  |  | ||||||
|  |     assert config_path.exists() | ||||||
|  |     assert config_path.is_dir() | ||||||
|  |     assert config_path.is_absolute() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_absolute_config_dir_property(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test absolute_config_dir is a Path object.""" | ||||||
|  |     assert isinstance(dashboard_settings.absolute_config_dir, Path) | ||||||
|  |     assert dashboard_settings.absolute_config_dir.exists() | ||||||
|  |     assert dashboard_settings.absolute_config_dir.is_dir() | ||||||
|  |     assert dashboard_settings.absolute_config_dir.is_absolute() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_symlink_inside_config(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path with symlink that points inside config dir.""" | ||||||
|  |     target = dashboard_settings.absolute_config_dir / "target.yaml" | ||||||
|  |     target.touch() | ||||||
|  |     symlink = dashboard_settings.absolute_config_dir / "link.yaml" | ||||||
|  |     symlink.symlink_to(target) | ||||||
|  |     result = dashboard_settings.rel_path("link.yaml") | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "link.yaml") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_symlink_outside_config(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path with symlink that points outside config dir.""" | ||||||
|  |     with tempfile.NamedTemporaryFile(suffix=".yaml") as tmp: | ||||||
|  |         symlink = dashboard_settings.absolute_config_dir / "external_link.yaml" | ||||||
|  |         symlink.symlink_to(tmp.name) | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             dashboard_settings.rel_path("external_link.yaml") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_with_none_arg(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path handles None arguments gracefully.""" | ||||||
|  |     result = dashboard_settings.rel_path("None") | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "None") | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_rel_path_with_numeric_args(dashboard_settings: DashboardSettings) -> None: | ||||||
|  |     """Test rel_path handles numeric arguments.""" | ||||||
|  |     result = dashboard_settings.rel_path("123", "456.789") | ||||||
|  |     expected = str(Path(dashboard_settings.config_dir) / "123" / "456.789") | ||||||
|  |     assert result == expected | ||||||
							
								
								
									
										230
									
								
								tests/dashboard/test_web_server_paths.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								tests/dashboard/test_web_server_paths.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | |||||||
|  | """Tests for dashboard web_server Path-related functionality.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import gzip | ||||||
|  | import os | ||||||
|  | from pathlib import Path | ||||||
|  | from unittest.mock import MagicMock, patch | ||||||
|  |  | ||||||
|  | from esphome.dashboard import web_server | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_base_frontend_path_production() -> None: | ||||||
|  |     """Test get_base_frontend_path in production mode.""" | ||||||
|  |     mock_module = MagicMock() | ||||||
|  |     mock_module.where.return_value = "/usr/local/lib/esphome_dashboard" | ||||||
|  |  | ||||||
|  |     with ( | ||||||
|  |         patch.dict(os.environ, {}, clear=True), | ||||||
|  |         patch.dict("sys.modules", {"esphome_dashboard": mock_module}), | ||||||
|  |     ): | ||||||
|  |         result = web_server.get_base_frontend_path() | ||||||
|  |         assert result == "/usr/local/lib/esphome_dashboard" | ||||||
|  |         mock_module.where.assert_called_once() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_base_frontend_path_dev_mode() -> None: | ||||||
|  |     """Test get_base_frontend_path in development mode.""" | ||||||
|  |     test_path = "/home/user/esphome/dashboard" | ||||||
|  |  | ||||||
|  |     with patch.dict(os.environ, {"ESPHOME_DASHBOARD_DEV": test_path}): | ||||||
|  |         result = web_server.get_base_frontend_path() | ||||||
|  |  | ||||||
|  |         # The function uses os.path.abspath which doesn't resolve symlinks | ||||||
|  |         # We need to match that behavior | ||||||
|  |         # The actual function adds "/" to the path, so we simulate that | ||||||
|  |         test_path_with_slash = test_path if test_path.endswith("/") else test_path + "/" | ||||||
|  |         expected = os.path.abspath( | ||||||
|  |             os.path.join(os.getcwd(), test_path_with_slash, "esphome_dashboard") | ||||||
|  |         ) | ||||||
|  |         assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_base_frontend_path_dev_mode_with_trailing_slash() -> None: | ||||||
|  |     """Test get_base_frontend_path in dev mode with trailing slash.""" | ||||||
|  |     test_path = "/home/user/esphome/dashboard/" | ||||||
|  |  | ||||||
|  |     with patch.dict(os.environ, {"ESPHOME_DASHBOARD_DEV": test_path}): | ||||||
|  |         result = web_server.get_base_frontend_path() | ||||||
|  |  | ||||||
|  |         # The function uses os.path.abspath which doesn't resolve symlinks | ||||||
|  |         expected = os.path.abspath(str(Path.cwd() / test_path / "esphome_dashboard")) | ||||||
|  |         assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_base_frontend_path_dev_mode_relative_path() -> None: | ||||||
|  |     """Test get_base_frontend_path with relative dev path.""" | ||||||
|  |     test_path = "./dashboard" | ||||||
|  |  | ||||||
|  |     with patch.dict(os.environ, {"ESPHOME_DASHBOARD_DEV": test_path}): | ||||||
|  |         result = web_server.get_base_frontend_path() | ||||||
|  |  | ||||||
|  |         # The function uses os.path.abspath which doesn't resolve symlinks | ||||||
|  |         # We need to match that behavior | ||||||
|  |         # The actual function adds "/" to the path, so we simulate that | ||||||
|  |         test_path_with_slash = test_path if test_path.endswith("/") else test_path + "/" | ||||||
|  |         expected = os.path.abspath( | ||||||
|  |             os.path.join(os.getcwd(), test_path_with_slash, "esphome_dashboard") | ||||||
|  |         ) | ||||||
|  |         assert result == expected | ||||||
|  |         assert Path(result).is_absolute() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_path_single_component() -> None: | ||||||
|  |     """Test get_static_path with single path component.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path("file.js") | ||||||
|  |  | ||||||
|  |         assert result == os.path.join("/base/frontend", "static", "file.js") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_path_multiple_components() -> None: | ||||||
|  |     """Test get_static_path with multiple path components.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path("js", "esphome", "index.js") | ||||||
|  |  | ||||||
|  |         assert result == os.path.join( | ||||||
|  |             "/base/frontend", "static", "js", "esphome", "index.js" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_path_empty_args() -> None: | ||||||
|  |     """Test get_static_path with no arguments.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path() | ||||||
|  |  | ||||||
|  |         assert result == os.path.join("/base/frontend", "static") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_path_with_pathlib_path() -> None: | ||||||
|  |     """Test get_static_path with Path objects.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         path_obj = Path("js") / "app.js" | ||||||
|  |         result = web_server.get_static_path(str(path_obj)) | ||||||
|  |  | ||||||
|  |         assert result == os.path.join("/base/frontend", "static", "js", "app.js") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_file_url_production() -> None: | ||||||
|  |     """Test get_static_file_url in production mode.""" | ||||||
|  |     web_server.get_static_file_url.cache_clear() | ||||||
|  |     mock_module = MagicMock() | ||||||
|  |     mock_file = MagicMock() | ||||||
|  |     mock_file.read.return_value = b"test content" | ||||||
|  |     mock_file.__enter__ = MagicMock(return_value=mock_file) | ||||||
|  |     mock_file.__exit__ = MagicMock(return_value=None) | ||||||
|  |  | ||||||
|  |     with ( | ||||||
|  |         patch.dict(os.environ, {}, clear=True), | ||||||
|  |         patch.dict("sys.modules", {"esphome_dashboard": mock_module}), | ||||||
|  |         patch("esphome.dashboard.web_server.get_static_path") as mock_get_path, | ||||||
|  |         patch("esphome.dashboard.web_server.open", create=True, return_value=mock_file), | ||||||
|  |     ): | ||||||
|  |         mock_get_path.return_value = "/fake/path/js/app.js" | ||||||
|  |         result = web_server.get_static_file_url("js/app.js") | ||||||
|  |         assert result.startswith("./static/js/app.js?hash=") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_file_url_dev_mode() -> None: | ||||||
|  |     """Test get_static_file_url in development mode.""" | ||||||
|  |     with patch.dict(os.environ, {"ESPHOME_DASHBOARD_DEV": "/dev/path"}): | ||||||
|  |         web_server.get_static_file_url.cache_clear() | ||||||
|  |         result = web_server.get_static_file_url("js/app.js") | ||||||
|  |  | ||||||
|  |         assert result == "./static/js/app.js" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_static_file_url_index_js_special_case() -> None: | ||||||
|  |     """Test get_static_file_url replaces index.js with entrypoint.""" | ||||||
|  |     web_server.get_static_file_url.cache_clear() | ||||||
|  |     mock_module = MagicMock() | ||||||
|  |     mock_module.entrypoint.return_value = "main.js" | ||||||
|  |  | ||||||
|  |     with ( | ||||||
|  |         patch.dict(os.environ, {}, clear=True), | ||||||
|  |         patch.dict("sys.modules", {"esphome_dashboard": mock_module}), | ||||||
|  |     ): | ||||||
|  |         result = web_server.get_static_file_url("js/esphome/index.js") | ||||||
|  |         assert result == "./static/js/esphome/main.js" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_load_file_path(tmp_path: Path) -> None: | ||||||
|  |     """Test loading a file.""" | ||||||
|  |     test_file = tmp_path / "test.txt" | ||||||
|  |     test_file.write_bytes(b"test content") | ||||||
|  |  | ||||||
|  |     with open(test_file, "rb") as f: | ||||||
|  |         content = f.read() | ||||||
|  |     assert content == b"test content" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_load_file_compressed_path(tmp_path: Path) -> None: | ||||||
|  |     """Test loading a compressed file.""" | ||||||
|  |     test_file = tmp_path / "test.txt.gz" | ||||||
|  |  | ||||||
|  |     with gzip.open(test_file, "wb") as gz: | ||||||
|  |         gz.write(b"compressed content") | ||||||
|  |  | ||||||
|  |     with gzip.open(test_file, "rb") as gz: | ||||||
|  |         content = gz.read() | ||||||
|  |     assert content == b"compressed content" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_path_normalization_in_static_path() -> None: | ||||||
|  |     """Test that paths are normalized correctly.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         # Test with separate components | ||||||
|  |         result1 = web_server.get_static_path("js", "app.js") | ||||||
|  |         result2 = web_server.get_static_path("js", "app.js") | ||||||
|  |  | ||||||
|  |         assert result1 == result2 | ||||||
|  |         assert result1 == os.path.join("/base/frontend", "static", "js", "app.js") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_windows_path_handling() -> None: | ||||||
|  |     """Test handling of Windows-style paths.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = r"C:\Program Files\esphome\frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path("js", "app.js") | ||||||
|  |  | ||||||
|  |         # os.path.join should handle this correctly on the platform | ||||||
|  |         expected = os.path.join( | ||||||
|  |             r"C:\Program Files\esphome\frontend", "static", "js", "app.js" | ||||||
|  |         ) | ||||||
|  |         assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_path_with_special_characters() -> None: | ||||||
|  |     """Test paths with special characters.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path("js-modules", "app_v1.0.js") | ||||||
|  |  | ||||||
|  |         assert result == os.path.join( | ||||||
|  |             "/base/frontend", "static", "js-modules", "app_v1.0.js" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_path_with_spaces() -> None: | ||||||
|  |     """Test paths with spaces.""" | ||||||
|  |     with patch("esphome.dashboard.web_server.get_base_frontend_path") as mock_base: | ||||||
|  |         mock_base.return_value = "/base/my frontend" | ||||||
|  |  | ||||||
|  |         result = web_server.get_static_path("my js", "my app.js") | ||||||
|  |  | ||||||
|  |         assert result == os.path.join( | ||||||
|  |             "/base/my frontend", "static", "my js", "my app.js" | ||||||
|  |         ) | ||||||
		Reference in New Issue
	
	Block a user