| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- """The one-shot pass that reaches library rows already stored (#2993).
- The forward fix classifies on content, which does nothing for the files a user
- has already imported -- the reporter's whole complaint was about files they had
- downloaded and imported before this existed. This backfill re-opens them.
- Two properties matter as much as the re-typing itself. It must run once: a
- genuine source 3MF keeps matching ``file_type = '3mf'`` forever, so an ungated
- pass would re-open every model in the library on every boot. And it must leave
- external rows alone: they point at a mount that can be slow, unmounted, or
- enormous, and startup is the worst place to discover that.
- """
- from __future__ import annotations
- import zipfile
- from pathlib import Path
- import pytest
- from sqlalchemy import text
- from sqlalchemy.ext.asyncio import create_async_engine
- from backend.app.core.database import run_migrations
- @pytest.fixture(autouse=True)
- def force_sqlite_dialect(monkeypatch):
- from backend.app.core import database as database_module, db_dialect
- monkeypatch.setattr(db_dialect, "is_sqlite", lambda: True)
- monkeypatch.setattr(db_dialect, "is_postgres", lambda: False)
- monkeypatch.setattr(database_module, "is_sqlite", lambda: True)
- @pytest.fixture(autouse=True)
- def base_dir(tmp_path, monkeypatch):
- """Relative file_path values resolve against settings.base_dir."""
- from backend.app.core.database import settings
- monkeypatch.setattr(settings, "base_dir", tmp_path)
- return tmp_path
- def _register_all_models():
- from backend.app.models import ( # noqa: F401
- ams_history,
- ams_label,
- api_key,
- archive,
- color_catalog,
- external_link,
- filament,
- group,
- kprofile_note,
- library,
- maintenance,
- notification,
- notification_template,
- print_log,
- print_queue,
- printer,
- project,
- project_bom,
- settings,
- slot_preset,
- smart_plug,
- smart_plug_energy_snapshot,
- spool,
- spool_assignment,
- spool_catalog,
- spool_k_profile,
- spool_usage_history,
- spoolbuddy_device,
- user,
- user_email_pref,
- virtual_printer,
- )
- @pytest.fixture
- async def engine():
- from backend.app.core.database import Base
- _register_all_models()
- eng = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
- async with eng.begin() as conn:
- await conn.run_sync(Base.metadata.create_all)
- yield eng
- await eng.dispose()
- def _write_3mf(path: Path, *, sliced: bool) -> None:
- path.parent.mkdir(parents=True, exist_ok=True)
- names = ["3D/3dmodel.model"] + (["Metadata/plate_3.gcode"] if sliced else ["Metadata/plate_1.png"])
- with zipfile.ZipFile(path, "w") as zf:
- for name in names:
- zf.writestr(name, b"x")
- async def _insert_file(conn, *, file_id: int, filename: str, path: str, external: bool = False) -> None:
- await conn.execute(
- text(
- "INSERT INTO library_files "
- "(id, filename, file_path, file_type, file_size, is_external, print_count) "
- "VALUES (:id, :filename, :path, '3mf', 0, :ext, 0)"
- ),
- {"id": file_id, "filename": filename, "path": path, "ext": 1 if external else 0},
- )
- async def _types(engine) -> dict[int, str]:
- async with engine.connect() as conn:
- return dict((await conn.execute(text("SELECT id, file_type FROM library_files ORDER BY id"))).fetchall())
- @pytest.mark.asyncio
- async def test_a_stored_sliced_3mf_is_re_typed(engine, base_dir):
- _write_3mf(base_dir / "files/sliced.3mf", sliced=True)
- _write_3mf(base_dir / "files/model.3mf", sliced=False)
- async with engine.begin() as conn:
- await _insert_file(conn, file_id=1, filename="Labyrinth.3mf", path="files/sliced.3mf")
- await _insert_file(conn, file_id=2, filename="Labyrinth.3mf", path="files/model.3mf")
- async with engine.begin() as conn:
- await run_migrations(conn)
- types = await _types(engine)
- assert types[1] == "gcode.3mf"
- assert types[2] == "3mf", "a genuine project must not gain a Print button"
- @pytest.mark.asyncio
- async def test_a_missing_file_does_not_stop_the_pass(engine, base_dir):
- """A library with holes in it still finishes -- the row after the gap is
- the one that proves it."""
- _write_3mf(base_dir / "files/sliced.3mf", sliced=True)
- async with engine.begin() as conn:
- await _insert_file(conn, file_id=1, filename="gone.3mf", path="files/gone.3mf")
- await _insert_file(conn, file_id=2, filename="Labyrinth.3mf", path="files/sliced.3mf")
- async with engine.begin() as conn:
- await run_migrations(conn)
- types = await _types(engine)
- assert types[1] == "3mf"
- assert types[2] == "gcode.3mf"
- @pytest.mark.asyncio
- async def test_external_rows_are_left_to_their_own_scan(engine, base_dir):
- """Even a sliced one. The folder's scan re-types it without putting a
- possibly-unreachable mount on the startup path."""
- _write_3mf(base_dir / "mount/sliced.3mf", sliced=True)
- async with engine.begin() as conn:
- await _insert_file(conn, file_id=1, filename="Labyrinth.3mf", path="mount/sliced.3mf", external=True)
- async with engine.begin() as conn:
- await run_migrations(conn)
- assert (await _types(engine))[1] == "3mf"
- @pytest.mark.asyncio
- async def test_it_runs_once(engine, base_dir):
- """Every boot re-runs the migration set. A source 3MF matches the query
- forever, so without the flag this re-opens the whole library each time."""
- _write_3mf(base_dir / "files/model.3mf", sliced=False)
- async with engine.begin() as conn:
- await _insert_file(conn, file_id=1, filename="Labyrinth.3mf", path="files/model.3mf")
- async with engine.begin() as conn:
- await run_migrations(conn)
- # Second boot: the file becomes readable-as-sliced, and must be ignored,
- # which can only happen if the pass is genuinely gated rather than merely
- # idempotent in its effect.
- _write_3mf(base_dir / "files/model.3mf", sliced=True)
- async with engine.begin() as conn:
- await run_migrations(conn)
- assert (await _types(engine))[1] == "3mf"
|