99
1010package org .opensearch .dataprepper .plugins .source .microsoft_office365 ;
1111
12+ import io .micrometer .core .instrument .Counter ;
13+ import io .micrometer .core .instrument .DistributionSummary ;
14+ import io .micrometer .core .instrument .Timer ;
1215import org .junit .jupiter .api .BeforeEach ;
1316import org .junit .jupiter .api .Test ;
1417import org .junit .jupiter .api .extension .ExtendWith ;
@@ -291,4 +294,147 @@ void testTokenRenewal() {
291294 assertEquals ("Bearer token-0" , requestTokens .get (0 ), "First request should use token-0" );
292295 assertEquals ("Bearer token-1" , requestTokens .get (1 ), "Second request should use token-1" );
293296 }
294- }
297+
298+ @ Test
299+ void testMetricsInitialization () {
300+ // Test metrics initialization during construction. This approach is used for metrics that are called
301+ // inside RetryHandler.executeWithRetry() static method calls, which would require complex static mocking
302+ // to test for invocation. Testing initialization ensures the metrics infrastructure is properly set up.
303+
304+ // Mock all required timers and counters for Office365RestClient constructor
305+ PluginMetrics mockPluginMetrics = org .mockito .Mockito .mock (PluginMetrics .class );
306+ Timer mockAuditLogFetchLatencyTimer = org .mockito .Mockito .mock (Timer .class );
307+ Timer mockSearchCallLatencyTimer = org .mockito .Mockito .mock (Timer .class );
308+ Counter mockAuditLogsRequestedCounter = org .mockito .Mockito .mock (Counter .class );
309+ Counter mockAuditLogRequestsFailedCounter = org .mockito .Mockito .mock (Counter .class );
310+ Counter mockAuditLogRequestsSuccessCounter = org .mockito .Mockito .mock (Counter .class );
311+ Counter mockSearchRequestsFailedCounter = org .mockito .Mockito .mock (Counter .class );
312+ Counter mockSearchRequestsSuccessCounter = org .mockito .Mockito .mock (Counter .class );
313+ DistributionSummary mockAuditLogRequestSizeSummary = org .mockito .Mockito .mock (DistributionSummary .class );
314+ DistributionSummary mockSearchRequestSizeSummary = org .mockito .Mockito .mock (DistributionSummary .class );
315+
316+ when (mockPluginMetrics .timer ("auditLogFetchLatency" )).thenReturn (mockAuditLogFetchLatencyTimer );
317+ when (mockPluginMetrics .timer ("searchCallLatency" )).thenReturn (mockSearchCallLatencyTimer );
318+ when (mockPluginMetrics .counter ("auditLogsRequested" )).thenReturn (mockAuditLogsRequestedCounter );
319+ when (mockPluginMetrics .counter ("auditLogRequestsFailed" )).thenReturn (mockAuditLogRequestsFailedCounter );
320+ when (mockPluginMetrics .counter ("auditLogRequestsSuccess" )).thenReturn (mockAuditLogRequestsSuccessCounter );
321+ when (mockPluginMetrics .counter ("searchRequestsFailed" )).thenReturn (mockSearchRequestsFailedCounter );
322+ when (mockPluginMetrics .counter ("searchRequestsSuccess" )).thenReturn (mockSearchRequestsSuccessCounter );
323+ when (mockPluginMetrics .summary ("auditLogRequestSizeBytes" )).thenReturn (mockAuditLogRequestSizeSummary );
324+ when (mockPluginMetrics .summary ("searchRequestSizeBytes" )).thenReturn (mockSearchRequestSizeSummary );
325+
326+ // Create Office365RestClient with mocked metrics
327+ Office365RestClient testClient = new Office365RestClient (authConfig , mockPluginMetrics );
328+
329+ // Verify all metrics were requested during construction
330+ verify (mockPluginMetrics ).timer ("auditLogFetchLatency" );
331+ verify (mockPluginMetrics ).timer ("searchCallLatency" );
332+ verify (mockPluginMetrics ).counter ("auditLogsRequested" );
333+ verify (mockPluginMetrics ).counter ("auditLogRequestsFailed" );
334+ verify (mockPluginMetrics ).counter ("auditLogRequestsSuccess" );
335+ verify (mockPluginMetrics ).counter ("searchRequestsFailed" );
336+ verify (mockPluginMetrics ).counter ("searchRequestsSuccess" );
337+ verify (mockPluginMetrics ).summary ("auditLogRequestSizeBytes" );
338+ verify (mockPluginMetrics ).summary ("searchRequestSizeBytes" );
339+ }
340+
341+ @ Test
342+ void testGetAuditLogMetricsInvocation () throws NoSuchFieldException , IllegalAccessException {
343+ // Test metrics for getAuditLog() method - both success and failure scenarios
344+
345+ // Create mock metrics and inject them
346+ Counter mockAuditLogsRequestedCounter = org .mockito .Mockito .mock (Counter .class );
347+ Counter mockAuditLogRequestsFailedCounter = org .mockito .Mockito .mock (Counter .class );
348+ Timer mockAuditLogFetchLatencyTimer = org .mockito .Mockito .mock (Timer .class );
349+ DistributionSummary mockAuditLogRequestSizeSummary = org .mockito .Mockito .mock (DistributionSummary .class );
350+
351+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "auditLogsRequestedCounter" , mockAuditLogsRequestedCounter );
352+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "auditLogRequestsFailedCounter" , mockAuditLogRequestsFailedCounter );
353+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "auditLogFetchLatencyTimer" , mockAuditLogFetchLatencyTimer );
354+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "auditLogRequestSizeSummary" , mockAuditLogRequestSizeSummary );
355+
356+ // Mock timer.record() to execute the lambda
357+ when (mockAuditLogFetchLatencyTimer .record (any (java .util .function .Supplier .class ))).thenAnswer (invocation -> {
358+ java .util .function .Supplier <?> supplier = invocation .getArgument (0 );
359+ return supplier .get ();
360+ });
361+
362+ String contentUri = "https://manage.office.com/api/v1.0/test-tenant/activity/feed/audit/123" ;
363+
364+ // Test success scenario
365+ String mockAuditLog = "{\" id\" :\" 123\" ,\" contentType\" :\" Audit.AzureActiveDirectory\" }" ;
366+ ResponseEntity <String > mockResponse = new ResponseEntity <>(mockAuditLog , HttpStatus .OK );
367+ when (restTemplate .exchange (eq (contentUri ), eq (HttpMethod .GET ), any (), eq (String .class )))
368+ .thenReturn (mockResponse );
369+
370+ office365RestClient .getAuditLog (contentUri );
371+
372+ // Verify success metrics
373+ verify (mockAuditLogsRequestedCounter ).increment (); // Called directly before RetryHandler
374+ verify (mockAuditLogFetchLatencyTimer ).record (any (java .util .function .Supplier .class )); // Timer wrapper
375+ verify (mockAuditLogRequestSizeSummary ).record (mockAuditLog .getBytes (java .nio .charset .StandardCharsets .UTF_8 ).length ); // Size metric inside RetryHandler
376+
377+ // Test failure scenario
378+ when (restTemplate .exchange (eq (contentUri ), eq (HttpMethod .GET ), any (), eq (String .class )))
379+ .thenThrow (new HttpClientErrorException (HttpStatus .INTERNAL_SERVER_ERROR ));
380+
381+ assertThrows (RuntimeException .class , () -> office365RestClient .getAuditLog (contentUri ));
382+
383+ // Verify failure metrics
384+ verify (mockAuditLogsRequestedCounter , times (2 )).increment (); // Called again before retry
385+ verify (mockAuditLogRequestsFailedCounter ).increment (); // Called in catch block outside RetryHandler
386+ }
387+
388+ @ Test
389+ void testSearchAuditLogsMetricsInvocation () throws NoSuchFieldException , IllegalAccessException {
390+ // Test metrics for searchAuditLogs() method - both success and failure scenarios
391+
392+ // Create mock metrics and inject them
393+ Counter mockSearchRequestsFailedCounter = org .mockito .Mockito .mock (Counter .class );
394+ Timer mockSearchCallLatencyTimer = org .mockito .Mockito .mock (Timer .class );
395+ DistributionSummary mockSearchRequestSizeSummary = org .mockito .Mockito .mock (DistributionSummary .class );
396+
397+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "searchRequestsFailedCounter" , mockSearchRequestsFailedCounter );
398+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "searchCallLatencyTimer" , mockSearchCallLatencyTimer );
399+ ReflectivelySetField .setField (Office365RestClient .class , office365RestClient , "searchRequestSizeSummary" , mockSearchRequestSizeSummary );
400+
401+ // Mock timer.record() to execute the lambda
402+ when (mockSearchCallLatencyTimer .record (any (java .util .function .Supplier .class ))).thenAnswer (invocation -> {
403+ java .util .function .Supplier <?> supplier = invocation .getArgument (0 );
404+ return supplier .get ();
405+ });
406+
407+ // Test success scenario
408+ List <Map <String , Object >> mockResults = Collections .singletonList (new HashMap <>());
409+ HttpHeaders responseHeaders = new HttpHeaders ();
410+ responseHeaders .setContentLength (1024L ); // Mock content length
411+ ResponseEntity <List <Map <String , Object >>> mockResponse = new ResponseEntity <>(mockResults , responseHeaders , HttpStatus .OK );
412+ when (restTemplate .exchange (anyString (), eq (HttpMethod .GET ), any (), any (ParameterizedTypeReference .class )))
413+ .thenReturn (mockResponse );
414+
415+ office365RestClient .searchAuditLogs (
416+ "Audit.AzureActiveDirectory" ,
417+ Instant .now ().minus (1 , ChronoUnit .HOURS ),
418+ Instant .now (),
419+ null
420+ );
421+
422+ // Verify success metrics
423+ verify (mockSearchCallLatencyTimer ).record (any (java .util .function .Supplier .class )); // Timer wrapper
424+ verify (mockSearchRequestSizeSummary ).record (1024L ); // Size metric inside RetryHandler
425+
426+ // Test failure scenario
427+ when (restTemplate .exchange (anyString (), eq (HttpMethod .GET ), any (), any (ParameterizedTypeReference .class )))
428+ .thenThrow (new HttpClientErrorException (HttpStatus .INTERNAL_SERVER_ERROR ));
429+
430+ assertThrows (RuntimeException .class , () -> office365RestClient .searchAuditLogs (
431+ "Audit.AzureActiveDirectory" ,
432+ Instant .now ().minus (1 , ChronoUnit .HOURS ),
433+ Instant .now (),
434+ null
435+ ));
436+
437+ // Verify failure metrics
438+ verify (mockSearchRequestsFailedCounter ).increment (); // Called in catch block outside RetryHandler
439+ }
440+ }
0 commit comments