Add FTRs to the cloud_links plugin (#150220)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Resolves https://github.com/elastic/kibana/issues/149206
This commit is contained in:
Alejandro Fernández Haro 2023-02-06 19:21:08 +01:00 committed by GitHub
parent 7ee34928ad
commit 8eb89aaf19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 301 additions and 0 deletions

View file

@ -243,6 +243,7 @@ enabled:
- x-pack/test/functional/config_security_basic.ts
- x-pack/test/functional/config.ccs.ts
- x-pack/test/functional/config.firefox.js
- x-pack/test/functional_cloud/config.ts
- x-pack/test/kubernetes_security/basic/config.ts
- x-pack/test/licensing_plugin/config.public.ts
- x-pack/test/licensing_plugin/config.ts

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { resolve } from 'path';
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config.base.js'));
const kibanaPort = functionalConfig.get('servers.kibana.port');
const idpPath = resolve(
__dirname,
'../security_api_integration/fixtures/saml/saml_provider/metadata.xml'
);
const samlIdPPlugin = resolve(
__dirname,
'../security_api_integration/fixtures/saml/saml_provider'
);
return {
...functionalConfig.getAll(),
rootTags: ['skipCloud'],
testFiles: [require.resolve('./tests')],
security: { disableTestUser: true },
junit: {
reportName: 'Cloud Integrations Functional Tests',
},
esTestCluster: {
...functionalConfig.get('esTestCluster'),
serverArgs: [
...functionalConfig.get('esTestCluster.serverArgs'),
'xpack.security.authc.token.enabled=true',
'xpack.security.authc.realms.saml.cloud-saml-kibana.order=0',
`xpack.security.authc.realms.saml.cloud-saml-kibana.idp.metadata.path=${idpPath}`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1',
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.entity_id=http://localhost:${kibanaPort}`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.logout=http://localhost:${kibanaPort}/logout`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7',
],
},
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
`--plugin-path=${samlIdPPlugin}`,
'--xpack.cloud.id=ftr_fake_cloud_id',
'--xpack.cloud.base_url=https://cloud.elastic.co',
'--xpack.cloud.deployment_url=/deployments/deploymentId',
'--xpack.cloud.organization_url=/organization/organizationId',
'--xpack.cloud.profile_url=/user/userId',
'--xpack.security.authc.selector.enabled=false',
`--xpack.security.authc.providers=${JSON.stringify({
basic: { 'cloud-basic': { order: 1 } },
saml: { 'cloud-saml-kibana': { order: 0, realm: 'cloud-saml-kibana' } },
})}`,
],
},
};
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GenericFtrProviderContext } from '@kbn/test';
import { pageObjects } from '../functional/page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;
export { pageObjects };

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { services as functionalServices } from '../functional/services';
export const services = functionalServices;

View file

@ -0,0 +1,138 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import Fs from 'fs/promises';
import Path from 'path';
import { isEqualWith } from 'lodash';
import type { Ecs, KibanaExecutionContext } from '@kbn/core/server';
import type { RetryService } from '@kbn/ftr-common-functional-services';
import { concatMap, defer, filter, firstValueFrom, ReplaySubject, scan, timeout } from 'rxjs';
export const logFilePath = Path.resolve(__dirname, './kibana.log');
export const ANY = Symbol('any');
let logstream$: ReplaySubject<Ecs> | undefined;
export function getExecutionContextFromLogRecord(record: Ecs | undefined): KibanaExecutionContext {
if (record?.log?.logger !== 'execution_context' || !record?.message) {
throw new Error(`The record is not an entry of execution context`);
}
return JSON.parse(record.message);
}
export function isExecutionContextLog(
record: Ecs | undefined,
executionContext: KibanaExecutionContext
) {
try {
const object = getExecutionContextFromLogRecord(record);
return isEqualWith(object, executionContext, function customizer(obj1: any, obj2: any) {
if (obj2 === ANY) return true;
});
} catch (e) {
return false;
}
}
// to avoid splitting log record containing \n symbol
const endOfLine = /(?<=})\s*\n/;
export async function assertLogContains({
description,
predicate,
retry,
}: {
description: string;
predicate: (record: Ecs) => boolean;
retry: RetryService;
}): Promise<void> {
// logs are written to disk asynchronously. I sacrificed performance to reduce flakiness.
await retry.waitFor(description, async () => {
if (!logstream$) {
logstream$ = getLogstream$();
}
try {
await firstValueFrom(logstream$.pipe(filter(predicate), timeout(5_000)));
return true;
} catch (err) {
return false;
}
});
}
/**
* Creates an observable that continuously tails the log file.
*/
function getLogstream$(): ReplaySubject<Ecs> {
const stream$ = new ReplaySubject<Ecs>();
defer(async function* () {
const fd = await Fs.open(logFilePath, 'rs');
while (!stream$.isStopped) {
const { bytesRead, buffer } = await fd.read();
if (bytesRead) {
yield buffer.toString('utf8', 0, bytesRead);
}
}
await fd.close();
})
.pipe(
scan<string, { buffer: string; records: Ecs[] }>(
({ buffer }, chunk) => {
const logString = buffer.concat(chunk);
const lines = logString.split(endOfLine);
const lastLine = lines.pop();
const records = lines.map((s) => JSON.parse(s));
let leftover = '';
if (lastLine) {
try {
const validRecord = JSON.parse(lastLine);
records.push(validRecord);
} catch (err) {
leftover = lastLine;
}
}
return { buffer: leftover, records };
},
{
records: [], // The ECS entries in the logs
buffer: '', // Accumulated leftovers from the previous operation
}
),
concatMap(({ records }) => records)
)
.subscribe(stream$);
// let the content start flowing
stream$.subscribe();
return stream$;
}
export function closeLogstream() {
logstream$?.complete();
logstream$ = undefined;
}
/**
* Truncates the log file to avoid tests looking at the logs from previous executions.
*/
export async function clearLogFile() {
closeLogstream();
await Fs.writeFile(logFilePath, '', 'utf8');
await forceSyncLogFile();
logstream$ = getLogstream$();
}
/**
* Force the completion of all the pending I/O operations in the OS related to the log file.
*/
export async function forceSyncLogFile() {
const fileDescriptor = await Fs.open(logFilePath);
await fileDescriptor.datasync();
await fileDescriptor.close();
}

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const find = getService('find');
const PageObjects = getPageObjects(['common', 'header']);
describe('Cloud Links integration', function () {
before(async () => {
// Create role mapping so user gets superuser access
await getService('esSupertest')
.post('/_security/role_mapping/cloud-saml-kibana')
.send({
roles: ['superuser'],
enabled: true,
rules: { field: { 'realm.name': 'cloud-saml-kibana' } },
})
.expect(200);
});
beforeEach(async () => {
await PageObjects.common.navigateToUrl('home');
await PageObjects.header.waitUntilLoadingHasFinished();
});
it('The button "Setup guides" is loaded', async () => {
expect(await find.byCssSelector('[data-test-subj="guideButtonRedirect"]')).to.not.be(null);
const cloudLink = await find.byLinkText('Setup guides');
expect(cloudLink).to.not.be(null);
});
it('"Manage this deployment" is appended to the nav list', async () => {
await PageObjects.common.clickAndValidate('toggleNavButton', 'collapsibleNavCustomNavLink');
const cloudLink = await find.byLinkText('Manage this deployment');
expect(cloudLink).to.not.be(null);
});
describe('Fills up the user menu items', () => {
it('Shows the button Edit profile', async () => {
await PageObjects.common.clickAndValidate('userMenuButton', 'userMenuLink__Edit profile');
const cloudLink = await find.byLinkText('Edit profile');
expect(cloudLink).to.not.be(null);
});
it('Shows the button Account & Billing', async () => {
await PageObjects.common.clickAndValidate(
'userMenuButton',
'userMenuLink__Account & Billing'
);
const cloudLink = await find.byLinkText('Account & Billing');
expect(cloudLink).to.not.be(null);
});
});
});
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Cloud Integrations', function () {
loadTestFile(require.resolve('./cloud_links'));
});
}