Convert uiSettings tests to TypeScript (#46776)

* convert create_or_upgrade_saved_config into TS

* convert service tests to TS

* convert router tests to TS

* simplify stub setup

* address comments
This commit is contained in:
Mikhail Shustov 2019-09-30 23:16:11 +02:00 committed by GitHub
parent 2df5484e8d
commit ee168f2765
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 377 additions and 373 deletions

View file

@ -77,6 +77,7 @@ declare module 'hapi' {
name: string,
factoryFn: (request: Request) => Record<string, any>
) => void;
uiSettingsServiceFactory: (options: any) => any;
}
interface Request {
@ -118,6 +119,7 @@ export default class KbnServer {
public close(): Promise<void>;
public afterPluginsInit(callback: () => void): void;
public applyLoggingConfiguration(settings: any): void;
public config: KibanaConfig;
}
// Re-export commonly used hapi types.

View file

@ -18,35 +18,23 @@
*/
import sinon from 'sinon';
import expect from '@kbn/expect';
import { SavedObjectsClient } from '../../../../../core/server';
import { SavedObjectsClient } from '../../../../src/core/server';
export const savedObjectsClientErrors = SavedObjectsClient.errors;
export function createObjectsClientStub(type, id, esDocSource = {}) {
export interface SavedObjectsClientStub {
update: sinon.SinonStub<any[], any>;
get: sinon.SinonStub<any[], any>;
create: sinon.SinonStub<any[], any>;
errors: typeof savedObjectsClientErrors;
}
export function createObjectsClientStub(esDocSource = {}): SavedObjectsClientStub {
const savedObjectsClient = {
update: sinon.stub(),
get: sinon.stub().returns({ attributes: esDocSource }),
create: sinon.stub(),
errors: savedObjectsClientErrors
errors: savedObjectsClientErrors,
};
savedObjectsClient.assertGetQuery = () => {
sinon.assert.calledOnce(savedObjectsClient.get);
const { args } = savedObjectsClient.get.getCall(0);
expect(args[0]).to.be(type);
expect(args[1]).to.eql(id);
};
savedObjectsClient.assertUpdateQuery = (expectedChanges) => {
sinon.assert.calledOnce(savedObjectsClient.update);
const { args } = savedObjectsClient.update.getCall(0);
expect(args[0]).to.be(type);
expect(args[1]).to.eql(id);
expect(args[2]).to.eql(expectedChanges);
};
return savedObjectsClient;
}

View file

@ -21,12 +21,14 @@ import sinon from 'sinon';
import expect from '@kbn/expect';
import Chance from 'chance';
import * as getUpgradeableConfigNS from '../get_upgradeable_config';
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
// @ts-ignore
import * as getUpgradeableConfigNS from './get_upgradeable_config';
// @ts-ignore
import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
const chance = new Chance();
describe('uiSettings/createOrUpgradeSavedConfig', function () {
describe('uiSettings/createOrUpgradeSavedConfig', function() {
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.restore());
@ -42,7 +44,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
type,
id: options.id,
version: 'foo',
}))
})),
};
async function run(options = {}) {
@ -51,7 +53,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
version,
buildNum,
logWithMetadata,
...options
...options,
});
sinon.assert.calledOnce(getUpgradeableConfig);
@ -70,44 +72,45 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
};
}
describe('nothing is upgradeable', function () {
describe('nothing is upgradeable', function() {
it('should create config with current version and buildNum', async () => {
const { run, savedObjectsClient } = setup();
await run();
sinon.assert.calledOnce(savedObjectsClient.create);
sinon.assert.calledWithExactly(savedObjectsClient.create, 'config', {
buildNum,
}, {
id: version
});
sinon.assert.calledWithExactly(
savedObjectsClient.create,
'config',
{
buildNum,
},
{
id: version,
}
);
});
});
describe('something is upgradeable', () => {
it('should merge upgraded attributes with current build number in new config', async () => {
const {
run,
getUpgradeableConfig,
savedObjectsClient
} = setup();
const { run, getUpgradeableConfig, savedObjectsClient } = setup();
const savedAttributes = {
buildNum: buildNum - 100,
[chance.word()]: chance.sentence(),
[chance.word()]: chance.sentence(),
[chance.word()]: chance.sentence()
[chance.word()]: chance.sentence(),
};
getUpgradeableConfig
.returns({ id: prevVersion, attributes: savedAttributes });
getUpgradeableConfig.returns({ id: prevVersion, attributes: savedAttributes });
await run();
sinon.assert.calledOnce(getUpgradeableConfig);
sinon.assert.calledOnce(savedObjectsClient.create);
sinon.assert.calledWithExactly(savedObjectsClient.create,
sinon.assert.calledWithExactly(
savedObjectsClient.create,
'config',
{
...savedAttributes,
@ -122,12 +125,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
it('should log a message for upgrades', async () => {
const { getUpgradeableConfig, logWithMetadata, run } = setup();
getUpgradeableConfig
.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
await run();
sinon.assert.calledOnce(logWithMetadata);
sinon.assert.calledWithExactly(logWithMetadata,
sinon.assert.calledWithExactly(
logWithMetadata,
['plugin', 'elasticsearch'],
sinon.match('Upgrade'),
sinon.match({
@ -140,8 +143,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
it('does not log when upgrade fails', async () => {
const { getUpgradeableConfig, logWithMetadata, run, savedObjectsClient } = setup();
getUpgradeableConfig
.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
savedObjectsClient.create.callsFake(async () => {
throw new Error('foo');
@ -171,7 +173,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
await run({ onWriteError });
sinon.assert.calledOnce(onWriteError);
sinon.assert.calledWithExactly(onWriteError, error, {
buildNum
buildNum,
});
});
@ -195,9 +197,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
try {
await run({
onWriteError: (error) => (
Promise.reject(new Error(`${error.message} bar`))
)
onWriteError: (error: Error) => Promise.reject(new Error(`${error.message} bar`)),
});
throw new Error('expected run() to reject');
} catch (error) {
@ -214,9 +214,9 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
try {
await run({
onWriteError: (error) => {
onWriteError: (error: Error) => {
throw new Error(`${error.message} bar`);
}
},
});
throw new Error('expected run() to reject');
} catch (error) {
@ -233,7 +233,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
try {
await run({
onWriteError: undefined
onWriteError: undefined,
});
throw new Error('expected run() to reject');
} catch (error) {

View file

@ -19,20 +19,27 @@
import sinon from 'sinon';
import expect from '@kbn/expect';
import { UnwrapPromise } from '@kbn/utility-types';
import { SavedObjectsClientContract } from 'src/core/server';
import KbnServer from '../../../../server/kbn_server';
import { createTestServers } from '../../../../../test_utils/kbn_server';
// @ts-ignore
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
describe('createOrUpgradeSavedConfig()', () => {
let savedObjectsClient;
let kbn;
let kbnServer;
let esServer;
let servers;
let savedObjectsClient: SavedObjectsClientContract;
let kbnServer: KbnServer;
let servers: ReturnType<typeof createTestServers>;
let esServer: UnwrapPromise<ReturnType<typeof servers['startES']>>;
let kbn: UnwrapPromise<ReturnType<typeof servers['startKibana']>>;
before(async function () {
beforeAll(async function() {
servers = createTestServers({
adjustTimeout: (t) => this.timeout(t),
adjustTimeout: t => {
jest.setTimeout(t);
},
settings: {},
});
esServer = await servers.startES();
kbn = await servers.startKibana();
@ -69,14 +76,13 @@ describe('createOrUpgradeSavedConfig()', () => {
]);
});
after(() => {
esServer.stop();
kbn.stop();
});
it('upgrades the previous version on each increment', async function () {
this.timeout(30000);
afterAll(async () => {
await esServer.stop();
await kbn.stop();
}, 30000);
it('upgrades the previous version on each increment', async function() {
jest.setTimeout(30000);
// ------------------------------------
// upgrade to 5.4.0
await createOrUpgradeSavedConfig({

View file

@ -19,11 +19,13 @@
import expect from '@kbn/expect';
import { isConfigVersionUpgradeable } from '../is_config_version_upgradeable';
import { pkg } from '../../../../utils';
// @ts-ignore
import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
// @ts-ignore
import { pkg } from '../../../utils';
describe('savedObjects/health_check/isConfigVersionUpgradeable', function () {
function isUpgradeableTest(savedVersion, kibanaVersion, expected) {
describe('savedObjects/health_check/isConfigVersionUpgradeable', function() {
function isUpgradeableTest(savedVersion: string, kibanaVersion: string, expected: boolean) {
it(`should return ${expected} for config version ${savedVersion} and kibana version ${kibanaVersion}`, () => {
expect(isConfigVersionUpgradeable(savedVersion, kibanaVersion)).to.be(expected);
});
@ -47,6 +49,6 @@ describe('savedObjects/health_check/isConfigVersionUpgradeable', function () {
isUpgradeableTest('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false);
isUpgradeableTest('5.0.0-alpha11', '5.0.0', false);
isUpgradeableTest('50.0.10-rc150-SNAPSHOT', '50.0.9', false);
isUpgradeableTest(undefined, pkg.version, false);
isUpgradeableTest(undefined as any, pkg.version, false);
isUpgradeableTest('@@version', pkg.version, false);
});

View file

@ -20,17 +20,21 @@
import sinon from 'sinon';
import expect from '@kbn/expect';
// @ts-ignore
import { Config } from '../../../server/config';
/* eslint-disable import/no-duplicates */
// @ts-ignore
import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory';
import { uiSettingsServiceFactory } from '../ui_settings_service_factory';
// @ts-ignore
import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request';
import { getUiSettingsServiceForRequest } from '../ui_settings_service_for_request';
/* eslint-enable import/no-duplicates */
// @ts-ignore
import { uiSettingsMixin } from '../ui_settings_mixin';
interface Decorators {
server: { [name: string]: any };
request: { [name: string]: any };
}
describe('uiSettingsMixin()', () => {
const sandbox = sinon.createSandbox();
@ -38,15 +42,15 @@ describe('uiSettingsMixin()', () => {
const config = Config.withDefaultSchema({
uiSettings: {
overrides: {
foo: 'bar'
}
}
foo: 'bar',
},
},
});
// maps of decorations passed to `server.decorate()`
const decorations = {
const decorations: Decorators = {
server: {},
request: {}
request: {},
};
// mock hapi server
@ -54,12 +58,12 @@ describe('uiSettingsMixin()', () => {
log: sinon.stub(),
route: sinon.stub(),
config: () => config,
addMemoizedFactoryToRequest(name, factory) {
this.decorate('request', name, function () {
addMemoizedFactoryToRequest(name: string, factory: (...args: any[]) => any) {
this.decorate('request', name, function(this: typeof server) {
return factory(this);
});
},
decorate: sinon.spy((type, name, value) => {
decorate: sinon.spy((type: keyof Decorators, name: string, value: any) => {
decorations[type][name] = value;
}),
};
@ -91,28 +95,38 @@ describe('uiSettingsMixin()', () => {
describe('server.uiSettingsServiceFactory()', () => {
it('decorates server with "uiSettingsServiceFactory"', () => {
const { decorations } = setup();
expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function');
expect(decorations.server)
.to.have.property('uiSettingsServiceFactory')
.a('function');
sandbox.stub(uiSettingsServiceFactoryNS, 'uiSettingsServiceFactory');
sinon.assert.notCalled(uiSettingsServiceFactory);
const uiSettingsServiceFactoryStub = sandbox.stub(
uiSettingsServiceFactoryNS,
'uiSettingsServiceFactory'
);
sinon.assert.notCalled(uiSettingsServiceFactoryStub);
decorations.server.uiSettingsServiceFactory();
sinon.assert.calledOnce(uiSettingsServiceFactory);
sinon.assert.calledOnce(uiSettingsServiceFactoryStub);
});
it('passes `server` and `options` argument to factory', () => {
const { decorations, server } = setup();
expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function');
expect(decorations.server)
.to.have.property('uiSettingsServiceFactory')
.a('function');
sandbox.stub(uiSettingsServiceFactoryNS, 'uiSettingsServiceFactory');
sinon.assert.notCalled(uiSettingsServiceFactory);
const uiSettingsServiceFactoryStub = sandbox.stub(
uiSettingsServiceFactoryNS,
'uiSettingsServiceFactory'
);
sinon.assert.notCalled(uiSettingsServiceFactoryStub);
decorations.server.uiSettingsServiceFactory({
foo: 'bar'
foo: 'bar',
});
sinon.assert.calledOnce(uiSettingsServiceFactory);
sinon.assert.calledWithExactly(uiSettingsServiceFactory, server, {
sinon.assert.calledOnce(uiSettingsServiceFactoryStub);
sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server, {
foo: 'bar',
overrides: {
foo: 'bar'
foo: 'bar',
},
getDefaults: sinon.match.func,
});
@ -122,33 +136,45 @@ describe('uiSettingsMixin()', () => {
describe('request.getUiSettingsService()', () => {
it('exposes "getUiSettingsService" on requests', () => {
const { decorations } = setup();
expect(decorations.request).to.have.property('getUiSettingsService').a('function');
expect(decorations.request)
.to.have.property('getUiSettingsService')
.a('function');
sandbox.stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest');
sinon.assert.notCalled(getUiSettingsServiceForRequest);
const getUiSettingsServiceForRequestStub = sandbox.stub(
getUiSettingsServiceForRequestNS,
'getUiSettingsServiceForRequest'
);
sinon.assert.notCalled(getUiSettingsServiceForRequestStub);
decorations.request.getUiSettingsService();
sinon.assert.calledOnce(getUiSettingsServiceForRequest);
sinon.assert.calledOnce(getUiSettingsServiceForRequestStub);
});
it('passes request to getUiSettingsServiceForRequest', () => {
const { server, decorations } = setup();
expect(decorations.request).to.have.property('getUiSettingsService').a('function');
expect(decorations.request)
.to.have.property('getUiSettingsService')
.a('function');
sandbox.stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest');
sinon.assert.notCalled(getUiSettingsServiceForRequest);
const getUiSettingsServiceForRequestStub = sandbox.stub(
getUiSettingsServiceForRequestNS,
'getUiSettingsServiceForRequest'
);
sinon.assert.notCalled(getUiSettingsServiceForRequestStub);
const request = {};
decorations.request.getUiSettingsService.call(request);
sinon.assert.calledWith(getUiSettingsServiceForRequest, server, request);
sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server, request);
});
});
describe('server.uiSettings()', () => {
it('throws an error, links to pr', () => {
const { decorations } = setup();
expect(decorations.server).to.have.property('uiSettings').a('function');
expect(decorations.server)
.to.have.property('uiSettings')
.a('function');
expect(() => {
decorations.server.uiSettings();
}).to.throwError('http://github.com');
}).to.throwError('http://github.com' as any); // incorrect typings
});
});
});

View file

@ -1,32 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export {
startServers,
getServices,
stopServers,
} from './servers';
export {
chance
} from './chance';
export {
assertSinonMatch,
} from './assert';

View file

@ -20,17 +20,11 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
import {
getServices,
chance,
assertSinonMatch,
} from './lib';
import { getServices, chance, assertSinonMatch } from './lib';
export function docExistsSuite() {
async function setup(options = {}) {
const {
initialSettings
} = options;
async function setup(options: any = {}) {
const { initialSettings } = options;
const { kbnServer, uiSettings, callCluster } = getServices();
@ -39,7 +33,7 @@ export function docExistsSuite() {
index: kbnServer.config.get('kibana.index'),
body: {
conflicts: 'proceed',
query: { match_all: {} }
query: { match_all: {} },
},
});
@ -55,29 +49,29 @@ export function docExistsSuite() {
const defaultIndex = chance.word({ length: 10 });
const { kbnServer } = await setup({
initialSettings: {
defaultIndex
}
defaultIndex,
},
});
const { statusCode, result } = await kbnServer.inject({
method: 'GET',
url: '/api/kibana/settings'
url: '/api/kibana/settings',
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
defaultIndex: {
userValue: defaultIndex
userValue: defaultIndex,
},
foo: {
userValue: 'bar',
isOverridden: true
isOverridden: true,
},
}
},
});
});
});
@ -91,24 +85,24 @@ export function docExistsSuite() {
method: 'POST',
url: '/api/kibana/settings/defaultIndex',
payload: {
value: defaultIndex
}
value: defaultIndex,
},
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
defaultIndex: {
userValue: defaultIndex
userValue: defaultIndex,
},
foo: {
userValue: 'bar',
isOverridden: true
isOverridden: true,
},
}
},
});
});
@ -119,15 +113,15 @@ export function docExistsSuite() {
method: 'POST',
url: '/api/kibana/settings/foo',
payload: {
value: 'baz'
}
value: 'baz',
},
});
expect(statusCode).to.be(400);
assertSinonMatch(result, {
error: 'Bad Request',
message: 'Unable to update "foo" because it is overridden',
statusCode: 400
statusCode: 400,
});
});
});
@ -142,25 +136,25 @@ export function docExistsSuite() {
url: '/api/kibana/settings',
payload: {
changes: {
defaultIndex
}
}
defaultIndex,
},
},
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
defaultIndex: {
userValue: defaultIndex
userValue: defaultIndex,
},
foo: {
userValue: 'bar',
isOverridden: true
isOverridden: true,
},
}
},
});
});
@ -172,16 +166,16 @@ export function docExistsSuite() {
url: '/api/kibana/settings',
payload: {
changes: {
foo: 'baz'
}
}
foo: 'baz',
},
},
});
expect(statusCode).to.be(400);
assertSinonMatch(result, {
error: 'Bad Request',
message: 'Unable to update "foo" because it is overridden',
statusCode: 400
statusCode: 400,
});
});
});
@ -191,27 +185,27 @@ export function docExistsSuite() {
const defaultIndex = chance.word({ length: 10 });
const { kbnServer, uiSettings } = await setup({
initialSettings: { defaultIndex }
initialSettings: { defaultIndex },
});
expect(await uiSettings.get('defaultIndex')).to.be(defaultIndex);
const { statusCode, result } = await kbnServer.inject({
method: 'DELETE',
url: '/api/kibana/settings/defaultIndex'
url: '/api/kibana/settings/defaultIndex',
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
foo: {
userValue: 'bar',
isOverridden: true
isOverridden: true,
},
}
},
});
});
it('returns a 400 if deleting overridden value', async () => {
@ -219,14 +213,14 @@ export function docExistsSuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'DELETE',
url: '/api/kibana/settings/foo'
url: '/api/kibana/settings/foo',
});
expect(statusCode).to.be(400);
assertSinonMatch(result, {
error: 'Bad Request',
message: 'Unable to update "foo" because it is overridden',
statusCode: 400
statusCode: 400,
});
});
});

View file

@ -20,11 +20,7 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
import {
getServices,
chance,
assertSinonMatch,
} from './lib';
import { getServices, chance, assertSinonMatch } from './lib';
export function docMissingSuite() {
// ensure the kibana index has no documents
@ -35,15 +31,15 @@ export function docMissingSuite() {
await kbnServer.inject({
method: 'POST',
url: '/api/kibana/settings/defaultIndex',
payload: { value: 'abc' }
payload: { value: 'abc' },
});
// delete all docs from kibana index to ensure savedConfig is not found
await callCluster('deleteByQuery', {
index: kbnServer.config.get('kibana.index'),
body: {
query: { match_all: {} }
}
query: { match_all: {} },
},
});
});
@ -53,7 +49,7 @@ export function docMissingSuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'GET',
url: '/api/kibana/settings'
url: '/api/kibana/settings',
});
expect(statusCode).to.be(200);
@ -64,9 +60,9 @@ export function docMissingSuite() {
},
foo: {
userValue: 'bar',
isOverridden: true
}
}
isOverridden: true,
},
},
});
});
});
@ -79,23 +75,23 @@ export function docMissingSuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'POST',
url: '/api/kibana/settings/defaultIndex',
payload: { value: defaultIndex }
payload: { value: defaultIndex },
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
defaultIndex: {
userValue: defaultIndex
userValue: defaultIndex,
},
foo: {
userValue: 'bar',
isOverridden: true
}
}
isOverridden: true,
},
},
});
});
});
@ -109,24 +105,24 @@ export function docMissingSuite() {
method: 'POST',
url: '/api/kibana/settings',
payload: {
changes: { defaultIndex }
}
changes: { defaultIndex },
},
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
defaultIndex: {
userValue: defaultIndex
userValue: defaultIndex,
},
foo: {
userValue: 'bar',
isOverridden: true
}
}
isOverridden: true,
},
},
});
});
});
@ -137,20 +133,20 @@ export function docMissingSuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'DELETE',
url: '/api/kibana/settings/defaultIndex'
url: '/api/kibana/settings/defaultIndex',
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
foo: {
userValue: 'bar',
isOverridden: true
}
}
isOverridden: true,
},
},
});
});
});

View file

@ -20,11 +20,7 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
import {
getServices,
chance,
assertSinonMatch,
} from './lib';
import { getServices, chance, assertSinonMatch } from './lib';
export function docMissingAndIndexReadOnlySuite() {
// ensure the kibana index has no documents
@ -35,15 +31,15 @@ export function docMissingAndIndexReadOnlySuite() {
await kbnServer.inject({
method: 'POST',
url: '/api/kibana/settings/defaultIndex',
payload: { value: 'abc' }
payload: { value: 'abc' },
});
// delete all docs from kibana index to ensure savedConfig is not found
await callCluster('deleteByQuery', {
index: kbnServer.config.get('kibana.index'),
body: {
query: { match_all: {} }
}
query: { match_all: {} },
},
});
// set the index to read only
@ -52,10 +48,10 @@ export function docMissingAndIndexReadOnlySuite() {
body: {
index: {
blocks: {
read_only: true
}
}
}
read_only: true,
},
},
},
});
});
@ -68,10 +64,10 @@ export function docMissingAndIndexReadOnlySuite() {
body: {
index: {
blocks: {
read_only: false
}
}
}
read_only: false,
},
},
},
});
});
@ -81,20 +77,20 @@ export function docMissingAndIndexReadOnlySuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'GET',
url: '/api/kibana/settings'
url: '/api/kibana/settings',
});
expect(statusCode).to.be(200);
assertSinonMatch(result, {
settings: {
buildNum: {
userValue: sinon.match.number
userValue: sinon.match.number,
},
foo: {
userValue: 'bar',
isOverridden: true
}
}
isOverridden: true,
},
},
});
});
});
@ -107,14 +103,14 @@ export function docMissingAndIndexReadOnlySuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'POST',
url: '/api/kibana/settings/defaultIndex',
payload: { value: defaultIndex }
payload: { value: defaultIndex },
});
expect(statusCode).to.be(403);
assertSinonMatch(result, {
error: 'Forbidden',
message: sinon.match('index read-only'),
statusCode: 403
statusCode: 403,
});
});
});
@ -128,15 +124,15 @@ export function docMissingAndIndexReadOnlySuite() {
method: 'POST',
url: '/api/kibana/settings',
payload: {
changes: { defaultIndex }
}
changes: { defaultIndex },
},
});
expect(statusCode).to.be(403);
assertSinonMatch(result, {
error: 'Forbidden',
message: sinon.match('index read-only'),
statusCode: 403
statusCode: 403,
});
});
});
@ -147,14 +143,14 @@ export function docMissingAndIndexReadOnlySuite() {
const { statusCode, result } = await kbnServer.inject({
method: 'DELETE',
url: '/api/kibana/settings/defaultIndex'
url: '/api/kibana/settings/defaultIndex',
});
expect(statusCode).to.be(403);
assertSinonMatch(result, {
error: 'Forbidden',
message: sinon.match('index read-only'),
statusCode: 403
statusCode: 403,
});
});
});

View file

@ -17,16 +17,13 @@
* under the License.
*/
import {
startServers,
stopServers,
} from './lib';
import { startServers, stopServers } from './lib';
import { docExistsSuite } from './doc_exists';
import { docMissingSuite } from './doc_missing';
import { docMissingAndIndexReadOnlySuite } from './doc_missing_and_index_read_only';
describe('uiSettings/routes', function () {
describe('uiSettings/routes', function() {
/**
* The "doc missing" and "index missing" tests verify how the uiSettings
* API behaves in between healthChecks, so they interact with the healthCheck
@ -43,12 +40,13 @@ describe('uiSettings/routes', function () {
* stupidly fragile and timing sensitive. #14163 should fix that, but until then
* this is the most stable way I've been able to get this to work.
*/
this.slow(2000);
this.timeout(10000);
jest.setTimeout(10000);
before(startServers);
beforeAll(startServers);
/* eslint-disable jest/valid-describe */
describe('doc missing', docMissingSuite);
describe('doc missing and index readonly', docMissingAndIndexReadOnlySuite);
describe('doc exists', docExistsSuite);
after(stopServers);
/* eslint-enable jest/valid-describe */
afterAll(stopServers);
});

View file

@ -19,7 +19,7 @@
import sinon from 'sinon';
export function assertSinonMatch(value, match) {
export function assertSinonMatch(value: any, match: any) {
const stub = sinon.stub();
stub(value);
sinon.assert.calledWithExactly(stub, match);

View file

@ -17,7 +17,8 @@
* under the License.
*/
export {
createObjectsClientStub,
savedObjectsClientErrors,
} from './create_objects_client_stub';
export { startServers, getServices, stopServers } from './servers';
export { chance } from './chance';
export { assertSinonMatch } from './assert';

View file

@ -17,23 +17,37 @@
* under the License.
*/
import { createTestServers } from '../../../../../../test_utils/kbn_server';
import { UnwrapPromise } from '@kbn/utility-types';
import { SavedObjectsClientContract } from 'src/core/server';
let kbnServer;
let services;
let servers;
let esServer;
let kbn;
import KbnServer from '../../../../../server/kbn_server';
import { createTestServers } from '../../../../../../test_utils/kbn_server';
import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch';
let kbnServer: KbnServer;
let servers: ReturnType<typeof createTestServers>;
let esServer: UnwrapPromise<ReturnType<typeof servers['startES']>>;
let kbn: UnwrapPromise<ReturnType<typeof servers['startKibana']>>;
interface AllServices {
kbnServer: KbnServer;
savedObjectsClient: SavedObjectsClientContract;
callCluster: CallCluster;
uiSettings: any;
deleteKibanaIndex: typeof deleteKibanaIndex;
}
let services: AllServices;
export async function startServers() {
servers = createTestServers({
adjustTimeout: (t) => this.timeout(t),
adjustTimeout: t => jest.setTimeout(t),
settings: {
kbn: {
uiSettings: {
overrides: {
foo: 'bar',
}
},
},
},
},
@ -43,9 +57,9 @@ export async function startServers() {
kbnServer = kbn.kbnServer;
}
async function deleteKibanaIndex(callCluster) {
async function deleteKibanaIndex(callCluster: CallCluster) {
const kibanaIndices = await callCluster('cat.indices', { index: '.kibana*', format: 'json' });
const indexNames = kibanaIndices.map(x => x.index);
const indexNames = kibanaIndices.map((x: any) => x.index);
if (!indexNames.length) {
return;
}
@ -65,7 +79,7 @@ export function getServices() {
const callCluster = esServer.es.getCallCluster();
const savedObjects = kbnServer.server.savedObjects;
const savedObjectsClient = savedObjects.getScopedSavedObjectsClient();
const savedObjectsClient = savedObjects.getScopedSavedObjectsClient({});
const uiSettings = kbnServer.server.uiSettingsServiceFactory({
savedObjectsClient,
@ -83,10 +97,10 @@ export function getServices() {
}
export async function stopServers() {
services = null;
kbnServer = null;
services = null!;
kbnServer = null!;
if (servers) {
esServer.stop();
kbn.stop();
await esServer.stop();
await kbn.stop();
}
}

View file

@ -18,33 +18,34 @@
*/
import expect from '@kbn/expect';
import { errors as esErrors } from 'elasticsearch';
import Chance from 'chance';
import sinon from 'sinon';
import { UiSettingsService } from '../ui_settings_service';
import * as createOrUpgradeSavedConfigNS from '../create_or_upgrade_saved_config/create_or_upgrade_saved_config';
import {
createObjectsClientStub,
savedObjectsClientErrors,
} from './lib';
// @ts-ignore
import { UiSettingsService } from './ui_settings_service';
// @ts-ignore
import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config';
import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub';
const TYPE = 'config';
const ID = 'kibana-version';
const BUILD_NUM = 1234;
const chance = new Chance();
interface SetupOptions {
getDefaults?: () => Record<string, any>;
defaults?: Record<string, any>;
esDocSource?: Record<string, any>;
overrides?: Record<string, any>;
}
describe('ui settings', () => {
const sandbox = sinon.createSandbox();
function setup(options = {}) {
const {
getDefaults,
defaults = {},
overrides,
esDocSource = {},
savedObjectsClient = createObjectsClientStub(TYPE, ID, esDocSource)
} = options;
function setup(options: SetupOptions = {}) {
const { getDefaults, defaults = {}, overrides, esDocSource = {} } = options;
const savedObjectsClient = createObjectsClientStub(esDocSource);
const uiSettings = new UiSettingsService({
type: TYPE,
@ -55,14 +56,34 @@ describe('ui settings', () => {
overrides,
});
const createOrUpgradeSavedConfig = sandbox.stub(createOrUpgradeSavedConfigNS, 'createOrUpgradeSavedConfig');
const createOrUpgradeSavedConfig = sandbox.stub(
createOrUpgradeSavedConfigNS,
'createOrUpgradeSavedConfig'
);
function assertGetQuery() {
sinon.assert.calledOnce(savedObjectsClient.get);
const { args } = savedObjectsClient.get.getCall(0);
expect(args[0]).to.be(TYPE);
expect(args[1]).to.eql(ID);
}
function assertUpdateQuery(expectedChanges: unknown) {
sinon.assert.calledOnce(savedObjectsClient.update);
const { args } = savedObjectsClient.update.getCall(0);
expect(args[0]).to.be(TYPE);
expect(args[1]).to.eql(ID);
expect(args[2]).to.eql(expectedChanges);
}
return {
uiSettings,
savedObjectsClient,
createOrUpgradeSavedConfig,
assertGetQuery: savedObjectsClient.assertGetQuery,
assertUpdateQuery: savedObjectsClient.assertUpdateQuery,
assertGetQuery,
assertUpdateQuery,
};
}
@ -75,15 +96,15 @@ describe('ui settings', () => {
});
it('updates a single value in one operation', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.setMany({ one: 'value' });
savedObjectsClient.assertUpdateQuery({ one: 'value' });
assertUpdateQuery({ one: 'value' });
});
it('updates several values in one operation', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.setMany({ one: 'value', another: 'val' });
savedObjectsClient.assertUpdateQuery({ one: 'value', another: 'val' });
assertUpdateQuery({ one: 'value', another: 'val' });
});
it('automatically creates the savedConfig if it is missing', async () => {
@ -117,14 +138,14 @@ describe('ui settings', () => {
it('throws an error if any key is overridden', async () => {
const { uiSettings } = setup({
overrides: {
foo: 'bar'
}
foo: 'bar',
},
});
try {
await uiSettings.setMany({
bar: 'box',
foo: 'baz'
foo: 'baz',
});
} catch (error) {
expect(error.message).to.be('Unable to update "foo" because it is overridden');
@ -139,16 +160,16 @@ describe('ui settings', () => {
});
it('updates single values by (key, value)', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.set('one', 'value');
savedObjectsClient.assertUpdateQuery({ one: 'value' });
assertUpdateQuery({ one: 'value' });
});
it('throws an error if the key is overridden', async () => {
const { uiSettings } = setup({
overrides: {
foo: 'bar'
}
foo: 'bar',
},
});
try {
@ -166,16 +187,16 @@ describe('ui settings', () => {
});
it('removes single values by key', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.remove('one');
savedObjectsClient.assertUpdateQuery({ one: null });
assertUpdateQuery({ one: null });
});
it('throws an error if the key is overridden', async () => {
const { uiSettings } = setup({
overrides: {
foo: 'bar'
}
foo: 'bar',
},
});
try {
@ -193,22 +214,22 @@ describe('ui settings', () => {
});
it('removes a single value', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.removeMany(['one']);
savedObjectsClient.assertUpdateQuery({ one: null });
assertUpdateQuery({ one: null });
});
it('updates several values in one operation', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertUpdateQuery } = setup();
await uiSettings.removeMany(['one', 'two', 'three']);
savedObjectsClient.assertUpdateQuery({ one: null, two: null, three: null });
assertUpdateQuery({ one: null, two: null, three: null });
});
it('throws an error if any key is overridden', async () => {
const { uiSettings } = setup({
overrides: {
foo: 'bar'
}
foo: 'bar',
},
});
try {
@ -239,16 +260,16 @@ describe('ui settings', () => {
const value = chance.word();
const { uiSettings } = setup({ defaults: { key: { value } } });
expect(await uiSettings.getDefaults()).to.eql({
key: { value }
key: { value },
});
});
});
describe('#getUserProvided()', () => {
it('pulls user configuration from ES', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings, assertGetQuery } = setup();
await uiSettings.getUserProvided();
savedObjectsClient.assertGetQuery();
assertGetQuery();
});
it('returns user configuration', async () => {
@ -258,7 +279,7 @@ describe('ui settings', () => {
expect(result).to.eql({
user: {
userValue: 'customized',
}
},
});
});
@ -268,89 +289,76 @@ describe('ui settings', () => {
const result = await uiSettings.getUserProvided();
expect(result).to.eql({
user: {
userValue: 'customized'
userValue: 'customized',
},
something: {
userValue: 'else'
}
userValue: 'else',
},
});
});
it('returns an empty object on 404 responses', async () => {
const { uiSettings } = setup({
async callCluster() {
throw new esErrors[404]();
}
});
it.skip('returns an empty object on NotFound responses', async () => {
const { uiSettings, savedObjectsClient } = setup();
const error = savedObjectsClientErrors.createGenericNotFoundError();
savedObjectsClient.get.throws(error);
expect(await uiSettings.getUserProvided({})).to.eql({});
});
it('returns an empty object on Forbidden responses', async () => {
const { uiSettings, savedObjectsClient } = setup();
const error = savedObjectsClientErrors.decorateForbiddenError(new Error());
savedObjectsClient.get.throws(error);
expect(await uiSettings.getUserProvided()).to.eql({});
});
it('returns an empty object on 403 responses', async () => {
const { uiSettings } = setup({
async callCluster() {
throw new esErrors[403]();
}
});
it('returns an empty object on EsUnavailable responses', async () => {
const { uiSettings, savedObjectsClient } = setup();
const error = savedObjectsClientErrors.decorateEsUnavailableError(new Error());
savedObjectsClient.get.throws(error);
expect(await uiSettings.getUserProvided()).to.eql({});
});
it('returns an empty object on NoConnections responses', async () => {
const { uiSettings } = setup({
async callCluster() {
throw new esErrors.NoConnections();
}
});
it('throws Unauthorized errors', async () => {
const { uiSettings, savedObjectsClient } = setup();
expect(await uiSettings.getUserProvided()).to.eql({});
});
it('throws 401 errors', async () => {
const { uiSettings } = setup({
savedObjectsClient: {
errors: savedObjectsClientErrors,
async get() {
throw new esErrors[401]();
}
}
});
const error = savedObjectsClientErrors.decorateNotAuthorizedError(new Error());
savedObjectsClient.get.throws(error);
try {
await uiSettings.getUserProvided();
throw new Error('expect getUserProvided() to throw');
} catch (err) {
expect(err).to.be.a(esErrors[401]);
expect(err).to.be(error);
}
});
it('throw when callCluster fails in some unexpected way', async () => {
const expectedUnexpectedError = new Error('unexpected');
it('throw when SavedObjectsClient throws in some unexpected way', async () => {
const { uiSettings, savedObjectsClient } = setup();
const { uiSettings } = setup({
savedObjectsClient: {
errors: savedObjectsClientErrors,
async get() {
throw expectedUnexpectedError;
}
}
});
const error = new Error('unexpected');
savedObjectsClient.get.throws(error);
try {
await uiSettings.getUserProvided();
throw new Error('expect getUserProvided() to throw');
} catch (err) {
expect(err).to.be(expectedUnexpectedError);
expect(err).to.be(error);
}
});
it('includes overridden values for overridden keys', async () => {
const esDocSource = {
user: 'customized'
user: 'customized',
};
const overrides = {
foo: 'bar'
foo: 'bar',
};
const { uiSettings } = setup({ esDocSource, overrides });
@ -369,9 +377,9 @@ describe('ui settings', () => {
describe('#getRaw()', () => {
it('pulls user configuration from ES', async () => {
const esDocSource = {};
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
const { uiSettings, assertGetQuery } = setup({ esDocSource });
await uiSettings.getRaw();
savedObjectsClient.assertGetQuery();
assertGetQuery();
});
it(`without user configuration it's equal to the defaults`, async () => {
@ -420,17 +428,17 @@ describe('ui settings', () => {
describe('#getAll()', () => {
it('pulls user configuration from ES', async () => {
const esDocSource = {};
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
const { uiSettings, assertGetQuery } = setup({ esDocSource });
await uiSettings.getAll();
savedObjectsClient.assertGetQuery();
assertGetQuery();
});
it(`returns defaults when es doc is empty`, async () => {
const esDocSource = { };
const esDocSource = {};
const defaults = { foo: { value: 'bar' } };
const { uiSettings } = setup({ esDocSource, defaults });
expect(await uiSettings.getAll()).to.eql({
foo: 'bar'
foo: 'bar',
});
});
@ -442,7 +450,7 @@ describe('ui settings', () => {
const defaults = {
foo: {
value: 'default'
value: 'default',
},
};
@ -461,12 +469,12 @@ describe('ui settings', () => {
const defaults = {
foo: {
value: 'default'
value: 'default',
},
};
const overrides = {
foo: 'bax'
foo: 'bax',
};
const { uiSettings } = setup({ esDocSource, defaults, overrides });
@ -480,9 +488,9 @@ describe('ui settings', () => {
describe('#get()', () => {
it('pulls user configuration from ES', async () => {
const esDocSource = {};
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
const { uiSettings, assertGetQuery } = setup({ esDocSource });
await uiSettings.get();
savedObjectsClient.assertGetQuery();
assertGetQuery();
});
it(`returns the promised value for a key`, async () => {
@ -525,7 +533,9 @@ describe('ui settings', () => {
it('returns the overridden value if the document does not exist', async () => {
const overrides = { dateFormat: 'foo' };
const { uiSettings, savedObjectsClient } = setup({ overrides });
savedObjectsClient.get.onFirstCall().throws(savedObjectsClientErrors.createGenericNotFoundError());
savedObjectsClient.get
.onFirstCall()
.throws(savedObjectsClientErrors.createGenericNotFoundError());
expect(await uiSettings.get('dateFormat')).to.be('foo');
});
});
@ -557,7 +567,10 @@ describe('ui settings', () => {
it('throws 400 Boom error when keys is overridden', () => {
const { uiSettings } = setup({ overrides: { foo: true } });
expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => {
expect(error).to.have.property('message', 'Unable to update "foo" because it is overridden');
expect(error).to.have.property(
'message',
'Unable to update "foo" because it is overridden'
);
expect(error).to.have.property('isBoom', true);
expect(error.output).to.have.property('statusCode', 400);
});