kibana/x-pack/plugins/cases/server/client/utils.test.ts
Jonathan Buttner aa7d8e8ab7
[ResponseOps][Cases] Add assignee field and filtering (#137532)
* Refactoring services, auth

* Adding suggest api and tests

* Working integration tests

* Switching suggest api tags

* Adding assignees field

* Adding tests for size and owner

* Adding assignee integration tests

* Starting user actions changes

* Using lodash for array comparison logic and tests

* Fixing type error

* Adding assignees user action

* [ResponseOps][Cases] Refactoring client args and authentication (#137345)

* Refactoring services, auth

* Fixing type errors

* Adding assignees migration and tests

* Fixing types and added more tests

* Fixing cypress test

* Fixing test

* Adding migration for assignees field and tests

* Adding comments and a few more tests

* Updating comments and spelling

* Addressing feedback

* Removing optional owners

* Forgot rest of files

* Adding default empty array for user actions

* Fixing test error

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2022-08-04 15:25:36 -04:00

832 lines
24 KiB
TypeScript

/*
* 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 {
arraysDifference,
buildNestedFilter,
buildRangeFilter,
constructQueryOptions,
sortToSnake,
} from './utils';
import { toElasticsearchQuery } from '@kbn/es-query';
import { CaseStatuses } from '../../common';
import { CaseSeverity } from '../../common/api';
describe('utils', () => {
describe('sortToSnake', () => {
it('transforms status correctly', () => {
expect(sortToSnake('status')).toBe('status');
});
it('transforms createdAt correctly', () => {
expect(sortToSnake('createdAt')).toBe('created_at');
});
it('transforms created_at correctly', () => {
expect(sortToSnake('created_at')).toBe('created_at');
});
it('transforms closedAt correctly', () => {
expect(sortToSnake('closedAt')).toBe('closed_at');
});
it('transforms closed_at correctly', () => {
expect(sortToSnake('closed_at')).toBe('closed_at');
});
it('transforms default correctly', () => {
expect(sortToSnake('not-exist')).toBe('created_at');
});
});
describe('buildRangeFilter', () => {
it('returns undefined if both the from and or are undefined', () => {
const node = buildRangeFilter({});
expect(node).toBeFalsy();
});
it('returns undefined if both the from and or are null', () => {
// @ts-expect-error
const node = buildRangeFilter({ from: null, to: null });
expect(node).toBeFalsy();
});
it('creates a range filter with only the from correctly', () => {
const node = buildRangeFilter({ from: 'now-1M' });
expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(`
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"cases.attributes.created_at": Object {
"gte": "now-1M",
},
},
},
],
},
}
`);
});
it('creates a range filter with only the to correctly', () => {
const node = buildRangeFilter({ to: 'now' });
expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(`
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"cases.attributes.created_at": Object {
"lte": "now",
},
},
},
],
},
}
`);
});
it('creates a range filter correctly', () => {
const node = buildRangeFilter({ from: 'now-1M', to: 'now' });
expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(`
Object {
"bool": Object {
"filter": Array [
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"cases.attributes.created_at": Object {
"gte": "now-1M",
},
},
},
],
},
},
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"cases.attributes.created_at": Object {
"lte": "now",
},
},
},
],
},
},
],
},
}
`);
});
it('creates a range filter with different field and saved object type provided', () => {
const node = buildRangeFilter({
from: 'now-1M',
to: 'now',
field: 'test',
savedObjectType: 'test-type',
});
expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(`
Object {
"bool": Object {
"filter": Array [
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"test-type.attributes.test": Object {
"gte": "now-1M",
},
},
},
],
},
},
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
"test-type.attributes.test": Object {
"lte": "now",
},
},
},
],
},
},
],
},
}
`);
});
it('escapes the query correctly', () => {
const node = buildRangeFilter({
from: '2022-04-27T12:55:47.576Z',
to: '2022-04-27T12:56:47.576Z',
field: '<weird field)',
savedObjectType: '.weird SO)',
});
expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(`
Object {
"bool": Object {
"filter": Array [
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
".weird SO).attributes.<weird field)": Object {
"gte": "2022-04-27T12:55:47.576Z",
},
},
},
],
},
},
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"range": Object {
".weird SO).attributes.<weird field)": Object {
"lte": "2022-04-27T12:56:47.576Z",
},
},
},
],
},
},
],
},
}
`);
});
});
describe('constructQueryOptions', () => {
it('creates a filter with the tags', () => {
const { filter } = constructQueryOptions({ tags: ['tag1', 'tag2'] });
expect(filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.tags",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "tag1",
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.tags",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "tag2",
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
}
`);
});
it('creates a filter with the reporters', () => {
expect(constructQueryOptions({ reporters: ['bob', 'sam'] }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.created_by.username",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "bob",
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.created_by.username",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "sam",
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
}
`);
});
it('creates a filter with the owner', () => {
expect(constructQueryOptions({ owner: 'observability' }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.owner",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "observability",
},
],
"function": "is",
"type": "function",
}
`);
});
it('creates a filter for the status', () => {
expect(constructQueryOptions({ status: CaseStatuses.open }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.status",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "open",
},
],
"function": "is",
"type": "function",
}
`);
});
it('creates a filter for the severity', () => {
expect(constructQueryOptions({ severity: CaseSeverity.CRITICAL }).filter)
.toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.severity",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "critical",
},
],
"function": "is",
"type": "function",
}
`);
});
it('creates a filter for the time range', () => {
expect(constructQueryOptions({ from: 'now-1M', to: 'now' }).filter).toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.created_at",
},
"gte",
Object {
"isQuoted": false,
"type": "literal",
"value": "now-1M",
},
],
"function": "range",
"type": "function",
},
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.created_at",
},
"lte",
Object {
"isQuoted": false,
"type": "literal",
"value": "now",
},
],
"function": "range",
"type": "function",
},
],
"function": "and",
"type": "function",
}
`);
});
it('sets filter to undefined when no options were passed', () => {
expect(constructQueryOptions({}).filter).toBeUndefined();
});
it('creates a filter with tags and reporters', () => {
expect(constructQueryOptions({ tags: ['tag1', 'tag2'], reporters: 'sam' }).filter)
.toMatchInlineSnapshot(`
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.tags",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "tag1",
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.tags",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "tag2",
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
Object {
"arguments": Array [
Object {
"isQuoted": false,
"type": "literal",
"value": "cases.attributes.created_by.username",
},
Object {
"isQuoted": false,
"type": "literal",
"value": "sam",
},
],
"function": "is",
"type": "function",
},
],
"function": "and",
"type": "function",
}
`);
});
});
describe('buildNestedFilter', () => {
it('returns undefined if filters is undefined', () => {
expect(buildNestedFilter({ field: '', nestedField: '', operator: 'or' })).toBeUndefined();
});
it('returns undefined when the filters array is empty', () => {
expect(
buildNestedFilter({ filters: [], field: '', nestedField: '', operator: 'or' })
).toBeUndefined();
});
it('returns a KueryNode for a single filter', () => {
expect(
toElasticsearchQuery(
buildNestedFilter({
filters: ['hello'],
field: 'uid',
nestedField: 'nestedField',
operator: 'or',
})!
)
).toMatchInlineSnapshot(`
Object {
"nested": Object {
"path": "cases.attributes.nestedField",
"query": Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"match": Object {
"cases.attributes.nestedField.uid": "hello",
},
},
],
},
},
"score_mode": "none",
},
}
`);
});
it("returns a KueryNode for multiple filters or'd together", () => {
expect(
toElasticsearchQuery(
buildNestedFilter({
filters: ['uid1', 'uid2'],
field: 'uid',
nestedField: 'nestedField',
operator: 'or',
})!
)
).toMatchInlineSnapshot(`
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"nested": Object {
"path": "cases.attributes.nestedField",
"query": Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"match": Object {
"cases.attributes.nestedField.uid": "uid1",
},
},
],
},
},
"score_mode": "none",
},
},
Object {
"nested": Object {
"path": "cases.attributes.nestedField",
"query": Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"match": Object {
"cases.attributes.nestedField.uid": "uid2",
},
},
],
},
},
"score_mode": "none",
},
},
],
},
}
`);
});
it("returns a KueryNode for multiple filters and'ed together", () => {
expect(
toElasticsearchQuery(
buildNestedFilter({
filters: ['uid1', 'uid2'],
field: 'uid',
nestedField: 'nestedField',
operator: 'and',
})!
)
).toMatchInlineSnapshot(`
Object {
"bool": Object {
"filter": Array [
Object {
"nested": Object {
"path": "cases.attributes.nestedField",
"query": Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"match": Object {
"cases.attributes.nestedField.uid": "uid1",
},
},
],
},
},
"score_mode": "none",
},
},
Object {
"nested": Object {
"path": "cases.attributes.nestedField",
"query": Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [
Object {
"match": Object {
"cases.attributes.nestedField.uid": "uid2",
},
},
],
},
},
"score_mode": "none",
},
},
],
},
}
`);
});
});
describe('arraysDifference', () => {
it('returns null if originalValue is null', () => {
expect(arraysDifference(null, [])).toBeNull();
});
it('returns null if originalValue is undefined', () => {
expect(arraysDifference(undefined, [])).toBeNull();
});
it('returns null if originalValue is not an array', () => {
// @ts-expect-error passing a string instead of an array
expect(arraysDifference('a string', [])).toBeNull();
});
it('returns null if updatedValue is null', () => {
expect(arraysDifference([], null)).toBeNull();
});
it('returns null if updatedValue is undefined', () => {
expect(arraysDifference([], undefined)).toBeNull();
});
it('returns null if updatedValue is not an array', () => {
expect(arraysDifference([], 'a string' as unknown as string[])).toBeNull();
});
it('returns null if the arrays are both empty', () => {
expect(arraysDifference([], [])).toBeNull();
});
describe('object arrays', () => {
it('returns null if the arrays are both equal with single string', () => {
expect(arraysDifference([{ uid: 'a' }], [{ uid: 'a' }])).toBeNull();
});
it('returns null if the arrays are both equal with multiple strings', () => {
expect(
arraysDifference([{ uid: 'a' }, { uid: 'b' }], [{ uid: 'a' }, { uid: 'b' }])
).toBeNull();
});
it("returns 'b' in the added items when the updated value contains an added value", () => {
expect(arraysDifference([{ uid: 'a' }], [{ uid: 'a' }, { uid: 'b' }]))
.toMatchInlineSnapshot(`
Object {
"addedItems": Array [
Object {
"uid": "b",
},
],
"deletedItems": Array [],
}
`);
});
it("returns 'b' in the deleted items when the updated value removes an item", () => {
expect(arraysDifference([{ uid: 'a' }, { uid: 'b' }], [{ uid: 'a' }]))
.toMatchInlineSnapshot(`
Object {
"addedItems": Array [],
"deletedItems": Array [
Object {
"uid": "b",
},
],
}
`);
});
it("returns 'a' and 'b' in the added items when the updated value adds both", () => {
expect(arraysDifference([], [{ uid: 'a' }, { uid: 'b' }])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [
Object {
"uid": "a",
},
Object {
"uid": "b",
},
],
"deletedItems": Array [],
}
`);
});
it("returns 'a' and 'b' in the deleted items when the updated value removes both", () => {
expect(arraysDifference([{ uid: 'a' }, { uid: 'b' }], [])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [],
"deletedItems": Array [
Object {
"uid": "a",
},
Object {
"uid": "b",
},
],
}
`);
});
it('returns the added and deleted values if the type of objects are different', () => {
expect(arraysDifference([{ uid: 'a' }], [{ uid: 'a', hi: '1' }])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [
Object {
"hi": "1",
"uid": "a",
},
],
"deletedItems": Array [
Object {
"uid": "a",
},
],
}
`);
});
});
describe('string arrays', () => {
it('returns null if the arrays are both equal with single string', () => {
expect(arraysDifference(['a'], ['a'])).toBeNull();
});
it('returns null if the arrays are both equal with multiple strings', () => {
expect(arraysDifference(['a', 'b'], ['a', 'b'])).toBeNull();
});
it("returns 'b' in the added items when the updated value contains an added value", () => {
expect(arraysDifference(['a'], ['a', 'b'])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [
"b",
],
"deletedItems": Array [],
}
`);
});
it("returns 'b' in the deleted items when the updated value removes an item", () => {
expect(arraysDifference(['a', 'b'], ['a'])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [],
"deletedItems": Array [
"b",
],
}
`);
});
it("returns 'a' and 'b' in the added items when the updated value adds both", () => {
expect(arraysDifference([], ['a', 'b'])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [
"a",
"b",
],
"deletedItems": Array [],
}
`);
});
it("returns 'a' and 'b' in the deleted items when the updated value removes both", () => {
expect(arraysDifference(['a', 'b'], [])).toMatchInlineSnapshot(`
Object {
"addedItems": Array [],
"deletedItems": Array [
"a",
"b",
],
}
`);
});
});
});
});