mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* [Snapshot & Restore] Add component tests for snapshot list search
* [Snapshot & Restore] Fix term search
* [Snapshot & Restore] Add error handling tests
* [Snapshot & Restore] Add code review suggestions
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit c4585b3382
)
This commit is contained in:
parent
e85b4f22ab
commit
25b073ff8f
5 changed files with 413 additions and 11 deletions
|
@ -12,6 +12,7 @@ import { setup as repositoryEditSetup } from './repository_edit.helpers';
|
|||
import { setup as policyAddSetup } from './policy_add.helpers';
|
||||
import { setup as policyEditSetup } from './policy_edit.helpers';
|
||||
import { setup as restoreSnapshotSetup } from './restore_snapshot.helpers';
|
||||
import { setup as snapshotListSetup } from './snapshot_list.helpers';
|
||||
|
||||
export type { TestBed } from '@kbn/test/jest';
|
||||
export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test/jest';
|
||||
|
@ -25,4 +26,5 @@ export const pageHelpers = {
|
|||
policyAdd: { setup: policyAddSetup },
|
||||
policyEdit: { setup: policyEditSetup },
|
||||
restoreSnapshot: { setup: restoreSnapshotSetup },
|
||||
snapshotList: { setup: snapshotListSetup },
|
||||
};
|
||||
|
|
|
@ -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 { act } from 'react-dom/test-utils';
|
||||
import { TestBedConfig, registerTestBed, TestBed } from '@kbn/test/jest';
|
||||
|
||||
import { BASE_PATH } from '../../../public/application/constants';
|
||||
import { SnapshotList } from '../../../public/application/sections/home/snapshot_list';
|
||||
import { WithAppDependencies } from './setup_environment';
|
||||
|
||||
const getTestBedConfig = (query?: string): TestBedConfig => ({
|
||||
memoryRouter: {
|
||||
initialEntries: [`${BASE_PATH}/snapshots${query ?? ''}`],
|
||||
componentRoutePath: `${BASE_PATH}/snapshots/:repositoryName?/:snapshotId*`,
|
||||
},
|
||||
});
|
||||
|
||||
const initTestBed = (query?: string) =>
|
||||
registerTestBed(WithAppDependencies(SnapshotList), getTestBedConfig(query))();
|
||||
|
||||
export interface SnapshotListTestBed extends TestBed {
|
||||
actions: {
|
||||
setSearchText: (value: string, advanceTime?: boolean) => void;
|
||||
searchErrorExists: () => boolean;
|
||||
getSearchErrorText: () => string;
|
||||
};
|
||||
}
|
||||
|
||||
const searchBarSelector = 'snapshotListSearch';
|
||||
const searchErrorSelector = 'snapshotListSearchError';
|
||||
|
||||
export const setup = async (query?: string): Promise<SnapshotListTestBed> => {
|
||||
const testBed = await initTestBed(query);
|
||||
const { form, component, find, exists } = testBed;
|
||||
|
||||
const setSearchText = async (value: string, advanceTime = true) => {
|
||||
await act(async () => {
|
||||
form.setInputValue(searchBarSelector, value);
|
||||
});
|
||||
component.update();
|
||||
if (advanceTime) {
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(500);
|
||||
});
|
||||
component.update();
|
||||
}
|
||||
};
|
||||
|
||||
const searchErrorExists = (): boolean => {
|
||||
return exists(searchErrorSelector);
|
||||
};
|
||||
|
||||
const getSearchErrorText = (): string => {
|
||||
return find(searchErrorSelector).text();
|
||||
};
|
||||
|
||||
return {
|
||||
...testBed,
|
||||
actions: {
|
||||
setSearchText,
|
||||
searchErrorExists,
|
||||
getSearchErrorText,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -32,16 +32,6 @@ jest.mock('@kbn/i18n-react', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../../common/constants', () => {
|
||||
const original = jest.requireActual('../../common/constants');
|
||||
|
||||
return {
|
||||
...original,
|
||||
// Mocking this value to a lower number in order to more easily trigger the max snapshots warning in the tests
|
||||
SNAPSHOT_LIST_MAX_SIZE: 2,
|
||||
};
|
||||
});
|
||||
|
||||
const removeWhiteSpaceOnArrayValues = (array: any[]) =>
|
||||
array.map((value) => {
|
||||
if (!value.trim) {
|
||||
|
|
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { EuiSearchBoxProps } from '@elastic/eui/src/components/search_bar/search_box';
|
||||
|
||||
import { useLoadSnapshots } from '../../public/application/services/http';
|
||||
import { DEFAULT_SNAPSHOT_LIST_PARAMS } from '../../public/application/lib';
|
||||
|
||||
import * as fixtures from '../../test/fixtures';
|
||||
import { SnapshotListTestBed } from './helpers/snapshot_list.helpers';
|
||||
import { REPOSITORY_NAME } from './helpers/constant';
|
||||
import { pageHelpers, getRandomString } from './helpers';
|
||||
|
||||
/*
|
||||
* We are mocking useLoadSnapshots instead of sinon fake server because it's not
|
||||
* spying on url parameters used in requests, for example /api/snapshot_restore/snapshots
|
||||
* ?sortField=startTimeInMillis&sortDirection=desc&pageIndex=0&pageSize=20
|
||||
* &searchField=repository&searchValue=test&searchMatch=must&searchOperator=exact
|
||||
* would be shown as url=/api/snapshot_restore/snapshots is sinon server
|
||||
*/
|
||||
jest.mock('../../public/application/services/http', () => ({
|
||||
useLoadSnapshots: jest.fn(),
|
||||
setUiMetricServiceSnapshot: () => {},
|
||||
setUiMetricService: () => {},
|
||||
}));
|
||||
|
||||
/*
|
||||
* Mocking EuiSearchBar because its onChange is not firing during tests
|
||||
*/
|
||||
jest.mock('@elastic/eui/lib/components/search_bar/search_box', () => {
|
||||
return {
|
||||
EuiSearchBox: (props: EuiSearchBoxProps) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockSearchBox'}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
props.onSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const { setup } = pageHelpers.snapshotList;
|
||||
|
||||
describe('<SnapshotList />', () => {
|
||||
let testBed: SnapshotListTestBed;
|
||||
let setSearchText: SnapshotListTestBed['actions']['setSearchText'];
|
||||
let searchErrorExists: SnapshotListTestBed['actions']['searchErrorExists'];
|
||||
let getSearchErrorText: SnapshotListTestBed['actions']['getSearchErrorText'];
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
const snapshot = fixtures.getSnapshot({
|
||||
repository: REPOSITORY_NAME,
|
||||
snapshot: getRandomString(),
|
||||
});
|
||||
const snapshots = [snapshot];
|
||||
(useLoadSnapshots as jest.Mock).mockReturnValue({
|
||||
error: null,
|
||||
isInitialRequest: false,
|
||||
isLoading: false,
|
||||
data: {
|
||||
snapshots,
|
||||
repositories: [REPOSITORY_NAME],
|
||||
policies: [],
|
||||
errors: {},
|
||||
total: snapshots.length,
|
||||
},
|
||||
resendRequest: () => {},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
testBed = await setup();
|
||||
({
|
||||
actions: { setSearchText, searchErrorExists, getSearchErrorText },
|
||||
} = testBed);
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
describe('url parameters', () => {
|
||||
test('query is updated with repository name from the url', async () => {
|
||||
testBed = await setup('?repository=test_repo');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'repository',
|
||||
searchValue: 'test_repo',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
|
||||
test('query is updated with snapshot policy name from the url', async () => {
|
||||
testBed = await setup('?policy=test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'policyName',
|
||||
searchValue: 'test_policy',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
|
||||
test('query is not updated with unknown params from the url', async () => {
|
||||
testBed = await setup('?some_param=test_param');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('debounce', () => {
|
||||
test('waits after input to update list params for search', async () => {
|
||||
const ADVANCE_TIME = false;
|
||||
await setSearchText('snapshot=test_snapshot', ADVANCE_TIME);
|
||||
// the last request was without any search params
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
});
|
||||
// advance the timers until after the debounce timeout
|
||||
// we use act because the component is updated when the timers advance
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
});
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('query parsing', () => {
|
||||
describe('snapshot', () => {
|
||||
test('term search is converted to partial snapshot search', async () => {
|
||||
await setSearchText('term_snapshot_search');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'term_snapshot_search',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding term search is converted to partial excluding snapshot search', async () => {
|
||||
await setSearchText('-test_snapshot');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('partial snapshot search is parsed', async () => {
|
||||
await setSearchText('snapshot:test_snapshot');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding partial snapshot search is parsed', async () => {
|
||||
await setSearchText('-snapshot:test_snapshot');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('exact snapshot search is parsed', async () => {
|
||||
await setSearchText('snapshot=test_snapshot');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding exact snapshot search is parsed', async () => {
|
||||
await setSearchText('-snapshot=test_snapshot');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'snapshot',
|
||||
searchValue: 'test_snapshot',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('repository', () => {
|
||||
test('partial repository search is parsed', async () => {
|
||||
await setSearchText('repository:test_repository');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'repository',
|
||||
searchValue: 'test_repository',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding partial repository search is parsed', async () => {
|
||||
await setSearchText('-repository:test_repository');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'repository',
|
||||
searchValue: 'test_repository',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('exact repository search is parsed', async () => {
|
||||
await setSearchText('repository=test_repository');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'repository',
|
||||
searchValue: 'test_repository',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding exact repository search is parsed', async () => {
|
||||
await setSearchText('-repository=test_repository');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'repository',
|
||||
searchValue: 'test_repository',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('policy', () => {
|
||||
test('partial policy search is parsed', async () => {
|
||||
await setSearchText('policyName:test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'policyName',
|
||||
searchValue: 'test_policy',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding partial policy search is parsed', async () => {
|
||||
await setSearchText('-policyName:test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'policyName',
|
||||
searchValue: 'test_policy',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'eq',
|
||||
});
|
||||
});
|
||||
|
||||
test('exact policy search is parsed', async () => {
|
||||
await setSearchText('policyName=test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'policyName',
|
||||
searchValue: 'test_policy',
|
||||
searchMatch: 'must',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
|
||||
test('excluding exact policy search is parsed', async () => {
|
||||
await setSearchText('-policyName=test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
searchField: 'policyName',
|
||||
searchValue: 'test_policy',
|
||||
searchMatch: 'must_not',
|
||||
searchOperator: 'exact',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
test(`doesn't allow more than 1 terms in the query`, async () => {
|
||||
await setSearchText('term1 term2');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
});
|
||||
expect(searchErrorExists()).toBeTruthy();
|
||||
expect(getSearchErrorText()).toEqual(
|
||||
'Invalid search: You can only use one clause in the search bar'
|
||||
);
|
||||
});
|
||||
|
||||
test(`doesn't allow more than 1 clauses in the query`, async () => {
|
||||
await setSearchText('snapshot=test_snapshot policyName:test_policy');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
});
|
||||
expect(searchErrorExists()).toBeTruthy();
|
||||
expect(getSearchErrorText()).toEqual(
|
||||
'Invalid search: You can only use one clause in the search bar'
|
||||
);
|
||||
});
|
||||
|
||||
test(`doesn't allow unknown properties in the query`, async () => {
|
||||
await setSearchText('unknown_field=test');
|
||||
expect(useLoadSnapshots).lastCalledWith({
|
||||
...DEFAULT_SNAPSHOT_LIST_PARAMS,
|
||||
});
|
||||
expect(searchErrorExists()).toBeTruthy();
|
||||
expect(getSearchErrorText()).toEqual('Invalid search: Unknown field `unknown_field`');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -152,12 +152,13 @@ export const SnapshotSearchBar: React.FunctionComponent<Props> = ({
|
|||
onChange={onSearchBarChange}
|
||||
toolsLeft={deleteButton}
|
||||
toolsRight={reloadButton}
|
||||
box={{ schema: searchSchema, incremental: true }}
|
||||
box={{ schema: searchSchema, incremental: true, 'data-test-subj': 'snapshotListSearch' }}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{error ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
data-test-subj="snapshotListSearchError"
|
||||
iconType="alert"
|
||||
color="danger"
|
||||
title={
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue