Skip to content

fix(uploads): detect upload success via response.ok so the editor stops failing every image#1339

Open
NiallJoeMaher wants to merge 1 commit into
developfrom
fix/s3-upload-ok-detection
Open

fix(uploads): detect upload success via response.ok so the editor stops failing every image#1339
NiallJoeMaher wants to merge 1 commit into
developfrom
fix/s3-upload-ok-detection

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

What

Image uploads in the article editor showed "Failed to upload image" on every attempt, even though the file actually reached S3.

uploadFile returned { ...response, fileLocation }. A fetch Response exposes ok, status, and url as prototype getters, not own-enumerable properties, so the spread copied none of them — callers read undefined for ok.

The editor's success check is if (!uploadResult.ok) throw new Error("Failed to upload image"), so !undefined threw on every upload. The presigned PUT returned 200 and the object landed in S3, but the UI reported failure and never inserted the image.

This was the remaining cause after the recent bucket-name trim (which fixed signing).

How

  • uploadFile now returns { ok, status, fileLocation } explicitly instead of spreading the Response.
  • Hardened the other three callers — profile photo, job logo, admin source logo — to check ok. Previously they trusted fileLocation, which is derived from response.url and is truthy even on a failed PUT, so a failed upload could silently be treated as success.
  • Added a regression test for uploadFile covering the ok-on-success / ok-on-failure contract.

Verification

  • New test fails against the old spread (result.okundefined) and passes with the fix.
  • Reproduced live: presigned URL targets the correct bucket, PUT returns 200, object loads as an <img> — only the client-side ok check was wrong.
  • prettier, eslint, and tsc clean on the changed files; unit tests pass.

…ps failing every image

uploadFile returned `{ ...response, fileLocation }`, but a fetch Response
exposes ok/status/url as prototype getters, not own enumerable properties —
so the spread dropped them and callers read `undefined` for `ok`.

The article editor's `if (!uploadResult.ok) throw` then reported failure on
*every* image upload, even though the presigned PUT returned 200 and the file
landed in S3. (The recent bucket-name trim fixed signing; this was the
remaining reason uploads still showed an error.)

Return ok/status explicitly, and harden the other callers (profile photo, job
logo, admin source logo) to check `ok` instead of trusting a fileLocation that
is derived from the URL regardless of whether the PUT succeeded. Adds a
regression test for uploadFile.
@NiallJoeMaher NiallJoeMaher requested a review from a team as a code owner June 17, 2026 15:51
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
codu Ready Ready Preview, Comment Jun 17, 2026 3:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

uploadFile in utils/s3helpers.ts is changed to return an explicit { ok, status, fileLocation } object instead of spreading the Response. Tests are added for both success and failure cases. Three callers (admin sources, job creation, and user settings) are updated to destructure and check the ok flag with early-exit error handling.

Changes

uploadFile Contract Fix and Callsite Updates

Layer / File(s) Summary
uploadFile return contract and tests
utils/s3helpers.ts, utils/s3helpers.test.ts
uploadFile now explicitly returns { ok, status, fileLocation } instead of spreading the full Response object. Vitest tests are added covering both HTTP 200 (ok: true, fileLocation set) and HTTP 403 (ok: false) outcomes.
Caller destructuring and ok-flag error handling
app/(app)/jobs/create/_client.tsx, app/(app)/settings/_client.tsx, app/(admin)/admin/sources/_client.tsx
All three upload handlers are updated to destructure { ok, fileLocation } from uploadFile and add early-exit error branches when ok is false, preventing downstream URL updates on failed uploads.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • codu-code/codu#1106: Modifies the company logo upload handling in app/(app)/jobs/create/_client.tsx, the same file updated here to use the new ok flag contract.
  • codu-code/codu#1283: Also modifies the job logo upload flow in app/(app)/jobs/create/_client.tsx including uploadFile result handling.

Poem

🐇 A Response once spread wide,
Its ok flag hidden inside.
Now explicit and neat,
Each field is complete —
No prototype trick shall misguide! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately identifies the main fix: correcting the upload success detection via response.ok and resolving the widespread image upload failures.
Description check ✅ Passed The description is comprehensive and well-structured, covering the root cause, solution approach, affected files, and verification details. However, it does not explicitly reference an issue number in the Fixes format required by the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/s3-upload-ok-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@utils/s3helpers.test.ts`:
- Around line 8-10: In the afterEach block in utils/s3helpers.test.ts, add a
call to vi.unstubAllGlobals() after or alongside the existing
vi.restoreAllMocks() call to ensure that any globals stubbed with vi.stubGlobal
(such as the fetch stub) are properly cleaned up after each test. This will
prevent stubbed globals from leaking into subsequent tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0be6b4e6-9423-483b-b13a-1ddb4eb46b7d

📥 Commits

Reviewing files that changed from the base of the PR and between e8922ee and b9011a9.

📒 Files selected for processing (5)
  • app/(admin)/admin/sources/_client.tsx
  • app/(app)/jobs/create/_client.tsx
  • app/(app)/settings/_client.tsx
  • utils/s3helpers.test.ts
  • utils/s3helpers.ts

Comment thread utils/s3helpers.test.ts
Comment on lines +8 to +10
afterEach(() => {
vi.restoreAllMocks();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Vitest v4.x, does vi.restoreAllMocks()revert globals stubbed withvi.stubGlobal()? If not, is vi.unstubAllGlobals() the intended cleanup API?

💡 Result:

In Vitest v4.x, vi.restoreAllMocks does not revert global variables stubbed with vi.stubGlobal [1][2]. vi.restoreAllMocks is specifically designed to restore the original implementations of spies created via vi.spyOn [1][2]. vi.unstubAllGlobals is the correct and intended API for cleaning up stubs created with vi.stubGlobal [1][3]. When you call vi.stubGlobal, Vitest tracks the original value (or state) of the global variable so that vi.unstubAllGlobals can restore it to its previous state [1][3]. Alternatively, you can enable the unstubGlobals configuration option in your Vitest config file to have Vitest automatically call vi.unstubAllGlobals after each test [4][5].

Citations:


🏁 Script executed:

fd "s3helpers.test.ts" -x wc -l {}

Repository: codu-code/codu

Length of output: 87


🏁 Script executed:

fd "s3helpers.test.ts" -x cat -n {}

Repository: codu-code/codu

Length of output: 1844


Unstub global fetch in teardown to prevent test-suite leakage.

The afterEach block at lines 8-10 calls vi.restoreAllMocks(), but this does not clean up globals stubbed with vi.stubGlobal("fetch", ...) at line 16. Each test will leave the stubbed fetch in place, potentially affecting subsequent tests. Add vi.unstubAllGlobals() to the teardown:

Suggested patch
  afterEach(() => {
    vi.restoreAllMocks();
+   vi.unstubAllGlobals();
  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/s3helpers.test.ts` around lines 8 - 10, In the afterEach block in
utils/s3helpers.test.ts, add a call to vi.unstubAllGlobals() after or alongside
the existing vi.restoreAllMocks() call to ensure that any globals stubbed with
vi.stubGlobal (such as the fetch stub) are properly cleaned up after each test.
This will prevent stubbed globals from leaking into subsequent tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant