From 40cf928fe256d70c8ec59b61fee6c831ff6362bc Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 22 Jul 2025 15:43:30 +0300 Subject: [PATCH] [Console] Fix different error code in status badge (#228889) (cherry picked from commit f15789615961e8cf3f6302702db1620c2ec3c4e6) --- .../use_send_current_request/send_request.ts | 17 ++- .../status_code_extraction.test.ts | 109 ++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/status_code_extraction.test.ts diff --git a/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/send_request.ts b/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/send_request.ts index 46fca70860442..7b00971f166f8 100644 --- a/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/send_request.ts +++ b/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/send_request.ts @@ -50,10 +50,21 @@ const extractStatusCodeAndText = (response: Response | undefined, path: string) // For ES requests, we need to extract the status code and text from the response // headers, due to the way the proxy set up to avoid mirroring the status code which could be 401 // and trigger a login prompt. See for more details: https://github.com/elastic/kibana/issues/140536 - const statusCode = parseInt(response?.headers.get('x-console-proxy-status-code') ?? '500', 10); - const statusText = response?.headers.get('x-console-proxy-status-text') ?? 'error'; + const proxyStatusCode = response?.headers.get('x-console-proxy-status-code'); + const proxyStatusText = response?.headers.get('x-console-proxy-status-text'); - return { statusCode, statusText }; + // If proxy headers are missing (e.g., validation errors), use the actual response status + if (!proxyStatusCode) { + return { + statusCode: parseInt(String(response?.status ?? 500), 10), + statusText: response?.statusText ?? 'error', + }; + } + + return { + statusCode: parseInt(proxyStatusCode, 10), + statusText: proxyStatusText ?? 'error', + }; }; let CURRENT_REQ_ID = 0; diff --git a/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/status_code_extraction.test.ts b/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/status_code_extraction.test.ts new file mode 100644 index 0000000000000..f34a9d072099e --- /dev/null +++ b/src/platform/plugins/shared/console/public/application/hooks/use_send_current_request/status_code_extraction.test.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { HttpSetup } from '@kbn/core/public'; + +jest.unmock('./send_request'); + +describe('Status Code Extraction in sendRequest', () => { + let mockHttp: jest.Mocked; + + beforeEach(() => { + mockHttp = { + post: jest.fn(), + } as any; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('extractStatusCodeAndText function behavior', () => { + it('should use proxy headers when available for ES requests', async () => { + const { sendRequest } = await import('./send_request'); + + const mockResponse = { + response: { + status: 200, // Actual HTTP status + statusText: 'OK', + headers: new Map([ + ['x-console-proxy-status-code', '404'], // ES status from proxy + ['x-console-proxy-status-text', 'Not Found'], + ['Content-Type', 'application/json'], + ]), + }, + body: JSON.stringify({ error: 'index_not_found_exception' }), + }; + + mockHttp.post.mockResolvedValue(mockResponse); + + const result = await sendRequest({ + http: mockHttp, + requests: [{ url: '/_search', method: 'GET', data: ['{}'] }], + }); + + expect(result[0].response.statusCode).toBe(404); // Should use proxy header + expect(result[0].response.statusText).toBe('Not Found'); + }); + + it('should fall back to actual response status when proxy headers are missing', async () => { + const { sendRequest } = await import('./send_request'); + + const mockResponse = { + response: { + status: 400, + statusText: 'Bad Request', + headers: new Map([['Content-Type', 'application/json']]), + }, + body: JSON.stringify({ + statusCode: 400, + error: 'Bad Request', + message: + "Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']. Received 'INVALIDMETHOD'.", + }), + }; + + mockHttp.post.mockResolvedValue(mockResponse); + + const result = await sendRequest({ + http: mockHttp, + requests: [{ url: '/_search', method: 'INVALIDMETHOD', data: ['{}'] }], + }); + + expect(result[0].response.statusCode).toBe(400); // Should use actual response status, not 500 + expect(result[0].response.statusText).toBe('Bad Request'); + }); + + it('should handle empty proxy header as missing header', async () => { + const { sendRequest } = await import('./send_request'); + + const mockResponse = { + response: { + status: 400, + statusText: 'Bad Request', + headers: new Map([ + ['x-console-proxy-status-code', ''], // Empty header value + ['Content-Type', 'application/json'], + ]), + }, + body: JSON.stringify({ error: 'validation error' }), + }; + + mockHttp.post.mockResolvedValue(mockResponse); + + const result = await sendRequest({ + http: mockHttp, + requests: [{ url: '/_search', method: 'INVALID', data: ['{}'] }], + }); + + expect(result[0].response.statusCode).toBe(400); // Should fall back to actual status + expect(result[0].response.statusText).toBe('Bad Request'); + }); + }); +});