Skip to content

Commit

Permalink
Add some tests for our risk score route
Browse files Browse the repository at this point in the history
Testing/documenting management/handling of parameters, mostly.
  • Loading branch information
rylnd committed May 1, 2023
1 parent c958ed0 commit e5d3106
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import {
} from './category_weights';
import type {
CalculateRiskScoreAggregations,
FullRiskScore,
GetScoresParams,
GetScoresResponse,
IdentifierType,
RiskScore,
RiskScoreBucket,
SimpleRiskScore,
} from './types';

const getFieldForIdentifierAgg = (identifierType: IdentifierType): string =>
Expand All @@ -41,7 +40,7 @@ const bucketToResponse = ({
enrichInputs?: boolean;
now: string;
identifierField: string;
}): SimpleRiskScore | FullRiskScore => ({
}): RiskScore => ({
'@timestamp': now,
identifierField,
identifierValue: bucket.key[identifierField],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 type { RiskScoreService } from './risk_score_service';
import type { RiskScore } from './types';

const createRiskScoreMock = (overrides: Partial<RiskScore> = {}): RiskScore => ({
'@timestamp': '2023-02-15T00:15:19.231Z',
identifierField: 'host.name',
identifierValue: 'hostname',
level: 'High',
totalScore: 149,
totalScoreNormalized: 85.332,
alertsScore: 85,
otherScore: 0,
notes: [],
riskiestInputs: [],
...overrides,
});

const createRiskScoreServiceMock = (): jest.Mocked<RiskScoreService> => ({
getScores: jest.fn(),
});

export const riskScoreServiceMock = {
create: createRiskScoreServiceMock,
createRiskScore: createRiskScoreMock,
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface RiskScoreService {
getScores: (params: GetScoresParams) => Promise<GetScoresResponse>;
}

export const buildRiskScoreService = ({
export const riskScoreService = ({
esClient,
logger,
}: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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 { loggerMock } from '@kbn/logging-mocks';

import { RISK_SCORES_URL } from '../../../../common/constants';
import {
serverMock,
requestContextMock,
requestMock,
} from '../../detection_engine/routes/__mocks__';
import { riskScoreService } from '../risk_score_service';
import { riskScoreServiceMock } from '../risk_score_service.mock';
import { riskScoringRoute } from './risk_scoring_route';

jest.mock('../risk_score_service');

describe('GET risk_engine/scores route', () => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let logger: ReturnType<typeof loggerMock.create>;
let mockRiskScoreService: ReturnType<typeof riskScoreServiceMock.create>;

beforeEach(() => {
jest.resetAllMocks();

server = serverMock.create();
logger = loggerMock.create();
({ clients, context } = requestContextMock.createTools());
mockRiskScoreService = riskScoreServiceMock.create();

clients.appClient.getAlertsIndex.mockReturnValue('default-alerts-index');
(riskScoreService as jest.Mock).mockReturnValue(mockRiskScoreService);

riskScoringRoute(server.router, logger);
});

const buildRequest = (body: object = {}) =>
requestMock.create({
method: 'get',
path: RISK_SCORES_URL,
body,
});

describe('parameters', () => {
describe('index / dataview', () => {
it('defaults to scoring the alerts index if no dataview is provided', async () => {
const request = buildRequest();

const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({ index: 'default-alerts-index' })
);
});

it('respects the provided dataview', async () => {
const request = buildRequest({ data_view_id: 'custom-dataview-id' });

// mock call to get dataview title
clients.savedObjectsClient.get.mockResolvedValueOnce({
id: '',
type: '',
references: [],
attributes: { title: 'custom-dataview-index' },
});
const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({ index: 'custom-dataview-index' })
);
});

it('defaults to the alerts index if dataview is not found', async () => {
const request = buildRequest({ data_view_id: 'custom-dataview-id' });

const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({ index: 'default-alerts-index' })
);
});
});

describe('date range', () => {
it('defaults to the last 15 days of data', async () => {
const request = buildRequest();
const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({ range: { start: 'now-15d', end: 'now' } })
);
});

it('respects the provided range if provided', async () => {
const request = buildRequest({ range: { start: 'now-30d', end: 'now-20d' } });
const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({ range: { start: 'now-30d', end: 'now-20d' } })
);
});

it.todo('rejects if date range is invalid');
});

describe('data filter', () => {
it('respects the provided filter if provided', async () => {
const request = buildRequest({
filter: {
bool: {
filter: [
{
ids: {
values: '1',
},
},
],
},
},
});
const response = await server.inject(request, requestContextMock.convertContext(context));

expect(response.status).toEqual(200);
expect(mockRiskScoreService.getScores).toHaveBeenCalledWith(
expect.objectContaining({
filter: {
bool: {
filter: [
{
ids: {
values: '1',
},
},
],
},
},
})
);
});

it.todo('rejects if filter is invalid');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { RISK_SCORES_URL } from '../../../../common/constants';
import { riskScoresRequestSchema } from '../../../../common/risk_engine/risk_scoring/risk_scores_request_schema';
import type { SecuritySolutionPluginRouter } from '../../../types';
import { buildRouteValidation } from '../../../utils/build_validation/route_validation';
import { buildRiskScoreService } from '../risk_score_service';
import { riskScoreService } from '../risk_score_service';
import { getRiskInputsIndex } from '../helpers';

export const riskScoringRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
Expand All @@ -30,7 +30,7 @@ export const riskScoringRoute = (router: SecuritySolutionPluginRouter, logger: L
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const soClient = (await context.core).savedObjects.client;
const siemClient = (await context.securitySolution).getAppClient();
const riskScoreService = buildRiskScoreService({
const riskScore = riskScoreService({
esClient,
logger,
});
Expand All @@ -56,7 +56,7 @@ export const riskScoringRoute = (router: SecuritySolutionPluginRouter, logger: L
siemClient.getAlertsIndex();

const range = userRange ?? { start: 'now-15d', end: 'now' };
const result = await riskScoreService.getScores({
const result = await riskScore.getScores({
debug,
enrichInputs,
index,
Expand Down
13 changes: 3 additions & 10 deletions x-pack/plugins/security_solution/server/lib/risk_engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface GetScoresResponse {
request: unknown;
response: unknown;
};
scores: SimpleRiskScore[] | FullRiskScore[];
scores: RiskScore[];
}

export interface SimpleRiskInput {
Expand All @@ -44,7 +44,7 @@ export interface SimpleRiskInput {

export type RiskInput = Ecs;

export interface BaseRiskScore {
export interface RiskScore {
'@timestamp': string;
identifierField: string;
identifierValue: string;
Expand All @@ -54,14 +54,7 @@ export interface BaseRiskScore {
alertsScore: number;
otherScore: number;
notes: string[];
}

export interface SimpleRiskScore extends BaseRiskScore {
riskiestInputs: SimpleRiskInput[];
}

export interface FullRiskScore extends BaseRiskScore {
riskiestInputs: RiskInput[];
riskiestInputs: SimpleRiskInput[] | RiskInput[];
}

export interface CalculateRiskScoreAggregations {
Expand Down

0 comments on commit e5d3106

Please sign in to comment.