fix(uploads): clear JSON Content-Type so multipart uploads set a boundary (#32)#39
Open
ClaydeCode wants to merge 3 commits into
Open
fix(uploads): clear JSON Content-Type so multipart uploads set a boundary (#32)#39ClaydeCode wants to merge 3 commits into
ClaydeCode wants to merge 3 commits into
Conversation
The global $http axios instance sets a default Content-Type: application/json. Sending a FormData body under that default makes axios serialize the FormData to JSON, so the browser never sets the multipart/form-data boundary and the backend can't find the file part, returning 422. Clear the per-request Content-Type on the avatar upload (Public.vue) and the custom-app upload (Apps.vue) so the browser sets the multipart boundary itself. Apps.vue previously set 'multipart/form-data' with no boundary, which fails the same way. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add a unit test that drives a main.js-shaped axios instance and asserts the FormData body reaches the adapter intact when Content-Type is cleared; a control case proves the JSON default alone serializes it to a string. Note the requirement in agents.md. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The first version reconstructed an inline axios instance and re-inlined the Content-Type override, so it verified axios behavior rather than the app: deleting the override from Public.vue/Apps.vue left every test green. Invoke the real uploadAvatar / uploadCustomApp methods with an injected $http mock and assert on the config they pass, so the override regression actually fails a test (confirmed by mutation). Keep one axios-level test pinning why 'undefined' is the correct value to send. Also note in agents.md that axios normalizes header case, which is the load-bearing reason the override cancels the differently-cased default. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #32 — uploading an image on the Public View page (and uploading a custom app on the Apps page) failed with a 422:
Root cause
$httpis the shared axios instance created insrc/main.jswith a defaultContent-Type: application/json. When aFormDatabody is sent under that default, axios'transformRequestserializes the FormData to a JSON string (lib/defaults/index.js:hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data). The body is no longer aFormData, so the browser never sets amultipart/form-data; boundary=...header and the backend finds nofilepart.src/views/Public.vueuploadAvatarnever overrode the JSON default.src/views/Apps.vueuploadCustomAppset'Content-Type': 'multipart/form-data'with no boundary — same failure mode.Fix
Clear the per-request
Content-Typeon both upload calls ({ headers: { 'Content-Type': undefined } }). With no JSON content-type, axios keeps the rawFormData; in a standard browser env axios' xhr adapter then drops the header entirely (lib/helpers/resolveConfig.js:headers.setContentType(undefined) // Let the browser set it) so the browser sets the multipart boundary. axios normalizes header case, soContent-Typecancels theContent-typedefault.Tests / docs
tests/unit/upload-content-type.spec.jsdrives the realuploadAvatar/uploadCustomAppmethods with an injected$httpmock and asserts they clear the Content-Type; a third test pins the axios behavior that makesundefinedthe correct value. Verified by mutation: reverting the override makes the tests fail.agents.mddocuments the upload gotcha under API Communication.Local verification:
test:unit(22 passing),lintclean,buildsucceeds.Recommended reading order
src/views/Public.vue,src/views/Apps.vue— the fixtests/unit/upload-content-type.spec.js— the guardagents.md— the documented gotchaReview panel
Ran three reviewers (adversarial always-on, plus test-adversary and DevEx since the diff adds real logic and tests). Security / DB / API-contract / UX did not fire — this is a client-side request-header change with no auth, schema, route, or visual surface.
Content-Type: undefineddrops the JSON default (case-insensitively) and that no other FormData/upload call sites exist. Raised the same test-teeth advisory as below.Public.vue/Apps.vueleft all tests green (proven by mutation). Resolved in7aa7e24: the tests now invoke the real component methods and assert on the config passed to$http; a mutation run confirms they fail when the override is removed. Advisory edge cases (multi-file loop, failure branch) — the multi-file path is now covered; the failure/doneCallbackbranches were left uncovered as out of scope for this fix.agents.md). Praised the naming, restraint on source comments, and doc placement.🤖 Generated with Claude Code