44 * you may not use this file except in compliance with the Elastic License.
55 */
66
7- import { LegacyClusterClient , Logger } from 'src/core/server' ;
7+ import { LegacyClusterClient } from 'src/core/server' ;
88import { elasticsearchServiceMock , loggingSystemMock } from 'src/core/server/mocks' ;
9- import { ClusterClientAdapter , IClusterClientAdapter } from './cluster_client_adapter' ;
9+ import {
10+ ClusterClientAdapter ,
11+ IClusterClientAdapter ,
12+ EVENT_BUFFER_LENGTH ,
13+ } from './cluster_client_adapter' ;
14+ import { contextMock } from './context.mock' ;
1015import { findOptionsSchema } from '../event_log_client' ;
16+ import { delay } from '../lib/delay' ;
1117
1218type EsClusterClient = Pick < jest . Mocked < LegacyClusterClient > , 'callAsInternalUser' | 'asScoped' > ;
19+ type MockedLogger = ReturnType < typeof loggingSystemMock [ 'createLogger' ] > ;
1320
14- let logger : Logger ;
21+ let logger : MockedLogger ;
1522let clusterClient : EsClusterClient ;
1623let clusterClientAdapter : IClusterClientAdapter ;
1724
@@ -21,22 +28,97 @@ beforeEach(() => {
2128 clusterClientAdapter = new ClusterClientAdapter ( {
2229 logger,
2330 clusterClientPromise : Promise . resolve ( clusterClient ) ,
31+ context : contextMock . create ( ) ,
2432 } ) ;
2533} ) ;
2634
2735describe ( 'indexDocument' , ( ) => {
28- test ( 'should call cluster client with given doc' , async ( ) => {
29- await clusterClientAdapter . indexDocument ( { args : true } ) ;
30- expect ( clusterClient . callAsInternalUser ) . toHaveBeenCalledWith ( 'index' , {
31- args : true ,
36+ test ( 'should call cluster client bulk with given doc' , async ( ) => {
37+ clusterClientAdapter . indexDocument ( { body : { message : 'foo' } , index : 'event-log' } ) ;
38+
39+ await retryUntil ( 'cluster client bulk called' , ( ) => {
40+ return clusterClient . callAsInternalUser . mock . calls . length !== 0 ;
41+ } ) ;
42+
43+ expect ( clusterClient . callAsInternalUser ) . toHaveBeenCalledWith ( 'bulk' , {
44+ body : [ { create : { _index : 'event-log' } } , { message : 'foo' } ] ,
3245 } ) ;
3346 } ) ;
3447
35- test ( 'should throw error when cluster client throws an error' , async ( ) => {
36- clusterClient . callAsInternalUser . mockRejectedValue ( new Error ( 'Fail' ) ) ;
37- await expect (
38- clusterClientAdapter . indexDocument ( { args : true } )
39- ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"Fail"` ) ;
48+ test ( 'should log an error when cluster client throws an error' , async ( ) => {
49+ clusterClient . callAsInternalUser . mockRejectedValue ( new Error ( 'expected failure' ) ) ;
50+ clusterClientAdapter . indexDocument ( { body : { message : 'foo' } , index : 'event-log' } ) ;
51+ await retryUntil ( 'cluster client bulk called' , ( ) => {
52+ return logger . error . mock . calls . length !== 0 ;
53+ } ) ;
54+
55+ const expectedMessage = `error writing bulk events: "expected failure"; docs: [{"create":{"_index":"event-log"}},{"message":"foo"}]` ;
56+ expect ( logger . error ) . toHaveBeenCalledWith ( expectedMessage ) ;
57+ } ) ;
58+ } ) ;
59+
60+ describe ( 'shutdown()' , ( ) => {
61+ test ( 'should work if no docs have been written' , async ( ) => {
62+ const result = await clusterClientAdapter . shutdown ( ) ;
63+ expect ( result ) . toBeFalsy ( ) ;
64+ } ) ;
65+
66+ test ( 'should work if some docs have been written' , async ( ) => {
67+ clusterClientAdapter . indexDocument ( { body : { message : 'foo' } , index : 'event-log' } ) ;
68+ const resultPromise = clusterClientAdapter . shutdown ( ) ;
69+
70+ await retryUntil ( 'cluster client bulk called' , ( ) => {
71+ return clusterClient . callAsInternalUser . mock . calls . length !== 0 ;
72+ } ) ;
73+
74+ const result = await resultPromise ;
75+ expect ( result ) . toBeFalsy ( ) ;
76+ } ) ;
77+ } ) ;
78+
79+ describe ( 'buffering documents' , ( ) => {
80+ test ( 'should write buffered docs after timeout' , async ( ) => {
81+ // write EVENT_BUFFER_LENGTH - 1 docs
82+ for ( let i = 0 ; i < EVENT_BUFFER_LENGTH - 1 ; i ++ ) {
83+ clusterClientAdapter . indexDocument ( { body : { message : `foo ${ i } ` } , index : 'event-log' } ) ;
84+ }
85+
86+ await retryUntil ( 'cluster client bulk called' , ( ) => {
87+ return clusterClient . callAsInternalUser . mock . calls . length !== 0 ;
88+ } ) ;
89+
90+ const expectedBody = [ ] ;
91+ for ( let i = 0 ; i < EVENT_BUFFER_LENGTH - 1 ; i ++ ) {
92+ expectedBody . push ( { create : { _index : 'event-log' } } , { message : `foo ${ i } ` } ) ;
93+ }
94+
95+ expect ( clusterClient . callAsInternalUser ) . toHaveBeenCalledWith ( 'bulk' , {
96+ body : expectedBody ,
97+ } ) ;
98+ } ) ;
99+
100+ test ( 'should write buffered docs after buffer exceeded' , async ( ) => {
101+ // write EVENT_BUFFER_LENGTH + 1 docs
102+ for ( let i = 0 ; i < EVENT_BUFFER_LENGTH + 1 ; i ++ ) {
103+ clusterClientAdapter . indexDocument ( { body : { message : `foo ${ i } ` } , index : 'event-log' } ) ;
104+ }
105+
106+ await retryUntil ( 'cluster client bulk called' , ( ) => {
107+ return clusterClient . callAsInternalUser . mock . calls . length >= 2 ;
108+ } ) ;
109+
110+ const expectedBody = [ ] ;
111+ for ( let i = 0 ; i < EVENT_BUFFER_LENGTH ; i ++ ) {
112+ expectedBody . push ( { create : { _index : 'event-log' } } , { message : `foo ${ i } ` } ) ;
113+ }
114+
115+ expect ( clusterClient . callAsInternalUser ) . toHaveBeenNthCalledWith ( 1 , 'bulk' , {
116+ body : expectedBody ,
117+ } ) ;
118+
119+ expect ( clusterClient . callAsInternalUser ) . toHaveBeenNthCalledWith ( 2 , 'bulk' , {
120+ body : [ { create : { _index : 'event-log' } } , { message : `foo 100` } ] ,
121+ } ) ;
40122 } ) ;
41123} ) ;
42124
@@ -575,3 +657,29 @@ describe('queryEventsBySavedObject', () => {
575657 ` ) ;
576658 } ) ;
577659} ) ;
660+
661+ type RetryableFunction = ( ) => boolean ;
662+
663+ const RETRY_UNTIL_DEFAULT_COUNT = 20 ;
664+ const RETRY_UNTIL_DEFAULT_WAIT = 1000 ; // milliseconds
665+
666+ async function retryUntil (
667+ label : string ,
668+ fn : RetryableFunction ,
669+ count : number = RETRY_UNTIL_DEFAULT_COUNT ,
670+ wait : number = RETRY_UNTIL_DEFAULT_WAIT
671+ ) : Promise < boolean > {
672+ while ( count > 0 ) {
673+ count -- ;
674+
675+ if ( fn ( ) ) return true ;
676+
677+ // eslint-disable-next-line no-console
678+ console . log ( `attempt failed waiting for "${ label } ", attempts left: ${ count } ` ) ;
679+
680+ if ( count === 0 ) return false ;
681+ await delay ( wait ) ;
682+ }
683+
684+ return false ;
685+ }
0 commit comments