Skip to content

Commit

Permalink
[Security Solution] Adds diff algorithm and unit tests for query fi…
Browse files Browse the repository at this point in the history
…elds (#190179)

## Summary

Related ticket: #187658

Adds diff algorithm for all the grouped `query` fields we have in the
`DiffableRule` type and prebuilt rule upgrade workflow. These include
`kql_query` (both inline and saved query), `eql_query`, and
`esql_query`. Also adds unit tests for all three algorithms.


### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
dplumlee authored Sep 9, 2024
1 parent 3f14fca commit f3be719
Show file tree
Hide file tree
Showing 7 changed files with 1,090 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
* 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 {
RuleEqlQuery,
ThreeVersionsOf,
} from '../../../../../../../../common/api/detection_engine';
import {
ThreeWayDiffOutcome,
ThreeWayMergeOutcome,
MissingVersion,
ThreeWayDiffConflict,
} from '../../../../../../../../common/api/detection_engine';
import { eqlQueryDiffAlgorithm } from './eql_query_diff_algorithm';

describe('eqlQueryDiffAlgorithm', () => {
it('returns current_version as merged output if there is no update - scenario AAA', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
})
);
});

it('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
})
);
});

it('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [{ query: 'some_field' }],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.target_version,
diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.NONE,
})
);
});

it('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
})
);
});

describe('if all three versions are different - scenario ABC', () => {
it('returns the current_version with a non-solvable conflict', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query two where false',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
})
);
});

it('returns the current_version with a non-solvable conflict if one subfield has an ABA scenario and another has an AAB', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where false',
language: 'eql',
filters: [{ field: 'some query' }],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
})
);
});
});

describe('if base_version is missing', () => {
it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: MissingVersion,
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
has_base_version: false,
base_version: undefined,
merged_version: mockVersions.current_version,
diff_outcome: ThreeWayDiffOutcome.MissingBaseNoUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
})
);
});

it('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => {
const mockVersions: ThreeVersionsOf<RuleEqlQuery> = {
base_version: MissingVersion,
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
};

const result = eqlQueryDiffAlgorithm(mockVersions);

expect(result).toEqual(
expect.objectContaining({
has_base_version: false,
base_version: undefined,
merged_version: mockVersions.target_version,
diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.SOLVABLE,
})
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 {
RuleEqlQuery,
ThreeVersionsOf,
} from '../../../../../../../../common/api/detection_engine/prebuilt_rules';
import { simpleDiffAlgorithm } from './simple_diff_algorithm';

/**
* Diff algorithm for eql query types
*/
export const eqlQueryDiffAlgorithm = (versions: ThreeVersionsOf<RuleEqlQuery>) =>
simpleDiffAlgorithm<RuleEqlQuery>(versions);
Loading

0 comments on commit f3be719

Please sign in to comment.