Add FTR tests for stream/buffer error responses (#188937)

## Summary

Fix https://github.com/elastic/kibana/issues/56305
This commit is contained in:
Pierre Gayvallet 2024-07-24 15:00:13 +02:00 committed by GitHub
parent 1b45a27853
commit 85a90a71e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 80 additions and 4 deletions

View file

@ -115,7 +115,7 @@ export class HapiResponseAdapter {
return response;
}
private toError(kibanaResponse: KibanaResponse<ResponseError | Buffer | stream.Readable>) {
private toError(kibanaResponse: KibanaResponse<ResponseError>) {
const { payload } = kibanaResponse;
// Special case for when we are proxying requests and want to enable streaming back error responses opaquely.
@ -153,7 +153,12 @@ function getErrorMessage(payload?: ResponseError): string {
if (!payload) {
throw new Error('expected error message to be provided');
}
if (typeof payload === 'string') return payload;
if (typeof payload === 'string') {
return payload;
}
if (isStreamOrBuffer(payload)) {
throw new Error(`can't resolve error message from stream or buffer`);
}
// for ES response errors include nested error reason message. it doesn't contain sensitive data.
if (isElasticsearchResponseError(payload)) {
return `[${payload.message}]: ${
@ -164,6 +169,10 @@ function getErrorMessage(payload?: ResponseError): string {
return getErrorMessage(payload.message);
}
function isStreamOrBuffer(payload: ResponseError): payload is stream.Stream | Buffer {
return Buffer.isBuffer(payload) || stream.isReadable(payload as stream.Readable);
}
function getErrorAttributes(payload?: ResponseError): ResponseErrorAttributes | undefined {
return typeof payload === 'object' && 'attributes' in payload ? payload.attributes : undefined;
}

View file

@ -39,6 +39,8 @@ export type ResponseErrorAttributes = Record<string, any>;
*/
export type ResponseError =
| string
| Buffer
| Stream
| Error
| {
message: string | Error;

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import type { Stream } from 'stream';
import type {
CustomHttpResponseOptions,
HttpResponseOptions,
@ -139,7 +138,7 @@ export interface KibanaErrorResponseFactory {
* Creates an error response with defined status code and payload.
* @param options - {@link CustomHttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client
*/
customError(options: CustomHttpResponseOptions<ResponseError | Buffer | Stream>): IKibanaResponse;
customError(options: CustomHttpResponseOptions<ResponseError>): IKibanaResponse;
}
/**

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { Readable } from 'stream';
import type { Plugin, CoreSetup } from '@kbn/core/server';
export class CoreHttpPlugin implements Plugin {
@ -87,6 +88,32 @@ export class CoreHttpPlugin implements Plugin {
},
});
});
router.get(
{
path: '/api/core_http/error_stream',
validate: false,
},
async (ctx, req, res) => {
return res.customError({
body: Readable.from(['error stream'], { objectMode: false }),
statusCode: 501,
});
}
);
router.get(
{
path: '/api/core_http/error_buffer',
validate: false,
},
async (ctx, req, res) => {
return res.customError({
body: Buffer.from('error buffer', 'utf8'),
statusCode: 501,
});
}
);
}
public start() {}

View file

@ -0,0 +1,38 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import '@kbn/core-provider-plugin/types';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
// routes defined in the `core_http` test plugin
describe('Custom errors', () => {
it('can serve an error response from stream', async () => {
await supertest
.get('/api/core_http/error_stream')
.expect(501)
.then((response) => {
const res = response.body.toString();
expect(res).to.eql('error stream');
});
});
it('can serve an error response from buffer', async () => {
await supertest
.get('/api/core_http/error_buffer')
.expect(501)
.then((response) => {
const res = response.body.toString();
expect(res).to.eql('error buffer');
});
});
});
}

View file

@ -26,5 +26,6 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
loadTestFile(require.resolve('./http'));
loadTestFile(require.resolve('./http_versioned'));
loadTestFile(require.resolve('./dynamic_contract_resolving'));
loadTestFile(require.resolve('./error_response'));
});
}