From 55759a3a50e688c4a32de79ecb6eb753863651fe Mon Sep 17 00:00:00 2001 From: Macky Date: Sat, 8 Aug 2026 09:49:24 +0700 Subject: [PATCH] fix(api): group create read product from multipart form (bug: 'provide product info' when no file attached) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GroupBuilder sends the create form as multipart/form-data (FormData). The code decided JSON vs form by checking — when no file was attached, request.files was empty/falsy, so it tried get_json() on a multipart body and lost the product field -> 400 'provide product info' even though the user filled the product name. Fix: branch on the Content-Type (multipart/form-data -> request.form) instead of request.files. Added a regression test (multipart create with product only -> 201). Verified live: multipart product-only create now returns 201. --- backend/app/api/group_routes.py | 5 ++++- backend/scripts/test_m1.py | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/backend/app/api/group_routes.py b/backend/app/api/group_routes.py index cc56c93..548582d 100644 --- a/backend/app/api/group_routes.py +++ b/backend/app/api/group_routes.py @@ -88,7 +88,10 @@ def create_group(): file.save(dest) saved_files.append(dest.name) - data = request.form.to_dict() if request.files else (request.get_json(silent=True) or {}) + if request.content_type and "multipart/form-data" in request.content_type: + data = request.form.to_dict() + else: + data = request.get_json(silent=True) or {} from ..services.file_parser import parse_document diff --git a/backend/scripts/test_m1.py b/backend/scripts/test_m1.py index 1bb60ba..3c39d5b 100644 --- a/backend/scripts/test_m1.py +++ b/backend/scripts/test_m1.py @@ -47,6 +47,18 @@ def main(): assert r.get_json()["group"]["status"] == "draft" print("[ok] group created (draft)") + # Regression: multipart (like the frontend FormData) with product field but NO file + # attached must still create the group (was 400 'provide product info' before fix). + r = client.post( + "/api/groups", + data={"product": "Multipart Product"}, + content_type="multipart/form-data", + headers=H, + ) + assert r.status_code == 201, f"multipart create failed: {r.get_json()}" + assert r.get_json()["group"]["title"] == "Multipart Product" + print("[ok] group created via multipart (product only) — regression fixed") + # analyze should fail cleanly (LLM None) r = client.post(f"/api/groups/{gid}/analyze", headers=H) assert r.status_code == 500, r.get_json() @@ -54,8 +66,9 @@ def main(): # list groups as admin r = client.get("/api/groups", headers=H) - assert r.status_code == 200 and len(r.get_json()["groups"]) == 1 - print("[ok] admin lists 1 group") + assert r.status_code == 200 and len(r.get_json()["groups"]) >= 1 + assert all(g["status"] == "draft" for g in r.get_json()["groups"]) + print("[ok] admin lists groups") # personas empty until analyze r = client.get(f"/api/groups/{gid}", headers=H)