Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import type { estypes } from '@elastic/elasticsearch';
import type {
AsyncSearchGetResponse,
ErrorResponseBase,
SqlGetAsyncResponse,
EsqlAsyncQueryResponse,
} from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import type { PublicMethodsOf } from '@kbn/utility-types';
Expand Down Expand Up @@ -549,7 +549,7 @@ export class SearchInterceptor {
...getTotalLoaded(shimmedResponse),
};
case ESQL_ASYNC_SEARCH_STRATEGY:
const esqlResponse = rawResponse.body as unknown as SqlGetAsyncResponse;
const esqlResponse = rawResponse.body as unknown as EsqlAsyncQueryResponse;
return {
id: esqlResponse.id,
rawResponse: esqlResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,37 @@ const mockAsyncResponse = {
};

describe('ES|QL async search strategy', () => {
const mockApiCaller = jest.fn();
const mockAsyncQuery = jest.fn();
const mockAsyncQueryGet = jest.fn();
const mockAsyncQueryDelete = jest.fn();
const mockAsyncQueryStop = jest.fn();
const mockLogger: any = {
debug: () => {},
error: () => {},
};
const mockDeps = {
uiSettingsClient: {
get: jest.fn(),
},
esClient: {
asCurrentUser: {
transport: { request: mockApiCaller },
esql: {
asyncQuery: mockAsyncQuery,
asyncQueryGet: mockAsyncQueryGet,
asyncQueryDelete: mockAsyncQueryDelete,
asyncQueryStop: mockAsyncQueryStop,
},
},
},
} as unknown as SearchStrategyDependencies;

const mockSearchConfig = getMockSearchConfig({});

beforeEach(() => {
mockApiCaller.mockClear();
mockAsyncQuery.mockClear();
mockAsyncQueryGet.mockClear();
mockAsyncQueryDelete.mockClear();
mockAsyncQueryStop.mockClear();
});

it('returns a strategy with `search and `cancel`', async () => {
Expand All @@ -66,7 +78,7 @@ describe('ES|QL async search strategy', () => {
describe('search', () => {
describe('no sessionId', () => {
it('makes a POST request with params when no ID provided', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQuery.mockResolvedValueOnce(mockAsyncResponse);

const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);
const params = {
Expand All @@ -83,14 +95,14 @@ describe('ES|QL async search strategy', () => {
)
.toPromise();

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0].body;
expect(mockAsyncQuery).toBeCalled();
const request = mockAsyncQuery.mock.calls[0][0];
expect(request.query).toEqual(params.query);
expect(request).toHaveProperty('keep_alive', '60000ms');
});

it('makes a GET request to async search with ID', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQueryGet.mockResolvedValueOnce(mockAsyncResponse);

const params = {
query: 'from logs* | limit 10',
Expand All @@ -99,15 +111,15 @@ describe('ES|QL async search strategy', () => {

await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0];
expect(request.path).toContain('foo');
expect(request.querystring).toHaveProperty('wait_for_completion_timeout');
expect(request.querystring).toHaveProperty('keep_alive', '60000ms');
expect(mockAsyncQueryGet).toBeCalled();
const request = mockAsyncQueryGet.mock.calls[0][0];
expect(request.id).toBe('foo');
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive', '60000ms');
});

it('allows overriding keep_alive and wait_for_completion_timeout', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQueryGet.mockResolvedValueOnce(mockAsyncResponse);

const params = {
query: 'from logs* | limit 10',
Expand All @@ -118,41 +130,37 @@ describe('ES|QL async search strategy', () => {

await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0];
expect(request.path).toContain('foo');
expect(request.querystring).toHaveProperty('wait_for_completion_timeout', '10s');
expect(request.querystring).toHaveProperty('keep_alive', '5m');
expect(mockAsyncQueryGet).toBeCalled();
const request = mockAsyncQueryGet.mock.calls[0][0];
expect(request.id).toBe('foo');
expect(request).toHaveProperty('wait_for_completion_timeout', '10s');
expect(request).toHaveProperty('keep_alive', '5m');
});

it('sets transport options on POST requests', async () => {
const transportOptions = { maxRetries: 1 };
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQuery.mockResolvedValueOnce(mockAsyncResponse);
const params = { query: 'from logs' };
const esSearch = esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);

await firstValueFrom(
esSearch.search({ params }, { transport: transportOptions }, mockDeps)
);

expect(mockApiCaller).toHaveBeenNthCalledWith(
expect(mockAsyncQuery).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
method: 'POST',
path: '/_query/async',
body: {
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
keep_on_completion: false,
query: 'from logs',
},
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
keep_on_completion: false,
query: 'from logs',
}),
expect.objectContaining({ maxRetries: 1, meta: true, signal: undefined })
);
});

it('sets transport options on GET requests', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQueryGet.mockResolvedValueOnce(mockAsyncResponse);
const params = {
query: 'from logs* | limit 10',
};
Expand All @@ -162,21 +170,19 @@ describe('ES|QL async search strategy', () => {
esSearch.search({ id: 'foo', params }, { transport: { maxRetries: 1 } }, mockDeps)
);

expect(mockApiCaller).toHaveBeenNthCalledWith(
expect(mockAsyncQueryGet).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
path: '/_query/async/foo',
querystring: {
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
},
id: 'foo',
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
}),
expect.objectContaining({ maxRetries: 1, meta: true, signal: undefined })
);
});

it('sets wait_for_completion_timeout and keep_alive in the request', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQuery.mockResolvedValueOnce(mockAsyncResponse);

const params = {
query: 'from logs* | limit 10',
Expand All @@ -185,29 +191,27 @@ describe('ES|QL async search strategy', () => {

await esSearch.search({ params }, {}, mockDeps).toPromise();

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0].body;
expect(mockAsyncQuery).toBeCalled();
const request = mockAsyncQuery.mock.calls[0][0];
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive');
});

it('calls /stop with the given ID when using options.retrieveResults: true', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
it('calls asyncQueryStop with the given ID when using options.retrieveResults: true', async () => {
mockAsyncQueryStop.mockResolvedValueOnce(mockAsyncResponse);

const id = 'FlBvQU5CS3BKVEdPcWM1V2lkYXNUbXccVmNhQl9wcWFRdG1WYzE4N2tsOFNNdzozNjMzOQ==';
const params = { query: 'from logs* | limit 10' };
const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ id, params }, { retrieveResults: true }, mockDeps).toPromise();

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0];
expect(request.path).toEqual(
'/_query/async/FlBvQU5CS3BKVEdPcWM1V2lkYXNUbXccVmNhQl9wcWFRdG1WYzE4N2tsOFNNdzozNjMzOQ==/stop'
);
expect(mockAsyncQueryStop).toBeCalled();
const request = mockAsyncQueryStop.mock.calls[0][0];
expect(request.id).toEqual(id);
});

it('should delete when aborted', async () => {
mockApiCaller.mockResolvedValueOnce({
mockAsyncQuery.mockResolvedValueOnce({
...mockAsyncResponse,
body: {
...mockAsyncResponse.body,
Expand All @@ -231,9 +235,9 @@ describe('ES|QL async search strategy', () => {
} catch (e) {
err = e;
}
expect(mockApiCaller).toBeCalled();
expect(mockAsyncQuery).toBeCalled();
expect(err).not.toBeUndefined();
expect(mockApiCaller).toBeCalled();
expect(mockAsyncQuery).toBeCalled();
});
});

Expand All @@ -246,7 +250,7 @@ describe('ES|QL async search strategy', () => {
meta: {} as any,
});

mockApiCaller.mockRejectedValue(errResponse);
mockAsyncQuery.mockRejectedValue(errResponse);

const params = {
query: 'from logs* | limit 10',
Expand All @@ -259,7 +263,7 @@ describe('ES|QL async search strategy', () => {
} catch (e) {
err = e;
}
expect(mockApiCaller).toBeCalled();
expect(mockAsyncQuery).toBeCalled();
expect(err).toBeInstanceOf(KbnSearchError);
expect(err?.statusCode).toBe(404);
expect(err?.message).toBe(errResponse.message);
Expand All @@ -269,7 +273,7 @@ describe('ES|QL async search strategy', () => {
it('throws normalized error if Error is thrown', async () => {
const errResponse = new Error('not good');

mockApiCaller.mockRejectedValue(errResponse);
mockAsyncQuery.mockRejectedValue(errResponse);

const params = {
query: 'from logs* | limit 10',
Expand All @@ -282,7 +286,7 @@ describe('ES|QL async search strategy', () => {
} catch (e) {
err = e;
}
expect(mockApiCaller).toBeCalled();
expect(mockAsyncQuery).toBeCalled();
expect(err).toBeInstanceOf(KbnSearchError);
expect(err?.statusCode).toBe(500);
expect(err?.message).toBe(errResponse.message);
Expand All @@ -292,16 +296,16 @@ describe('ES|QL async search strategy', () => {

describe('cancel', () => {
it('makes a DELETE request to async search with the provided ID', async () => {
mockApiCaller.mockResolvedValueOnce(200);
mockAsyncQueryDelete.mockResolvedValueOnce(200);

const id = 'some_id';
const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);

await esSearch.cancel!(id, {}, mockDeps);

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0];
expect(request.path).toContain(id);
expect(mockAsyncQueryDelete).toBeCalled();
const request = mockAsyncQueryDelete.mock.calls[0][0];
expect(request.id).toBe(id);
});

it('throws normalized error on ResponseError', async () => {
Expand All @@ -312,7 +316,7 @@ describe('ES|QL async search strategy', () => {
warnings: [],
meta: {} as any,
});
mockApiCaller.mockRejectedValue(errResponse);
mockAsyncQueryDelete.mockRejectedValue(errResponse);

const id = 'some_id';
const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);
Expand All @@ -324,7 +328,7 @@ describe('ES|QL async search strategy', () => {
err = e;
}

expect(mockApiCaller).toBeCalled();
expect(mockAsyncQueryDelete).toBeCalled();
expect(err).toBeInstanceOf(KbnServerError);
expect(err?.statusCode).toBe(400);
expect(err?.message).toBe(errResponse.message);
Expand All @@ -334,22 +338,22 @@ describe('ES|QL async search strategy', () => {

describe('extend', () => {
it('makes a GET request to async search with the provided ID and keepAlive', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
mockAsyncQueryGet.mockResolvedValueOnce(mockAsyncResponse);

const id = 'some_other_id';
const keepAlive = '1d';
const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger);

await esSearch.extend!(id, keepAlive, {}, mockDeps);

expect(mockApiCaller).toBeCalled();
const request = mockApiCaller.mock.calls[0][0];
expect(request.querystring).toEqual({ id, keep_alive: keepAlive });
expect(mockAsyncQueryGet).toBeCalled();
const request = mockAsyncQueryGet.mock.calls[0][0];
expect(request).toEqual({ id, keep_alive: keepAlive });
});

it('throws normalized error on ElasticsearchClientError', async () => {
const errResponse = new errors.ElasticsearchClientError('something is wrong with EsClient');
mockApiCaller.mockRejectedValue(errResponse);
mockAsyncQueryGet.mockRejectedValue(errResponse);

const id = 'some_other_id';
const keepAlive = '1d';
Expand All @@ -362,7 +366,7 @@ describe('ES|QL async search strategy', () => {
err = e;
}

expect(mockApiCaller).toBeCalled();
expect(mockAsyncQueryGet).toBeCalled();
expect(err).toBeInstanceOf(KbnServerError);
expect(err?.statusCode).toBe(500);
expect(err?.message).toBe(errResponse.message);
Expand Down
Loading
Loading