From 2f71df160bb4fe689a63a7e48cca5b17c26e51bb Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 19 Dec 2019 10:59:27 +0100 Subject: [PATCH] NP licensing add functional tests (#53002) * fix comment * introduce core provider plugin for integration tests * platform functional tests use core_provider_plugin for testing * add 3 scenario for licensing plugins: server, client, legacy * remove unused code * run all licensing_plugin tests on CI * remove duplicated config * address comments * declare global type for core provider --- test/plugin_functional/config.js | 5 +- .../plugins/core_plugin_b/public/plugin.tsx | 15 +- .../plugins/core_provider_plugin/index.ts | 36 ++++ .../plugins/core_provider_plugin/package.json | 17 ++ .../public/index.ts | 11 +- .../core_provider_plugin/tsconfig.json | 14 ++ .../types.ts} | 28 +-- .../plugins/ui_settings_plugin/kibana.json | 2 +- .../plugins/ui_settings_plugin/tsconfig.json | 3 - .../test_suites/core_plugins/ui_plugins.ts | 45 ++--- .../test_suites/core_plugins/ui_settings.ts | 20 +- x-pack/scripts/functional_tests.js | 2 + x-pack/test/licensing_plugin/apis/changes.ts | 177 ------------------ x-pack/test/licensing_plugin/config.legacy.ts | 15 ++ x-pack/test/licensing_plugin/config.public.ts | 29 +++ x-pack/test/licensing_plugin/config.ts | 4 +- x-pack/test/licensing_plugin/legacy/index.ts | 16 ++ .../test/licensing_plugin/legacy/updates.ts | 71 +++++++ x-pack/test/licensing_plugin/public/index.ts | 16 ++ .../test/licensing_plugin/public/updates.ts | 112 +++++++++++ x-pack/test/licensing_plugin/scenario.ts | 91 +++++++++ .../{apis => server}/header.ts | 1 + .../{apis => server}/index.ts | 5 +- .../licensing_plugin/{apis => server}/info.ts | 1 + .../test/licensing_plugin/server/updates.ts | 78 ++++++++ 25 files changed, 579 insertions(+), 235 deletions(-) create mode 100644 test/plugin_functional/plugins/core_provider_plugin/index.ts create mode 100644 test/plugin_functional/plugins/core_provider_plugin/package.json rename test/plugin_functional/plugins/{ui_settings_plugin => core_provider_plugin}/public/index.ts (78%) create mode 100644 test/plugin_functional/plugins/core_provider_plugin/tsconfig.json rename test/plugin_functional/plugins/{ui_settings_plugin/public/plugin.tsx => core_provider_plugin/types.ts} (66%) delete mode 100644 x-pack/test/licensing_plugin/apis/changes.ts create mode 100644 x-pack/test/licensing_plugin/config.legacy.ts create mode 100644 x-pack/test/licensing_plugin/config.public.ts create mode 100644 x-pack/test/licensing_plugin/legacy/index.ts create mode 100644 x-pack/test/licensing_plugin/legacy/updates.ts create mode 100644 x-pack/test/licensing_plugin/public/index.ts create mode 100644 x-pack/test/licensing_plugin/public/updates.ts create mode 100644 x-pack/test/licensing_plugin/scenario.ts rename x-pack/test/licensing_plugin/{apis => server}/header.ts (93%) rename x-pack/test/licensing_plugin/{apis => server}/index.ts (76%) rename x-pack/test/licensing_plugin/{apis => server}/info.ts (95%) create mode 100644 x-pack/test/licensing_plugin/server/updates.ts diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index 172dec9a1d111..87026ce25d9aa 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -57,11 +57,12 @@ export default async function({ readConfigFile }) { ...functionalConfig.get('kbnTestServer'), serverArgs: [ ...functionalConfig.get('kbnTestServer.serverArgs'), + + // Required to load new platform plugins via `--plugin-path` flag. + '--env.name=development', ...plugins.map( pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), - // Required to load new platform plugins via `--plugin-path` flag. - '--env.name=development', ], }, }; diff --git a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx index 5c8e1d03d5a4a..bda1557bdaf91 100644 --- a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx @@ -22,9 +22,6 @@ import { CorePluginAPluginSetup } from '../../core_plugin_a/public/plugin'; declare global { interface Window { - corePluginB?: string; - hasAccessToInjectedMetadata?: boolean; - receivedStartServices?: boolean; env?: PluginInitializerContext['env']; } } @@ -39,12 +36,6 @@ export class CorePluginBPlugin window.env = pluginContext.env; } public setup(core: CoreSetup, deps: CorePluginBDeps) { - window.corePluginB = `Plugin A said: ${deps.core_plugin_a.getGreeting()}`; - window.hasAccessToInjectedMetadata = 'getInjectedVar' in core.injectedMetadata; - core.getStartServices().then(([coreStart, plugins]) => { - window.receivedStartServices = 'overlays' in coreStart; - }); - core.application.register({ id: 'bar', title: 'Bar', @@ -53,6 +44,12 @@ export class CorePluginBPlugin return renderApp(context, params); }, }); + + return { + sayHi() { + return `Plugin A said: ${deps.core_plugin_a.getGreeting()}`; + }, + }; } public start() {} diff --git a/test/plugin_functional/plugins/core_provider_plugin/index.ts b/test/plugin_functional/plugins/core_provider_plugin/index.ts new file mode 100644 index 0000000000000..01f3a67c6b554 --- /dev/null +++ b/test/plugin_functional/plugins/core_provider_plugin/index.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; + +// eslint-disable-next-line import/no-default-export +export default function CoreProviderPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'core-provider', + require: [], + publicDir: resolve(__dirname, 'public'), + init: (server: Legacy.Server) => ({}), + uiExports: { + hacks: [resolve(__dirname, 'public/index')], + }, + }; + + return new kibana.Plugin(config); +} diff --git a/test/plugin_functional/plugins/core_provider_plugin/package.json b/test/plugin_functional/plugins/core_provider_plugin/package.json new file mode 100644 index 0000000000000..941503b934cbb --- /dev/null +++ b/test/plugin_functional/plugins/core_provider_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "core_provider_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_provider_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts similarity index 78% rename from test/plugin_functional/plugins/ui_settings_plugin/public/index.ts rename to test/plugin_functional/plugins/core_provider_plugin/public/index.ts index 3c5997132d460..c74928203db56 100644 --- a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts +++ b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts @@ -16,6 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { UiSettingsPlugin } from './plugin'; +import { npSetup, npStart } from 'ui/new_platform'; +import '../types'; -export const plugin = () => new UiSettingsPlugin(); +window.__coreProvider = { + setup: npSetup, + start: npStart, + testUtils: { + delay: (ms: number) => new Promise(res => setTimeout(res, ms)), + }, +}; diff --git a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json new file mode 100644 index 0000000000000..c29959197958d --- /dev/null +++ b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "types.ts", + "public/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx b/test/plugin_functional/plugins/core_provider_plugin/types.ts similarity index 66% rename from test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx rename to test/plugin_functional/plugins/core_provider_plugin/types.ts index 883d203b4c37a..bf19578c37baa 100644 --- a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_provider_plugin/types.ts @@ -16,22 +16,22 @@ * specific language governing permissions and limitations * under the License. */ - -import { CoreSetup, Plugin } from 'kibana/public'; +import { LegacyCoreSetup, LegacyCoreStart } from 'kibana/public'; declare global { interface Window { - uiSettingsPlugin?: Record; - uiSettingsPluginValue?: string; + __coreProvider: { + setup: { + core: LegacyCoreSetup; + plugins: Record; + }; + start: { + core: LegacyCoreStart; + plugins: Record; + }; + testUtils: { + delay: (ms: number) => Promise; + }; + }; } } - -export class UiSettingsPlugin implements Plugin { - public setup(core: CoreSetup) { - window.uiSettingsPlugin = core.uiSettings.getAll().ui_settings_plugin; - window.uiSettingsPluginValue = core.uiSettings.get('ui_settings_plugin'); - } - - public start() {} - public stop() {} -} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json index 05d2dca0af937..35e4c35490e2f 100644 --- a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json +++ b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json @@ -4,5 +4,5 @@ "kibanaVersion": "kibana", "configPath": ["ui_settings_plugin"], "server": true, - "ui": true + "ui": false } diff --git a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json index 1ba21f11b7de2..7c170405bbfc7 100644 --- a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json +++ b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json @@ -5,9 +5,6 @@ "skipLibCheck": true }, "include": [ - "index.ts", - "public/**/*.ts", - "public/**/*.tsx", "server/**/*.ts", "../../../../typings/**/*", ], diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts index ff53583546487..b76463ee76739 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts @@ -19,6 +19,7 @@ import expect from '@kbn/expect'; import { PluginFunctionalProviderContext } from '../../services'; +import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; // eslint-disable-next-line import/no-default-export export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { @@ -31,22 +32,35 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider await PageObjects.common.navigateToApp('settings'); }); - it('should attach string to window.corePluginB', async () => { - const corePluginB = await browser.execute('return window.corePluginB'); - expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`); + it('should run the new platform plugins', async () => { + expect( + await browser.execute(() => { + return window.__coreProvider.setup.plugins.core_plugin_b.sayHi(); + }) + ).to.be('Plugin A said: Hello from Plugin A!'); }); }); - describe('have injectedMetadata service provided', function describeIndexTests() { + describe('should have access to the core services', function describeIndexTests() { before(async () => { - await PageObjects.common.navigateToApp('bar'); + await PageObjects.common.navigateToApp('settings'); + }); + + it('to injectedMetadata service', async () => { + expect( + await browser.execute(() => { + return window.__coreProvider.setup.core.injectedMetadata.getKibanaBuildNumber(); + }) + ).to.be.a('number'); }); - it('should attach boolean to window.hasAccessToInjectedMetadata', async () => { - const hasAccessToInjectedMetadata = await browser.execute( - 'return window.hasAccessToInjectedMetadata' - ); - expect(hasAccessToInjectedMetadata).to.equal(true); + it('to start services via coreSetup.getStartServices', async () => { + expect( + await browser.executeAsync(async cb => { + const [coreStart] = await window.__coreProvider.setup.core.getStartServices(); + cb(Boolean(coreStart.overlays)); + }) + ).to.be(true); }); }); @@ -61,16 +75,5 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider expect(envData.packageInfo.version).to.be.a('string'); }); }); - - describe('have access to start services via coreSetup.getStartServices', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('bar'); - }); - - it('should attach boolean to window.receivedStartServices', async () => { - const receivedStartServices = await browser.execute('return window.receivedStartServices'); - expect(receivedStartServices).to.equal(true); - }); - }); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts index 2b4227ee798e3..dec79fd15f4dd 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts +++ b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts @@ -18,6 +18,7 @@ */ import expect from '@kbn/expect'; import { PluginFunctionalProviderContext } from '../../services'; +import '../../plugins/core_provider_plugin/types'; // eslint-disable-next-line import/no-default-export export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { @@ -31,15 +32,30 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }); it('client plugins have access to registered settings', async () => { - const settings = await browser.execute('return window.uiSettingsPlugin'); + const settings = await browser.execute(() => { + return window.__coreProvider.setup.core.uiSettings.getAll().ui_settings_plugin; + }); + expect(settings).to.eql({ category: ['any'], description: 'just for testing', name: 'from_ui_settings_plugin', value: '2', }); - const settingsValue = await browser.execute('return window.uiSettingsPluginValue'); + + const settingsValue = await browser.execute(() => { + return window.__coreProvider.setup.core.uiSettings.get('ui_settings_plugin'); + }); + expect(settingsValue).to.be('2'); + + const settingsValueViaObservables = await browser.executeAsync(async (callback: Function) => { + window.__coreProvider.setup.core.uiSettings + .get$('ui_settings_plugin') + .subscribe(v => callback(v)); + }); + + expect(settingsValueViaObservables).to.be('2'); }); it('server plugins have access to registered settings', async () => { diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index e50b19462fff6..33d64f31cab74 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -35,4 +35,6 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/ui_capabilities/spaces_only/config'), require.resolve('../test/upgrade_assistant_integration/config'), require.resolve('../test/licensing_plugin/config'), + require.resolve('../test/licensing_plugin/config.public'), + require.resolve('../test/licensing_plugin/config.legacy'), ]); diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts deleted file mode 100644 index cf4fecfa32d94..0000000000000 --- a/x-pack/test/licensing_plugin/apis/changes.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../services'; -import { PublicLicenseJSON } from '../../../plugins/licensing/server'; - -const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); - -export default function({ getService, getPageObjects }: FtrProviderContext) { - const supertest = getService('supertest'); - const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); - const security = getService('security'); - const PageObjects = getPageObjects(['common', 'security']); - const testSubjects = getService('testSubjects'); - - const scenario = { - async setup() { - await security.role.create('license_manager-role', { - elasticsearch: { - cluster: ['all'], - }, - kibana: [ - { - base: ['all'], - spaces: ['*'], - }, - ], - }); - - await security.user.create('license_manager_user', { - password: 'license_manager_user-password', - roles: ['license_manager-role'], - full_name: 'license_manager user', - }); - - // ensure we're logged out so we can login as the appropriate users - await PageObjects.security.forceLogout(); - await PageObjects.security.login('license_manager_user', 'license_manager_user-password'); - }, - - async teardown() { - await security.role.delete('license_manager-role'); - }, - - async startBasic() { - const response = await esSupertestWithoutAuth - .post('/_license/start_basic?acknowledge=true') - .auth('license_manager_user', 'license_manager_user-password') - .expect(200); - - expect(response.body.basic_was_started).to.be(true); - }, - - async startTrial() { - const response = await esSupertestWithoutAuth - .post('/_license/start_trial?acknowledge=true') - .auth('license_manager_user', 'license_manager_user-password') - .expect(200); - - expect(response.body.trial_was_started).to.be(true); - }, - - async deleteLicense() { - const response = await esSupertestWithoutAuth - .delete('/_license') - .auth('license_manager_user', 'license_manager_user-password') - .expect(200); - - expect(response.body.acknowledged).to.be(true); - }, - - async getLicense(): Promise { - // > --xpack.licensing.api_polling_frequency set in test config - // to wait for Kibana server to re-fetch the license from Elasticsearch - await delay(1000); - - const { body } = await supertest.get('/api/licensing/info').expect(200); - return body; - }, - }; - - describe('changes in license types', () => { - after(async () => { - await scenario.startBasic(); - }); - - it('provides changes in license types', async () => { - await scenario.setup(); - const initialLicense = await scenario.getLicense(); - expect(initialLicense.license?.type).to.be('basic'); - // security enabled explicitly in test config - expect(initialLicense.features?.security).to.eql({ - isAvailable: true, - isEnabled: true, - }); - - const { - body: legacyInitialLicense, - headers: legacyInitialLicenseHeaders, - } = await supertest.get('/api/xpack/v1/info').expect(200); - - expect(legacyInitialLicense.license?.type).to.be('basic'); - expect(legacyInitialLicense.features).to.have.property('security'); - expect(legacyInitialLicenseHeaders['kbn-xpack-sig']).to.be.a('string'); - - // license hasn't changed - const refetchedLicense = await scenario.getLicense(); - expect(refetchedLicense.license?.type).to.be('basic'); - expect(refetchedLicense.signature).to.be(initialLicense.signature); - - const { - body: legacyRefetchedLicense, - headers: legacyRefetchedLicenseHeaders, - } = await supertest.get('/api/xpack/v1/info').expect(200); - - expect(legacyRefetchedLicense.license?.type).to.be('basic'); - expect(legacyRefetchedLicenseHeaders['kbn-xpack-sig']).to.be( - legacyInitialLicenseHeaders['kbn-xpack-sig'] - ); - - // server allows to request trial only once. - // other attempts will throw 403 - await scenario.startTrial(); - const trialLicense = await scenario.getLicense(); - expect(trialLicense.license?.type).to.be('trial'); - expect(trialLicense.signature).to.not.be(initialLicense.signature); - - expect(trialLicense.features?.security).to.eql({ - isAvailable: true, - isEnabled: true, - }); - - const { body: legacyTrialLicense, headers: legacyTrialLicenseHeaders } = await supertest - .get('/api/xpack/v1/info') - .expect(200); - - expect(legacyTrialLicense.license?.type).to.be('trial'); - expect(legacyTrialLicense.features).to.have.property('security'); - expect(legacyTrialLicenseHeaders['kbn-xpack-sig']).to.not.be( - legacyInitialLicenseHeaders['kbn-xpack-sig'] - ); - - await scenario.startBasic(); - const basicLicense = await scenario.getLicense(); - expect(basicLicense.license?.type).to.be('basic'); - expect(basicLicense.signature).not.to.be(initialLicense.signature); - - expect(basicLicense.features?.security).to.eql({ - isAvailable: true, - isEnabled: true, - }); - - const { body: legacyBasicLicense, headers: legacyBasicLicenseHeaders } = await supertest - .get('/api/xpack/v1/info') - .expect(200); - expect(legacyBasicLicense.license?.type).to.be('basic'); - expect(legacyBasicLicense.features).to.have.property('security'); - expect(legacyBasicLicenseHeaders['kbn-xpack-sig']).to.not.be( - legacyInitialLicenseHeaders['kbn-xpack-sig'] - ); - - await scenario.deleteLicense(); - const inactiveLicense = await scenario.getLicense(); - expect(inactiveLicense.signature).to.not.be(initialLicense.signature); - expect(inactiveLicense).to.not.have.property('license'); - expect(inactiveLicense.features?.security).to.eql({ - isAvailable: false, - isEnabled: true, - }); - // banner shown only when license expired not just deleted - await testSubjects.missingOrFail('licenseExpiredBanner'); - }); - }); -} diff --git a/x-pack/test/licensing_plugin/config.legacy.ts b/x-pack/test/licensing_plugin/config.legacy.ts new file mode 100644 index 0000000000000..27dc3df9944ad --- /dev/null +++ b/x-pack/test/licensing_plugin/config.legacy.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const commonConfig = await readConfigFile(require.resolve('./config')); + + return { + ...commonConfig.getAll(), + testFiles: [require.resolve('./legacy')], + }; +} diff --git a/x-pack/test/licensing_plugin/config.public.ts b/x-pack/test/licensing_plugin/config.public.ts new file mode 100644 index 0000000000000..42209aa49bcb4 --- /dev/null +++ b/x-pack/test/licensing_plugin/config.public.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import path from 'path'; +import { KIBANA_ROOT } from '@kbn/test'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const commonConfig = await readConfigFile(require.resolve('./config')); + + return { + ...commonConfig.getAll(), + testFiles: [require.resolve('./public')], + kbnTestServer: { + serverArgs: [ + ...commonConfig.get('kbnTestServer.serverArgs'), + + // Required to load new platform plugin provider via `--plugin-path` flag. + '--env.name=development', + `--plugin-path=${path.resolve( + KIBANA_ROOT, + 'test/plugin_functional/plugins/core_provider_plugin' + )}`, + ], + }, + }; +} diff --git a/x-pack/test/licensing_plugin/config.ts b/x-pack/test/licensing_plugin/config.ts index 9a83a6f6b5a0b..60d44cbd4c47f 100644 --- a/x-pack/test/licensing_plugin/config.ts +++ b/x-pack/test/licensing_plugin/config.ts @@ -22,7 +22,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { }; return { - testFiles: [require.resolve('./apis')], + testFiles: [require.resolve('./server')], servers, services, pageObjects, @@ -43,7 +43,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { ...functionalTestsConfig.get('kbnTestServer'), serverArgs: [ ...functionalTestsConfig.get('kbnTestServer.serverArgs'), - '--xpack.licensing.api_polling_frequency=300', + '--xpack.licensing.api_polling_frequency=100', ], }, diff --git a/x-pack/test/licensing_plugin/legacy/index.ts b/x-pack/test/licensing_plugin/legacy/index.ts new file mode 100644 index 0000000000000..5c45b8f097baf --- /dev/null +++ b/x-pack/test/licensing_plugin/legacy/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ loadTestFile }: FtrProviderContext) { + describe('Legacy licensing plugin', function() { + this.tags('ciGroup2'); + // MUST BE LAST! CHANGES LICENSE TYPE! + loadTestFile(require.resolve('./updates')); + }); +} diff --git a/x-pack/test/licensing_plugin/legacy/updates.ts b/x-pack/test/licensing_plugin/legacy/updates.ts new file mode 100644 index 0000000000000..14657368c78ae --- /dev/null +++ b/x-pack/test/licensing_plugin/legacy/updates.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; +import { createScenario } from '../scenario'; +import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; + +// eslint-disable-next-line import/no-default-export +export default function(ftrContext: FtrProviderContext) { + const { getService } = ftrContext; + const supertest = getService('supertest'); + const testSubjects = getService('testSubjects'); + + const scenario = createScenario(ftrContext); + + describe('changes in license types', () => { + after(async () => { + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + await scenario.teardown(); + }); + + it('provides changes in license types', async () => { + await scenario.setup(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + const { + body: legacyInitialLicense, + headers: legacyInitialLicenseHeaders, + } = await supertest.get('/api/xpack/v1/info').expect(200); + + expect(legacyInitialLicense.license?.type).to.be('basic'); + expect(legacyInitialLicense.features).to.have.property('security'); + expect(legacyInitialLicenseHeaders['kbn-xpack-sig']).to.be.a('string'); + + await scenario.startTrial(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + const { body: legacyTrialLicense, headers: legacyTrialLicenseHeaders } = await supertest + .get('/api/xpack/v1/info') + .expect(200); + + expect(legacyTrialLicense.license?.type).to.be('trial'); + expect(legacyTrialLicense.features).to.have.property('security'); + expect(legacyTrialLicenseHeaders['kbn-xpack-sig']).to.not.be( + legacyInitialLicenseHeaders['kbn-xpack-sig'] + ); + + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + const { body: legacyBasicLicense, headers: legacyBasicLicenseHeaders } = await supertest + .get('/api/xpack/v1/info') + .expect(200); + expect(legacyBasicLicense.license?.type).to.be('basic'); + expect(legacyBasicLicense.features).to.have.property('security'); + expect(legacyBasicLicenseHeaders['kbn-xpack-sig']).to.not.be( + legacyInitialLicenseHeaders['kbn-xpack-sig'] + ); + + await scenario.deleteLicense(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + // banner shown only when license expired not just deleted + await testSubjects.missingOrFail('licenseExpiredBanner'); + }); + }); +} diff --git a/x-pack/test/licensing_plugin/public/index.ts b/x-pack/test/licensing_plugin/public/index.ts new file mode 100644 index 0000000000000..3e1445d9a4aab --- /dev/null +++ b/x-pack/test/licensing_plugin/public/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ loadTestFile }: FtrProviderContext) { + describe('Licensing plugin public client', function() { + this.tags('ciGroup2'); + // MUST BE LAST! CHANGES LICENSE TYPE! + loadTestFile(require.resolve('./updates')); + }); +} diff --git a/x-pack/test/licensing_plugin/public/updates.ts b/x-pack/test/licensing_plugin/public/updates.ts new file mode 100644 index 0000000000000..80822f6fb2505 --- /dev/null +++ b/x-pack/test/licensing_plugin/public/updates.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; +import { LicensingPluginSetup } from '../../../plugins/licensing/public'; +import { createScenario } from '../scenario'; +import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; + +// eslint-disable-next-line import/no-default-export +export default function(ftrContext: FtrProviderContext) { + const { getService } = ftrContext; + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + + const scenario = createScenario(ftrContext); + + describe('changes in license types', () => { + after(async () => { + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + await scenario.teardown(); + }); + + it('provides changes in license types', async () => { + await scenario.setup(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + expect( + await browser.executeAsync(async (cb: Function) => { + const { setup, testUtils } = window.__coreProvider; + // this call enforces signature check to detect license update + // and causes license re-fetch + await setup.core.http.get('/'); + await testUtils.delay(100); + + const licensing: LicensingPluginSetup = setup.plugins.licensing; + licensing.license$.subscribe(license => cb(license.type)); + }) + ).to.be('basic'); + + // license hasn't changed + await scenario.waitForPluginToDetectLicenseUpdate(); + + expect( + await browser.executeAsync(async (cb: Function) => { + const { setup, testUtils } = window.__coreProvider; + // this call enforces signature check to detect license update + // and causes license re-fetch + await setup.core.http.get('/'); + await testUtils.delay(100); + + const licensing: LicensingPluginSetup = setup.plugins.licensing; + licensing.license$.subscribe(license => cb(license.type)); + }) + ).to.be('basic'); + + await scenario.startTrial(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + expect( + await browser.executeAsync(async (cb: Function) => { + const { setup, testUtils } = window.__coreProvider; + // this call enforces signature check to detect license update + // and causes license re-fetch + await setup.core.http.get('/'); + await testUtils.delay(100); + + const licensing: LicensingPluginSetup = setup.plugins.licensing; + licensing.license$.subscribe(license => cb(license.type)); + }) + ).to.be('trial'); + + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + expect( + await browser.executeAsync(async (cb: Function) => { + const { setup, testUtils } = window.__coreProvider; + // this call enforces signature check to detect license update + // and causes license re-fetch + await setup.core.http.get('/'); + await testUtils.delay(100); + + const licensing: LicensingPluginSetup = setup.plugins.licensing; + licensing.license$.subscribe(license => cb(license.type)); + }) + ).to.be('basic'); + + await scenario.deleteLicense(); + await scenario.waitForPluginToDetectLicenseUpdate(); + + expect( + await browser.executeAsync(async (cb: Function) => { + const { setup, testUtils } = window.__coreProvider; + // this call enforces signature check to detect license update + // and causes license re-fetch + await setup.core.http.get('/'); + await testUtils.delay(100); + + const licensing: LicensingPluginSetup = setup.plugins.licensing; + licensing.license$.subscribe(license => cb(license.type)); + }) + ).to.be(null); + + // banner shown only when license expired not just deleted + await testSubjects.missingOrFail('licenseExpiredBanner'); + }); + }); +} diff --git a/x-pack/test/licensing_plugin/scenario.ts b/x-pack/test/licensing_plugin/scenario.ts new file mode 100644 index 0000000000000..46837dfc1be91 --- /dev/null +++ b/x-pack/test/licensing_plugin/scenario.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from './services'; +import { PublicLicenseJSON } from '../../plugins/licensing/server'; +import '../../../test/plugin_functional/plugins/core_provider_plugin/types'; + +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); + +export function createScenario({ getService, getPageObjects }: FtrProviderContext) { + const supertest = getService('supertest'); + const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); + const security = getService('security'); + const PageObjects = getPageObjects(['common', 'security']); + + const scenario = { + async setup() { + await security.role.create('license_manager-role', { + elasticsearch: { + cluster: ['all'], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + await security.user.create('license_manager_user', { + password: 'license_manager_user-password', + roles: ['license_manager-role'], + full_name: 'license_manager user', + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + await PageObjects.security.login('license_manager_user', 'license_manager_user-password'); + }, + + // make sure a license is present, otherwise the security is not available anymore. + async teardown() { + await security.role.delete('license_manager-role'); + await security.user.delete('license_manager_user'); + }, + + // elasticsearch allows to downgrade a license only once. other attempts will throw 403. + async startBasic() { + const response = await esSupertestWithoutAuth + .post('/_license/start_basic?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.basic_was_started).to.be(true); + }, + + // elasticsearch allows to request trial only once. other attempts will throw 403. + async startTrial() { + const response = await esSupertestWithoutAuth + .post('/_license/start_trial?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.trial_was_started).to.be(true); + }, + + async deleteLicense() { + const response = await esSupertestWithoutAuth + .delete('/_license') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.acknowledged).to.be(true); + }, + + async getLicense(): Promise { + const { body } = await supertest.get('/api/licensing/info').expect(200); + return body; + }, + + async waitForPluginToDetectLicenseUpdate() { + // > --xpack.licensing.api_polling_frequency set in test config + // to wait for Kibana server to re-fetch the license from Elasticsearch + await delay(500); + }, + }; + return scenario; +} diff --git a/x-pack/test/licensing_plugin/apis/header.ts b/x-pack/test/licensing_plugin/server/header.ts similarity index 93% rename from x-pack/test/licensing_plugin/apis/header.ts rename to x-pack/test/licensing_plugin/server/header.ts index 8d95054feaaf2..d2073e8773f18 100644 --- a/x-pack/test/licensing_plugin/apis/header.ts +++ b/x-pack/test/licensing_plugin/server/header.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../services'; +// eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/licensing_plugin/apis/index.ts b/x-pack/test/licensing_plugin/server/index.ts similarity index 76% rename from x-pack/test/licensing_plugin/apis/index.ts rename to x-pack/test/licensing_plugin/server/index.ts index fbc0449dcd8fc..374bfcc0aa6b4 100644 --- a/x-pack/test/licensing_plugin/apis/index.ts +++ b/x-pack/test/licensing_plugin/server/index.ts @@ -6,13 +6,14 @@ import { FtrProviderContext } from '../services'; +// eslint-disable-next-line import/no-default-export export default function({ loadTestFile }: FtrProviderContext) { - describe('Licensing plugin', function() { + describe('Licensing plugin server client', function() { this.tags('ciGroup2'); loadTestFile(require.resolve('./info')); loadTestFile(require.resolve('./header')); // MUST BE LAST! CHANGES LICENSE TYPE! - loadTestFile(require.resolve('./changes')); + loadTestFile(require.resolve('./updates')); }); } diff --git a/x-pack/test/licensing_plugin/apis/info.ts b/x-pack/test/licensing_plugin/server/info.ts similarity index 95% rename from x-pack/test/licensing_plugin/apis/info.ts rename to x-pack/test/licensing_plugin/server/info.ts index 7ec009d85cd09..cce042c718b7b 100644 --- a/x-pack/test/licensing_plugin/apis/info.ts +++ b/x-pack/test/licensing_plugin/server/info.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../services'; +// eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/licensing_plugin/server/updates.ts b/x-pack/test/licensing_plugin/server/updates.ts new file mode 100644 index 0000000000000..ca0fb37069b3f --- /dev/null +++ b/x-pack/test/licensing_plugin/server/updates.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; +import { createScenario } from '../scenario'; +import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; + +// eslint-disable-next-line import/no-default-export +export default function(ftrContext: FtrProviderContext) { + const { getService } = ftrContext; + const testSubjects = getService('testSubjects'); + + const scenario = createScenario(ftrContext); + + describe('changes in license types', () => { + after(async () => { + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + await scenario.teardown(); + }); + + it('provides changes in license types', async () => { + await scenario.setup(); + await scenario.waitForPluginToDetectLicenseUpdate(); + const initialLicense = await scenario.getLicense(); + expect(initialLicense.license?.type).to.be('basic'); + // security enabled explicitly in test config + expect(initialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + // license hasn't changed + await scenario.waitForPluginToDetectLicenseUpdate(); + const refetchedLicense = await scenario.getLicense(); + expect(refetchedLicense.license?.type).to.be('basic'); + expect(refetchedLicense.signature).to.be(initialLicense.signature); + + await scenario.startTrial(); + await scenario.waitForPluginToDetectLicenseUpdate(); + const trialLicense = await scenario.getLicense(); + expect(trialLicense.license?.type).to.be('trial'); + expect(trialLicense.signature).to.not.be(initialLicense.signature); + + expect(trialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.startBasic(); + await scenario.waitForPluginToDetectLicenseUpdate(); + const basicLicense = await scenario.getLicense(); + expect(basicLicense.license?.type).to.be('basic'); + expect(basicLicense.signature).not.to.be(initialLicense.signature); + + expect(basicLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.deleteLicense(); + await scenario.waitForPluginToDetectLicenseUpdate(); + const inactiveLicense = await scenario.getLicense(); + expect(inactiveLicense.signature).to.not.be(initialLicense.signature); + expect(inactiveLicense).to.not.have.property('license'); + expect(inactiveLicense.features?.security).to.eql({ + isAvailable: false, + isEnabled: true, + }); + + // banner shown only when license expired not just deleted + await testSubjects.missingOrFail('licenseExpiredBanner'); + }); + }); +}