diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/RepetitionDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/RepetitionDTO.java new file mode 100644 index 0000000..d5293fd --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/RepetitionDTO.java @@ -0,0 +1,11 @@ +package it.fabioformosa.quartzmanager.api.dto; + +public interface RepetitionDTO { + Integer getRepeatCount(); + + Long getRepeatInterval(); + + void setRepeatCount(Integer repeatCount); + + void setRepeatInterval(Long repeatInterval); +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/SimpleTriggerInputDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/SimpleTriggerInputDTO.java index 7966189..d5e4f24 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/SimpleTriggerInputDTO.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/SimpleTriggerInputDTO.java @@ -1,21 +1,19 @@ package it.fabioformosa.quartzmanager.api.dto; +import it.fabioformosa.quartzmanager.api.validators.ValidRepetition; import lombok.*; import lombok.experimental.SuperBuilder; -import javax.validation.constraints.NotNull; - @SuperBuilder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) @Data @ToString(callSuper = true) -public class SimpleTriggerInputDTO extends TriggerCommandDTO { +@ValidRepetition +public class SimpleTriggerInputDTO extends TriggerCommandDTO implements RepetitionDTO { - @NotNull private Integer repeatCount; - @NotNull private Long repeatInterval; } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/TriggerCommandDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/TriggerCommandDTO.java index e3e0266..7d4d249 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/TriggerCommandDTO.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/dto/TriggerCommandDTO.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; import lombok.experimental.SuperBuilder; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotBlank; import java.util.Date; @SuperBuilder @@ -15,7 +15,7 @@ import java.util.Date; @Data public class TriggerCommandDTO { - @NotNull + @NotBlank private String jobClass; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetition.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetition.java new file mode 100644 index 0000000..2384d25 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetition.java @@ -0,0 +1,19 @@ +package it.fabioformosa.quartzmanager.api.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = ValidRepetitionValidator.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidRepetition { + + String message() default "Invalid repetition values"; + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidator.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidator.java new file mode 100644 index 0000000..578808d --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidator.java @@ -0,0 +1,15 @@ +package it.fabioformosa.quartzmanager.api.validators; + +import it.fabioformosa.quartzmanager.api.dto.RepetitionDTO; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class ValidRepetitionValidator implements ConstraintValidator { + + @Override + public boolean isValid(RepetitionDTO repetitionDTO, ConstraintValidatorContext constraintValidatorContext) { + return (repetitionDTO.getRepeatCount() == null && repetitionDTO.getRepeatInterval() == null) || + (repetitionDTO.getRepeatCount() != null && repetitionDTO.getRepeatInterval() != null); + } +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerTest.java index 7620378..a981950 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerTest.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerTest.java @@ -1,18 +1,16 @@ package it.fabioformosa.quartzmanager.api.controllers; import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests; -import it.fabioformosa.quartzmanager.api.controllers.utils.InvalidSimpleTriggerCommandDTOProvider; +import it.fabioformosa.quartzmanager.api.common.utils.DateUtils; import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils; import it.fabioformosa.quartzmanager.api.controllers.utils.TriggerUtils; +import it.fabioformosa.quartzmanager.api.dto.MisfireInstruction; import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO; import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO; import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerInputDTO; import it.fabioformosa.quartzmanager.api.services.SimpleTriggerService; -import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -55,16 +53,8 @@ class SimpleTriggerControllerTest { } @Test - void givenAnExistingTrigger_whenGetIsCalled_then404IsReturned() throws Exception { - Mockito.when(simpleTriggerService.getSimpleTriggerByName("not_existing_trigger_name")).thenThrow(new TriggerNotFoundException("not_existing_trigger_name")); - - mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/not_existing_trigger_name") - .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isNotFound()); - } - - @Test - void givenASimpleTriggerCommandDTO_whenPosted_thenANewSimpleTriggerIsCreated() throws Exception { - SimpleTriggerInputDTO simpleTriggerInputDTO = buildSimpleTriggerCommandDTO(); + void givenACompleteSimpleTriggerCommandDTO_whenPosted_thenANewSimpleTriggerIsCreated() throws Exception { + SimpleTriggerInputDTO simpleTriggerInputDTO = buildACompleteSimpleTriggerCommandDTO(); SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO); Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO); mockMvc.perform( @@ -77,27 +67,20 @@ class SimpleTriggerControllerTest { ; } - private SimpleTriggerInputDTO buildSimpleTriggerCommandDTO() { + private SimpleTriggerInputDTO buildACompleteSimpleTriggerCommandDTO() { return SimpleTriggerInputDTO.builder() .jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob") .startDate(new Date()) - .repeatCount(20) - .repeatInterval(20000L) + .endDate(DateUtils.addHoursToNow(6)) + .misfireInstruction(MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW) + .repeatCount(5) + .repeatInterval(1000L * 60 * 60) .build(); } - @ParameterizedTest - @ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class) - void givenAnInvalidSimpleTriggerCommandDTO_whenPostedANewTrigger_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerComandDTO) throws Exception { - mockMvc.perform(post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtils.toJson(invalidSimpleTriggerComandDTO))) - .andExpect(MockMvcResultMatchers.status().is4xxClientError()); - } - @Test void givenATriggerName_whenPutSimpleTriggerCommandDTO_thenTheSimpleTriggerIsRescheduled() throws Exception { - SimpleTriggerInputDTO simpleTriggerInputDTO = buildSimpleTriggerCommandDTO(); + SimpleTriggerInputDTO simpleTriggerInputDTO = buildACompleteSimpleTriggerCommandDTO(); SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO); SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder() .triggerName("mytrigger") @@ -112,13 +95,4 @@ class SimpleTriggerControllerTest { .andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO))); } - @ParameterizedTest - @ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class) - void givenAnInvalidSimpleTriggerCommandDTO_whenATriggerIsRescheduled_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerCommandTO) throws Exception { - mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtils.toJson(invalidSimpleTriggerCommandTO))) - .andExpect(MockMvcResultMatchers.status().is4xxClientError()); - } - } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerValidationTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerValidationTest.java new file mode 100644 index 0000000..33e6612 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/SimpleTriggerControllerValidationTest.java @@ -0,0 +1,93 @@ +package it.fabioformosa.quartzmanager.api.controllers; + +import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests; +import it.fabioformosa.quartzmanager.api.controllers.utils.InvalidSimpleTriggerCommandDTOProvider; +import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils; +import it.fabioformosa.quartzmanager.api.controllers.utils.TriggerUtils; +import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO; +import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerInputDTO; +import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException; +import it.fabioformosa.quartzmanager.api.services.SimpleTriggerService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +@ContextConfiguration(classes = {QuartManagerApplicationTests.class}) +@WebMvcTest(controllers = SimpleTriggerController.class, properties = { + "quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs" +}) +class SimpleTriggerControllerValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SimpleTriggerService simpleTriggerService; + + @AfterEach + void cleanUp(){ + Mockito.reset(simpleTriggerService); + } + + + @Test + void givenANotExistingTrigger_whenGetIsCalled_then404IsReturned() throws Exception { + Mockito.when(simpleTriggerService.getSimpleTriggerByName("not_existing_trigger_name")).thenThrow(new TriggerNotFoundException("not_existing_trigger_name")); + + mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/not_existing_trigger_name") + .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + + @Test + void givenAMinimalSimpleTriggerCommandDTO_whenPosted_thenANewSimpleTriggerIsCreated() throws Exception { + SimpleTriggerInputDTO simpleTriggerInputDTO = buildAMinimalSimpleTriggerCommandDTO(); + SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("my-minimal-trigger"); + Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO); + mockMvc.perform( + post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/my-minimal-trigger") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.toJson(simpleTriggerInputDTO)) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO))) + ; + } + + private SimpleTriggerInputDTO buildAMinimalSimpleTriggerCommandDTO() { + return SimpleTriggerInputDTO.builder() + .jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob") + .build(); + } + + @ParameterizedTest + @ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class) + void givenAnInvalidSimpleTriggerCommandDTO_whenPostedANewTrigger_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerComandDTO) throws Exception { + mockMvc.perform(post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.toJson(invalidSimpleTriggerComandDTO))) + .andExpect(MockMvcResultMatchers.status().is4xxClientError()); + } + + @ParameterizedTest + @ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class) + void givenAnInvalidSimpleTriggerCommandDTO_whenATriggerIsRescheduled_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerCommandTO) throws Exception { + mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.toJson(invalidSimpleTriggerCommandTO))) + .andExpect(MockMvcResultMatchers.status().is4xxClientError()); + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/utils/InvalidSimpleTriggerCommandDTOProvider.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/utils/InvalidSimpleTriggerCommandDTOProvider.java index 938ce66..3745f9f 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/utils/InvalidSimpleTriggerCommandDTOProvider.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/controllers/utils/InvalidSimpleTriggerCommandDTOProvider.java @@ -9,7 +9,7 @@ import java.util.stream.Stream; public class InvalidSimpleTriggerCommandDTOProvider implements ArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + public Stream provideArguments(ExtensionContext extensionContext) { return Stream.of( Arguments.of(SimpleTriggerInputDTO.builder().build()), Arguments.of(SimpleTriggerInputDTO.builder().repeatCount(1).build()), diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidatorTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidatorTest.java new file mode 100644 index 0000000..b07f78b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/validators/ValidRepetitionValidatorTest.java @@ -0,0 +1,51 @@ +package it.fabioformosa.quartzmanager.api.validators; + +import it.fabioformosa.quartzmanager.api.dto.RepetitionDTO; +import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerInputDTO; +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ValidRepetitionValidatorTest { + + private ValidRepetitionValidator validRepetitionValidator = new ValidRepetitionValidator(); + + @Test + public void givenACountAndIntervalSet_whenTheValidatorIsCalled_shouldReturnValid() { + RepetitionDTO repetitionDTO = new SimpleTriggerInputDTO(); + repetitionDTO.setRepeatCount(10); + repetitionDTO.setRepeatInterval(1000L); + boolean valid = validRepetitionValidator.isValid(repetitionDTO, null); + Assertions.assertThat(valid).isTrue(); + } + + @Test + public void givenACountAndIntervalUnSet_whenTheValidatorIsCalled_shouldReturnInValid() { + RepetitionDTO repetitionDTO = new SimpleTriggerInputDTO(); + boolean valid = validRepetitionValidator.isValid(repetitionDTO, null); + Assertions.assertThat(valid).isTrue(); + } + + @ParameterizedTest + @CsvSource({"10, ", ",1000"}) + public void givenACountAndIntervalNotSet_whenTheValidatorIsCalled_shouldReturnInValid(String repeatCountStr, String repeatIntervalStr) { + Integer repeatCount = null; + if (StringUtils.isNotBlank(repeatCountStr)) + repeatCount = Integer.valueOf(repeatCountStr); + + Long repeatInterval = null; + if (StringUtils.isNotBlank(repeatIntervalStr)) + repeatInterval = Long.valueOf(repeatIntervalStr); + + RepetitionDTO repetitionDTO = new SimpleTriggerInputDTO(); + repetitionDTO.setRepeatInterval(repeatInterval); + repetitionDTO.setRepeatCount(repeatCount); + + boolean valid = validRepetitionValidator.isValid(repetitionDTO, null); + Assertions.assertThat(valid).isFalse(); + } + + +}