From a8af7f71ec985e086bb89def6e17a9dac041dff3 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:33:16 -0400 Subject: [PATCH] Fix spaces API integration tests --- .../saved_objects/spaces/data.json | 23 +++++---- .../common/lib/space_test_utils.ts | 29 +++++++++++ .../common/suites/copy_to_space.ts | 45 ++++++++--------- .../common/suites/delete.ts | 43 ++++------------- .../suites/resolve_copy_to_space_conflicts.ts | 48 +++++++++++++++++-- 5 files changed, 120 insertions(+), 68 deletions(-) diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index ed52be26c7e538..6ad9c3d3bdad18 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -153,7 +153,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_default", "name": "CTS IP 1" } ], @@ -181,7 +181,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_default", "name": "CTS IP 1" } ], @@ -209,7 +209,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_default", "name": "CTS IP 1" } ], @@ -272,7 +272,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_space_1", "name": "CTS IP 1" } ], @@ -301,7 +301,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_space_1", "name": "CTS IP 1" } ], @@ -330,7 +330,7 @@ "references": [ { "type": "index-pattern", - "id": "cts_ip_1", + "id": "cts_ip_1_space_1", "name": "CTS IP 1" } ], @@ -345,15 +345,17 @@ { "type": "_doc", "value": { - "id": "index-pattern:cts_ip_1", + "id": "index-pattern:cts_ip_1_default", "index": ".kibana", "source": { + "originId": "cts_ip_1", "index-pattern": { "title": "Copy to Space index pattern 1 from default space" }, "references": [], "type": "index-pattern", - "updated_at": "2017-09-21T18:49:16.270Z" + "updated_at": "2017-09-21T18:49:16.270Z", + "namespaces": ["default"] }, "type": "_doc" } @@ -362,16 +364,17 @@ { "type": "_doc", "value": { - "id": "space_1:index-pattern:cts_ip_1", + "id": "index-pattern:cts_ip_1_space_1", "index": ".kibana", "source": { + "originId": "cts_ip_1", "index-pattern": { "title": "Copy to Space index pattern 1 from space_1 space" }, "references": [], "type": "index-pattern", "updated_at": "2017-09-21T18:49:16.270Z", - "namespace": "space_1" + "namespaces": ["space_1"] }, "type": "_doc" } diff --git a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts index 12333fc7460709..28b19d5db20b60 100644 --- a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts +++ b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export function getUrlPrefix(spaceId?: string) { @@ -35,3 +36,31 @@ export function getTestScenariosForSpace(spaceId: string) { return [explicitScenario]; } + +export function getAggregatedSpaceData(es: KibanaClient, objectTypes: string[]) { + return es.search({ + index: '.kibana', + body: { + size: 0, + runtime_mappings: { + normalized_namespace: { + type: 'keyword', + script: ` + if (doc["namespaces"].size() > 0) { + emit(doc["namespaces"].value); + } else if (doc["namespace"].size() > 0) { + emit(doc["namespace"].value); + } + `, + }, + }, + query: { terms: { type: objectTypes } }, + aggs: { + count: { + terms: { field: 'normalized_namespace', missing: DEFAULT_SPACE_ID, size: 10 }, + aggs: { countByType: { terms: { field: 'type', missing: 'UNKNOWN', size: 10 } } }, + }, + }, + }, + }); +} diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 27f1e55c3a90a7..ab46102ac27f99 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -11,7 +11,7 @@ import { EsArchiver } from '@kbn/es-archiver'; import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { CopyResponse } from '../../../../plugins/spaces/server/lib/copy_to_spaces'; -import { getUrlPrefix } from '../lib/space_test_utils'; +import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; type TestResponse = Record; @@ -68,6 +68,9 @@ const INITIAL_COUNTS: Record> = { space_1: { dashboard: 2, visualization: 3, 'index-pattern': 1 }, space_2: { dashboard: 1 }, }; +const UUID_PATTERN = new RegExp( + /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i +); const getDestinationWithoutConflicts = () => 'space_2'; const getDestinationWithConflicts = (originSpaceId?: string) => @@ -79,19 +82,11 @@ export function copyToSpaceTestSuiteFactory( supertest: SuperTest ) { const collectSpaceContents = async () => { - const { body: response } = await es.search({ - index: '.kibana', - body: { - size: 0, - query: { terms: { type: ['visualization', 'dashboard', 'index-pattern'] } }, - aggs: { - count: { - terms: { field: 'namespace', missing: DEFAULT_SPACE_ID, size: 10 }, - aggs: { countByType: { terms: { field: 'type', missing: 'UNKNOWN', size: 10 } } }, - }, - }, - }, - }); + const { body: response } = await getAggregatedSpaceData(es, [ + 'visualization', + 'dashboard', + 'index-pattern', + ]); const aggs = response.aggregations as Record< string, @@ -187,18 +182,23 @@ export function copyToSpaceTestSuiteFactory( async (resp: TestResponse) => { const destination = getDestinationWithoutConflicts(); const result = resp.body as CopyResponse; + + const indexPatternDestinationId = result[destination].successResults![0].destinationId; + expect(indexPatternDestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID + expect(result).to.eql({ [destination]: { success: true, successCount: 5, successResults: [ { - id: 'cts_ip_1', + id: `cts_ip_1_${spaceId}`, type: 'index-pattern', meta: { icon: 'indexPatternApp', title: `Copy to Space index pattern 1 from ${spaceId} space`, }, + destinationId: indexPatternDestinationId, }, { id: `cts_vis_1_${spaceId}`, @@ -309,13 +309,14 @@ export function copyToSpaceTestSuiteFactory( successCount: 5, successResults: [ { - id: 'cts_ip_1', + id: `cts_ip_1_${spaceId}`, type: 'index-pattern', meta: { icon: 'indexPatternApp', title: `Copy to Space index pattern 1 from ${spaceId} space`, }, overwrite: true, + destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId }, { id: `cts_vis_1_${spaceId}`, @@ -387,8 +388,11 @@ export function copyToSpaceTestSuiteFactory( }, }, { - error: { type: 'conflict' }, - id: 'cts_ip_1', + error: { + type: 'conflict', + destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId + }, + id: `cts_ip_1_${spaceId}`, title: `Copy to Space index pattern 1 from ${spaceId} space`, type: 'index-pattern', meta: { @@ -437,9 +441,6 @@ export function copyToSpaceTestSuiteFactory( // a 403 error actually comes back as an HTTP 200 response const statusCode = outcome === 'noAccess' ? 403 : 200; const type = 'sharedtype'; - const v4 = new RegExp( - /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i - ); const noConflictId = `${spaceId}_only`; const exactMatchId = 'each_space'; const inexactMatchId = `conflict_1_${spaceId}`; @@ -463,7 +464,7 @@ export function copyToSpaceTestSuiteFactory( expect(success).to.eql(true); expect(successCount).to.eql(1); const destinationId = successResults![0].destinationId; - expect(destinationId).to.match(v4); + expect(destinationId).to.match(UUID_PATTERN); const meta = { title, icon: 'beaker' }; expect(successResults).to.eql([{ type, id: sourceId, meta, destinationId }]); expect(errors).to.be(undefined); diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index 57fa6d8533890f..aaca4fa843d67f 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; -import { getTestScenariosForSpace } from '../lib/space_test_utils'; +import { getAggregatedSpaceData, getTestScenariosForSpace } from '../lib/space_test_utils'; import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; @@ -43,38 +43,15 @@ export function deleteTestSuiteFactory( // Query ES to ensure that we deleted everything we expected, and nothing we didn't // Grouping first by namespace, then by saved object type - const { body: response } = await es.search({ - index: '.kibana', - body: { - size: 0, - query: { - terms: { - type: ['visualization', 'dashboard', 'space', 'index-pattern'], - // TODO: add assertions for config objects -- these assertions were removed because of flaky behavior in #92358, but we should - // consider adding them again at some point, especially if we convert config objects to `namespaceType: 'multiple-isolated'` in - // the future. - }, - }, - aggs: { - count: { - terms: { - field: 'namespace', - missing: 'default', - size: 10, - }, - aggs: { - countByType: { - terms: { - field: 'type', - missing: 'UNKNOWN', - size: 10, - }, - }, - }, - }, - }, - }, - }); + const { body: response } = await getAggregatedSpaceData(es, [ + 'visualization', + 'dashboard', + 'space', + 'index-pattern', + // TODO: add assertions for config objects -- these assertions were removed because of flaky behavior in #92358, but we should + // consider adding them again at some point, especially if we convert config objects to `namespaceType: 'multiple-isolated'` in + // the future. + ]); // @ts-expect-error @elastic/elasticsearch doesn't defined `count.buckets`. const buckets = response.aggregations?.count.buckets; diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index b66949cbffe004..f0519103e9e334 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -82,8 +82,18 @@ export function resolveCopyToSpaceConflictsSuite( expect(result).to.eql({ [destination]: { success: true, - successCount: 1, + successCount: 2, successResults: [ + { + id: `cts_ip_1_${sourceSpaceId}`, + type: 'index-pattern', + meta: { + title: `Copy to Space index pattern 1 from ${sourceSpaceId} space`, + icon: 'indexPatternApp', + }, + destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId + overwrite: true, + }, { id: 'cts_vis_3', type: 'visualization', @@ -145,6 +155,19 @@ export function resolveCopyToSpaceConflictsSuite( success: false, successCount: 0, errors: [ + { + error: { + type: 'conflict', + destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId + }, + id: `cts_ip_1_${sourceSpaceId}`, + title: `Copy to Space index pattern 1 from ${sourceSpaceId} space`, + meta: { + title: `Copy to Space index pattern 1 from ${sourceSpaceId} space`, + icon: 'indexPatternApp', + }, + type: 'index-pattern', + }, { error: { type: 'conflict' }, id: 'cts_vis_3', @@ -445,6 +468,7 @@ export function resolveCopyToSpaceConflictsSuite( const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' }; const visualizationObject = { type: 'visualization', id: 'cts_vis_3' }; + const indexPatternObject = { type: 'index-pattern', id: `cts_ip_1_${spaceId}` }; it(`should return ${tests.withReferencesNotOverwriting.statusCode} when not overwriting, with references`, async () => { const destination = getDestinationSpace(spaceId); @@ -456,7 +480,16 @@ export function resolveCopyToSpaceConflictsSuite( objects: [dashboardObject], includeReferences: true, createNewCopies: false, - retries: { [destination]: [{ ...visualizationObject, overwrite: false }] }, + retries: { + [destination]: [ + { + ...indexPatternObject, + destinationId: `cts_ip_1_${destination}`, + overwrite: false, + }, + { ...visualizationObject, overwrite: false }, + ], + }, }) .expect(tests.withReferencesNotOverwriting.statusCode) .then(tests.withReferencesNotOverwriting.response); @@ -472,7 +505,16 @@ export function resolveCopyToSpaceConflictsSuite( objects: [dashboardObject], includeReferences: true, createNewCopies: false, - retries: { [destination]: [{ ...visualizationObject, overwrite: true }] }, + retries: { + [destination]: [ + { + ...indexPatternObject, + destinationId: `cts_ip_1_${destination}`, + overwrite: true, + }, + { ...visualizationObject, overwrite: true }, + ], + }, }) .expect(tests.withReferencesOverwriting.statusCode) .then(tests.withReferencesOverwriting.response);