fix(feedback): honor atomic image delivery response

Production’s single-message feedback endpoint uploads text and images atomically, then returns 202 {"ok":true} without an imagesDelivered field. Treating that omission as false warned users that every successful production attachment had failed.

Treat a settled successful JSON response with ok: true and no image field as delivered. Explicit imagesDelivered: false still surfaces partial delivery, while malformed, oversized, aborted, and stalled bodies remain unconfirmed or fail through the existing response bound and timeout path.
This commit is contained in:
Brennan Benson
2026-07-29 19:12:54 -07:00
parent 223013a38d
commit d74b394adb
2 changed files with 9 additions and 9 deletions
+6 -2
View File
@@ -77,16 +77,20 @@ export function appendFeedbackImagesToFormData(
}
}
/** A 2xx confirms the text; only the response contract can confirm its images. */
/** Atomic servers omit the image field after both the text and images land. */
export async function readFeedbackImagesDelivered(response: Response): Promise<boolean> {
try {
const parsed: unknown = await readFetchResponseJsonWithinLimit(
response,
MAX_FEEDBACK_IMAGE_RESPONSE_BYTES
)
if (typeof parsed === 'object' && parsed !== null && 'imagesDelivered' in parsed) {
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
return false
}
if ('imagesDelivered' in parsed) {
return (parsed as { imagesDelivered?: unknown }).imagesDelivered === true
}
return (parsed as { ok?: unknown }).ok === true
} catch {}
return false
}
+3 -7
View File
@@ -412,11 +412,7 @@ describe('submitFeedback', () => {
}
function jsonResponse(body: unknown): Response {
return {
ok: true,
status: 202,
json: async () => body
} as unknown as Response
return Response.json(body, { status: 202 })
}
it('sends attached images as multipart form parts', async () => {
@@ -447,12 +443,12 @@ describe('submitFeedback', () => {
})
})
it('reports unconfirmed delivery when a 2xx omits the image result', async () => {
it('accepts the production atomic-success response when it omits the image result', async () => {
fetchMock.mockResolvedValue(jsonResponse({ ok: true }))
await expect(submitFeedback(imageSubmitArgs([pngImage()]))).resolves.toEqual({
ok: true,
imagesDelivered: false
imagesDelivered: true
})
})