From 4c0b5c5e9fb967f78c2486758b6580f469f144c4 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 24 Oct 2024 06:59:31 -0700 Subject: [PATCH] [Reporting/Tests] Improvements for task stability in serverless tests (#195841) ## Summary Continuation of https://github.com/elastic/kibana/pull/192417. This PR attempts to further improve task stability of the reporting task. The original goals were: 1. Ensure the test data that is needed for the report gets loaded 2. Wait for report jobs to finish before the test completes. Errors in task success metrics also occur if the task triggers after resources for the report, such as a saved search, are removed before the task triggers. During development of this PR, more issues were discovered: 3. Requests to internal endpoints should use cookie credentials 4. The CSV export from ES|QL test was hitting a 404 error when it tried to download the CSV. That error was included in the test. In other words, that test was fundamentaly broken. ## Testing locally 1. Run the serverless functional tests: 1. **Reporting management app**: `node scripts/functional_tests.js --config=x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts --grep=Reporting` 1. **CSV export in Discover**: `node scripts/functional_tests.js --config=x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts --grep=CSV` 1. **Reporting API integration tests**: `node scripts/functional_tests.js --config=x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts --grep=Reporting` 3. Ensure that there are no error logs from Task Manager regarding task failure --------- Co-authored-by: Dzmitry Lemechko --- .../reporting/__snapshots__/csv_v2_esql.snap | 31 +++++- .../common/reporting/csv_v2_esql.ts | 25 +++-- .../common/reporting/datastream.ts | 18 ++- .../common/reporting/generate_csv_discover.ts | 84 +++++++------- .../common/reporting/management.ts | 105 +++++++++--------- .../common/reporting/management.ts | 47 ++++---- .../shared/services/svl_reporting.ts | 35 +++--- 7 files changed, 191 insertions(+), 154 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/__snapshots__/csv_v2_esql.snap b/x-pack/test_serverless/api_integration/test_suites/common/reporting/__snapshots__/csv_v2_esql.snap index 83c8c2982c4f..e6ceffaa3b4a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/__snapshots__/csv_v2_esql.snap +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/__snapshots__/csv_v2_esql.snap @@ -1,21 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Reporting CSV Generation from ES|QL export from non-timebased data view csv from es|ql csv file matches 1`] = `"{\\"statusCode\\":404,\\"error\\":\\"Not Found\\",\\"message\\":\\"Not Found\\"}"`; +exports[`Reporting CSV Generation from ES|QL export from non-timebased data view csv from es|ql csv file matches 1`] = ` +"eon,epoch,era,period +Phanerozoic,\\" Pliocene\\",Cenozoic,Neogene +Phanerozoic,\\" Holocene\\",Cenozoic,Quaternary +Phanerozoic,,Mesozoic,Cretaceous +Phanerozoic,,Mesozoic,Jurassic +Phanerozoic,,Paleozoic,Cambrian +Proterozoic,,Paleozoic,Permian +Archean,,, +Hadean,,, +" +`; exports[`Reporting CSV Generation from ES|QL export from non-timebased data view csv from es|ql job response data is correct 1`] = ` Object { - "contentDisposition": undefined, - "contentType": "application/json; charset=utf-8", + "contentDisposition": "attachment; filename=CSV%20Report.csv", + "contentType": "text/csv; charset=utf-8", "title": "CSV Report", } `; -exports[`Reporting CSV Generation from ES|QL export from timebased data view csv from es|ql export with time filter csv file matches 1`] = `"{\\"statusCode\\":404,\\"error\\":\\"Not Found\\",\\"message\\":\\"Not Found\\"}"`; +exports[`Reporting CSV Generation from ES|QL export from timebased data view csv from es|ql export with time filter csv file matches 1`] = ` +"\\"@message\\" +\\"143.84.142.7 - - [2015-09-20T00:00:00.000Z] \\"\\"GET /uploads/steven-hawley.jpg HTTP/1.1\\"\\" 200 1623 \\"\\"-\\"\\" \\"\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\"\\"\\" +\\"193.164.192.47 - - [2015-09-20T00:30:34.206Z] \\"\\"GET /uploads/michael-foreman.jpg HTTP/1.1\\"\\" 200 8537 \\"\\"-\\"\\" \\"\\"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\\"\\"\\" +\\"176.7.244.68 - - [2015-09-20T00:32:42.058Z] \\"\\"GET /uploads/james-pawelczyk.jpg HTTP/1.1\\"\\" 200 9196 \\"\\"-\\"\\" \\"\\"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\\"\\"\\" +\\"237.56.90.184 - - [2015-09-20T00:35:21.445Z] \\"\\"GET /uploads/david-leestma.jpg HTTP/1.1\\"\\" 200 9790 \\"\\"-\\"\\" \\"\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\"\\"\\" +\\"255.56.89.50 - - [2015-09-20T00:43:01.353Z] \\"\\"GET /uploads/michael-r-barratt.jpg HTTP/1.1\\"\\" 200 9583 \\"\\"-\\"\\" \\"\\"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\\"\\"\\" +" +`; exports[`Reporting CSV Generation from ES|QL export from timebased data view csv from es|ql export with time filter job response data is correct 1`] = ` Object { - "contentDisposition": undefined, - "contentType": "application/json; charset=utf-8", + "contentDisposition": "attachment; filename=Untitled%20discover%20search.csv", + "contentType": "text/csv; charset=utf-8", "title": "Untitled discover search", } `; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/csv_v2_esql.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/csv_v2_esql.ts index e74eb14ac31d..022f22a144b6 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/csv_v2_esql.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/csv_v2_esql.ts @@ -9,20 +9,20 @@ import expect from '@kbn/expect'; import request from 'supertest'; import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; -import { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { CookieCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; import type { ReportApiJSON } from '@kbn/reporting-common/types'; import type { JobParamsCsvFromSavedObject } from '@kbn/reporting-export-types-csv-common'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const es = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); const log = getService('log'); const reportingAPI = getService('svlReportingApi'); const svlCommonApi = getService('svlCommonApi'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; + const samlAuth = getService('samlAuth'); + let cookieCredentials: CookieCredentials; let internalReqHeader: InternalRequestHeader; // Helper function @@ -38,7 +38,12 @@ export default ({ getService }: FtrProviderContext) => { }; log.info(`sending request for query: ${JSON.stringify(job.locatorParams[0].params.query)}`); - return await reportingAPI.createReportJobInternal('csv_v2', job, roleAuthc, internalReqHeader); + return await reportingAPI.createReportJobInternal( + 'csv_v2', + job, + cookieCredentials, + internalReqHeader + ); }; describe('CSV Generation from ES|QL', () => { @@ -84,7 +89,7 @@ export default ({ getService }: FtrProviderContext) => { }; before(async () => { await loadTimelessData(); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); }); @@ -112,8 +117,8 @@ export default ({ getService }: FtrProviderContext) => { }, ], })); - await reportingAPI.waitForJobToFinish(path, roleAuthc, internalReqHeader); - response = await supertest.get(path); + await reportingAPI.waitForJobToFinish(path, cookieCredentials, internalReqHeader); + response = await supertestWithoutAuth.get(path).set(cookieCredentials); csvFile = response.text; }); @@ -184,8 +189,8 @@ export default ({ getService }: FtrProviderContext) => { ], title: 'Untitled discover search', })); - await reportingAPI.waitForJobToFinish(path, roleAuthc, internalReqHeader); - response = await supertest.get(path); + await reportingAPI.waitForJobToFinish(path, cookieCredentials, internalReqHeader); + response = await supertestWithoutAuth.get(path).set(cookieCredentials); csvFile = response.text; }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts index ce9fe313ecf8..671b42f5a02a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts @@ -6,8 +6,12 @@ */ import { expect } from 'expect'; +import { + CookieCredentials, + InternalRequestHeader, + RoleCredentials, +} from '@kbn/ftr-common-functional-services'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -16,7 +20,9 @@ export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const svlUserManager = getService('svlUserManager'); + const samlAuth = getService('samlAuth'); let roleAuthc: RoleCredentials; + let cookieCredentials: CookieCredentials; let internalReqHeader: InternalRequestHeader; const archives: Record = { @@ -30,12 +36,13 @@ export default function ({ getService }: FtrProviderContext) { const generatedReports = new Set(); before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); await esArchiver.load(archives.ecommerce.data); await kibanaServer.importExport.load(archives.ecommerce.savedObjects); - const { job } = await reportingAPI.createReportJobInternal( + const { job, path } = await reportingAPI.createReportJobInternal( 'csv_searchsource', { browserTimezone: 'UTC', @@ -48,16 +55,17 @@ export default function ({ getService }: FtrProviderContext) { title: 'Ecommerce Data', version: '8.15.0', }, - roleAuthc, + cookieCredentials, internalReqHeader ); + await reportingAPI.waitForJobToFinish(path, cookieCredentials, internalReqHeader); generatedReports.add(job.id); }); after(async () => { for (const reportId of generatedReports) { - await reportingAPI.deleteReport(reportId, roleAuthc, internalReqHeader); + await reportingAPI.deleteReport(reportId, cookieCredentials, internalReqHeader); } await esArchiver.unload(archives.ecommerce.data); @@ -69,7 +77,7 @@ export default function ({ getService }: FtrProviderContext) { const { status, body } = await supertestWithoutAuth .get(`/api/index_management/data_streams/.kibana-reporting`) .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader); + .set(roleAuthc.apiKeyHeader); // use API key since the datastream management API is a public endpoint svlCommonApi.assertResponseStatusCode(200, status, body); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts index dd070d9a84aa..c654e5e307f8 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import type { SortDirection } from '@kbn/data-plugin/common'; import type { JobParamsCSV } from '@kbn/reporting-export-types-csv-common'; import type { Filter } from '@kbn/es-query'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { CookieCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -17,8 +17,8 @@ export default function ({ getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const reportingAPI = getService('svlReportingApi'); const svlCommonApi = getService('svlCommonApi'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; + const samlAuth = getService('samlAuth'); + let cookieCredentials: CookieCredentials; let internalReqHeader: InternalRequestHeader; /* @@ -79,7 +79,7 @@ export default function ({ getService }: FtrProviderContext) { this.timeout(12 * 60 * 1000); before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); }); @@ -90,10 +90,6 @@ export default function ({ getService }: FtrProviderContext) { }); }); - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - describe('exported CSV', () => { before(async () => { await esArchiver.load(archives.ecommerce.data); @@ -169,13 +165,13 @@ export default function ({ getService }: FtrProviderContext) { title: 'Ecommerce Data', version: '8.14.0', }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(124183); @@ -212,11 +208,11 @@ export default function ({ getService }: FtrProviderContext) { title: 'Untitled discover search', version: '8.14.0', }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); - return reportingAPI.getCompletedJobOutput(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); + return reportingAPI.getCompletedJobOutput(res.path, cookieCredentials, internalReqHeader); } it('includes an unmapped field to the report', async () => { @@ -359,13 +355,13 @@ export default function ({ getService }: FtrProviderContext) { }, }, }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(1270683); @@ -411,13 +407,13 @@ export default function ({ getService }: FtrProviderContext) { }, }, }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(918298); @@ -469,13 +465,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['@timestamp', 'clientip', 'extension'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(3020); @@ -515,13 +511,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['@timestamp', 'clientip', 'extension'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(3020); @@ -555,13 +551,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['date', 'message'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(103); @@ -584,13 +580,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['date', 'message'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(103); @@ -627,13 +623,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['date', 'message', '_id', '_index'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(134); @@ -659,13 +655,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: ['name', 'power'], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(274); @@ -743,13 +739,13 @@ export default function ({ getService }: FtrProviderContext) { }, columns: [], }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(356); @@ -809,13 +805,13 @@ export default function ({ getService }: FtrProviderContext) { }, }, }), - roleAuthc, + cookieCredentials, internalReqHeader ); - await reportingAPI.waitForJobToFinish(res.path, roleAuthc, internalReqHeader); + await reportingAPI.waitForJobToFinish(res.path, cookieCredentials, internalReqHeader); const csvFile = await reportingAPI.getCompletedJobOutput( res.path, - roleAuthc, + cookieCredentials, internalReqHeader ); expect((csvFile as string).length).to.be(4845684); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/management.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/management.ts index 62e9f4eaf8ac..ad1088ae0ebd 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/management.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/management.ts @@ -5,74 +5,79 @@ * 2.0. */ -import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common/src/constants'; import expect from '@kbn/expect'; import { INTERNAL_ROUTES } from '@kbn/reporting-common'; -import { ReportApiJSON } from '@kbn/reporting-common/types'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import type { ReportApiJSON } from '@kbn/reporting-common/types'; +import type { CookieCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting']; -const INTERNAL_HEADER: [string, string] = [X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'Kibana']; export default ({ getService }: FtrProviderContext) => { - const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); const reportingAPI = getService('svlReportingApi'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlCommonApi = getService('svlCommonApi'); - const svlUserManager = getService('svlUserManager'); - let adminUser: RoleCredentials; + const samlAuth = getService('samlAuth'); + let cookieCredentials: CookieCredentials; let internalReqHeader: InternalRequestHeader; + const archives = { + ecommerce: { + data: 'x-pack/test/functional/es_archives/reporting/ecommerce', + savedObjects: 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce', + }, + }; + describe('Reporting Management', function () { + let reportJob: ReportApiJSON; + let path: string; + before(async () => { - adminUser = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); - }); - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(adminUser); - }); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); - describe('Deletion', () => { - let reportJob: ReportApiJSON; + await esArchiver.load(archives.ecommerce.data); + await kibanaServer.importExport.load(archives.ecommerce.savedObjects); - const createJob = async (roleAuthc: RoleCredentials): Promise => { - log.info(`request report job with ApiKey ${adminUser.apiKey.name}`); - const { job } = await reportingAPI.createReportJobInternal( - 'csv_searchsource', - { - browserTimezone: 'UTC', - objectType: 'search', - searchSource: { - index: '5193f870-d861-11e9-a311-0fa548c5f953', - query: { language: 'kuery', query: '' }, - version: true, - }, - title: 'Ecommerce Data', - version: '8.15.0', + // generate a report that can be deleted in the test + const result = await reportingAPI.createReportJobInternal( + 'csv_searchsource', + { + browserTimezone: 'UTC', + objectType: 'search', + searchSource: { + index: '5193f870-d861-11e9-a311-0fa548c5f953', + query: { language: 'kuery', query: '' }, + version: true, }, - roleAuthc, - internalReqHeader - ); - log.info(`created report job ${job.id} with ApiKey ${adminUser.apiKey.name}`); - return job; - }; + title: 'Ecommerce Data', + version: '8.15.0', + }, + cookieCredentials, + internalReqHeader + ); - before(async () => { - reportJob = await createJob(adminUser); - }); + path = result.path; + reportJob = result.job; + + await reportingAPI.waitForJobToFinish(path, cookieCredentials, internalReqHeader); + }); + + after(async () => { + await esArchiver.unload(archives.ecommerce.data); + await kibanaServer.importExport.unload(archives.ecommerce.savedObjects); + }); - it(`user can delete a report they've created`, async () => { - // for this test, we don't need to wait for the job to finish or verify the result - const response = await supertestWithoutAuth - .delete(`${INTERNAL_ROUTES.JOBS.DELETE_PREFIX}/${reportJob.id}`) - .set(...API_HEADER) - .set(...INTERNAL_HEADER) - .set(adminUser.apiKeyHeader); + it(`user can delete a report they've created`, async () => { + const response = await supertestWithoutAuth + .delete(`${INTERNAL_ROUTES.JOBS.DELETE_PREFIX}/${reportJob.id}`) + .set(...API_HEADER) + .set(internalReqHeader) + .set(cookieCredentials); - expect(response.status).to.be(200); - expect(response.body).to.eql({ deleted: true }); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ deleted: true }); }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts b/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts index 2ea17190ee86..35a1d1ec1872 100644 --- a/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts +++ b/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts @@ -8,10 +8,11 @@ import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import { CSV_REPORT_TYPE_V2, - JobParamsCsvFromSavedObject, + type JobParamsCsvFromSavedObject, } from '@kbn/reporting-export-types-csv-common'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import type { CookieCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { ReportApiJSON } from '@kbn/reporting-common/types'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); @@ -20,10 +21,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'svlCommonPage', 'header']); const reportingAPI = getService('svlReportingApi'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); - let roleAuthc: RoleCredentials; - let roleName: string; + const samlAuth = getService('samlAuth'); + let cookieCredentials: CookieCredentials; let internalReqHeader: InternalRequestHeader; const navigateToReportingManagement = async () => { @@ -39,6 +38,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Reporting Management app', function () { // security_exception: action [indices:admin/create] is unauthorized for user [elastic] with effective roles [superuser] on restricted indices [.reporting-2020.04.19], this action is granted by the index privileges [create_index,manage,all] this.tags('failsOnMKI'); + + let reportJob: ReportApiJSON; + let path: string; + const savedObjectsArchive = 'test/functional/fixtures/kbn_archiver/discover'; const job: JobParamsCsvFromSavedObject = { @@ -57,31 +60,31 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Kibana CI and MKI use different users before('initialize saved object archive', async () => { - roleName = 'admin'; - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope(roleName); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); // add test saved search object await kibanaServer.importExport.load(savedObjectsArchive); + + // generate a report that can be tested to show in the listing + const result = await reportingAPI.createReportJobInternal( + CSV_REPORT_TYPE_V2, + job, + cookieCredentials, + internalReqHeader + ); + + path = result.path; + reportJob = result.job; }); after('clean up archives', async () => { await kibanaServer.importExport.unload(savedObjectsArchive); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await reportingAPI.waitForJobToFinish(path, cookieCredentials, internalReqHeader); }); it(`user sees a job they've created`, async () => { - const { - job: { id: jobId }, - } = await reportingAPI.createReportJobInternal( - CSV_REPORT_TYPE_V2, - job, - roleAuthc, - internalReqHeader - ); - await navigateToReportingManagement(); - await testSubjects.existOrFail(`viewReportingLink-${jobId}`); + await testSubjects.existOrFail(`viewReportingLink-${reportJob.id}`); }); }); }; diff --git a/x-pack/test_serverless/shared/services/svl_reporting.ts b/x-pack/test_serverless/shared/services/svl_reporting.ts index f056543e72e2..305b30865888 100644 --- a/x-pack/test_serverless/shared/services/svl_reporting.ts +++ b/x-pack/test_serverless/shared/services/svl_reporting.ts @@ -5,18 +5,17 @@ * 2.0. */ -import expect from '@kbn/expect'; import { INTERNAL_ROUTES } from '@kbn/reporting-common'; import type { ReportingJobResponse } from '@kbn/reporting-plugin/server/types'; import rison from '@kbn/rison'; +import { CookieCredentials } from '@kbn/ftr-common-functional-services'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; -import { RoleCredentials } from '.'; import { InternalRequestHeader } from '.'; const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting']; /** - * Services to create roles and users for security testing + * Services to handle report job lifecycle phases for tests */ export function SvlReportingServiceProvider({ getService }: FtrProviderContext) { const log = getService('log'); @@ -31,32 +30,34 @@ export function SvlReportingServiceProvider({ getService }: FtrProviderContext) async createReportJobInternal( jobType: string, job: object, - roleAuthc: RoleCredentials, + cookieCredentials: CookieCredentials, internalReqHeader: InternalRequestHeader ) { const requestPath = `${INTERNAL_ROUTES.GENERATE_PREFIX}/${jobType}`; log.debug(`POST request to ${requestPath}`); - const { status, body } = await supertestWithoutAuth + const { body }: { status: number; body: ReportingJobResponse } = await supertestWithoutAuth .post(requestPath) .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) - .send({ jobParams: rison.encode(job) }); + .set(cookieCredentials) + .send({ jobParams: rison.encode(job) }) + .expect(200); - expect(status).to.be(200); + log.info(`ReportingAPI.createReportJobInternal created report job` + ` ${body.job.id}`); return { - job: (body as ReportingJobResponse).job, - path: (body as ReportingJobResponse).path, + job: body.job, + path: body.path, }; }, /* - * This function is only used in the API tests + * If a test requests a report, it must wait for the job to finish before deleting the report. + * Otherwise, report task success metrics will be affected. */ async waitForJobToFinish( downloadReportPath: string, - roleAuthc: RoleCredentials, + cookieCredentials: CookieCredentials, internalReqHeader: InternalRequestHeader, options?: { timeout?: number } ) { @@ -69,7 +70,7 @@ export function SvlReportingServiceProvider({ getService }: FtrProviderContext) .responseType('blob') .set(...API_HEADER) .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader); + .set(cookieCredentials); if (response.status === 500) { throw new Error(`Report at path ${downloadReportPath} has failed`); @@ -101,13 +102,13 @@ export function SvlReportingServiceProvider({ getService }: FtrProviderContext) */ async getCompletedJobOutput( downloadReportPath: string, - roleAuthc: RoleCredentials, + cookieCredentials: CookieCredentials, internalReqHeader: InternalRequestHeader ) { const response = await supertestWithoutAuth .get(`${downloadReportPath}?elasticInternalOrigin=true`) .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader); + .set(cookieCredentials); return response.text as unknown; }, @@ -116,14 +117,14 @@ export function SvlReportingServiceProvider({ getService }: FtrProviderContext) */ async deleteReport( reportId: string, - roleAuthc: RoleCredentials, + cookieCredentials: CookieCredentials, internalReqHeader: InternalRequestHeader ) { log.debug(`ReportingAPI.deleteReport ${INTERNAL_ROUTES.JOBS.DELETE_PREFIX}/${reportId}`); const response = await supertestWithoutAuth .delete(INTERNAL_ROUTES.JOBS.DELETE_PREFIX + `/${reportId}`) .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) + .set(cookieCredentials) .set('kbn-xsrf', 'xxx') .expect(200); return response.text as unknown;