diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index 594c273..98514ec 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -131,11 +131,16 @@ func (h *Handler) ListInstances() http.HandlerFunc { return } - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(instances); err != nil { + // Marshal to bytes first to set Content-Length header + data, err := json.Marshal(instances) + if err != nil { http.Error(w, "Failed to encode instances: "+err.Error(), http.StatusInternalServerError) return } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(len(data))) + w.Write(data) } } diff --git a/webui/src/lib/__tests__/api.test.ts b/webui/src/lib/__tests__/api.test.ts index 87e8ac7..2eda209 100644 --- a/webui/src/lib/__tests__/api.test.ts +++ b/webui/src/lib/__tests__/api.test.ts @@ -11,11 +11,13 @@ describe('API Error Handling', () => { }) it('converts HTTP errors to meaningful messages', async () => { - mockFetch.mockResolvedValue({ + const mockResponse = { ok: false, status: 409, - text: () => Promise.resolve('Instance already exists') - }) + text: () => Promise.resolve('Instance already exists'), + clone: function() { return this } + } + mockFetch.mockResolvedValue(mockResponse) await expect(instancesApi.create('existing', {})) .rejects @@ -23,11 +25,13 @@ describe('API Error Handling', () => { }) it('handles empty error responses gracefully', async () => { - mockFetch.mockResolvedValue({ + const mockResponse = { ok: false, status: 500, - text: () => Promise.resolve('') - }) + text: () => Promise.resolve(''), + clone: function() { return this } + } + mockFetch.mockResolvedValue(mockResponse) await expect(instancesApi.list()) .rejects diff --git a/webui/src/lib/api.ts b/webui/src/lib/api.ts index f7ecdf2..4e180e0 100644 --- a/webui/src/lib/api.ts +++ b/webui/src/lib/api.ts @@ -49,11 +49,8 @@ async function apiCall( } else { // Handle empty responses for JSON endpoints const contentLength = response.headers.get('content-length'); - if (contentLength === '0' || contentLength === null) { - const text = await response.text(); - if (text.trim() === '') { - return {} as T; // Return empty object for empty JSON responses - } + if (contentLength === '0') { + return {} as T; // Return empty object for empty JSON responses } const data = await response.json() as T; return data; diff --git a/webui/src/lib/errorUtils.ts b/webui/src/lib/errorUtils.ts index 1860bf9..85cdf03 100644 --- a/webui/src/lib/errorUtils.ts +++ b/webui/src/lib/errorUtils.ts @@ -26,7 +26,8 @@ export async function handleApiError(response: Response): Promise { } if (!response.ok) { - const errorMessage = await parseErrorResponse(response) + // Clone the response before reading to avoid consuming the body stream + const errorMessage = await parseErrorResponse(response.clone()) throw new Error(errorMessage) } } \ No newline at end of file