Skip to content

Commit

Permalink
[Security Solution][Endpoint] Add tests for pending status API changes (
Browse files Browse the repository at this point in the history
#115998)

* add tests for pending status api changes

related to /pull/115441

refs elastic/security-team/issues/1705

* update mock

refs /pull/116214

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
ashokaditya and kibanamachine authored Nov 1, 2021
1 parent 2f55e68 commit 8fd0215
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class MockResponse {
private command: ISOLATION_ACTIONS = 'isolate';
private comment?: string;
private error?: string;
private ack?: boolean;

constructor() {}

Expand All @@ -154,9 +155,61 @@ export class MockResponse {
command: this.command,
comment: this.comment,
},
action_response: {
endpoint: {
ack: this.ack,
},
},
};
}

public withAck(ack?: boolean) {
this.ack = ack;
return this;
}

public forAction(id: string) {
this.actionID = id;
return this;
}
public forAgent(id: string) {
this.agent = id;
return this;
}
}

export const aMockResponse = (actionID: string, agentID: string, ack?: boolean): MockResponse => {
return new MockResponse().forAction(actionID).forAgent(agentID).withAck(ack);
};

export class MockEndpointResponse {
private actionID: string = uuid.v4();
private ts: moment.Moment = moment();
private started: moment.Moment = moment();
private completed: moment.Moment = moment();
private agent: string = '';
private command: ISOLATION_ACTIONS = 'isolate';
private comment?: string;
private error?: string;

constructor() {}

public build(): LogsEndpointActionResponse {
return {
'@timestamp': this.ts.toISOString(),
EndpointActions: {
action_id: this.actionID,
completed_at: this.completed.toISOString(),
data: {
command: this.command,
comment: this.comment,
},
started_at: this.started.toISOString(),
},
agent: { id: this.agent },
error: { message: this.error ?? '' },
};
}
public forAction(id: string) {
this.actionID = id;
return this;
Expand All @@ -167,6 +220,6 @@ export class MockResponse {
}
}

export const aMockResponse = (actionID: string, agentID: string): MockResponse => {
return new MockResponse().forAction(actionID).forAgent(agentID);
export const aMockEndpointResponse = (actionID: string, agentID: string): MockEndpointResponse => {
return new MockEndpointResponse().forAction(actionID).forAgent(agentID);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ import {
} from '../../mocks';
import { registerActionStatusRoutes } from './status';
import uuid from 'uuid';
import { aMockAction, aMockResponse, MockAction, mockSearchResult, MockResponse } from './mocks';
import {
aMockAction,
aMockResponse,
aMockEndpointResponse,
MockEndpointResponse,
MockAction,
mockSearchResult,
MockResponse,
} from './mocks';

describe('Endpoint Action Status', () => {
describe('schema', () => {
Expand Down Expand Up @@ -62,7 +70,11 @@ describe('Endpoint Action Status', () => {
// convenience for calling the route and handler for action status
let getPendingStatus: (reqParams?: any) => Promise<jest.Mocked<KibanaResponseFactory>>;
// convenience for injecting mock responses for actions index and responses
let havingActionsAndResponses: (actions: MockAction[], responses: any[]) => void;
let havingActionsAndResponses: (
actions: MockAction[],
responses: MockResponse[],
endpointResponses?: MockEndpointResponse[]
) => void;

beforeEach(() => {
const esClientMock = elasticsearchServiceMock.createScopedClusterClient();
Expand Down Expand Up @@ -94,12 +106,19 @@ describe('Endpoint Action Status', () => {
return mockResponse;
};

havingActionsAndResponses = (actions: MockAction[], responses: MockResponse[]) => {
havingActionsAndResponses = (
actions: MockAction[],
responses: MockResponse[],
endpointResponses?: MockEndpointResponse[]
) => {
esClientMock.asCurrentUser.search = jest.fn().mockImplementation((req) => {
const size = req.size ? req.size : 10;

const items: any[] =
req.index === '.fleet-actions' ? actions.splice(0, size) : responses.splice(0, size);
req.index === '.fleet-actions'
? actions.splice(0, size)
: req.index === '.logs-endpoint.action.responses' && !!endpointResponses
? endpointResponses
: responses.splice(0, size);

if (items.length > 0) {
return Promise.resolve(mockSearchResult(items.map((x) => x.build())));
Expand Down Expand Up @@ -311,5 +330,125 @@ describe('Endpoint Action Status', () => {
},
});
});

describe('with endpoint response index', () => {
it('should respond with 1 pending action response when no endpoint response', async () => {
const mockAgentID = 'XYZABC-000';
const actionID = 'some-known-action_id';
havingActionsAndResponses(
[aMockAction().withAgent(mockAgentID).withID(actionID)],
[aMockResponse(actionID, mockAgentID, true)]
);
(endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest
.fn()
.mockReturnValue({
findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]),
});
const response = await getPendingStatus({
query: {
agent_ids: [mockAgentID],
},
});

expect(response.ok).toBeCalled();
expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1);
expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID);
});

it('should respond with 0 pending action response when there is a matching endpoint response', async () => {
const mockAgentID = 'XYZABC-000';
const actionID = 'some-known-action_id';
havingActionsAndResponses(
[aMockAction().withAgent(mockAgentID).withID(actionID)],
[aMockResponse(actionID, mockAgentID, true)],
[aMockEndpointResponse(actionID, mockAgentID)]
);
(endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest
.fn()
.mockReturnValue({
findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]),
});
const response = await getPendingStatus({
query: {
agent_ids: [mockAgentID],
},
});

expect(response.ok).toBeCalled();
expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1);
expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID);
});

it('should include a total count of a pending action response', async () => {
const mockAgentId = 'XYZABC-000';
const actionIds = ['action_id_0', 'action_id_1'];
havingActionsAndResponses(
[
aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[0]),
aMockAction().withAgent(mockAgentId).withAction('isolate').withID(actionIds[1]),
],
[
aMockResponse(actionIds[0], mockAgentId, true),
aMockResponse(actionIds[1], mockAgentId, true),
]
);
(endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest
.fn()
.mockReturnValue({
findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]),
});
const response = await getPendingStatus({
query: {
agent_ids: [mockAgentId],
},
});
expect(response.ok).toBeCalled();
expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1);
expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentId);
expect(
(response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate
).toEqual(2);
});

it('should show multiple pending action responses, and their counts', async () => {
const mockAgentID = 'XYZABC-000';
const actionIds = ['ack_0', 'ack_1', 'ack_2', 'ack_3', 'ack_4'];
havingActionsAndResponses(
[
aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[0]),
aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[1]),
aMockAction().withAgent(mockAgentID).withAction('isolate').withID(actionIds[2]),
aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[3]),
aMockAction().withAgent(mockAgentID).withAction('unisolate').withID(actionIds[4]),
],
[
aMockResponse(actionIds[0], mockAgentID, true),
aMockResponse(actionIds[1], mockAgentID, true),
aMockResponse(actionIds[2], mockAgentID, true),
aMockResponse(actionIds[3], mockAgentID, true),
aMockResponse(actionIds[4], mockAgentID, true),
]
);
(endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest
.fn()
.mockReturnValue({
findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]),
});
const response = await getPendingStatus({
query: {
agent_ids: [mockAgentID],
},
});
expect(response.ok).toBeCalled();
expect((response.ok.mock.calls[0][0]?.body as any)?.data).toHaveLength(1);
expect((response.ok.mock.calls[0][0]?.body as any)?.data[0].agent_id).toEqual(mockAgentID);
expect(
(response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.isolate
).toEqual(3);
expect(
(response.ok.mock.calls[0][0]?.body as any)?.data[0].pending_actions.unisolate
).toEqual(2);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,6 @@ export const getPendingActionCounts = async (
agentIDs
);

//

const pending: EndpointPendingActions[] = [];
for (const agentId of agentIDs) {
const agentResponses = responses[agentId];
Expand Down Expand Up @@ -270,11 +268,11 @@ export const getPendingActionCounts = async (
};

/**
* Returns a boolean for search result
* Returns a string of action ids for search result
*
* @param esClient
* @param actionIds
* @param agentIds
* @param agentId
*/
const hasEndpointResponseDoc = async ({
actionIds,
Expand Down Expand Up @@ -307,7 +305,7 @@ const hasEndpointResponseDoc = async ({
};

/**
* Returns back a map of elastic Agent IDs to array of Action IDs that have received a response.
* Returns back a map of elastic Agent IDs to array of action responses that have a response.
*
* @param esClient
* @param metadataService
Expand Down

0 comments on commit 8fd0215

Please sign in to comment.