1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-16 10:12:21 +01:00

[dashboard] Fix archive handler incorrectly deleting build folders instead of archiving them

This commit is contained in:
J. Nick Koston
2025-09-14 19:22:33 -05:00
parent af3e1788d1
commit f857fa1f0d
2 changed files with 122 additions and 12 deletions

View File

@@ -1039,11 +1039,11 @@ class ArchiveRequestHandler(BaseHandler):
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is not None: if storage_json is not None:
# Delete build folder (if exists) # Move build folder to archive (if exists)
name = storage_json.name name = storage_json.name
build_folder = os.path.join(settings.config_dir, name) build_folder = os.path.join(settings.config_dir, name)
if build_folder is not None: if os.path.exists(build_folder):
shutil.rmtree(build_folder, os.path.join(archive_path, name)) shutil.move(build_folder, os.path.join(archive_path, name))
class UnArchiveRequestHandler(BaseHandler): class UnArchiveRequestHandler(BaseHandler):

View File

@@ -589,7 +589,7 @@ async def test_archive_request_handler_post(
mock_ext_storage_path: MagicMock, mock_ext_storage_path: MagicMock,
tmp_path: Path, tmp_path: Path,
) -> None: ) -> None:
"""Test ArchiveRequestHandler.post method.""" """Test ArchiveRequestHandler.post method without storage_json."""
# Set up temp directories # Set up temp directories
config_dir = Path(get_fixture_path("conf")) config_dir = Path(get_fixture_path("conf"))
@@ -599,14 +599,18 @@ async def test_archive_request_handler_post(
test_config = config_dir / "test_archive.yaml" test_config = config_dir / "test_archive.yaml"
test_config.write_text("esphome:\n name: test_archive\n") test_config.write_text("esphome:\n name: test_archive\n")
# Archive the configuration # Mock storage_json to return None (no storage)
response = await dashboard.fetch( with patch("esphome.dashboard.web_server.StorageJSON.load") as mock_load:
"/archive", mock_load.return_value = None
method="POST",
body="configuration=test_archive.yaml", # Archive the configuration
headers={"Content-Type": "application/x-www-form-urlencoded"}, response = await dashboard.fetch(
) "/archive",
assert response.code == 200 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 # Verify file was moved to archive
assert not test_config.exists() assert not test_config.exists()
@@ -616,6 +620,112 @@ async def test_archive_request_handler_post(
).read_text() == "esphome:\n name: test_archive\n" ).read_text() == "esphome:\n name: test_archive\n"
@pytest.mark.asyncio
async def test_archive_handler_with_build_folder(
dashboard: DashboardTestHelper,
mock_archive_storage_path: MagicMock,
mock_ext_storage_path: MagicMock,
mock_dashboard_settings: MagicMock,
tmp_path: Path,
) -> None:
"""Test ArchiveRequestHandler.post with storage_json and build folder."""
# Set up temp directories
config_dir = tmp_path / "config"
config_dir.mkdir()
archive_dir = tmp_path / "archive"
archive_dir.mkdir()
# Create a test configuration file
configuration = "test_device.yaml"
test_config = config_dir / configuration
test_config.write_text("esphome:\n name: test_device\n")
# Create build folder with content
build_folder = config_dir / "test_device"
build_folder.mkdir()
(build_folder / "firmware.bin").write_text("binary content")
(build_folder / ".pioenvs").mkdir()
# Mock settings to use our temp directory
mock_dashboard_settings.config_dir = str(config_dir)
mock_dashboard_settings.rel_path.return_value = str(test_config)
mock_archive_storage_path.return_value = str(archive_dir)
# Mock storage_json with device name
with patch("esphome.dashboard.web_server.StorageJSON.load") as mock_load:
mock_storage = MagicMock()
mock_storage.name = "test_device"
mock_load.return_value = mock_storage
# Archive the configuration
response = await dashboard.fetch(
"/archive",
method="POST",
body=f"configuration={configuration}",
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert response.code == 200
# Verify config file was moved to archive
assert not test_config.exists()
assert (archive_dir / configuration).exists()
# Verify build folder was moved to archive
assert not build_folder.exists()
assert (archive_dir / "test_device").exists()
assert (archive_dir / "test_device" / "firmware.bin").exists()
@pytest.mark.asyncio
async def test_archive_handler_no_build_folder(
dashboard: DashboardTestHelper,
mock_archive_storage_path: MagicMock,
mock_ext_storage_path: MagicMock,
mock_dashboard_settings: MagicMock,
tmp_path: Path,
) -> None:
"""Test ArchiveRequestHandler.post with storage_json but no build folder."""
# Set up temp directories
config_dir = tmp_path / "config"
config_dir.mkdir()
archive_dir = tmp_path / "archive"
archive_dir.mkdir()
# Create a test configuration file
configuration = "test_device.yaml"
test_config = config_dir / configuration
test_config.write_text("esphome:\n name: test_device\n")
# Note: No build folder created
# Mock settings to use our temp directory
mock_dashboard_settings.config_dir = str(config_dir)
mock_dashboard_settings.rel_path.return_value = str(test_config)
mock_archive_storage_path.return_value = str(archive_dir)
# Mock storage_json with device name
with patch("esphome.dashboard.web_server.StorageJSON.load") as mock_load:
mock_storage = MagicMock()
mock_storage.name = "test_device"
mock_load.return_value = mock_storage
# Archive the configuration (should not fail even without build folder)
response = await dashboard.fetch(
"/archive",
method="POST",
body=f"configuration={configuration}",
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert response.code == 200
# Verify config file was moved to archive
assert not test_config.exists()
assert (archive_dir / configuration).exists()
# Verify no build folder in archive (since it didn't exist)
assert not (archive_dir / "test_device").exists()
@pytest.mark.skipif(os.name == "nt", reason="Unix sockets are not supported on Windows") @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") @pytest.mark.usefixtures("mock_trash_storage_path", "mock_archive_storage_path")
def test_start_web_server_with_unix_socket(tmp_path: Path) -> None: def test_start_web_server_with_unix_socket(tmp_path: Path) -> None: