From 7b5494bafbb354eb457a393682d9686de4a6af98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 11:15:14 +0000 Subject: [PATCH] feat(api): default download endpoint to processed file version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change default `version` param from 'original' to 'processed' so GET /api/files/{id}/download (no param) returns the processed file - Update docstring to reflect new default - Add tests: ?version=processed, default→processed, invalid→400 - Fix test_file_download_missing_mime_type to use explicit ?version=original - Add File Download section to docs/API.md Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/api/files.py | 6 +-- docs/API.md | 26 +++++++++++ tests/test_api_files_comprehensive.py | 62 ++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/app/api/files.py b/app/api/files.py index b40fbaf3..c60d98ad 100644 --- a/app/api/files.py +++ b/app/api/files.py @@ -916,14 +916,14 @@ def download_file( request: Request, file_id: int, db: DbSession, - version: str = Query("original", description="original or processed"), + version: str = Query("processed", description="'processed' (default) or 'original'"), ): """ - Download file (original or processed version) as attachment. + Download file (processed or original version) as attachment. Args: file_id: ID of the file - version: "original" for tmp file, "processed" for processed file + version: "processed" (default) for the post-processing file, "original" for the raw upload Returns: File content as attachment download diff --git a/docs/API.md b/docs/API.md index f0d6f87e..bc5daecb 100644 --- a/docs/API.md +++ b/docs/API.md @@ -388,6 +388,32 @@ curl "http:///api/files/123/preview?version=processed" - `404`: File not found in database or on disk - `400`: Invalid version parameter +### File Download + +**GET** `/api/files/{file_id}/download` + +Download a file as an attachment. The `Content-Disposition` header is set to `attachment` with the original filename so the browser prompts a save dialog. + +**Parameters**: +- `version` (optional, default: `processed`): Either `processed` or `original` + - `processed` *(default)*: Downloads the post-processing file (with embedded metadata) + - `original`: Downloads the raw file as originally uploaded + +**Response**: File content with `Content-Disposition: attachment; filename=""`. + +**Example**: +```bash +# Download processed file (default) +curl -OJ "http:///api/files/123/download" + +# Download original upload +curl -OJ "http:///api/files/123/download?version=original" +``` + +**Error Responses**: +- `404`: File not found in database or on disk +- `400`: Invalid `version` parameter (must be `processed` or `original`) + ### Batch Processing **POST** `/api/processall` diff --git a/tests/test_api_files_comprehensive.py b/tests/test_api_files_comprehensive.py index 655a9335..43ffb2c0 100644 --- a/tests/test_api_files_comprehensive.py +++ b/tests/test_api_files_comprehensive.py @@ -663,6 +663,66 @@ class TestFileDownload: response = client.get("/api/files/99999/download?version=original") assert response.status_code == 404 + def test_download_processed_file_success(self, client: TestClient, db_session, tmp_path): + """Test downloading processed file via ?version=processed.""" + processed_dir = tmp_path / "processed" + processed_dir.mkdir() + processed_file = processed_dir / "test.pdf" + processed_file.write_bytes(b"%PDF-1.4 processed") + + file = FileRecord( + filehash="hashproc", + original_filename="test.pdf", + local_filename=str(tmp_path / "test.pdf"), + processed_file_path=str(processed_file), + file_size=1024, + mime_type="application/pdf", + ) + db_session.add(file) + db_session.commit() + + response = client.get(f"/api/files/{file.id}/download?version=processed") + assert response.status_code == 200 + assert "attachment" in response.headers["content-disposition"] + assert "test.pdf" in response.headers["content-disposition"] + + def test_download_default_returns_processed(self, client: TestClient, db_session, tmp_path): + """Test that GET /download without ?version defaults to the processed file.""" + processed_dir = tmp_path / "processed" + processed_dir.mkdir() + processed_file = processed_dir / "default.pdf" + processed_file.write_bytes(b"%PDF-1.4 default") + + file = FileRecord( + filehash="hashdefault", + original_filename="default.pdf", + local_filename=str(tmp_path / "default.pdf"), + processed_file_path=str(processed_file), + file_size=1024, + mime_type="application/pdf", + ) + db_session.add(file) + db_session.commit() + + response = client.get(f"/api/files/{file.id}/download") + assert response.status_code == 200 + assert "attachment" in response.headers["content-disposition"] + + def test_download_invalid_version_returns_400(self, client: TestClient, db_session): + """Test that an invalid ?version value returns HTTP 400.""" + file = FileRecord( + filehash="hashinv", + original_filename="test.pdf", + local_filename="/nonexistent/test.pdf", + file_size=1024, + mime_type="application/pdf", + ) + db_session.add(file) + db_session.commit() + + response = client.get(f"/api/files/{file.id}/download?version=invalid") + assert response.status_code == 400 + @pytest.mark.unit class TestUIUpload: @@ -1149,6 +1209,6 @@ class TestAdditionalFileOperations: db_session.add(file) db_session.commit() - response = client.get(f"/api/files/{file.id}/download") + response = client.get(f"/api/files/{file.id}/download?version=original") assert response.status_code == 200 # Should default to application/pdf