3030import org .opensearch .dataprepper .plugins .source .source_crawler .coordination .state .DimensionalTimeSliceWorkerProgressState ;
3131import org .opensearch .dataprepper .metrics .PluginMetrics ;
3232import org .opensearch .dataprepper .plugins .source .microsoft_office365 .service .Office365Service ;
33+ import org .opensearch .dataprepper .plugins .source .microsoft_office365 .exception .Office365Exception ;
3334import org .slf4j .Logger ;
3435import org .slf4j .LoggerFactory ;
3536
5354import static org .mockito .Mockito .doAnswer ;
5455import static org .mockito .Mockito .doThrow ;
5556import static org .mockito .Mockito .mock ;
57+ import static org .mockito .Mockito .never ;
5658import static org .mockito .Mockito .verify ;
5759import static org .mockito .Mockito .when ;
60+ import static org .mockito .Mockito .times ;
5861
5962@ ExtendWith (MockitoExtension .class )
6063@ MockitoSettings (strictness = Strictness .LENIENT )
@@ -80,6 +83,15 @@ class Office365CrawlerClientTest {
8083 @ Mock
8184 private Timer bufferWriteLatencyTimer ;
8285
86+ @ Mock
87+ private Counter apiCallsCounter ;
88+
89+ @ Mock
90+ private Counter nonRetryableErrorsCounter ;
91+
92+ @ Mock
93+ private Counter retryableErrorsCounter ;
94+
8395 @ Mock
8496 private static Logger log ;
8597
@@ -92,7 +104,16 @@ static void setupLogger() {
92104 @ BeforeEach
93105 void setUp () {
94106 when (pluginMetrics .timer (anyString ())).thenReturn (bufferWriteLatencyTimer );
95- when (pluginMetrics .counter (anyString ())).thenReturn (mock (Counter .class ));
107+ // Configure specific counters
108+ when (pluginMetrics .counter ("apiCalls" )).thenReturn (apiCallsCounter );
109+ when (pluginMetrics .counter ("nonRetryableErrors" )).thenReturn (nonRetryableErrorsCounter );
110+ when (pluginMetrics .counter ("retryableErrors" )).thenReturn (retryableErrorsCounter );
111+ // Configure other known counters
112+ when (pluginMetrics .counter ("bufferWriteAttempts" )).thenReturn (mock (Counter .class ));
113+ when (pluginMetrics .counter ("bufferWriteSuccess" )).thenReturn (mock (Counter .class ));
114+ when (pluginMetrics .counter ("bufferWriteRetrySuccess" )).thenReturn (mock (Counter .class ));
115+ when (pluginMetrics .counter ("bufferWriteRetryAttempts" )).thenReturn (mock (Counter .class ));
116+ when (pluginMetrics .counter ("bufferWriteFailures" )).thenReturn (mock (Counter .class ));
96117 when (state .getStartTime ()).thenReturn (Instant .now ().minus (Duration .ofHours (1 )));
97118 when (state .getEndTime ()).thenReturn (Instant .now ());
98119 when (state .getDimensionType ()).thenReturn ("Exchange" );
@@ -111,15 +132,14 @@ void testExecutePartition() throws Exception {
111132 AuditLogsResponse response = new AuditLogsResponse (
112133 Arrays .asList (Map .of (
113134 "contentId" , "ID1" ,
114- "contentUri" , "uri1"
115- )), null );
135+ "contentUri" , "uri1" )),
136+ null );
116137
117138 when (service .searchAuditLogs (
118139 eq ("Exchange" ),
119140 any (Instant .class ),
120141 any (Instant .class ),
121- isNull ()
122- )).thenReturn (response );
142+ isNull ())).thenReturn (response );
123143
124144 when (service .getAuditLog (anyString ()))
125145 .thenReturn ("{\" Workload\" :\" Exchange\" ,\" Operation\" :\" Test\" }" );
@@ -143,6 +163,11 @@ void testExecutePartition() throws Exception {
143163 assertNotNull (record .getData ());
144164 assertEquals ("Exchange" , record .getData ().getMetadata ().getAttribute ("contentType" ));
145165 }
166+
167+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog
168+ verify (apiCallsCounter , times (2 )).increment ();
169+ verify (nonRetryableErrorsCounter , never ()).increment ();
170+ verify (retryableErrorsCounter , never ()).increment ();
146171 }
147172
148173 @ Test
@@ -154,18 +179,18 @@ void testExecutePartitionWithJsonProcessingError() throws Exception {
154179 AuditLogsResponse response = new AuditLogsResponse (
155180 Arrays .asList (Map .of (
156181 "contentId" , "ID1" ,
157- "contentUri" , "uri1"
158- )), null );
182+ "contentUri" , "uri1" )),
183+ null );
159184
160185 when (service .searchAuditLogs (
161186 anyString (),
162187 any (Instant .class ),
163188 any (Instant .class ),
164- any ()
165- )).thenReturn (response );
189+ any ())).thenReturn (response );
166190
167191 when (service .getAuditLog (anyString ())).thenReturn ("{\" invalid\" :json}" );
168- when (mockObjectMapper .readTree (anyString ())).thenThrow (new JsonProcessingException ("Test error" ) {});
192+ when (mockObjectMapper .readTree (anyString ())).thenThrow (new JsonProcessingException ("Test error" ) {
193+ });
169194
170195 doAnswer (invocation -> {
171196 Runnable runnable = invocation .getArgument (0 );
@@ -176,6 +201,11 @@ void testExecutePartitionWithJsonProcessingError() throws Exception {
176201 client .executePartition (state , buffer , acknowledgementSet );
177202
178203 verify (buffer ).writeAll (argThat (list -> list .isEmpty ()), anyInt ());
204+
205+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog, JSON processing errors are non-retryable
206+ verify (apiCallsCounter , times (2 )).increment ();
207+ verify (nonRetryableErrorsCounter ).increment ();
208+ verify (retryableErrorsCounter , never ()).increment ();
179209 }
180210
181211 @ Test
@@ -185,15 +215,14 @@ void testBufferWriteWithAcknowledgements() throws Exception {
185215 AuditLogsResponse response = new AuditLogsResponse (
186216 Arrays .asList (Map .of (
187217 "contentId" , "ID1" ,
188- "contentUri" , "uri1"
189- )), null );
218+ "contentUri" , "uri1" )),
219+ null );
190220
191221 when (service .searchAuditLogs (
192222 anyString (),
193223 any (Instant .class ),
194224 any (Instant .class ),
195- any ()
196- )).thenReturn (response );
225+ any ())).thenReturn (response );
197226
198227 when (service .getAuditLog (anyString ()))
199228 .thenReturn ("{\" Workload\" :\" Exchange\" ,\" Operation\" :\" Test\" }" );
@@ -210,6 +239,11 @@ void testBufferWriteWithAcknowledgements() throws Exception {
210239 verify (acknowledgementSet ).add (any (Event .class ));
211240 verify (acknowledgementSet ).complete ();
212241 verify (buffer ).writeAll (any (), anyInt ());
242+
243+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog
244+ verify (apiCallsCounter , times (2 )).increment ();
245+ verify (nonRetryableErrorsCounter , never ()).increment ();
246+ verify (retryableErrorsCounter , never ()).increment ();
213247 }
214248
215249 @ Test
@@ -219,15 +253,14 @@ void testBufferWriteTimeout() throws Exception {
219253 AuditLogsResponse response = new AuditLogsResponse (
220254 Arrays .asList (Map .of (
221255 "contentId" , "ID1" ,
222- "contentUri" , "uri1"
223- )), null );
256+ "contentUri" , "uri1" )),
257+ null );
224258
225259 when (service .searchAuditLogs (
226260 anyString (),
227261 any (Instant .class ),
228262 any (Instant .class ),
229- any ()
230- )).thenReturn (response );
263+ any ())).thenReturn (response );
231264
232265 when (service .getAuditLog (anyString ()))
233266 .thenReturn ("{\" Workload\" :\" Exchange\" ,\" Operation\" :\" Test\" }" );
@@ -247,6 +280,12 @@ void testBufferWriteTimeout() throws Exception {
247280
248281 assertEquals ("Error writing to buffer" , exception .getMessage ());
249282 verify (buffer ).writeAll (any (), anyInt ());
283+
284+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog, and retryable error counter
285+ // since buffer errors are retryable
286+ verify (apiCallsCounter , times (2 )).increment ();
287+ verify (nonRetryableErrorsCounter , never ()).increment ();
288+ verify (retryableErrorsCounter ).increment ();
250289 }
251290
252291 @ Test
@@ -256,14 +295,14 @@ void testNonRetryableError() throws Exception {
256295 AuditLogsResponse response = new AuditLogsResponse (
257296 Arrays .asList (Map .of (
258297 "contentId" , "ID1" ,
259- "contentUri" , "uri1" )), null );
298+ "contentUri" , "uri1" )),
299+ null );
260300
261301 when (service .searchAuditLogs (
262302 anyString (),
263303 any (Instant .class ),
264304 any (Instant .class ),
265- any ()
266- )).thenReturn (response );
305+ any ())).thenReturn (response );
267306
268307 when (service .getAuditLog (anyString ())).thenReturn (null );
269308
@@ -276,6 +315,11 @@ void testNonRetryableError() throws Exception {
276315 client .executePartition (state , buffer , acknowledgementSet );
277316
278317 verify (buffer ).writeAll (argThat (list -> list .isEmpty ()), anyInt ());
318+
319+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog, null content causes non-retryable error
320+ verify (apiCallsCounter , times (2 )).increment ();
321+ verify (nonRetryableErrorsCounter ).increment ();
322+ verify (retryableErrorsCounter , never ()).increment ();
279323 }
280324
281325 @ Test
@@ -285,15 +329,14 @@ void testMissingWorkloadField() throws Exception {
285329 AuditLogsResponse response = new AuditLogsResponse (
286330 Arrays .asList (Map .of (
287331 "contentId" , "ID1" ,
288- "contentUri" , "uri1"
289- )), null );
332+ "contentUri" , "uri1" )),
333+ null );
290334
291335 when (service .searchAuditLogs (
292336 anyString (),
293337 any (Instant .class ),
294338 any (Instant .class ),
295- any ()
296- )).thenReturn (response );
339+ any ())).thenReturn (response );
297340
298341 when (service .getAuditLog (anyString ())).thenReturn ("{\" Operation\" :\" Test\" }" );
299342
@@ -306,5 +349,10 @@ void testMissingWorkloadField() throws Exception {
306349 client .executePartition (state , buffer , acknowledgementSet );
307350
308351 verify (buffer ).writeAll (argThat (list -> list .isEmpty ()), anyInt ());
352+
353+ // Verify counters - 2 API calls: searchAuditLogs + getAuditLog, missing workload field causes non-retryable error
354+ verify (apiCallsCounter , times (2 )).increment ();
355+ verify (nonRetryableErrorsCounter ).increment ();
356+ verify (retryableErrorsCounter , never ()).increment ();
309357 }
310358}
0 commit comments