mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 10:15:04 +02:00
Merge pull request #2610 from Person2099/feature/upload-prefer-filename-for-name
Expose prefer_filename_for_name on manual archive upload endpoint
This commit is contained in:
@@ -3429,10 +3429,28 @@ async def get_plate_preview(
|
||||
async def upload_archive(
|
||||
file: UploadFile = File(...),
|
||||
printer_id: int | None = None,
|
||||
prefer_filename_for_name: bool = Query(
|
||||
False,
|
||||
description=(
|
||||
"Name the archive after the uploaded filename instead of the print_name "
|
||||
"embedded in the 3MF's metadata. Off by default, which keeps the embedded "
|
||||
"name. Turn it on when the filename you send is the meaningful one — an "
|
||||
"integration naming files after its own jobs, or a file whose embedded "
|
||||
"title is a stale name from whoever originally sliced it."
|
||||
),
|
||||
),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User | None = RequirePermissionIfAuthEnabled(Permission.ARCHIVES_CREATE),
|
||||
):
|
||||
"""Manually upload a 3MF file to archive."""
|
||||
"""Manually upload a 3MF file to archive.
|
||||
|
||||
prefer_filename_for_name is the same flag the FTP review flow and
|
||||
virtual-printer dispatch already pass to ArchiveService.archive_print —
|
||||
this endpoint just didn't expose it (#1152 follow-up). Those callers derive
|
||||
it from the VP-scoped `virtual_printer_archive_name_source` setting; here it
|
||||
is per-request, because the caller is an API client that knows whether the
|
||||
filename it sent is the meaningful one (#2609).
|
||||
"""
|
||||
if not file.filename or not file.filename.endswith(".3mf"):
|
||||
raise HTTPException(400, "File must be a .3mf file")
|
||||
|
||||
@@ -3458,6 +3476,7 @@ async def upload_archive(
|
||||
printer_id=printer_id,
|
||||
source_file=temp_path,
|
||||
created_by_id=current_user.id if current_user else None,
|
||||
prefer_filename_for_name=prefer_filename_for_name,
|
||||
)
|
||||
|
||||
if not archive:
|
||||
@@ -3473,10 +3492,22 @@ async def upload_archive(
|
||||
async def upload_archives_bulk(
|
||||
files: list[UploadFile] = File(...),
|
||||
printer_id: int | None = None,
|
||||
prefer_filename_for_name: bool = Query(
|
||||
False,
|
||||
description=(
|
||||
"Name each archive after its uploaded filename instead of the print_name "
|
||||
"embedded in the 3MF's metadata. Applies to every file in the batch. Off "
|
||||
"by default, which keeps the embedded name."
|
||||
),
|
||||
),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User | None = RequirePermissionIfAuthEnabled(Permission.ARCHIVES_CREATE),
|
||||
):
|
||||
"""Bulk upload multiple 3MF files to archive."""
|
||||
"""Bulk upload multiple 3MF files to archive.
|
||||
|
||||
prefer_filename_for_name applies to every file in the batch. See
|
||||
upload_archive for the flag's lineage.
|
||||
"""
|
||||
from backend.app.api.routes.library import validate_print_file_upload
|
||||
|
||||
results = []
|
||||
@@ -3511,6 +3542,7 @@ async def upload_archives_bulk(
|
||||
printer_id=printer_id,
|
||||
source_file=temp_path,
|
||||
created_by_id=current_user.id if current_user else None,
|
||||
prefer_filename_for_name=prefer_filename_for_name,
|
||||
)
|
||||
|
||||
if archive:
|
||||
|
||||
@@ -4,6 +4,7 @@ Tests the full request/response cycle for /api/v1/archives/ endpoints.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
@@ -12,6 +13,122 @@ from httpx import AsyncClient
|
||||
class TestArchivesAPI:
|
||||
"""Integration tests for /api/v1/archives/ endpoints."""
|
||||
|
||||
# ========================================================================
|
||||
# Upload endpoint
|
||||
# ========================================================================
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize("prefer_filename_for_name", [True, False])
|
||||
async def test_upload_archive_forwards_prefer_filename_for_name(
|
||||
self, async_client: AsyncClient, archive_factory, printer_factory, prefer_filename_for_name
|
||||
):
|
||||
"""POST /archives/upload must forward prefer_filename_for_name to
|
||||
ArchiveService.archive_print unchanged — this flag lets a caller (e.g.
|
||||
the manual upload UI or an external integration) ask for the uploaded
|
||||
filename to win over the 3MF's embedded print_name (#1152 follow-up:
|
||||
the flag existed on the service but wasn't exposed on this route).
|
||||
|
||||
archive_print is mocked (real 3MF metadata extraction isn't under test
|
||||
here) but its return value is a real PrintArchive row from the
|
||||
factory, so ArchiveResponse.model_validate in the route still exercises
|
||||
real serialization instead of masking a broken response behind a bare
|
||||
MagicMock.
|
||||
"""
|
||||
printer = await printer_factory()
|
||||
archive = await archive_factory(printer.id, print_name="Mocked Return Archive")
|
||||
archive_print_mock = AsyncMock(return_value=archive)
|
||||
|
||||
files = {"file": ("My Print (final).gcode.3mf", b"PK\x03\x04fake3mf", "application/octet-stream")}
|
||||
params = {"prefer_filename_for_name": prefer_filename_for_name}
|
||||
|
||||
with patch(
|
||||
"backend.app.api.routes.archives.ArchiveService.archive_print",
|
||||
archive_print_mock,
|
||||
):
|
||||
response = await async_client.post("/api/v1/archives/upload", files=files, params=params)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert archive_print_mock.await_count == 1
|
||||
kwargs = archive_print_mock.await_args.kwargs
|
||||
assert kwargs.get("prefer_filename_for_name") is prefer_filename_for_name
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_upload_archive_defaults_prefer_filename_for_name_false(
|
||||
self, async_client: AsyncClient, archive_factory, printer_factory
|
||||
):
|
||||
"""Omitting the query param must not change existing behavior for
|
||||
callers that predate this flag."""
|
||||
printer = await printer_factory()
|
||||
archive = await archive_factory(printer.id, print_name="Mocked Return Archive")
|
||||
archive_print_mock = AsyncMock(return_value=archive)
|
||||
|
||||
files = {"file": ("existing-caller.gcode.3mf", b"PK\x03\x04fake3mf", "application/octet-stream")}
|
||||
|
||||
with patch(
|
||||
"backend.app.api.routes.archives.ArchiveService.archive_print",
|
||||
archive_print_mock,
|
||||
):
|
||||
response = await async_client.post("/api/v1/archives/upload", files=files)
|
||||
|
||||
assert response.status_code == 200
|
||||
kwargs = archive_print_mock.await_args.kwargs
|
||||
assert kwargs.get("prefer_filename_for_name") is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize("prefer_filename_for_name", [True, False])
|
||||
async def test_upload_archives_bulk_forwards_prefer_filename_for_name(
|
||||
self, async_client: AsyncClient, archive_factory, printer_factory, prefer_filename_for_name
|
||||
):
|
||||
"""POST /archives/upload-bulk must forward prefer_filename_for_name to
|
||||
ArchiveService.archive_print for every file in the batch, keeping this
|
||||
route consistent with the single-file /archives/upload endpoint."""
|
||||
printer = await printer_factory()
|
||||
archive = await archive_factory(printer.id, print_name="Mocked Return Archive")
|
||||
archive_print_mock = AsyncMock(return_value=archive)
|
||||
|
||||
files = [
|
||||
("files", ("first.gcode.3mf", b"PK\x03\x04fake3mf", "application/octet-stream")),
|
||||
("files", ("second.gcode.3mf", b"PK\x03\x04fake3mf", "application/octet-stream")),
|
||||
]
|
||||
params = {"prefer_filename_for_name": prefer_filename_for_name}
|
||||
|
||||
with patch(
|
||||
"backend.app.api.routes.archives.ArchiveService.archive_print",
|
||||
archive_print_mock,
|
||||
):
|
||||
response = await async_client.post("/api/v1/archives/upload-bulk", files=files, params=params)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert archive_print_mock.await_count == 2
|
||||
for call in archive_print_mock.await_args_list:
|
||||
assert call.kwargs.get("prefer_filename_for_name") is prefer_filename_for_name
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_upload_archives_bulk_defaults_prefer_filename_for_name_false(
|
||||
self, async_client: AsyncClient, archive_factory, printer_factory
|
||||
):
|
||||
"""Omitting the query param on the bulk route must not change existing
|
||||
behavior for callers that predate this flag."""
|
||||
printer = await printer_factory()
|
||||
archive = await archive_factory(printer.id, print_name="Mocked Return Archive")
|
||||
archive_print_mock = AsyncMock(return_value=archive)
|
||||
|
||||
files = [("files", ("existing-caller.gcode.3mf", b"PK\x03\x04fake3mf", "application/octet-stream"))]
|
||||
|
||||
with patch(
|
||||
"backend.app.api.routes.archives.ArchiveService.archive_print",
|
||||
archive_print_mock,
|
||||
):
|
||||
response = await async_client.post("/api/v1/archives/upload-bulk", files=files)
|
||||
|
||||
assert response.status_code == 200
|
||||
kwargs = archive_print_mock.await_args.kwargs
|
||||
assert kwargs.get("prefer_filename_for_name") is False
|
||||
|
||||
# ========================================================================
|
||||
# List endpoints
|
||||
# ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user