mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add additional dashboard and main tests (#10688)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
		| @@ -556,6 +556,66 @@ def test_start_web_server_with_address_port( | ||||
|     assert (archive_dir / "old.yaml").exists() | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_edit_request_handler_get(dashboard: DashboardTestHelper) -> None: | ||||
|     """Test EditRequestHandler.get method.""" | ||||
|     # Test getting a valid yaml file | ||||
|     response = await dashboard.fetch("/edit?configuration=pico.yaml") | ||||
|     assert response.code == 200 | ||||
|     assert response.headers["content-type"] == "application/yaml" | ||||
|     content = response.body.decode() | ||||
|     assert "esphome:" in content  # Verify it's a valid ESPHome config | ||||
|  | ||||
|     # Test getting a non-existent file | ||||
|     with pytest.raises(HTTPClientError) as exc_info: | ||||
|         await dashboard.fetch("/edit?configuration=nonexistent.yaml") | ||||
|     assert exc_info.value.code == 404 | ||||
|  | ||||
|     # Test getting a non-yaml file | ||||
|     with pytest.raises(HTTPClientError) as exc_info: | ||||
|         await dashboard.fetch("/edit?configuration=test.txt") | ||||
|     assert exc_info.value.code == 404 | ||||
|  | ||||
|     # Test path traversal attempt | ||||
|     with pytest.raises(HTTPClientError) as exc_info: | ||||
|         await dashboard.fetch("/edit?configuration=../../../etc/passwd") | ||||
|     assert exc_info.value.code == 404 | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_archive_request_handler_post( | ||||
|     dashboard: DashboardTestHelper, | ||||
|     mock_archive_storage_path: MagicMock, | ||||
|     mock_ext_storage_path: MagicMock, | ||||
|     tmp_path: Path, | ||||
| ) -> None: | ||||
|     """Test ArchiveRequestHandler.post method.""" | ||||
|  | ||||
|     # Set up temp directories | ||||
|     config_dir = Path(get_fixture_path("conf")) | ||||
|     archive_dir = tmp_path / "archive" | ||||
|  | ||||
|     # Create a test configuration file | ||||
|     test_config = config_dir / "test_archive.yaml" | ||||
|     test_config.write_text("esphome:\n  name: test_archive\n") | ||||
|  | ||||
|     # Archive the configuration | ||||
|     response = await dashboard.fetch( | ||||
|         "/archive", | ||||
|         method="POST", | ||||
|         body="configuration=test_archive.yaml", | ||||
|         headers={"Content-Type": "application/x-www-form-urlencoded"}, | ||||
|     ) | ||||
|     assert response.code == 200 | ||||
|  | ||||
|     # Verify file was moved to archive | ||||
|     assert not test_config.exists() | ||||
|     assert (archive_dir / "test_archive.yaml").exists() | ||||
|     assert ( | ||||
|         archive_dir / "test_archive.yaml" | ||||
|     ).read_text() == "esphome:\n  name: test_archive\n" | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(os.name == "nt", reason="Unix sockets are not supported on Windows") | ||||
| @pytest.mark.usefixtures("mock_trash_storage_path", "mock_archive_storage_path") | ||||
| def test_start_web_server_with_unix_socket(tmp_path: Path) -> None: | ||||
|   | ||||
| @@ -9,18 +9,27 @@ from typing import Any | ||||
| from unittest.mock import MagicMock, Mock, patch | ||||
|  | ||||
| import pytest | ||||
| from pytest import CaptureFixture | ||||
|  | ||||
| from esphome.__main__ import choose_upload_log_host, show_logs, upload_program | ||||
| from esphome.__main__ import ( | ||||
|     choose_upload_log_host, | ||||
|     command_rename, | ||||
|     command_wizard, | ||||
|     show_logs, | ||||
|     upload_program, | ||||
| ) | ||||
| from esphome.const import ( | ||||
|     CONF_BROKER, | ||||
|     CONF_DISABLED, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_MDNS, | ||||
|     CONF_MQTT, | ||||
|     CONF_NAME, | ||||
|     CONF_OTA, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PLATFORM, | ||||
|     CONF_PORT, | ||||
|     CONF_SUBSTITUTIONS, | ||||
|     CONF_USE_ADDRESS, | ||||
|     CONF_WIFI, | ||||
|     KEY_CORE, | ||||
| @@ -170,6 +179,14 @@ def mock_has_mqtt_logging() -> Generator[Mock]: | ||||
|         yield mock | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def mock_run_external_process() -> Generator[Mock]: | ||||
|     """Mock run_external_process for testing.""" | ||||
|     with patch("esphome.__main__.run_external_process") as mock: | ||||
|         mock.return_value = 0  # Default to success | ||||
|         yield mock | ||||
|  | ||||
|  | ||||
| def test_choose_upload_log_host_with_string_default() -> None: | ||||
|     """Test with a single string default device.""" | ||||
|     result = choose_upload_log_host( | ||||
| @@ -606,6 +623,9 @@ class MockArgs: | ||||
|     password: str | None = None | ||||
|     client_id: str | None = None | ||||
|     topic: str | None = None | ||||
|     configuration: str | None = None | ||||
|     name: str | None = None | ||||
|     dashboard: bool = False | ||||
|  | ||||
|  | ||||
| def test_upload_program_serial_esp32( | ||||
| @@ -1053,3 +1073,178 @@ def test_show_logs_platform_specific_handler( | ||||
|     assert result == 0 | ||||
|     mock_import.assert_called_once_with("esphome.components.custom_platform") | ||||
|     mock_module.show_logs.assert_called_once_with(config, args, devices) | ||||
|  | ||||
|  | ||||
| def test_command_wizard(tmp_path: Path) -> None: | ||||
|     """Test command_wizard function.""" | ||||
|     config_file = tmp_path / "test.yaml" | ||||
|  | ||||
|     # Mock wizard.wizard to avoid interactive prompts | ||||
|     with patch("esphome.wizard.wizard") as mock_wizard: | ||||
|         mock_wizard.return_value = 0 | ||||
|  | ||||
|         args = MockArgs(configuration=str(config_file)) | ||||
|         result = command_wizard(args) | ||||
|  | ||||
|         assert result == 0 | ||||
|         mock_wizard.assert_called_once_with(str(config_file)) | ||||
|  | ||||
|  | ||||
| def test_command_rename_invalid_characters( | ||||
|     tmp_path: Path, capfd: CaptureFixture[str] | ||||
| ) -> None: | ||||
|     """Test command_rename with invalid characters in name.""" | ||||
|     setup_core(tmp_path=tmp_path) | ||||
|  | ||||
|     # Test with invalid character (space) | ||||
|     args = MockArgs(name="invalid name") | ||||
|     result = command_rename(args, {}) | ||||
|  | ||||
|     assert result == 1 | ||||
|     captured = capfd.readouterr() | ||||
|     assert "invalid character" in captured.out.lower() | ||||
|  | ||||
|  | ||||
| def test_command_rename_complex_yaml( | ||||
|     tmp_path: Path, capfd: CaptureFixture[str] | ||||
| ) -> None: | ||||
|     """Test command_rename with complex YAML that cannot be renamed.""" | ||||
|     config_file = tmp_path / "test.yaml" | ||||
|     config_file.write_text("# Complex YAML without esphome section\nsome_key: value\n") | ||||
|     setup_core(tmp_path=tmp_path) | ||||
|     CORE.config_path = str(config_file) | ||||
|  | ||||
|     args = MockArgs(name="newname") | ||||
|     result = command_rename(args, {}) | ||||
|  | ||||
|     assert result == 1 | ||||
|     captured = capfd.readouterr() | ||||
|     assert "complex yaml" in captured.out.lower() | ||||
|  | ||||
|  | ||||
| def test_command_rename_success( | ||||
|     tmp_path: Path, | ||||
|     capfd: CaptureFixture[str], | ||||
|     mock_run_external_process: Mock, | ||||
| ) -> None: | ||||
|     """Test successful rename of a simple configuration.""" | ||||
|     config_file = tmp_path / "oldname.yaml" | ||||
|     config_file.write_text(""" | ||||
| esphome: | ||||
|   name: oldname | ||||
|  | ||||
| esp32: | ||||
|   board: nodemcu-32s | ||||
|  | ||||
| wifi: | ||||
|   ssid: "test" | ||||
|   password: "test1234" | ||||
| """) | ||||
|     setup_core(tmp_path=tmp_path) | ||||
|     CORE.config_path = str(config_file) | ||||
|  | ||||
|     # Set up CORE.config to avoid ValueError when accessing CORE.address | ||||
|     CORE.config = {CONF_ESPHOME: {CONF_NAME: "oldname"}} | ||||
|  | ||||
|     args = MockArgs(name="newname", dashboard=False) | ||||
|  | ||||
|     # Simulate successful validation and upload | ||||
|     mock_run_external_process.return_value = 0 | ||||
|  | ||||
|     result = command_rename(args, {}) | ||||
|  | ||||
|     assert result == 0 | ||||
|  | ||||
|     # Verify new file was created | ||||
|     new_file = tmp_path / "newname.yaml" | ||||
|     assert new_file.exists() | ||||
|  | ||||
|     # Verify old file was removed | ||||
|     assert not config_file.exists() | ||||
|  | ||||
|     # Verify content was updated | ||||
|     content = new_file.read_text() | ||||
|     assert ( | ||||
|         'name: "newname"' in content | ||||
|         or "name: 'newname'" in content | ||||
|         or "name: newname" in content | ||||
|     ) | ||||
|  | ||||
|     captured = capfd.readouterr() | ||||
|     assert "SUCCESS" in captured.out | ||||
|  | ||||
|  | ||||
| def test_command_rename_with_substitutions( | ||||
|     tmp_path: Path, | ||||
|     mock_run_external_process: Mock, | ||||
| ) -> None: | ||||
|     """Test rename with substitutions in YAML.""" | ||||
|     config_file = tmp_path / "oldname.yaml" | ||||
|     config_file.write_text(""" | ||||
| substitutions: | ||||
|   device_name: oldname | ||||
|  | ||||
| esphome: | ||||
|   name: ${device_name} | ||||
|  | ||||
| esp32: | ||||
|   board: nodemcu-32s | ||||
| """) | ||||
|     setup_core(tmp_path=tmp_path) | ||||
|     CORE.config_path = str(config_file) | ||||
|  | ||||
|     # Set up CORE.config to avoid ValueError when accessing CORE.address | ||||
|     CORE.config = { | ||||
|         CONF_ESPHOME: {CONF_NAME: "oldname"}, | ||||
|         CONF_SUBSTITUTIONS: {"device_name": "oldname"}, | ||||
|     } | ||||
|  | ||||
|     args = MockArgs(name="newname", dashboard=False) | ||||
|  | ||||
|     mock_run_external_process.return_value = 0 | ||||
|  | ||||
|     result = command_rename(args, {}) | ||||
|  | ||||
|     assert result == 0 | ||||
|  | ||||
|     # Verify substitution was updated | ||||
|     new_file = tmp_path / "newname.yaml" | ||||
|     content = new_file.read_text() | ||||
|     assert 'device_name: "newname"' in content | ||||
|  | ||||
|  | ||||
| def test_command_rename_validation_failure( | ||||
|     tmp_path: Path, | ||||
|     capfd: CaptureFixture[str], | ||||
|     mock_run_external_process: Mock, | ||||
| ) -> None: | ||||
|     """Test rename when validation fails.""" | ||||
|     config_file = tmp_path / "oldname.yaml" | ||||
|     config_file.write_text(""" | ||||
| esphome: | ||||
|   name: oldname | ||||
|  | ||||
| esp32: | ||||
|   board: nodemcu-32s | ||||
| """) | ||||
|     setup_core(tmp_path=tmp_path) | ||||
|     CORE.config_path = str(config_file) | ||||
|  | ||||
|     args = MockArgs(name="newname", dashboard=False) | ||||
|  | ||||
|     # First call for validation fails | ||||
|     mock_run_external_process.return_value = 1 | ||||
|  | ||||
|     result = command_rename(args, {}) | ||||
|  | ||||
|     assert result == 1 | ||||
|  | ||||
|     # Verify new file was created but then removed due to failure | ||||
|     new_file = tmp_path / "newname.yaml" | ||||
|     assert not new_file.exists() | ||||
|  | ||||
|     # Verify old file still exists (not removed on failure) | ||||
|     assert config_file.exists() | ||||
|  | ||||
|     captured = capfd.readouterr() | ||||
|     assert "Rename failed" in captured.out | ||||
|   | ||||
		Reference in New Issue
	
	Block a user