| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """A sliced 3MF is sliced whatever it is called (#2993).
- Downloading an archive that shows the green GCODE badge and re-importing it
- produced, for some archives, a source-only project with no Print button. The
- G-code was never lost -- the download serves the stored file byte for byte.
- What differed was who was asked: the Archives card looked inside the zip, while
- the library decided from the filename alone.
- That splits on how the print reached the printer, which is why it looked
- random. Bambu Studio's LAN send names a file ``Foo.gcode.3mf``; a per-plate
- export or a cloud-dispatched print arrives as ``Foo.3mf``, G-code and all. Both
- archive fine, both badge fine, and only the second one came back as a project.
- These tests pin the two halves to one answer, and pin the escape hatch that
- keeps ingest cheap: the file is opened only when the name has not already
- settled it.
- """
- from __future__ import annotations
- import zipfile
- from pathlib import Path
- import pytest
- from backend.app.api.routes.library import classify_file_type
- from backend.app.utils.threemf_tools import carries_gcode, names_carry_gcode
- SLICED = ["3D/3dmodel.model", "Metadata/plate_3.gcode", "Metadata/plate_3.gcode.md5"]
- SOURCE = ["3D/3dmodel.model", "Metadata/plate_1.png", "Metadata/project_settings.config"]
- def _write_3mf(path: Path, names: list[str]) -> Path:
- with zipfile.ZipFile(path, "w") as zf:
- for name in names:
- zf.writestr(name, b"x")
- return path
- class TestTheSharedAnswer:
- def test_plate_gcode_makes_it_sliced(self):
- assert names_carry_gcode(SLICED) is True
- def test_a_project_export_is_not(self):
- assert names_carry_gcode(SOURCE) is False
- def test_a_slicer_that_does_not_use_plate_naming_still_counts(self):
- """Deferring to default_plate_gcode_name rather than matching
- ``Metadata/plate_<n>.gcode`` is the point: a file that is executable on
- a printer must not be filed as a model because of where its G-code
- member sits."""
- assert names_carry_gcode(["3D/3dmodel.model", "output.gcode"]) is True
- def test_an_unreadable_file_reads_as_not_sliced(self, tmp_path):
- """The pre-existing behaviour for anything that cannot be opened. An
- ingest path must not fail on a truncated upload."""
- broken = tmp_path / "broken.3mf"
- broken.write_bytes(b"PK\x03\x04not-a-zip")
- assert carries_gcode(broken) is False
- assert carries_gcode(tmp_path / "absent.3mf") is False
- class TestClassification:
- def test_the_reported_file(self, tmp_path):
- """The whole bug in one line: same bytes, name says project."""
- sliced = _write_3mf(tmp_path / "Labyrinth.3mf", SLICED)
- assert classify_file_type("Labyrinth.3mf") == "3mf"
- assert classify_file_type("Labyrinth.3mf", sliced) == "gcode.3mf"
- def test_a_genuine_project_stays_a_project(self, tmp_path):
- """The guard that keeps this from swallowing the model library: an
- unsliced 3MF must not gain a Print button."""
- source = _write_3mf(tmp_path / "Labyrinth.3mf", SOURCE)
- assert classify_file_type("Labyrinth.3mf", source) == "3mf"
- def test_a_name_that_already_says_sliced_needs_no_file(self):
- """Not a micro-optimisation: the upload path classifies before the
- bytes are anywhere, and the external scan runs over a mount. Neither
- may depend on the file being openable when the name is enough."""
- assert classify_file_type("Labyrinth.gcode.3mf", Path("/does/not/exist.3mf")) == "gcode.3mf"
- @pytest.mark.parametrize("filename", ["model.stl", "preview.png", "README", "model.gcode"])
- def test_nothing_else_is_sniffed(self, filename, tmp_path):
- """Only `.3mf` is ambiguous. Handing a path for anything else must not
- change its type or open the file."""
- before = classify_file_type(filename)
- assert classify_file_type(filename, _write_3mf(tmp_path / "z.3mf", SLICED)) == before
- class TestTheTwoSidesAgree:
- def test_the_archives_badge_and_the_library_now_say_the_same_thing(self, tmp_path):
- """The card promised G-code and the File Manager denied it. Asserted
- against the archive endpoint's own expression, so this fails if that
- side is ever pointed back at a private copy of the rule."""
- from backend.app.api.routes.archives import names_carry_gcode as archives_predicate
- sliced = _write_3mf(tmp_path / "Labyrinth.3mf", SLICED)
- with zipfile.ZipFile(sliced) as zf:
- badge = archives_predicate(zf.namelist())
- assert badge is True
- assert classify_file_type("Labyrinth.3mf", sliced) == "gcode.3mf"
|