import expect from '@kbn/expect' ; import { SuperTest } from 'supertest' ; import type { Client } from '@elastic/elasticsearch' ; import type { SavedObjectReference , SavedObjectsImportRetry } from 'src/core/server' ; import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases' ; import { SPACES } from '../lib/spaces' ; import { expectResponses , getUrlPrefix , getTestTitle } from '../lib/saved_object_test_utils' ; import { ExpectResponseBody , TestCase , TestDefinition , TestSuite } from '../lib/types' ; export interface ResolveImportErrorsTestDefinition extends TestDefinition { request : { objects : Array < { type : string ; id : string ; originId ? : string ; references ? : SavedObjectReference [ ] ; } > ; retries : Array < { type : string ; id : string ; overwrite : boolean ; destinationId ? : string ; replaceReferences ? : SavedObjectsImportRetry [ 'replaceReferences' ] ; } > ; } ; overwrite : boolean ; createNewCopies : boolean ; } export type ResolveImportErrorsTestSuite = TestSuite < ResolveImportErrorsTestDefinition > ; export type FailureType = 'unsupported_type' | 'conflict' ; export interface ResolveImportErrorsTestCase extends Omit < TestCase , 'failure' > { originId ? : string ; expectedNewId ? : string ; references ? : SavedObjectReference [ ] ; replaceReferences ? : SavedObjectsImportRetry [ 'replaceReferences' ] ; successParam ? : string ; failureType ? : FailureType ; } const NEW_ATTRIBUTE_KEY = 'title' ; const NEW_ATTRIBUTE_VAL = `New attribute value ${ Date . now ( ) }` ; const { HIDDEN , ... REMAINING_CASES } = CASES ; export const TEST_CASES = Object . freeze ( { ... REMAINING_CASES , CONFLICT_1A_OBJ : Object . freeze ( { type : 'sharedtype' , id : `conflict_1a` , originId : `conflict_1` , expectedNewId : 'some-random-id' , } ) , CONFLICT_1B_OBJ : Object . freeze ( { type : 'sharedtype' , id : `conflict_1b` , originId : `conflict_1` , expectedNewId : 'another-random-id' , } ) , CONFLICT_2C_OBJ : Object . freeze ( { type : 'sharedtype' , id : `conflict_2c` , originId : `conflict_2` , expectedNewId : `conflict_2a` , } ) , CONFLICT_3A_OBJ : Object . freeze ( { type : 'sharedtype' , id : `conflict_3a` , originId : `conflict_3` , expectedNewId : `conflict_3` , } ) , CONFLICT_4_OBJ : Object . freeze ( { type : 'sharedtype' , id : `conflict_4` , expectedNewId : `conflict_4a` , } ) , } ) ; export const SPECIAL_TEST_CASES = Object . freeze ( { HIDDEN , OUTBOUND_REFERENCE_ORIGIN_MATCH_1_OBJ : Object . freeze ( { type : 'sharedtype' , id : 'outbound-reference-origin-match-1' , references : [ { name : '1' , type : 'index-pattern' , id : 'inbound-reference-origin-match-1' } ] , } ) , OUTBOUND_REFERENCE_ORIGIN_MATCH_2_OBJ : Object . freeze ( { type : 'sharedtype' , id : 'outbound-reference-origin-match-2' , references : [ { name : '1' , type : 'index-pattern' , id : 'inbound-reference-origin-match-2' } ] , replaceReferences : [ { type : 'index-pattern' , from : 'inbound-reference-origin-match-2' , to : 'inbound-reference-origin-match-2a' , } , ] , } ) , } ) ; /**
 * Test cases have additional properties that we don't want to send in HTTP Requests
 */ const createRequest = ( { type , id , originId , expectedNewId , references , replaceReferences , successParam , } , overwrite ) : ResolveImportErrorsTestDefinition [ 'request' ] => ( { objects : [ { type , id , ... ( originId && { originId } ) , ... ( references && { references } ) } ] , retries : [ { type , id , overwrite , ... ( expectedNewId && { destinationId : expectedNewId } ) , ... ( replaceReferences && { replaceReferences } ) , ... ( successParam === 'createNewCopy' && { createNewCopy : true } ) , } , ] , } ) ; export const resolveImportErrorsTestCaseFailures = { failUnsupportedType : ( condition ? ) : { failureType ? : 'unsupported_type' ; } => condition !== false ? { failureType : 'unsupported_type' } : { } , failConflict : ( condition ? ) : { failureType ? : 'conflict' ; } => condition !== false ? { failureType : 'conflict' } : { } , } ; export function resolveImportErrorsTestSuiteFactory ( es , esArchiver , supertest ) { const expectSavedObjectForbidden = ( action , typeOrTypes ) => expectResponses . forbiddenTypes ( action ) ( typeOrTypes ) ; const expectResponseBody = ( testCases , statusCode , singleRequest , overwrite , createNewCopies , spaceId = SPACES . DEFAULT . spaceId ) : ExpectResponseBody => async ( response ) => { const testCaseArray = Array . isArray ( testCases ) ? testCases : [ testCases ] ; if ( statusCode === 403 ) { const types = testCaseArray . map ( ( x ) => x . type ) ; await expectSavedObjectForbidden ( 'bulk_create' , types ) ( response ) ; } else { const { success , successCount , successResults , errors } = response . body ; const expectedSuccesses = testCaseArray . filter ( ( x ) => ! x . failureType ) ; const expectedFailures = testCaseArray . filter ( ( x ) => x . failureType ) ; expect ( success ) . to . eql ( expectedFailures . length === 0 ) ; expect ( successCount ) . to . eql ( expectedSuccesses . length ) ; if ( expectedFailures . length ) { expect ( errors ) . to . have . length ( expectedFailures . length ) ; } else { expect ( response . body ) . not . to . have . property ( 'errors' ) ; } for ( let i = 0 ; i < expectedSuccesses . length ; i ++ ) { const { type , id , successParam , expectedNewId } = expectedSuccesses [ i ] ; const object = ( successResults as Array < Record < string , unknown > > ) . find ( ( x ) => x . type === type && x . id === id ) ; expect ( object ) . not . to . be ( undefined ) ; const destinationId = object ! . destinationId as string ; if ( successParam === 'destinationId' ) { if ( expectedNewId ) { expect ( destinationId ) . to . be ( expectedNewId ) ; } else { expect ( destinationId ) . to . match ( /^[0-9a-f-]{36}$/ ) ; } } else if ( successParam === 'createNewCopies' || successParam === 'createNewCopy' ) { expect ( destinationId ) . to . be ( expectedNewId ! ) ; } else { expect ( destinationId ) . to . be ( undefined ) ; } const createNewCopy = object ! . createNewCopy as boolean | undefined ; if ( successParam === 'createNewCopy' ) { expect ( createNewCopy ) . to . be ( true ) ; } else { expect ( createNewCopy ) . to . be ( undefined ) ; } if ( ! singleRequest || overwrite || createNewCopies ) { const { _source } = await expectResponses . successCreated ( es , spaceId , type , destinationId ?? id ) ; expect ( _source ?. [ type ] [ NEW_ATTRIBUTE_KEY ] ) . to . eql ( NEW_ATTRIBUTE_VAL ) ; } } for ( let i = 0 ; i < expectedFailures . length ; i ++ ) { const { type , id , failureType , expectedNewId } = expectedFailures [ i ] ; const object = ( errors as Array < Record < string , unknown > > ) . find ( ( x ) => x . type === type && x . id === id ) ; expect ( object ) . not . to . be ( undefined ) ; const expectedError = { type : failureType } ; switch ( failureType ! ) { case 'unsupported_type' : break ; case 'conflict' : if ( expectedNewId ) { expectedError . destinationId = expectedNewId ; } break ; } expect ( object ! . error ) . to . eql ( expectedError ) ; } } } ; const createTestDefinitions = ( testCases , forbidden , options ) : ResolveImportErrorsTestDefinition [ ] => { const cases = Array . isArray ( testCases ) ? testCases : [ testCases ] ; const responseStatusCode = forbidden ? 403 : 200 ; const { overwrite = false , createNewCopies = false , spaceId , singleRequest , responseBodyOverride , } = options ; if ( ! singleRequest ) { return cases . map ( ( x ) => ( { title : getTestTitle ( x , responseStatusCode ) , request : createRequest ( x , overwrite ) , responseStatusCode , responseBody : responseBodyOverride || expectResponseBody ( x , responseStatusCode , false , overwrite , createNewCopies , spaceId ) , overwrite , createNewCopies , } ) ) ; } return [ { title : getTestTitle ( cases , responseStatusCode ) , request : cases . map ( ( x ) => createRequest ( x , overwrite ) ) . reduce ( ( acc , cur ) => ( { objects : [ ... acc . objects , ... cur . objects ] , retries : [ ... acc . retries , ... cur . retries ] , } ) ) , responseStatusCode , responseBody : responseBodyOverride || expectResponseBody ( cases , responseStatusCode , true , overwrite , createNewCopies , spaceId ) , overwrite , createNewCopies , } , ] ; } ; const makeResolveImportErrorsTest = ( describeFn ) => ( description , definition ) => { const { user , spaceId = SPACES . DEFAULT . spaceId , tests } = definition ; describeFn ( description , ( ) => { before ( ( ) => esArchiver . load ( 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces' ) ) ; after ( ( ) => esArchiver . unload ( 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces' ) ) ; const attrs = { attributes : { [ NEW_ATTRIBUTE_KEY ] : NEW_ATTRIBUTE_VAL } } ; for ( const test of tests ) { it ( `should return ${ test . responseStatusCode } ${ test . title }` , async ( ) => { const requestBody = test . request . objects . map ( ( obj ) => JSON . stringify ( { ... obj , ... attrs } ) ) . join ( '\n' ) ; const query = test . createNewCopies ? '?createNewCopies=true' : '' ; await supertest . post ( `${ getUrlPrefix ( spaceId ) }/api/saved_objects/_resolve_import_errors${ query }` ) . auth ( user ?. username , user ?. password ) . field ( 'retries' , JSON . stringify ( test . request . retries ) ) . attach ( 'file' , Buffer . from ( requestBody , 'utf8' ) , 'export.ndjson' ) . expect ( test . responseStatusCode ) . then ( test . responseBody ) ; } ) ; } } ) ; } ; const addTests = makeResolveImportErrorsTest ( describe ) ; addTests . only = makeResolveImportErrorsTest ( describe . only ) ; return { addTests , createTestDefinitions , expectSavedObjectForbidden , } ; }