Expose prefer_filename_for_name on manual archive upload endpoint

POST /api/v1/archives/upload now accepts an optional
prefer_filename_for_name query param, forwarded to
ArchiveService.archive_print. Same flag already used by the FTP
pending-uploads review flow and virtual-printer dispatch (#1152);
this endpoint just didn't expose it. Default False, no behavior
change for existing callers.

Fixes #2609
This commit is contained in:
Sebastian Keet
2026-07-19 23:41:33 +10:00
parent 69c0effb92
commit 63c9f8fc18
2 changed files with 74 additions and 1 deletions
+10 -1
View File
@@ -3312,10 +3312,18 @@ async def get_plate_preview(
async def upload_archive(
file: UploadFile = File(...),
printer_id: int | None = None,
prefer_filename_for_name: bool = False,
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: When True, use the uploaded filename stem as the
archive's display name even if the 3MF embeds a `print_name` in its
metadata. Same flag already used by the FTP review flow and virtual-printer
dispatch (see ArchiveService.archive_print) — this endpoint just didn't
expose it (#1152 follow-up).
"""
if not file.filename or not file.filename.endswith(".3mf"):
raise HTTPException(400, "File must be a .3mf file")
@@ -3341,6 +3349,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:
@@ -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,69 @@ 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, db_session, 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, db_session
):
"""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
# ========================================================================
# List endpoints
# ========================================================================