Skip to content

Commit

Permalink
[Security Solution] migrate to new GET metadata list API (#119123)
Browse files Browse the repository at this point in the history
  • Loading branch information
joeypoon authored Nov 23, 2021
1 parent 079db96 commit edf66a5
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 180 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { HostStatus } from '../types';
import { GetMetadataListRequestSchemaV2 } from './metadata';

describe('endpoint metadata schema', () => {
describe('GetMetadataListRequestSchemaV2', () => {
const query = GetMetadataListRequestSchemaV2.query;

it('should return correct query params when valid', () => {
const queryParams = {
page: 1,
pageSize: 20,
kuery: 'some kuery',
hostStatuses: [HostStatus.HEALTHY.toString()],
};
expect(query.validate(queryParams)).toEqual(queryParams);
});

it('should correctly use default values', () => {
const expected = { page: 0, pageSize: 10 };
expect(query.validate(undefined)).toEqual(expected);
expect(query.validate({ page: undefined })).toEqual(expected);
expect(query.validate({ pageSize: undefined })).toEqual(expected);
expect(query.validate({ page: undefined, pageSize: undefined })).toEqual(expected);
});

it('should throw if page param is not a number', () => {
expect(() => query.validate({ page: 'notanumber' })).toThrowError();
});

it('should throw if page param is less than 0', () => {
expect(() => query.validate({ page: -1 })).toThrowError();
});

it('should throw if pageSize param is not a number', () => {
expect(() => query.validate({ pageSize: 'notanumber' })).toThrowError();
});

it('should throw if pageSize param is less than 1', () => {
expect(() => query.validate({ pageSize: 0 })).toThrowError();
});

it('should throw if pageSize param is greater than 10000', () => {
expect(() => query.validate({ pageSize: 10001 })).toThrowError();
});

it('should throw if kuery is not string', () => {
expect(() => query.validate({ kuery: 123 })).toThrowError();
});

it('should work with valid hostStatus', () => {
const queryParams = { hostStatuses: [HostStatus.HEALTHY, HostStatus.UPDATING] };
const expected = { page: 0, pageSize: 10, ...queryParams };
expect(query.validate(queryParams)).toEqual(expected);
});

it('should throw if invalid hostStatus', () => {
expect(() =>
query.validate({ hostStatuses: [HostStatus.UNHEALTHY, 'invalidstatus'] })
).toThrowError();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema, TypeOf } from '@kbn/config-schema';
import { HostStatus } from '../types';

export const GetMetadataListRequestSchemaV2 = {
query: schema.object(
{
page: schema.number({ defaultValue: 0, min: 0 }),
pageSize: schema.number({ defaultValue: 10, min: 1, max: 10000 }),
kuery: schema.maybe(schema.string()),
hostStatuses: schema.maybe(
schema.arrayOf(
schema.oneOf([
schema.literal(HostStatus.HEALTHY.toString()),
schema.literal(HostStatus.OFFLINE.toString()),
schema.literal(HostStatus.UPDATING.toString()),
schema.literal(HostStatus.UNHEALTHY.toString()),
schema.literal(HostStatus.INACTIVE.toString()),
])
)
),
},
{ defaultValue: { page: 0, pageSize: 10 } }
),
};

export type GetMetadataListRequestQuery = TypeOf<typeof GetMetadataListRequestSchemaV2.query>;
10 changes: 3 additions & 7 deletions x-pack/plugins/security_solution/common/endpoint/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1235,18 +1235,14 @@ export interface ListPageRouteState {
/**
* REST API standard base response for list types
*/
export interface BaseListResponse {
data: unknown[];
interface BaseListResponse<D = unknown> {
data: D[];
page: number;
pageSize: number;
total: number;
sort?: string;
sortOrder?: 'asc' | 'desc';
}

/**
* Returned by the server via GET /api/endpoint/metadata
*/
export interface MetadataListResponse extends BaseListResponse {
data: HostInfo[];
}
export type MetadataListResponse = BaseListResponse<HostInfo>;
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
ActivityLog,
HostInfo,
HostPolicyResponse,
HostResultList,
HostStatus,
MetadataListResponse,
} from '../../../../common/endpoint/types';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
import { FleetActionGenerator } from '../../../../common/endpoint/data_generators/fleet_action_generator';
Expand Down Expand Up @@ -43,7 +43,7 @@ import {
} from '../mocks';

type EndpointMetadataHttpMocksInterface = ResponseProvidersInterface<{
metadataList: () => HostResultList;
metadataList: () => MetadataListResponse;
metadataDetails: () => HostInfo;
}>;
export const endpointMetadataHttpMocks = httpHandlerMockFactory<EndpointMetadataHttpMocksInterface>(
Expand Down Expand Up @@ -72,6 +72,30 @@ export const endpointMetadataHttpMocks = httpHandlerMockFactory<EndpointMetadata
};
},
},
{
id: 'metadataList',
path: HOST_METADATA_LIST_ROUTE,
method: 'get',
handler: () => {
const generator = new EndpointDocGenerator('seed');

return {
data: Array.from({ length: 10 }, () => {
const endpoint = {
metadata: generator.generateHostMetadata(),
host_status: HostStatus.UNHEALTHY,
};

generator.updateCommonInfo();

return endpoint;
}),
total: 10,
page: 0,
pageSize: 10,
};
},
},
{
id: 'metadataDetails',
path: HOST_METADATA_GET_ROUTE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import { Action } from 'redux';
import { EuiSuperDatePickerRecentRange } from '@elastic/eui';
import type { DataViewBase } from '@kbn/es-query';
import {
HostResultList,
HostInfo,
GetHostPolicyResponse,
HostIsolationRequestBody,
ISOLATION_ACTIONS,
MetadataListResponse,
} from '../../../../../common/endpoint/types';
import { ServerApiError } from '../../../../common/types';
import { GetPolicyListResponse } from '../../policy/types';
import { EndpointState } from '../types';

export interface ServerReturnedEndpointList {
type: 'serverReturnedEndpointList';
payload: HostResultList;
payload: MetadataListResponse;
}

export interface ServerFailedToReturnEndpointList {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ import { applyMiddleware, Store, createStore } from 'redux';

import { coreMock } from '../../../../../../../../src/core/public/mocks';

import { HostResultList, AppLocation } from '../../../../../common/endpoint/types';
import { AppLocation, MetadataListResponse } from '../../../../../common/endpoint/types';
import { DepsStartMock, depsStartMock } from '../../../../common/mock/endpoint';

import { endpointMiddlewareFactory } from './middleware';

import { endpointListReducer } from './reducer';

import { uiQueryParams } from './selectors';
import { mockEndpointResultList } from './mock_endpoint_result_list';
import {
mockEndpointResultList,
setEndpointListApiMockImplementation,
} from './mock_endpoint_result_list';
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
import {
MiddlewareActionSpyHelper,
createSpyMiddleware,
} from '../../../../common/store/test_utils';
import { getEndpointListPath } from '../../../common/routing';
import { HOST_METADATA_LIST_ROUTE } from '../../../../../common/endpoint/constants';

jest.mock('../../policy/store/services/ingest', () => ({
sendGetAgentPolicyList: () => Promise.resolve({ items: [] }),
Expand All @@ -40,8 +44,8 @@ describe('endpoint list pagination: ', () => {
let queryParams: () => EndpointIndexUIQueryParams;
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
let actionSpyMiddleware;
const getEndpointListApiResponse = (): HostResultList => {
return mockEndpointResultList({ request_page_size: 1, request_page_index: 1, total: 10 });
const getEndpointListApiResponse = (): MetadataListResponse => {
return mockEndpointResultList({ pageSize: 1, page: 0, total: 10 });
};

let historyPush: (params: EndpointIndexUIQueryParams) => void;
Expand All @@ -63,13 +67,15 @@ describe('endpoint list pagination: ', () => {
historyPush = (nextQueryParams: EndpointIndexUIQueryParams): void => {
return history.push(getEndpointListPath({ name: 'endpointList', ...nextQueryParams }));
};

setEndpointListApiMockImplementation(fakeHttpServices);
});

describe('when the user enteres the endpoint list for the first time', () => {
it('the api is called with page_index and page_size defaulting to 0 and 10 respectively', async () => {
const apiResponse = getEndpointListApiResponse();
fakeHttpServices.post.mockResolvedValue(apiResponse);
expect(fakeHttpServices.post).not.toHaveBeenCalled();
fakeHttpServices.get.mockResolvedValue(apiResponse);
expect(fakeHttpServices.get).not.toHaveBeenCalled();

store.dispatch({
type: 'userChangedUrl',
Expand All @@ -79,11 +85,12 @@ describe('endpoint list pagination: ', () => {
},
});
await waitForAction('serverReturnedEndpointList');
expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', {
body: JSON.stringify({
paging_properties: [{ page_index: '0' }, { page_size: '10' }],
filters: { kql: '' },
}),
expect(fakeHttpServices.get).toHaveBeenCalledWith(HOST_METADATA_LIST_ROUTE, {
query: {
page: '0',
pageSize: '10',
kuery: '',
},
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('EndpointList store concerns', () => {
const loadDataToStore = () => {
dispatch({
type: 'serverReturnedEndpointList',
payload: mockEndpointResultList({ request_page_size: 1, request_page_index: 1, total: 10 }),
payload: mockEndpointResultList({ pageSize: 1, page: 0, total: 10 }),
});
};

Expand Down Expand Up @@ -101,8 +101,8 @@ describe('EndpointList store concerns', () => {

test('it handles `serverReturnedEndpointList', () => {
const payload = mockEndpointResultList({
request_page_size: 1,
request_page_index: 1,
page: 0,
pageSize: 1,
total: 10,
});
dispatch({
Expand All @@ -111,9 +111,9 @@ describe('EndpointList store concerns', () => {
});

const currentState = store.getState();
expect(currentState.hosts).toEqual(payload.hosts);
expect(currentState.pageSize).toEqual(payload.request_page_size);
expect(currentState.pageIndex).toEqual(payload.request_page_index);
expect(currentState.hosts).toEqual(payload.data);
expect(currentState.pageSize).toEqual(payload.pageSize);
expect(currentState.pageIndex).toEqual(payload.page);
expect(currentState.total).toEqual(payload.total);
});
});
Expand Down
Loading

0 comments on commit edf66a5

Please sign in to comment.