|
43 | 43 | import static org.awaitility.Awaitility.await; |
44 | 44 | import static org.mockito.ArgumentMatchers.any; |
45 | 45 | import static org.mockito.ArgumentMatchers.eq; |
| 46 | +import static org.mockito.ArgumentMatchers.isNull; |
46 | 47 | import static org.mockito.Mockito.doNothing; |
47 | 48 | import static org.mockito.Mockito.doThrow; |
48 | 49 | import static org.mockito.Mockito.mock; |
49 | 50 | import static org.mockito.Mockito.mockStatic; |
50 | 51 | import static org.mockito.Mockito.never; |
| 52 | +import static org.mockito.Mockito.times; |
51 | 53 | import static org.mockito.Mockito.verify; |
52 | 54 | import static org.mockito.Mockito.verifyNoInteractions; |
53 | 55 | import static org.mockito.Mockito.verifyNoMoreInteractions; |
@@ -187,6 +189,69 @@ void test_data_file_loader_throws_exception_then_give_up_partition() { |
187 | 189 | verify(sourceCoordinator).giveUpPartition(dataFilePartition); |
188 | 190 | } |
189 | 191 |
|
| 192 | + @Test |
| 193 | + void test_when_getPartition_initially_returns_empty_then_retries_and_updates_load_status() { |
| 194 | + final String exportTaskId = UUID.randomUUID().toString(); |
| 195 | + |
| 196 | + final GlobalState loadStatusGlobalState = mock(GlobalState.class); |
| 197 | + final int totalFiles = 5; |
| 198 | + final Map<String, Object> loadStatusMap = new LoadStatus(totalFiles, totalFiles - 2).toMap(); |
| 199 | + when(loadStatusGlobalState.getProgressState()).thenReturn(Optional.of(loadStatusMap)); |
| 200 | + |
| 201 | + // First call returns empty (transient failure), second call returns valid state |
| 202 | + when(sourceCoordinator.getPartition(exportTaskId)) |
| 203 | + .thenReturn(Optional.empty()) |
| 204 | + .thenReturn(Optional.of(loadStatusGlobalState)); |
| 205 | + |
| 206 | + createObjectUnderTest().updateLoadStatus(exportTaskId, Duration.ofSeconds(2)); |
| 207 | + |
| 208 | + verify(sourceCoordinator).saveProgressStateForPartition(any(GlobalState.class), isNull()); |
| 209 | + } |
| 210 | + |
| 211 | + @Test |
| 212 | + void test_when_last_file_loaded_with_stream_enabled_then_creates_stream_trigger_partition() { |
| 213 | + final String exportTaskId = UUID.randomUUID().toString(); |
| 214 | + final String dbIdentifier = UUID.randomUUID().toString(); |
| 215 | + when(sourceConfig.isStreamEnabled()).thenReturn(true); |
| 216 | + when(sourceConfig.getDbIdentifier()).thenReturn(dbIdentifier); |
| 217 | + |
| 218 | + final GlobalState loadStatusGlobalState = mock(GlobalState.class); |
| 219 | + final int totalFiles = 3; |
| 220 | + final Map<String, Object> loadStatusMap = new LoadStatus(totalFiles, totalFiles - 1).toMap(); |
| 221 | + when(loadStatusGlobalState.getProgressState()).thenReturn(Optional.of(loadStatusMap)); |
| 222 | + when(sourceCoordinator.getPartition(exportTaskId)).thenReturn(Optional.of(loadStatusGlobalState)); |
| 223 | + |
| 224 | + createObjectUnderTest().updateLoadStatus(exportTaskId, Duration.ofSeconds(2)); |
| 225 | + |
| 226 | + verify(sourceCoordinator).createPartition(any(GlobalState.class)); |
| 227 | + } |
| 228 | + |
| 229 | + @Test |
| 230 | + void test_when_createPartition_throws_transiently_then_retries_and_partition_is_completed() { |
| 231 | + final String exportTaskId = UUID.randomUUID().toString(); |
| 232 | + final String dbIdentifier = UUID.randomUUID().toString(); |
| 233 | + when(sourceConfig.isStreamEnabled()).thenReturn(true); |
| 234 | + when(sourceConfig.getDbIdentifier()).thenReturn(dbIdentifier); |
| 235 | + |
| 236 | + final GlobalState loadStatusGlobalState = mock(GlobalState.class); |
| 237 | + final int totalFiles = 3; |
| 238 | + final Map<String, Object> loadStatusMap = new LoadStatus(totalFiles, totalFiles - 1).toMap(); |
| 239 | + when(loadStatusGlobalState.getProgressState()).thenReturn(Optional.of(loadStatusMap)); |
| 240 | + when(sourceCoordinator.getPartition(exportTaskId)).thenReturn(Optional.of(loadStatusGlobalState)); |
| 241 | + |
| 242 | + // createPartition throws once then succeeds — verifies retry behavior |
| 243 | + when(sourceCoordinator.createPartition(any(GlobalState.class))) |
| 244 | + .thenThrow(new RuntimeException("DynamoDB error")) |
| 245 | + .thenReturn(true); |
| 246 | + |
| 247 | + createObjectUnderTest().updateLoadStatus(exportTaskId, Duration.ofSeconds(2)); |
| 248 | + |
| 249 | + // saveProgressStateForPartition must be called exactly once — no retry/overshoot |
| 250 | + verify(sourceCoordinator, times(1)).saveProgressStateForPartition(any(GlobalState.class), isNull()); |
| 251 | + // createPartition retried: first threw, second succeeded |
| 252 | + verify(sourceCoordinator, times(2)).createPartition(any(GlobalState.class)); |
| 253 | + } |
| 254 | + |
190 | 255 | @Disabled("Flaky test, needs to be fixed") |
191 | 256 | @Test |
192 | 257 | void test_shutdown() throws InterruptedException { |
|
0 commit comments