Skip to content

fix(api/offers): return JSON content type - #1670

Closed
flcrom wants to merge 3 commits into
sourcebot-dev:mainfrom
flcrom:fix/offers-json-content-type
Closed

flcrom wants to merge 3 commits into
sourcebot-dev:mainfrom
flcrom:fix/offers-json-content-type

Conversation

@flcrom

@flcrom flcrom commented Sep 18, 2026

Copy link
Copy Markdown

The public offers endpoint serializes a JSON body but currently serves it as text/plain. This switches the response to Response.json(...) while preserving the cache header.

A route test now checks the Lighthouse request, JSON content type, cache header, and response body.

Fixes #1595

Tested:

  • focused route regression test passed under independent QA
  • original route failed that test on text/plain as expected
  • web lint passed under independent QA
  • git diff --check passed

The full web suite could not complete on the available 1.9 GiB/no-swap runner; dependency installation exceeded its resource limit. This PR is being submitted with that limitation stated explicitly.


Note

Low Risk
Small public API response-header fix with no auth or data-model changes; behavior for JSON parsers is improved with regression test coverage.

Overview
Fixes the public GET /api/offers response so clients see application/json instead of text/plain while the body stays the same JSON payload.

The handler now uses Response.json(offers, …) instead of manually stringifying into a generic Response, and still sets Cache-Control: public, max-age=300. A focused Vitest route test asserts the Lighthouse offers call (with install id), content type, cache header, and body; the unreleased changelog notes the fix.

Reviewed by Cursor Bugbot for commit f834eb1. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes the public offers endpoint to return application/json instead of text/plain using Response.json, preserving the existing Cache-Control header.

  • Adds a route test for the Lighthouse request and JSON response shape (content type, cache header, and body).
  • Records the fix in the changelog under Unreleased.
  • Fixes fix(api/offers): return Content-Type: application/json #1595.
  • Full web test suite could not run on the provided runner due to resource limits; the focused route test passed.

Written for commit f834eb1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Fixed the offers API response to use the correct JSON content type.
    • Preserved public caching for offers responses for up to five minutes.
  • Tests

    • Added coverage verifying the offers response format, content type, caching behavior, and returned data.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 9e55c61d-48b3-4dd3-a69e-3410508213e4

📥 Commits

Reviewing files that changed from the base of the PR and between 3c0e5ca and f834eb1.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/web/src/app/api/(server)/offers/route.test.ts
  • packages/web/src/app/api/(server)/offers/route.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


Walkthrough

The /api/offers GET handler now uses Response.json, which sets the JSON content type. Tests verify the billing call, response body, content type, and cache header. The changelog records the fix.

Changes

Offers JSON response

Layer / File(s) Summary
JSON response and route validation
packages/web/src/app/api/(server)/offers/route.ts, packages/web/src/app/api/(server)/offers/route.test.ts, CHANGELOG.md
The route returns offers with Response.json and preserves the five-minute public cache header. Tests verify the request, body, content type, and cache header. The changelog records the correction.

Priority: ⚪ Not assessed

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: brendan-kellam

Merge Risk: ⚪ Minimal · up to f834e

The endpoint now returns its existing offers payload with the expected JSON content type and preserved caching behavior. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive For #1595, Response.json(offers) sets the JSON content type, and the summary states that the cache header and response body remain unchanged. The focused route test verifies the content type, cache … Provide a successful full web suite result, or other reviewable evidence that the complete suite passes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: correcting the JSON content type returned by the offers API.
Out of Scope Changes check ✅ Passed The changes are limited to the /api/offers route, its focused automated test, and an unreleased changelog entry. These changes support #1595. No OpenAPI or unrelated route changes are reported.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Full details: Linked Issues check

Explanation

For #1595, Response.json(offers) sets the JSON content type, and the summary states that the cache header and response body remain unchanged. The focused route test verifies the content type, cache header, billing call, and body. The required full web suite result of 1154/1154 is not established because the suite stopped at runner resource limits.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Warning

⚠️ This pull request shows signs of AI-generated slop (ai_padded_prose). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.


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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 3 files

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/web/src/app/api/(server)/offers/route.ts">

<violation number="1" location="packages/web/src/app/api/(server)/offers/route.ts:11">
P2: When `client.offers` resolves to a `ServiceError` (Lighthouse unreachable, or response body fails schema validation), this endpoint still responds HTTP 200 and stamps the error body with `Cache-Control: public, max-age=300`, so the error gets served as a successful 200 and cached publicly for 5 minutes. `client.offers` is typed `Promise<OffersResponse | ServiceError>` (packages/web/src/features/billing/client.ts, `requestLighthouse` returns `lighthouseUnreachable` with `statusCode: 500`), but this touched response never branches on that. Since this PR is rewriting the exact response construction, handle the error case: respond with the error's status code and skip the public cache header.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

});

return new Response(JSON.stringify(offers), {
return Response.json(offers, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When client.offers resolves to a ServiceError (Lighthouse unreachable, or response body fails schema validation), this endpoint still responds HTTP 200 and stamps the error body with Cache-Control: public, max-age=300, so the error gets served as a successful 200 and cached publicly for 5 minutes. client.offers is typed Promise<OffersResponse | ServiceError> (packages/web/src/features/billing/client.ts, requestLighthouse returns lighthouseUnreachable with statusCode: 500), but this touched response never branches on that. Since this PR is rewriting the exact response construction, handle the error case: respond with the error's status code and skip the public cache header.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/src/app/api/(server)/offers/route.ts, line 11:

<comment>When `client.offers` resolves to a `ServiceError` (Lighthouse unreachable, or response body fails schema validation), this endpoint still responds HTTP 200 and stamps the error body with `Cache-Control: public, max-age=300`, so the error gets served as a successful 200 and cached publicly for 5 minutes. `client.offers` is typed `Promise<OffersResponse | ServiceError>` (packages/web/src/features/billing/client.ts, `requestLighthouse` returns `lighthouseUnreachable` with `statusCode: 500`), but this touched response never branches on that. Since this PR is rewriting the exact response construction, handle the error case: respond with the error's status code and skip the public cache header.</comment>

<file context>
@@ -8,9 +8,9 @@ export const GET = apiHandler(async () => {
     });
 
-    return new Response(JSON.stringify(offers), {
+    return Response.json(offers, {
         headers: {
             'Cache-Control': 'public, max-age=300'
</file context>

@brendan-kellam

Copy link
Copy Markdown
Contributor

hey, #1595 was a bit of a bogus issue, so going to close this PR. sorry about that

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.

fix(api/offers): return Content-Type: application/json

2 participants