import { OpenSearchDashboardsMigratorMock , migratorInstanceMock , clientProviderInstanceMock , typeRegistryInstanceMock , } from './saved_objects_service.test.mocks' ; import { BehaviorSubject } from 'rxjs' ; import { ByteSizeValue } from '@osd/config-schema' ; import { errors as opensearchErrors } from '@elastic/elasticsearch' ; import { SavedObjectsService } from './saved_objects_service' ; import { mockCoreContext } from '../core_context.mock' ; import { Env } from '../config' ; import { configServiceMock } from '../mocks' ; import { opensearchServiceMock } from '../opensearch/opensearch_service.mock' ; import { opensearchClientMock } from '../opensearch/client/mocks' ; import { httpServiceMock } from '../http/http_service.mock' ; import { httpServerMock } from '../http/http_server.mocks' ; import { SavedObjectsClientFactoryProvider } from './service/lib' ; import { NodesVersionCompatibility } from '../opensearch/version_check/ensure_opensearch_version' ; import { SavedObjectsRepository } from './service/lib/repository' ; jest . mock ( './service/lib/repository' ) ; describe ( 'SavedObjectsService' , ( ) => { const createCoreContext = ( { skipMigration = true , env , } = { } ) => { const configService = configServiceMock . create ( { atPath : { skip : true } } ) ; configService . atPath . mockImplementation ( ( path ) => { if ( path === 'migrations' ) { return new BehaviorSubject ( { skip : skipMigration } ) ; } return new BehaviorSubject ( { maxImportPayloadBytes : new ByteSizeValue ( 0 ) , maxImportExportSize : new ByteSizeValue ( 0 ) , } ) ; } ) ; return mockCoreContext . create ( { configService , env } ) ; } ; const createSetupDeps = ( ) => { const opensearchMock = opensearchServiceMock . createInternalSetup ( ) ; return { http : httpServiceMock . createInternalSetupContract ( ) , opensearch : opensearchMock , } ; } ; const createStartDeps = ( pluginsInitialized = true ) => { return { pluginsInitialized , opensearch : opensearchServiceMock . createInternalStart ( ) , } ; } ; afterEach ( ( ) => { jest . clearAllMocks ( ) ; } ) ; describe ( '#setup()' , ( ) => { describe ( '#setClientFactoryProvider' , ( ) => { it ( 'registers the factory to the clientProvider' , async ( ) => { const coreContext = createCoreContext ( ) ; const soService = new SavedObjectsService ( coreContext ) ; const setup = await soService . setup ( createSetupDeps ( ) ) ; const factory = jest . fn ( ) ; const factoryProvider = ( ) => factory ; setup . setClientFactoryProvider ( factoryProvider ) ; await soService . start ( createStartDeps ( ) ) ; expect ( clientProviderInstanceMock . setClientFactory ) . toHaveBeenCalledWith ( factory ) ; } ) ; it ( 'throws if a factory is already registered' , async ( ) => { const coreContext = createCoreContext ( ) ; const soService = new SavedObjectsService ( coreContext ) ; const setup = await soService . setup ( createSetupDeps ( ) ) ; const firstFactory = ( ) => jest . fn ( ) ; const secondFactory = ( ) => jest . fn ( ) ; setup . setClientFactoryProvider ( firstFactory ) ; expect ( ( ) => { setup . setClientFactoryProvider ( secondFactory ) ; } ) . toThrowErrorMatchingInlineSnapshot ( `"custom client factory is already set, and can only be set once"` ) ; } ) ; } ) ; describe ( '#addClientWrapper' , ( ) => { it ( 'registers the wrapper to the clientProvider' , async ( ) => { const coreContext = createCoreContext ( ) ; const soService = new SavedObjectsService ( coreContext ) ; const setup = await soService . setup ( createSetupDeps ( ) ) ; const wrapperA = jest . fn ( ) ; const wrapperB = jest . fn ( ) ; setup . addClientWrapper ( 1 , 'A' , wrapperA ) ; setup . addClientWrapper ( 2 , 'B' , wrapperB ) ; await soService . start ( createStartDeps ( ) ) ; expect ( clientProviderInstanceMock . addClientWrapperFactory ) . toHaveBeenCalledTimes ( 2 ) ; expect ( clientProviderInstanceMock . addClientWrapperFactory ) . toHaveBeenCalledWith ( 1 , 'A' , wrapperA ) ; expect ( clientProviderInstanceMock . addClientWrapperFactory ) . toHaveBeenCalledWith ( 2 , 'B' , wrapperB ) ; } ) ; } ) ; describe ( '#registerType' , ( ) => { it ( 'registers the type to the internal typeRegistry' , async ( ) => { const coreContext = createCoreContext ( ) ; const soService = new SavedObjectsService ( coreContext ) ; const setup = await soService . setup ( createSetupDeps ( ) ) ; const type = { name : 'someType' , hidden : false , namespaceType : 'single' as 'single' , mappings : { properties : { } } , } ; setup . registerType ( type ) ; expect ( typeRegistryInstanceMock . registerType ) . toHaveBeenCalledTimes ( 1 ) ; expect ( typeRegistryInstanceMock . registerType ) . toHaveBeenCalledWith ( type ) ; } ) ; } ) ; } ) ; describe ( '#start()' , ( ) => { it ( 'creates a OpenSearchDashboardsMigrator which retries NoLivingConnectionsError errors from OpenSearch client' , async ( ) => { const coreContext = createCoreContext ( ) ; const soService = new SavedObjectsService ( coreContext ) ; const coreSetup = createSetupDeps ( ) ; const coreStart = createStartDeps ( ) ; coreStart . opensearch . client . asInternalUser . indices . create = jest . fn ( ) . mockImplementationOnce ( ( ) => Promise . reject ( new opensearchErrors . NoLivingConnectionsError ( 'reason' , { } as any ) ) ) . mockImplementationOnce ( ( ) => opensearchClientMock . createSuccessTransportRequestPromise ( 'success' ) ) ; await soService . setup ( coreSetup ) ; await soService . start ( coreStart , 1 ) ; const response = await OpenSearchDashboardsMigratorMock . mock . calls [ 0 ] [ 0 ] . client . indices . create ( ) ; return expect ( response . body ) . toBe ( 'success' ) ; } ) ; it ( 'skips OpenSearchDashboardsMigrator migrations when pluginsInitialized=false' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; await soService . setup ( createSetupDeps ( ) ) ; await soService . start ( createStartDeps ( false ) ) ; expect ( migratorInstanceMock . runMigrations ) . not . toHaveBeenCalled ( ) ; } ) ; it ( 'skips OpenSearchDashboardsMigrator migrations when migrations.skip=true' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : true } ) ; const soService = new SavedObjectsService ( coreContext ) ; await soService . setup ( createSetupDeps ( ) ) ; await soService . start ( createStartDeps ( ) ) ; expect ( migratorInstanceMock . runMigrations ) . not . toHaveBeenCalled ( ) ; } ) ; it ( 'waits for all opensearch nodes to be compatible before running migrations' , async ( done ) => { expect . assertions ( 2 ) ; const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const setupDeps = createSetupDeps ( ) ; setupDeps . opensearch . opensearchNodesCompatibility$ = new BehaviorSubject ( { isCompatible : false , incompatibleNodes : [ ] , warningNodes : [ ] , opensearchDashboardsVersion : '8.0.0' , } ) ; await soService . setup ( setupDeps ) ; soService . start ( createStartDeps ( ) ) ; expect ( migratorInstanceMock . runMigrations ) . toHaveBeenCalledTimes ( 0 ) ; ( ( setupDeps . opensearch . opensearchNodesCompatibility$ as any ) as BehaviorSubject < NodesVersionCompatibility > ) . next ( { isCompatible : true , incompatibleNodes : [ ] , warningNodes : [ ] , opensearchDashboardsVersion : '8.0.0' , } ) ; setImmediate ( ( ) => { expect ( migratorInstanceMock . runMigrations ) . toHaveBeenCalledTimes ( 1 ) ; done ( ) ; } ) ; } ) ; it ( 'resolves with OpenSearchDashboardsMigrator after waiting for migrations to complete' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; await soService . setup ( createSetupDeps ( ) ) ; expect ( migratorInstanceMock . runMigrations ) . toHaveBeenCalledTimes ( 0 ) ; await soService . start ( createStartDeps ( ) ) ; expect ( migratorInstanceMock . runMigrations ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'throws when calling setup APIs once started' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const setup = await soService . setup ( createSetupDeps ( ) ) ; await soService . start ( createStartDeps ( ) ) ; expect ( ( ) => { setup . setClientFactoryProvider ( jest . fn ( ) ) ; } ) . toThrowErrorMatchingInlineSnapshot ( `"cannot call \`setClientFactoryProvider\` after service startup."` ) ; expect ( ( ) => { setup . addClientWrapper ( 0 , 'dummy' , jest . fn ( ) ) ; } ) . toThrowErrorMatchingInlineSnapshot ( `"cannot call \`addClientWrapper\` after service startup."` ) ; expect ( ( ) => { setup . registerType ( { name : 'someType' , hidden : false , namespaceType : 'single' as 'single' , mappings : { properties : { } } , } ) ; } ) . toThrowErrorMatchingInlineSnapshot ( `"cannot call \`registerType\` after service startup."` ) ; } ) ; describe ( '#getTypeRegistry' , ( ) => { it ( 'returns the internal type registry of the service' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; await soService . setup ( createSetupDeps ( ) ) ; const { getTypeRegistry } = await soService . start ( createStartDeps ( ) ) ; expect ( getTypeRegistry ( ) ) . toBe ( typeRegistryInstanceMock ) ; } ) ; } ) ; describe ( '#createScopedRepository' , ( ) => { it ( 'creates a respository scoped to the user' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const coreSetup = createSetupDeps ( ) ; await soService . setup ( coreSetup ) ; const coreStart = createStartDeps ( ) ; const { createScopedRepository } = await soService . start ( coreStart ) ; const req = httpServerMock . createOpenSearchDashboardsRequest ( ) ; createScopedRepository ( req ) ; expect ( coreStart . opensearch . client . asScoped ) . toHaveBeenCalledWith ( req ) ; const [ [ , , , , includedHiddenTypes ] , ] = ( SavedObjectsRepository . createRepository as jest . Mocked < any > ) . mock . calls ; expect ( includedHiddenTypes ) . toEqual ( [ ] ) ; } ) ; it ( 'creates a respository including hidden types when specified' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const coreSetup = createSetupDeps ( ) ; await soService . setup ( coreSetup ) ; const coreStart = createStartDeps ( ) ; const { createScopedRepository } = await soService . start ( coreStart ) ; const req = httpServerMock . createOpenSearchDashboardsRequest ( ) ; createScopedRepository ( req , [ 'someHiddenType' ] ) ; const [ [ , , , , includedHiddenTypes ] , ] = ( SavedObjectsRepository . createRepository as jest . Mocked < any > ) . mock . calls ; expect ( includedHiddenTypes ) . toEqual ( [ 'someHiddenType' ] ) ; } ) ; } ) ; describe ( '#createInternalRepository' , ( ) => { it ( 'creates a respository using the admin user' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const coreSetup = createSetupDeps ( ) ; await soService . setup ( coreSetup ) ; const coreStart = createStartDeps ( ) ; const { createInternalRepository } = await soService . start ( coreStart ) ; createInternalRepository ( ) ; const [ [ , , , client , includedHiddenTypes ] , ] = ( SavedObjectsRepository . createRepository as jest . Mocked < any > ) . mock . calls ; expect ( coreStart . opensearch . client . asInternalUser ) . toBe ( client ) ; expect ( includedHiddenTypes ) . toEqual ( [ ] ) ; } ) ; it ( 'creates a respository including hidden types when specified' , async ( ) => { const coreContext = createCoreContext ( { skipMigration : false } ) ; const soService = new SavedObjectsService ( coreContext ) ; const coreSetup = createSetupDeps ( ) ; await soService . setup ( coreSetup ) ; const { createInternalRepository } = await soService . start ( createStartDeps ( ) ) ; createInternalRepository ( [ 'someHiddenType' ] ) ; const [ [ , , , , includedHiddenTypes ] , ] = ( SavedObjectsRepository . createRepository as jest . Mocked < any > ) . mock . calls ; expect ( includedHiddenTypes ) . toEqual ( [ 'someHiddenType' ] ) ; } ) ; } ) ; } ) ; } ) ;