fix(api): infer /documents/{id}/download Content-Type from filename when ext is omitted (#15052) (#15053)

## Summary

- Align **GET `/api/v1/documents/<doc_id>/download`** with
**`/preview`**: resolve extension and MIME type from the stored document
name when the **`ext` query parameter is omitted**, instead of
defaulting to `markdown`.
- When **`?ext=`** is present, behavior stays the same as before
(explicit extension / `Content-Type` mapping).
- Enforce the same access + document lookup pattern as preview
(**`accessible`** + **`get_by_id`**).
- Extend unit tests for the no-`ext` PDF filename case.

## Test plan

- [x] `uv run pytest
test/testcases/test_web_api/test_document_app/test_document_metadata.py::TestDocumentMetadataUnit::test_download_attachment_success_and_exception_unit`
- [x] Optional: `curl -sSI` against
`/api/v1/documents/<pdf_doc_id>/download` without `ext` and confirm
`Content-Type: application/pdf`

Fixes #15052.
This commit is contained in:
kpdev
2026-05-20 22:57:01 -07:00
committed by Jin Hai
parent 440153c378
commit 6932615852
2 changed files with 36 additions and 2 deletions

View File

@@ -420,6 +420,11 @@ class TestDocumentMetadataUnit:
# From here on the user is authorized; exercise the original branches.
monkeypatch.setattr(module.DocumentService, "accessible", lambda _doc_id, _user_id: True)
monkeypatch.setattr(
module.DocumentService,
"get_by_id",
lambda _doc_id: (True, SimpleNamespace(name="stub.bin", type=module.FileType.OTHER.value)),
)
async def fake_thread_pool_exec(*_args, **_kwargs):
return b"attachment"
@@ -441,6 +446,18 @@ class TestDocumentMetadataUnit:
assert res.headers["content_type"] == "application/abc"
assert res.headers["extension"] == "abc"
# No `ext` query param: infer MIME/extension from the stored document filename (aligned with /preview).
monkeypatch.setattr(module, "request", _DummyRequest(args={}))
monkeypatch.setattr(
module.DocumentService,
"get_by_id",
lambda _doc_id: (True, SimpleNamespace(name="Annual report.PDF", type=module.FileType.PDF.value)),
)
res = _run(module.download_attachment(attachment_id="att1"))
assert isinstance(res, _DummyResponse)
assert res.headers["content_type"] == "application/pdf"
assert res.headers["extension"] == "pdf"
async def raise_error(*_args, **_kwargs):
raise RuntimeError("download boom")