|
21 | 21 | import org.junit.jupiter.api.Assertions; |
22 | 22 | import org.junit.jupiter.api.Test; |
23 | 23 |
|
| 24 | +import org.springframework.batch.core.ExitStatus; |
24 | 25 | import org.springframework.batch.core.job.JobExecution; |
25 | 26 | import org.springframework.batch.core.job.JobInstance; |
26 | 27 | import org.springframework.batch.core.job.parameters.JobParameters; |
27 | 28 | import org.springframework.batch.core.repository.JobRepository; |
28 | 29 | import org.springframework.batch.core.repository.support.ResourcelessJobRepository; |
| 30 | +import org.springframework.batch.core.step.FatalStepExecutionException; |
29 | 31 | import org.springframework.batch.core.step.StepExecution; |
30 | 32 | import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; |
31 | 33 | import org.springframework.batch.core.step.builder.StepBuilder; |
| 34 | +import org.springframework.batch.core.step.skip.NeverSkipItemSkipPolicy; |
| 35 | +import org.springframework.batch.core.step.skip.NonSkippableProcessException; |
32 | 36 | import org.springframework.batch.infrastructure.item.ItemProcessor; |
33 | 37 | import org.springframework.batch.infrastructure.item.ItemReader; |
34 | 38 | import org.springframework.batch.infrastructure.item.ItemWriter; |
35 | 39 | import org.springframework.batch.infrastructure.item.support.ListItemReader; |
36 | 40 | import org.springframework.batch.infrastructure.item.support.ListItemWriter; |
37 | 41 | import org.springframework.batch.infrastructure.support.transaction.ResourcelessTransactionManager; |
| 42 | +import org.springframework.core.retry.RetryPolicy; |
38 | 43 | import org.springframework.core.task.SimpleAsyncTaskExecutor; |
39 | 44 |
|
40 | 45 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 46 | +import static org.junit.jupiter.api.Assertions.assertInstanceOf; |
41 | 47 | import static org.mockito.Mockito.mock; |
42 | 48 | import static org.mockito.Mockito.times; |
43 | 49 | import static org.mockito.Mockito.verify; |
@@ -215,4 +221,50 @@ void testExplicitRetryConfigurationTakesPrecedence() throws Exception { |
215 | 221 | "RuntimeException should not be retried when only IllegalStateException is configured"); |
216 | 222 | } |
217 | 223 |
|
| 224 | + @Test |
| 225 | + void testDoSkipInProcessShouldThrowNonSkippableProcessExceptionWhenSkipPolicyReturnsFalse() throws Exception { |
| 226 | + // given - fault-tolerant step with NeverSkipItemSkipPolicy and retry limit |
| 227 | + ItemReader<String> reader = new ListItemReader<>(List.of("item1", "item2", "item3")); |
| 228 | + |
| 229 | + ItemProcessor<String, String> processor = item -> { |
| 230 | + if ("item2".equals(item)) { |
| 231 | + throw new RuntimeException("Processing failed for item2"); |
| 232 | + } |
| 233 | + return item.toUpperCase(); |
| 234 | + }; |
| 235 | + |
| 236 | + ItemWriter<String> writer = chunk -> { |
| 237 | + }; |
| 238 | + |
| 239 | + JobRepository jobRepository = new ResourcelessJobRepository(); |
| 240 | + ChunkOrientedStep<String, String> step = new ChunkOrientedStep<>("step", 3, reader, writer, jobRepository); |
| 241 | + step.setItemProcessor(processor); |
| 242 | + step.setFaultTolerant(true); |
| 243 | + step.setRetryPolicy(RetryPolicy.withMaxRetries(1)); // retry once (initial + 1 |
| 244 | + // retry) |
| 245 | + step.setSkipPolicy(new NeverSkipItemSkipPolicy()); // never skip |
| 246 | + step.afterPropertiesSet(); |
| 247 | + |
| 248 | + JobInstance jobInstance = new JobInstance(1L, "job"); |
| 249 | + JobExecution jobExecution = new JobExecution(1L, jobInstance, new JobParameters()); |
| 250 | + StepExecution stepExecution = new StepExecution(1L, "step", jobExecution); |
| 251 | + |
| 252 | + // when - execute step |
| 253 | + step.execute(stepExecution); |
| 254 | + |
| 255 | + // then - should fail with FatalStepExecutionException having |
| 256 | + // NonSkippableProcessException as cause |
| 257 | + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); |
| 258 | + assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); |
| 259 | + Throwable throwable = stepExecution.getFailureExceptions().get(0); |
| 260 | + assertInstanceOf(FatalStepExecutionException.class, throwable, |
| 261 | + "Expected FatalStepExecutionException when skip policy rejects skipping"); |
| 262 | + Throwable cause = throwable.getCause(); |
| 263 | + assertInstanceOf(NonSkippableProcessException.class, cause, |
| 264 | + "Expected NonSkippableProcessException as cause when skip policy rejects skipping"); |
| 265 | + assertEquals("Skip policy rejected skipping item", cause.getMessage()); |
| 266 | + assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode()); |
| 267 | + assertEquals(0, stepExecution.getProcessSkipCount(), "Process skip count should be 0"); |
| 268 | + } |
| 269 | + |
218 | 270 | } |
0 commit comments