mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
When FTP download of a 3MF fails (e.g. BambuStudio-initiated prints), the fallback archive has file_path="". Path.exists() on settings.base_dir / "" resolves to the base directory itself and returns True, so ZipFile() then fails with [Errno 21] Is a directory. Replace .exists() with .is_file() across all 15 archive route checks and 1 in main.py. Add file_path truthiness guard for finish photo capture to prevent saving photos under the base directory.
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
"""Tests for archive file_path guard against empty paths and directories (#475).
|
|
|
|
When a 3mf download fails (e.g. BambuStudio-initiated prints), the fallback
|
|
archive is created with file_path="". Previously, `settings.base_dir / ""`
|
|
resolved to the base directory itself, which passed `exists()` but caused
|
|
`[Errno 21] Is a directory` when opened as a ZipFile.
|
|
|
|
The fix replaces `.exists()` with `.is_file()` across all archive endpoints,
|
|
and adds an `archive.file_path` truthiness check for photo capture.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
class TestIsFileGuard:
|
|
"""Verify that is_file() correctly rejects directories and empty paths."""
|
|
|
|
def test_empty_path_resolves_to_parent(self, tmp_path: Path):
|
|
"""Path('') / '' resolves to the parent directory (which exists but is not a file)."""
|
|
base_dir = tmp_path / "data"
|
|
base_dir.mkdir()
|
|
|
|
file_path = base_dir / ""
|
|
# exists() returns True (the directory exists) — this was the old broken check
|
|
assert file_path.exists()
|
|
# is_file() returns False (it's a directory, not a file)
|
|
assert not file_path.is_file()
|
|
|
|
def test_real_file_passes_is_file(self, tmp_path: Path):
|
|
"""A real 3mf file passes is_file()."""
|
|
fake_3mf = tmp_path / "archive" / "test.3mf"
|
|
fake_3mf.parent.mkdir(parents=True)
|
|
fake_3mf.write_bytes(b"PK\x03\x04") # ZIP magic bytes
|
|
|
|
assert fake_3mf.is_file()
|
|
|
|
def test_nonexistent_file_fails_is_file(self, tmp_path: Path):
|
|
"""A nonexistent path fails is_file()."""
|
|
missing = tmp_path / "archive" / "missing.3mf"
|
|
assert not missing.is_file()
|
|
|
|
def test_directory_fails_is_file(self, tmp_path: Path):
|
|
"""A directory path fails is_file()."""
|
|
dir_path = tmp_path / "archive"
|
|
dir_path.mkdir()
|
|
assert not dir_path.is_file()
|
|
|
|
|
|
class TestFallbackArchiveFilePath:
|
|
"""Verify that a fallback archive (file_path='') is handled safely."""
|
|
|
|
def test_base_dir_slash_empty_string_is_base_dir(self, tmp_path: Path):
|
|
"""Joining base_dir with empty string produces base_dir (a directory)."""
|
|
base_dir = tmp_path / "data"
|
|
base_dir.mkdir()
|
|
|
|
# Simulate: file_path = settings.base_dir / archive.file_path
|
|
# where archive.file_path = ""
|
|
file_path = base_dir / ""
|
|
|
|
# The resolved path IS the directory itself
|
|
assert file_path.resolve() == base_dir.resolve()
|
|
# exists() says True (this caused the old bug)
|
|
assert file_path.exists()
|
|
# is_file() says False (this is the fix)
|
|
assert not file_path.is_file()
|
|
|
|
def test_archive_file_path_empty_string_is_falsy(self):
|
|
"""Empty string file_path is falsy (used for photo capture guard)."""
|
|
file_path = ""
|
|
assert not file_path
|
|
|
|
def test_archive_file_path_real_is_truthy(self):
|
|
"""Real file_path is truthy."""
|
|
file_path = "archive/2026/02/test.3mf"
|
|
assert file_path
|
|
|
|
|
|
class TestPhotoPathDerivation:
|
|
"""Verify that photo directory derivation is safe with empty file_path."""
|
|
|
|
def test_empty_file_path_parent_is_dot(self):
|
|
"""Path('').parent is '.' — would resolve to base_dir instead of archive dir."""
|
|
parent = Path("").parent
|
|
assert str(parent) == "."
|
|
|
|
def test_real_file_path_parent_is_archive_dir(self):
|
|
"""Real file_path parent gives the correct archive directory."""
|
|
parent = Path("archive/2026/02/test.3mf").parent
|
|
assert str(parent) == "archive/2026/02"
|