diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index b7fbe629246eca..bb28c1460c9c23 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index ccdddc87dbc18a..0764451c74575c 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index ab686f4d5ffec0..72bcc07ab98cfa 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -30,13 +30,7 @@ import { Browsers } from './remote/browsers'; export async function BrowserProvider({ getService }: FtrProviderContext) { const log = getService('log'); - const { driver, browserType, consoleLog$ } = await getService('__webdriver__').init(); - - consoleLog$.subscribe(({ message, level }) => { - log[level === 'SEVERE' || level === 'error' ? 'error' : 'debug']( - `browser[${level}] ${message}` - ); - }); + const { driver, browserType } = await getService('__webdriver__').init(); const isW3CEnabled = (driver as any).executor_.w3c === true; diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 90ff55fbebde55..69c27936210951 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -20,14 +20,11 @@ import Fs from 'fs'; import { resolve } from 'path'; -import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; -import { logging } from 'selenium-webdriver'; import { FtrProviderContext } from '../../ftr_provider_context'; import { initWebDriver } from './webdriver'; import { Browsers } from './browsers'; -import { pollForLogEntry$ } from './poll_for_log_entry'; export async function RemoteProvider({ getService }: FtrProviderContext) { const lifecycle = getService('lifecycle'); @@ -37,7 +34,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { const collectCoverage: boolean = !!process.env.CODE_COVERAGE; const coveragePrefix = 'coveragejson:'; const coverageDir = resolve(__dirname, '../../../../target/kibana-coverage/functional'); - let logSubscription: undefined | Rx.Subscription; + let coverageCounter = 1; type BrowserStorage = 'sessionStorage' | 'localStorage'; const clearBrowserStorage = async (storageType: BrowserStorage) => { @@ -50,6 +47,14 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } }; + const writeCoverage = (coverageJson: string) => { + const id = coverageCounter++; + const timestamp = Date.now(); + const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); + log.info('writing coverage to', path); + Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); + }; + const { driver, By, until, consoleLog$ } = await initWebDriver( log, browserType, @@ -69,46 +74,35 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { caps.get('chrome').chromedriverVersion }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` ); - - if (collectCoverage) { - let coverageCounter = 1; - // We are running xpack tests with different configs and cleanup will delete collected coverage - // del.sync(coverageDir); - Fs.mkdirSync(coverageDir, { recursive: true }); - - logSubscription = pollForLogEntry$( - driver, - logging.Type.BROWSER, - config.get('browser.logPollingMs'), - lifecycle.cleanup.after$ - ) - .pipe( - mergeMap(logEntry => { - if (logEntry.message.includes(coveragePrefix)) { - const id = coverageCounter++; - const timestamp = Date.now(); - const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); - const [, coverageJsonBase64] = logEntry.message.split(coveragePrefix); - const coverageJson = Buffer.from(coverageJsonBase64, 'base64').toString('utf8'); - - log.info('writing coverage to', path); - Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); - - // filter out this message - return []; - } - - return [logEntry]; - }) - ) - .subscribe({ - next({ message, level: { name: level } }) { - const msg = message.replace(/\\n/g, '\n'); - log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); - }, - }); - } } + // code coverage is supported only in Chrome browser + if (collectCoverage) { + // We are running xpack tests with different configs and cleanup will delete collected coverage + // del.sync(coverageDir); + Fs.mkdirSync(coverageDir, { recursive: true }); + } + + consoleLog$ + .pipe( + mergeMap(logEntry => { + if (collectCoverage && logEntry.message.includes(coveragePrefix)) { + const [, coverageJsonBase64] = logEntry.message.split(coveragePrefix); + const coverageJson = Buffer.from(coverageJsonBase64, 'base64').toString('utf8'); + writeCoverage(coverageJson); + + // filter out this message + return []; + } + + return [logEntry]; + }) + ) + .subscribe({ + next({ message, level }) { + const msg = message.replace(/\\n/g, '\n'); + log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); + }, + }); lifecycle.beforeTests.add(async () => { // hard coded default, can be overridden per suite using `browser.setWindowSize()` @@ -144,8 +138,15 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }); lifecycle.cleanup.add(async () => { - if (logSubscription) { - await new Promise(r => logSubscription!.add(r)); + // Getting the last piece of code coverage before closing browser + if (collectCoverage) { + const coverageJson = await driver + .executeScript('return window.__coverage__') + .catch(() => undefined) + .then(coverage => coverage && JSON.stringify(coverage)); + if (coverageJson) { + writeCoverage(coverageJson); + } } await driver.quit(); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx index e99268efb9da47..a5d23227eda517 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx @@ -119,7 +119,9 @@ export const SignalsTableComponent = React.memo( const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([DEFAULT_SIGNALS_INDEX]); // TODO Get from new FrankInspired XavierHook + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([ + `${DEFAULT_SIGNALS_INDEX}-default`, + ]); // TODO Get from new FrankInspired XavierHook const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const core = useKibanaCore(); @@ -265,7 +267,9 @@ export const SignalsTableComponent = React.memo( [createTimelineCallback, filterGroup, kbnVersion] ); - const defaultIndices = useMemo(() => [`${DEFAULT_SIGNALS_INDEX}`], [DEFAULT_SIGNALS_INDEX]); + const defaultIndices = useMemo(() => [`${DEFAULT_SIGNALS_INDEX}-default`], [ + `${DEFAULT_SIGNALS_INDEX}-default`, + ]); const defaultFiltersMemo = useMemo( () => [ ...defaultFilters, diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts b/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts index 8c340f5525a4c0..8c47b318da9bdc 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts @@ -8,7 +8,7 @@ import { ILicense } from '../../../../../../../plugins/licensing/server'; import { licenseCheck } from '../license'; describe('license check', () => { - let mockLicense: Pick; + let mockLicense: Pick; it('throws for null license', () => { expect(licenseCheck(undefined)).toMatchSnapshot(); @@ -16,7 +16,7 @@ describe('license check', () => { it('throws for unsupported license type', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(false), + hasAtLeast: jest.fn().mockReturnValue(false), isActive: false, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); @@ -24,7 +24,7 @@ describe('license check', () => { it('throws for inactive license', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(true), + hasAtLeast: jest.fn().mockReturnValue(true), isActive: false, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); @@ -32,7 +32,7 @@ describe('license check', () => { it('returns result for a valid license', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(true), + hasAtLeast: jest.fn().mockReturnValue(true), isActive: true, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts b/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts index 24ce0205414fa4..b8b5722d798777 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts @@ -11,7 +11,7 @@ export interface UMLicenseStatusResponse { message: string; } export type UMLicenseCheck = ( - license?: Pick + license?: Pick ) => UMLicenseStatusResponse; export const licenseCheck: UMLicenseCheck = license => { @@ -21,7 +21,7 @@ export const licenseCheck: UMLicenseCheck = license => { statusCode: 400, }; } - if (!license.isOneOf(['basic', 'standard', 'gold', 'platinum', 'enterprise', 'trial'])) { + if (!license.hasAtLeast('basic')) { return { message: 'License not supported', statusCode: 401, diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js index c369f90cec1d18..ed2043e00b9a73 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { licensingMock } from '../../../../../plugins/licensing/server/licensing.mock'; +import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; import { XPackInfoLicense } from './xpack_info_license'; function getXPackInfoLicense(getRawLicense) { diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md index 339abb77ced880..cab40b48718af0 100644 --- a/x-pack/plugins/licensing/README.md +++ b/x-pack/plugins/licensing/README.md @@ -56,7 +56,7 @@ chrome.navLinks.update('myPlugin', { "requiredPlugins": ["licensing"], // my_plugin/server/plugin.ts -import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing' +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/server' interface SetupDeps { licensing: LicensingPluginSetup; @@ -77,7 +77,8 @@ class MyPlugin { } } -// my_plugin/client/plugin.ts +// my_plugin/public/plugin.ts +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/public' class MyPlugin { setup(core: CoreSetup, deps: SetupDeps) { deps.licensing.license$.subscribe(license => { diff --git a/x-pack/plugins/licensing/common/license.test.ts b/x-pack/plugins/licensing/common/license.test.ts index 8035a92952e107..622572a6a95baf 100644 --- a/x-pack/plugins/licensing/common/license.test.ts +++ b/x-pack/plugins/licensing/common/license.test.ts @@ -9,10 +9,10 @@ import { LICENSE_CHECK_STATE } from './types'; import { licenseMock } from './licensing.mock'; describe('License', () => { - const basicLicense = licenseMock.create(); - const basicExpiredLicense = licenseMock.create({ license: { status: 'expired' } }); - const goldLicense = licenseMock.create({ license: { type: 'gold' } }); - const enterpriseLicense = licenseMock.create({ license: { type: 'enterprise' } }); + const basicLicense = licenseMock.createLicense(); + const basicExpiredLicense = licenseMock.createLicense({ license: { status: 'expired' } }); + const goldLicense = licenseMock.createLicense({ license: { type: 'gold' } }); + const enterpriseLicense = licenseMock.createLicense({ license: { type: 'enterprise' } }); const errorMessage = 'unavailable'; const errorLicense = new License({ error: errorMessage, signature: '' }); @@ -50,34 +50,23 @@ describe('License', () => { expect(unavailableLicense.isActive).toBe(false); }); - it('isBasic', () => { - expect(basicLicense.isBasic).toBe(true); - expect(goldLicense.isBasic).toBe(false); - expect(enterpriseLicense.isBasic).toBe(false); - expect(errorLicense.isBasic).toBe(false); - expect(unavailableLicense.isBasic).toBe(false); - }); + it('hasAtLeast', () => { + expect(basicLicense.hasAtLeast('platinum')).toBe(false); + expect(basicLicense.hasAtLeast('gold')).toBe(false); + expect(basicLicense.hasAtLeast('basic')).toBe(true); - it('isNotBasic', () => { - expect(basicLicense.isNotBasic).toBe(false); - expect(goldLicense.isNotBasic).toBe(true); - expect(enterpriseLicense.isNotBasic).toBe(true); - expect(errorLicense.isNotBasic).toBe(false); - expect(unavailableLicense.isNotBasic).toBe(false); - }); + expect(errorLicense.hasAtLeast('basic')).toBe(false); - it('isOneOf', () => { - expect(basicLicense.isOneOf('platinum')).toBe(false); - expect(basicLicense.isOneOf(['platinum'])).toBe(false); - expect(basicLicense.isOneOf(['gold', 'platinum'])).toBe(false); - expect(basicLicense.isOneOf(['platinum', 'gold'])).toBe(false); - expect(basicLicense.isOneOf(['basic', 'gold'])).toBe(true); - expect(basicLicense.isOneOf(['basic'])).toBe(true); - expect(basicLicense.isOneOf('basic')).toBe(true); + expect(unavailableLicense.hasAtLeast('basic')).toBe(false); - expect(errorLicense.isOneOf(['basic', 'gold', 'platinum'])).toBe(false); + expect(goldLicense.hasAtLeast('basic')).toBe(true); + expect(goldLicense.hasAtLeast('gold')).toBe(true); + expect(goldLicense.hasAtLeast('platinum')).toBe(false); - expect(unavailableLicense.isOneOf(['basic', 'gold', 'platinum'])).toBe(false); + expect(enterpriseLicense.hasAtLeast('basic')).toBe(true); + expect(enterpriseLicense.hasAtLeast('platinum')).toBe(true); + expect(enterpriseLicense.hasAtLeast('enterprise')).toBe(true); + expect(enterpriseLicense.hasAtLeast('trial')).toBe(false); }); it('getUnavailableReason', () => { @@ -115,9 +104,13 @@ describe('License', () => { }); it('throws in case of unknown license type', () => { - expect( - () => basicLicense.check('ccr', 'any' as any).state - ).toThrowErrorMatchingInlineSnapshot(`"\\"any\\" is not a valid license type"`); + expect(() => basicLicense.check('ccr', 'any' as any)).toThrowErrorMatchingInlineSnapshot( + `"\\"any\\" is not a valid license type"` + ); + + expect(() => basicLicense.hasAtLeast('any' as any)).toThrowErrorMatchingInlineSnapshot( + `"\\"any\\" is not a valid license type"` + ); }); }); }); diff --git a/x-pack/plugins/licensing/common/license.ts b/x-pack/plugins/licensing/common/license.ts index 8423fed1d6a4e6..41f3c73db9c060 100644 --- a/x-pack/plugins/licensing/common/license.ts +++ b/x-pack/plugins/licensing/common/license.ts @@ -26,8 +26,6 @@ export class License implements ILicense { public readonly error?: string; public readonly isActive: boolean; public readonly isAvailable: boolean; - public readonly isBasic: boolean; - public readonly isNotBasic: boolean; public readonly uid?: string; public readonly status?: LicenseStatus; @@ -70,8 +68,6 @@ export class License implements ILicense { } this.isActive = this.status === 'active'; - this.isBasic = this.isActive && this.type === 'basic'; - this.isNotBasic = this.isActive && this.type !== 'basic'; } toJSON() { @@ -89,23 +85,20 @@ export class License implements ILicense { } } - isOneOf(candidateLicenses: LicenseType | LicenseType[]) { - if (!this.type) { + hasAtLeast(minimumLicenseRequired: LicenseType) { + const type = this.type; + if (!type) { return false; } - if (!Array.isArray(candidateLicenses)) { - candidateLicenses = [candidateLicenses]; + if (!(minimumLicenseRequired in LICENSE_TYPE)) { + throw new Error(`"${minimumLicenseRequired}" is not a valid license type`); } - return candidateLicenses.includes(this.type); + return LICENSE_TYPE[minimumLicenseRequired] <= LICENSE_TYPE[type]; } check(pluginName: string, minimumLicenseRequired: LicenseType) { - if (!(minimumLicenseRequired in LICENSE_TYPE)) { - throw new Error(`"${minimumLicenseRequired}" is not a valid license type`); - } - if (!this.isAvailable) { return { state: LICENSE_CHECK_STATE.Unavailable, @@ -117,26 +110,24 @@ export class License implements ILicense { }; } - const type = this.type!; - if (!this.isActive) { return { state: LICENSE_CHECK_STATE.Expired, message: i18n.translate('xpack.licensing.check.errorExpiredMessage', { defaultMessage: 'You cannot use {pluginName} because your {licenseType} license has expired.', - values: { licenseType: type, pluginName }, + values: { licenseType: this.type!, pluginName }, }), }; } - if (LICENSE_TYPE[type] < LICENSE_TYPE[minimumLicenseRequired]) { + if (!this.hasAtLeast(minimumLicenseRequired)) { return { state: LICENSE_CHECK_STATE.Invalid, message: i18n.translate('xpack.licensing.check.errorUnsupportedMessage', { defaultMessage: 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType: type, pluginName }, + values: { licenseType: this.type!, pluginName }, }), }; } diff --git a/x-pack/plugins/licensing/common/license_update.test.ts b/x-pack/plugins/licensing/common/license_update.test.ts index e714edfbdd88c1..7e2410913f6986 100644 --- a/x-pack/plugins/licensing/common/license_update.test.ts +++ b/x-pack/plugins/licensing/common/license_update.test.ts @@ -7,7 +7,7 @@ import { Subject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { ILicense, LicenseType } from './types'; +import { ILicense } from './types'; import { createLicenseUpdate } from './license_update'; import { licenseMock } from './licensing.mock'; @@ -15,14 +15,11 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const stop$ = new Subject(); describe('licensing update', () => { it('loads updates when triggered', async () => { - const types: LicenseType[] = ['basic', 'gold']; - const trigger$ = new Subject(); const fetcher = jest .fn() - .mockImplementation(() => - Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) - ); + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'basic' } })) + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'gold' } })); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); @@ -38,8 +35,8 @@ describe('licensing update', () => { }); it('starts with initial value if presents', async () => { - const initialLicense = licenseMock.create({ license: { type: 'platinum' } }); - const fetchedLicense = licenseMock.create({ license: { type: 'gold' } }); + const initialLicense = licenseMock.createLicense({ license: { type: 'platinum' } }); + const fetchedLicense = licenseMock.createLicense({ license: { type: 'gold' } }); const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(fetchedLicense); @@ -55,14 +52,11 @@ describe('licensing update', () => { it('does not emit if license has not changed', async () => { const trigger$ = new Subject(); - let i = 0; const fetcher = jest .fn() - .mockImplementation(() => - Promise.resolve( - ++i < 3 ? licenseMock.create() : licenseMock.create({ license: { type: 'gold' } }) - ) - ); + .mockResolvedValueOnce(licenseMock.createLicense()) + .mockResolvedValueOnce(licenseMock.createLicense()) + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'gold' } })); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); trigger$.next(); @@ -83,7 +77,7 @@ describe('licensing update', () => { it('new subscriptions does not force re-fetch', async () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); @@ -103,9 +97,9 @@ describe('licensing update', () => { new Promise(resolve => { if (firstCall) { firstCall = false; - setTimeout(() => resolve(licenseMock.create()), delayMs); + setTimeout(() => resolve(licenseMock.createLicense()), delayMs); } else { - resolve(licenseMock.create({ license: { type: 'gold' } })); + resolve(licenseMock.createLicense({ license: { type: 'gold' } })); } }) ); @@ -126,7 +120,7 @@ describe('licensing update', () => { it('completes license$ stream when stop$ is triggered', () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); let completed = false; @@ -138,7 +132,7 @@ describe('licensing update', () => { it('stops fetching when stop$ is triggered', () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; @@ -152,8 +146,8 @@ describe('licensing update', () => { it('refreshManually guarantees license fetching', async () => { const trigger$ = new Subject(); - const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); - const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + const firstLicense = licenseMock.createLicense({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.createLicense({ license: { uid: 'second', type: 'gold' } }); const fetcher = jest .fn() diff --git a/x-pack/plugins/licensing/common/licensing.mock.ts b/x-pack/plugins/licensing/common/licensing.mock.ts index 52721703fcb737..bbe63d5c0d70a3 100644 --- a/x-pack/plugins/licensing/common/licensing.mock.ts +++ b/x-pack/plugins/licensing/common/licensing.mock.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PublicLicense, PublicFeatures } from './types'; +import { ILicense, PublicLicense, PublicFeatures, LICENSE_CHECK_STATE } from './types'; import { License } from './license'; function createLicense({ @@ -40,6 +40,22 @@ function createLicense({ }); } +const createLicenseMock = () => { + const mock: jest.Mocked = { + isActive: true, + isAvailable: true, + signature: '', + toJSON: jest.fn(), + getUnavailableReason: jest.fn(), + getFeature: jest.fn(), + check: jest.fn(), + hasAtLeast: jest.fn(), + }; + mock.check.mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }); + mock.hasAtLeast.mockReturnValue(true); + return mock; +}; export const licenseMock = { - create: createLicense, + createLicense, + createLicenseMock, }; diff --git a/x-pack/plugins/licensing/common/types.ts b/x-pack/plugins/licensing/common/types.ts index c4b9f4ebad19d2..78c31963da9b15 100644 --- a/x-pack/plugins/licensing/common/types.ts +++ b/x-pack/plugins/licensing/common/types.ts @@ -140,16 +140,6 @@ export interface ILicense { */ isAvailable: boolean; - /** - * Determine if the type of the license is basic, and also active. - */ - isBasic: boolean; - - /** - * Determine if the type of the license is not basic, and also active. - */ - isNotBasic: boolean; - /** * Returns */ @@ -166,10 +156,10 @@ export interface ILicense { getUnavailableReason: () => string | undefined; /** - * Determine if the provided license types match against the license type. - * @param candidateLicenses license types to intersect against the license. + * Determine if license type >= minimal required license type. + * @param minimumLicenseRequired the minimum valid license required for the given feature */ - isOneOf(candidateLicenses: LicenseType | LicenseType[]): boolean; + hasAtLeast(minimumLicenseRequired: LicenseType): boolean; /** * For a given plugin and license type, receive information about the status of the license. diff --git a/x-pack/plugins/licensing/public/licensing.mock.ts b/x-pack/plugins/licensing/public/mocks.ts similarity index 89% rename from x-pack/plugins/licensing/public/licensing.mock.ts rename to x-pack/plugins/licensing/public/mocks.ts index e2ed070017847e..68b280c5341f26 100644 --- a/x-pack/plugins/licensing/public/licensing.mock.ts +++ b/x-pack/plugins/licensing/public/mocks.ts @@ -8,7 +8,7 @@ import { LicensingPluginSetup } from './types'; import { licenseMock } from '../common/licensing.mock'; const createSetupMock = () => { - const license = licenseMock.create(); + const license = licenseMock.createLicense(); const mock: jest.Mocked = { license$: new BehaviorSubject(license), refresh: jest.fn(), @@ -20,5 +20,5 @@ const createSetupMock = () => { export const licensingMock = { createSetup: createSetupMock, - createLicense: licenseMock.create, + ...licenseMock, }; diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 4469f26836b185..01545ee8d48b4a 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -30,8 +30,12 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); - const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + const firstLicense = licenseMock.createLicense({ + license: { uid: 'first', type: 'basic' }, + }); + const secondLicense = licenseMock.createLicense({ + license: { uid: 'second', type: 'gold' }, + }); coreSetup.http.get.mockResolvedValueOnce(firstLicense).mockResolvedValueOnce(secondLicense); const { license$, refresh } = await plugin.setup(coreSetup); @@ -53,7 +57,7 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create(); + const fetchedLicense = licenseMock.createLicense(); coreSetup.http.get.mockResolvedValue(fetchedLicense); const { refresh } = await plugin.setup(coreSetup); @@ -71,7 +75,7 @@ describe('licensing plugin', () => { describe('#license$', () => { it('starts with license saved in sessionStorage if available', async () => { const sessionStorage = coreMock.createStorage(); - const savedLicense = licenseMock.create({ license: { uid: 'saved' } }); + const savedLicense = licenseMock.createLicense({ license: { uid: 'saved' } }); sessionStorage.getItem.mockReturnValue(JSON.stringify(savedLicense)); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); @@ -90,12 +94,12 @@ describe('licensing plugin', () => { const types: LicenseType[] = ['gold', 'platinum']; const sessionStorage = coreMock.createStorage(); - sessionStorage.getItem.mockReturnValue(JSON.stringify(licenseMock.create())); + sessionStorage.getItem.mockReturnValue(JSON.stringify(licenseMock.createLicense())); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); coreSetup.http.get.mockImplementation(() => - Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) + Promise.resolve(licenseMock.createLicense({ license: { type: types.shift() } })) ); const { license$, refresh } = await plugin.setup(coreSetup); @@ -123,7 +127,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create({ license: { uid: 'fresh' } }); + const fetchedLicense = licenseMock.createLicense({ license: { uid: 'fresh' } }); coreSetup.http.get.mockResolvedValue(fetchedLicense); const { license$, refresh } = await plugin.setup(coreSetup); @@ -196,7 +200,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); - coreSetup.http.get.mockResolvedValue(licenseMock.create({ signature: 'signature-1' })); + coreSetup.http.get.mockResolvedValue(licenseMock.createLicense({ signature: 'signature-1' })); let registeredInterceptor: HttpInterceptor; coreSetup.http.intercept.mockImplementation((interceptor: HttpInterceptor) => { @@ -321,7 +325,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); coreSetup.http.get.mockResolvedValueOnce( - licenseMock.create({ license: { status: 'active', type: 'gold' } }) + licenseMock.createLicense({ license: { status: 'active', type: 'gold' } }) ); const { refresh } = await plugin.setup(coreSetup); @@ -338,8 +342,12 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const activeLicense = licenseMock.create({ license: { status: 'active', type: 'gold' } }); - const expiredLicense = licenseMock.create({ license: { status: 'expired', type: 'gold' } }); + const activeLicense = licenseMock.createLicense({ + license: { status: 'active', type: 'gold' }, + }); + const expiredLicense = licenseMock.createLicense({ + license: { status: 'expired', type: 'gold' }, + }); coreSetup.http.get .mockResolvedValueOnce(activeLicense) .mockResolvedValueOnce(expiredLicense) diff --git a/x-pack/plugins/licensing/server/licensing.mock.ts b/x-pack/plugins/licensing/server/licensing.mock.ts deleted file mode 100644 index b2059e36fd0c03..00000000000000 --- a/x-pack/plugins/licensing/server/licensing.mock.ts +++ /dev/null @@ -1,29 +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 { BehaviorSubject } from 'rxjs'; -import { LicensingPluginSetup } from './types'; -import { licenseMock } from '../common/licensing.mock'; - -const createSetupMock = () => { - const license = licenseMock.create(); - const mock: jest.Mocked = { - license$: new BehaviorSubject(license), - refresh: jest.fn(), - createLicensePoller: jest.fn(), - }; - mock.refresh.mockResolvedValue(license); - mock.createLicensePoller.mockReturnValue({ - license$: mock.license$, - refresh: mock.refresh, - }); - - return mock; -}; - -export const licensingMock = { - createSetup: createSetupMock, - createLicense: licenseMock.create, -}; diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts index 20e7f34c3ce3c4..91fb1672f020d4 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts @@ -11,8 +11,8 @@ import { createRouteHandlerContext } from './licensing_route_handler_context'; describe('createRouteHandlerContext', () => { it('returns a function providing the last license value', async () => { - const firstLicense = licenseMock.create(); - const secondLicense = licenseMock.create(); + const firstLicense = licenseMock.createLicense(); + const secondLicense = licenseMock.createLicense(); const license$ = new BehaviorSubject(firstLicense); const routeHandler = createRouteHandlerContext(license$); diff --git a/x-pack/plugins/licensing/server/mocks.ts b/x-pack/plugins/licensing/server/mocks.ts index 237636d1630178..d622e3f71eff54 100644 --- a/x-pack/plugins/licensing/server/mocks.ts +++ b/x-pack/plugins/licensing/server/mocks.ts @@ -3,5 +3,27 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { BehaviorSubject } from 'rxjs'; +import { LicensingPluginSetup } from './types'; +import { licenseMock } from '../common/licensing.mock'; -export * from './licensing.mock'; +const createSetupMock = () => { + const license = licenseMock.createLicense(); + const mock: jest.Mocked = { + license$: new BehaviorSubject(license), + refresh: jest.fn(), + createLicensePoller: jest.fn(), + }; + mock.refresh.mockResolvedValue(license); + mock.createLicensePoller.mockReturnValue({ + license$: mock.license$, + refresh: mock.refresh, + }); + + return mock; +}; + +export const licensingMock = { + createSetup: createSetupMock, + ...licenseMock, +}; diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts index 9acfcef0ac8df9..9a588a9ac84601 100644 --- a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts @@ -11,7 +11,7 @@ import { licenseMock } from '../common/licensing.mock'; describe('createOnPreResponseHandler', () => { it('sets license.signature header immediately for non-error responses', async () => { const refresh = jest.fn(); - const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const license$ = new BehaviorSubject(licenseMock.createLicense({ signature: 'foo' })); const toolkit = httpServiceMock.createOnPreResponseToolkit(); const interceptor = createOnPreResponseHandler(refresh, license$); @@ -26,8 +26,8 @@ describe('createOnPreResponseHandler', () => { }); }); it('sets license.signature header after refresh for non-error responses', async () => { - const updatedLicense = licenseMock.create({ signature: 'bar' }); - const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const updatedLicense = licenseMock.createLicense({ signature: 'bar' }); + const license$ = new BehaviorSubject(licenseMock.createLicense({ signature: 'foo' })); const refresh = jest.fn().mockImplementation( () => new Promise(resolve => { diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index f3b307cdeabfe8..f4fa5e00e2387d 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -5,21 +5,13 @@ */ import { of, BehaviorSubject } from 'rxjs'; -import { ILicense } from '../../../licensing/public'; +import { licensingMock } from '../../../licensing/public/mocks'; import { SecurityLicenseService } from './license_service'; -function getMockRawLicense({ isAvailable = false } = {}) { - return ({ - isAvailable, - isOneOf: jest.fn(), - getFeature: jest.fn(), - } as unknown) as jest.Mocked; -} - describe('license features', function() { it('should display error when ES is unavailable', () => { const serviceSetup = new SecurityLicenseService().setup({ - license$: of((undefined as unknown) as ILicense), + license$: of(undefined as any), }); expect(serviceSetup.license.getFeatures()).toEqual({ showLogin: true, @@ -33,8 +25,10 @@ describe('license features', function() { }); it('should display error when X-Pack is unavailable', () => { + const rawLicenseMock = licensingMock.createLicenseMock(); + rawLicenseMock.isAvailable = false; const serviceSetup = new SecurityLicenseService().setup({ - license$: of(getMockRawLicense({ isAvailable: false })), + license$: of(rawLicenseMock), }); expect(serviceSetup.license.getFeatures()).toEqual({ showLogin: true, @@ -48,7 +42,9 @@ describe('license features', function() { }); it('should notify consumers of licensed feature changes', () => { - const rawLicense$ = new BehaviorSubject(getMockRawLicense({ isAvailable: false })); + const rawLicenseMock = licensingMock.createLicenseMock(); + rawLicenseMock.isAvailable = false; + const rawLicense$ = new BehaviorSubject(rawLicenseMock); const serviceSetup = new SecurityLicenseService().setup({ license$: rawLicense$, }); @@ -71,7 +67,7 @@ describe('license features', function() { ] `); - rawLicense$.next(getMockRawLicense({ isAvailable: true })); + rawLicense$.next(licensingMock.createLicenseMock()); expect(subscriptionHandler).toHaveBeenCalledTimes(2); expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(` Array [ @@ -92,10 +88,8 @@ describe('license features', function() { }); it('should show login page and other security elements, allow RBAC but forbid document level security if license is not platinum or trial.', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockImplementation(licenses => - Array.isArray(licenses) ? licenses.includes('basic') : licenses === 'basic' - ); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockReturnValue(false); mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); const serviceSetup = new SecurityLicenseService().setup({ @@ -114,8 +108,8 @@ describe('license features', function() { }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockReturnValue(false); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockReturnValue(false); mockRawLicense.getFeature.mockReturnValue({ isEnabled: false, isAvailable: true }); const serviceSetup = new SecurityLicenseService().setup({ @@ -133,14 +127,9 @@ describe('license features', function() { }); it('should allow to login, allow RBAC and document level security if license >= platinum', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockImplementation(licenses => { - const licenseArray = [licenses].flat(); - return ( - licenseArray.includes('trial') || - licenseArray.includes('platinum') || - licenseArray.includes('enterprise') - ); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockImplementation(license => { + return license === 'trial' || license === 'platinum' || license === 'enterprise'; }); mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 17cd5cfde87e44..0f9da03f9f6ec6 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -92,7 +92,7 @@ export class SecurityLicenseService { }; } - const isLicensePlatinumOrBetter = rawLicense.isOneOf(['enterprise', 'platinum', 'trial']); + const isLicensePlatinumOrBetter = rawLicense.hasAtLeast('platinum'); return { showLogin: true, allowLogin: true, diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 316f3373dd31e1..3879d611d46ebf 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -21,7 +21,7 @@ const validLicense = { isEnabled: true, }; }, - isOneOf: (...candidates) => true, + hasAtLeast: (...candidates) => true, } as ILicense; describe('SecurityNavControlService', () => {