Merge pull request #97 from fabioformosa/develop

v4.0.6
This commit is contained in:
Fabio Formosa
2022-12-03 17:17:00 +01:00
committed by GitHub
33 changed files with 730 additions and 209 deletions

View File

@@ -10,7 +10,7 @@
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
<packaging>pom</packaging>
@@ -69,27 +69,27 @@
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-api</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-security</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-persistence</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-ui</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -128,6 +128,19 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<configuration>
<excludes>
<exclude>**/OpenApiConfig.class</exclude>
<exclude>**/SecurityOpenApiConfig.class</exclude>
<exclude>**/QuartzModuleProperties.class</exclude>
<exclude>**/QuartManagerDemoApplication.class</exclude>
<exclude>**/ServletInitializer.class</exclude>
<exclude>**/SessionController.class</exclude>
<exclude>**/HealthCheckController.class</exclude>
<exclude>**/WebShowcaseOpenApiConfig.class</exclude>
<exclude>**/MisfireTestJob.class</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-common</artifactId>

View File

@@ -1,13 +1,20 @@
package it.fabioformosa.quartzmanager.api.common.utils;
import lombok.Getter;
import java.util.function.Function;
/**
*
* @param <R> success type
*/
@Getter
public class Try<R> {
private final Throwable failure;
private final R success;
public Try(Throwable failure, R success) {
private Try(Throwable failure, R success) {
this.failure = failure;
this.success = success;
}
@@ -16,11 +23,11 @@ public class Try<R> {
return success;
}
public static <R> Try<R> success(R r){
private static <R> Try<R> success(R r){
return new Try<>(null, r);
}
public static <R> Try<R> failure(Throwable e){
private static <R> Try<R> failure(Throwable e){
return new Try<>(e, null);
}
@@ -38,14 +45,6 @@ public class Try<R> {
return t -> Try.with(checkedFunction).apply(t).getSuccess();
}
public boolean isSuccess(){
return this.failure == null;
}
public boolean isFailure(){
return this.failure != null;
}
@FunctionalInterface
public static interface CheckedFunction<T, R> {
R apply(T t) throws java.lang.Exception;

View File

@@ -0,0 +1,42 @@
package it.fabioformosa.quartzmanager.api.common.utils;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Optional;
class TryTest {
String raiseExceptionIfHello(String greetings) throws Exception {
if("hello".equals(greetings))
throw new Exception("hello");
return greetings;
}
@Test
void givenAFunctionWhichRaisesAnException_whenSneakyThrowIsCalled_thenItReturnsNull(){
String hello = Optional.of("hello").map(Try.sneakyThrow(this::raiseExceptionIfHello)).orElse(null);
Assertions.assertThat(hello).isNull();
}
@Test
void givenAFunctionWhichDoesntRaiseAnException_whenSneakyThrowIsCalled_thenItReturnsTheValue(){
String hello = Optional.of("not hello").map(Try.sneakyThrow(this::raiseExceptionIfHello)).orElse(null);
Assertions.assertThat(hello).isEqualTo("not hello");
}
@Test
void givenAFunctionWhichRaisesAnException_whenTryWithIsCalled_thenItReturnsAFailureObj(){
Try<String> aTry = Optional.of("hello").map(greet -> Try.with(this::raiseExceptionIfHello).apply(greet)).get();
Assertions.assertThat(aTry.getFailure()).isNotNull();
Assertions.assertThat(aTry.getFailure().getMessage()).isEqualTo("hello");
}
@Test
void givenAFunctionWhichDoesntRaiseAnException_whenTryWithIsCalled_thenItReturnsTheValue(){
Try<String> aTry = Optional.of("not hello").map(greet -> Try.with(this::raiseExceptionIfHello).apply(greet)).get();
Assertions.assertThat(aTry.getFailure()).isNull();
Assertions.assertThat(aTry.getSuccess()).isEqualTo("not hello");
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-api</artifactId>
@@ -20,7 +20,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
<java.version>9</java.version>
<sonar.exclusions>**/QuartManagerApplicationTests.java</sonar.exclusions>
<sonar.exclusions>**/QuartManagerApplicationTests.java, **/OpenApiConfig.java</sonar.exclusions>
</properties>
<dependencies>

View File

@@ -16,10 +16,11 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RequestMapping(QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/jobs")
@RequestMapping(JobController.JOB_CONTROLLER_BASE_URL)
@SecurityRequirement(name = OpenAPIConfigConsts.QUARTZ_MANAGER_SEC_OAS_SCHEMA)
@RestController
public class JobController {
public static final String JOB_CONTROLLER_BASE_URL = QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/jobs";
private final JobService jobService;
public JobController(JobService jobService) {

View File

@@ -17,7 +17,8 @@ public class SchedulerToSchedulerDTO extends AbstractBaseConverterToDTO<Schedule
protected void convert(Scheduler source, SchedulerDTO target) {
target.setName(source.getSchedulerName());
target.setInstanceId(source.getSchedulerInstanceId());
target.setTriggerKeys(source.getTriggerKeys(GroupMatcher.anyTriggerGroup()));
if(!source.isShutdown())
target.setTriggerKeys(source.getTriggerKeys(GroupMatcher.anyTriggerGroup()));
target.setStatus(buildTheSchedulerStatus(source));
}

View File

@@ -2,6 +2,7 @@ package it.fabioformosa.quartzmanager.api.dto;
import it.fabioformosa.quartzmanager.api.enums.SchedulerStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.quartz.TriggerKey;
@@ -11,6 +12,7 @@ import java.util.Set;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class SchedulerDTO {
private String name;
private String instanceId;

View File

@@ -1,7 +1,13 @@
package it.fabioformosa.quartzmanager.api.dto;
import lombok.*;
import java.util.Date;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TriggerFiredBundleDTO {
private int timesTriggered;
@@ -18,22 +24,6 @@ public class TriggerFiredBundleDTO {
private String jobClass;
public Date getFinalFireTime() {
return finalFireTime;
}
public String getJobClass() {
return jobClass;
}
public String getJobKey() {
return jobKey;
}
public Date getNextFireTime() {
return nextFireTime;
}
public int getPercentage() {
if (this.repeatCount <= 0)
return -1;
@@ -41,44 +31,4 @@ public class TriggerFiredBundleDTO {
* 100);
}
public Date getPreviousFireTime() {
return previousFireTime;
}
public int getRepeatCount() {
return repeatCount;
}
public int getTimesTriggered() {
return timesTriggered;
}
public void setFinalFireTime(Date finalFireTime) {
this.finalFireTime = finalFireTime;
}
public void setJobClass(String jobClass) {
this.jobClass = jobClass;
}
public void setJobKey(String jobKey) {
this.jobKey = jobKey;
}
public void setNextFireTime(Date nextFireTime) {
this.nextFireTime = nextFireTime;
}
public void setPreviousFireTime(Date previousFireTime) {
this.previousFireTime = previousFireTime;
}
public void setRepeatCount(int repeatCount) {
this.repeatCount = repeatCount;
}
public void setTimesTriggered(int timesTriggered) {
this.timesTriggered = timesTriggered;
}
}

View File

@@ -8,11 +8,8 @@ public class ResourceConflictException extends RuntimeException {
private static final long serialVersionUID = 1791564636123821405L;
private final Long resourceId;
public ResourceConflictException(Long resourceId, String message) {
super(message);
this.resourceId = resourceId;
super("Conflict on resourceID " + resourceId + " " + message);
}
}

View File

@@ -1,16 +1,7 @@
package it.fabioformosa.quartzmanager.api.exceptions;
import lombok.Getter;
import lombok.ToString;
@ToString
@Getter
public class TriggerNotFoundException extends Exception {
private final String name;
public TriggerNotFoundException(String name) {
super("Trigger with name " + name + " not found!");
this.name = name;
}
}

View File

@@ -1,5 +1,8 @@
package it.fabioformosa.quartzmanager.api.jobs.entities;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
/**
@@ -8,6 +11,8 @@ import java.util.Date;
* @author Fabio.Formosa
*
*/
@Data
@ToString
public class LogRecord {
public enum LogType {
@@ -27,41 +32,4 @@ public class LogRecord {
date = new Date();
}
public Date getDate() {
return date;
}
public String getMessage() {
return message;
}
public String getThreadName() {
return threadName;
}
public LogType getType() {
return type;
}
public void setDate(Date date) {
this.date = date;
}
public void setMessage(String msg) {
message = msg;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public void setType(LogType type) {
this.type = type;
}
@Override
public String toString() {
return "LogRecord [date=" + date + ", type=" + type + ", message=" + message + "]";
}
}

View File

@@ -1,7 +1,6 @@
package it.fabioformosa.quartzmanager.api.services;
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO;
import it.fabioformosa.quartzmanager.api.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO;
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
import org.quartz.*;
@@ -34,7 +33,7 @@ public class SimpleTriggerService extends AbstractSchedulerService {
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
}
public TriggerDTO rescheduleSimpleTrigger(SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException {
public SimpleTriggerDTO rescheduleSimpleTrigger(SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException {
SimpleTrigger newSimpleTrigger = conversionService.convert(triggerCommandDTO, SimpleTrigger.class);
TriggerKey triggerKey = TriggerKey.triggerKey(triggerCommandDTO.getTriggerName());

View File

@@ -43,7 +43,7 @@ public class WebSocketProgressNotifier implements WebhookSender<TriggerFiredBund
JobDetail jobDetail = jobExecutionContext.getJobDetail();
triggerFiredBundleDTO.setJobKey(jobDetail.getKey().getName());
triggerFiredBundleDTO.setJobClass(trigger.getClass().getSimpleName());
triggerFiredBundleDTO.setJobClass(jobDetail.getJobClass().getName());
return triggerFiredBundleDTO;
}

View File

@@ -7,8 +7,10 @@ import org.quartz.Scheduler;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
class SchedulerConfigTest {
@@ -16,11 +18,8 @@ class SchedulerConfigTest {
public static final String QUARTZ_SCHEDULER_DEFAULT_NAME = "QuartzScheduler";
@Test
void givenASchedulerName_whenTheSchedulerIsInstatiated_thenTheSchedulerHasThatName() throws Exception {
QuartzModuleProperties quartzModuleProperties = new QuartzModuleProperties();
quartzModuleProperties.getProperties().put("org.quartz.scheduler.instanceName", TEST_SCHEDULER_NAME);
List<QuartzModuleProperties> quartzModulePropertiesList = new ArrayList<>();
quartzModulePropertiesList.add(quartzModuleProperties);
void givenASchedulerName_whenTheSchedulerIsInstantiated_thenTheSchedulerHasThatName() throws Exception {
List<QuartzModuleProperties> quartzModulePropertiesList = getQuartzModulePropertiesWithASchedulerName(TEST_SCHEDULER_NAME);
SchedulerConfig schedulerConfig = new SchedulerConfig(quartzModulePropertiesList);
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
@@ -31,8 +30,16 @@ class SchedulerConfigTest {
Assertions.assertThat(scheduler.getSchedulerName()).isEqualTo(TEST_SCHEDULER_NAME);
}
private static List<QuartzModuleProperties> getQuartzModulePropertiesWithASchedulerName(String schedulerName) {
QuartzModuleProperties quartzModuleProperties = new QuartzModuleProperties();
quartzModuleProperties.getProperties().put("org.quartz.scheduler.instanceName", schedulerName);
List<QuartzModuleProperties> quartzModulePropertiesList = new ArrayList<>();
quartzModulePropertiesList.add(quartzModuleProperties);
return quartzModulePropertiesList;
}
@Test
void givenNoSchedulerName_whenTheSchedulerIsInstatiated_thenTheSchedulerHasTheDefaultName() throws Exception {
void givenNoSchedulerName_whenTheSchedulerIsInstantiated_thenTheSchedulerHasTheDefaultName() throws Exception {
QuartzModuleProperties quartzModuleProperties = new QuartzModuleProperties();
List<QuartzModuleProperties> quartzModulePropertiesList = new ArrayList<>();
quartzModulePropertiesList.add(quartzModuleProperties);
@@ -46,4 +53,32 @@ class SchedulerConfigTest {
Assertions.assertThat(scheduler.getSchedulerName()).isEqualTo(QUARTZ_SCHEDULER_DEFAULT_NAME);
}
@Test
void givenAManagedProperties_whenTheSchedulerIsInstantiated_thenTheManagedPropsHavePriority() throws Exception {
List<QuartzModuleProperties> quartzModulePropertiesList = getQuartzModulePropertiesWithASchedulerName(TEST_SCHEDULER_NAME);
SchedulerConfig schedulerConfig = new SchedulerConfig(quartzModulePropertiesList);
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
Properties managedProps = new Properties();
String overridden_scheduler_name = "OVERRIDDEN_SCHEDULER_NAME";
managedProps.put("org.quartz.scheduler.instanceName", overridden_scheduler_name);
SchedulerFactoryBean schedulerFactoryBean = schedulerConfig.schedulerFactoryBean(schedulerConfig.jobFactory(applicationContext), managedProps);
schedulerFactoryBean.afterPropertiesSet();
Scheduler scheduler = schedulerFactoryBean.getScheduler();
Assertions.assertThat(scheduler.getSchedulerName()).isEqualTo(overridden_scheduler_name);
}
@Test
void givenAnEmptyManagedPropFile_whenSchedulerConfigRuns_thenItReturnsAnEmptyPropList() throws IOException {
List<QuartzModuleProperties> quartzModulePropertiesList = getQuartzModulePropertiesWithASchedulerName(TEST_SCHEDULER_NAME);
SchedulerConfig schedulerConfig = new SchedulerConfig(quartzModulePropertiesList);
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
Properties properties = schedulerConfig.quartzProperties();
Assertions.assertThat(properties).isEmpty();
}
}

View File

@@ -0,0 +1,46 @@
package it.fabioformosa.quartzmanager.api.controllers;
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils;
import it.fabioformosa.quartzmanager.api.jobs.SampleJob;
import it.fabioformosa.quartzmanager.api.services.JobService;
import org.junit.jupiter.api.Test;
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 java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
})
class JobControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private JobService jobService;
@Test
void whenGetListIsCalled_thenTheSimpleJobIsReturned() throws Exception {
Mockito.when(jobService.getJobClasses()).thenReturn(List.of(SampleJob.class));
List<String> expectedJobs = List.of(SampleJob.class.getName());
mockMvc.perform(get(JobController.JOB_CONTROLLER_BASE_URL)
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedJobs)));
Mockito.verify(jobService, Mockito.times(1)).getJobClasses();
}
}

View File

@@ -0,0 +1,28 @@
package it.fabioformosa.quartzmanager.api.controllers;
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
})
class ResourceConflictControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void whenAResourceConflictExceptionIsRaised_thenTheExceptionHandlerReturns409() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post(TestController.TEST_CONTROLLER_BASE_URL + "/test-conflict")
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isConflict());
}
}

View File

@@ -0,0 +1,89 @@
package it.fabioformosa.quartzmanager.api.controllers;
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils;
import it.fabioformosa.quartzmanager.api.dto.SchedulerDTO;
import it.fabioformosa.quartzmanager.api.enums.SchedulerStatus;
import it.fabioformosa.quartzmanager.api.services.SchedulerService;
import org.junit.jupiter.api.Test;
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.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
})
class SchedulerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SchedulerService schedulerService;
@Test
void whenTheGetIsCalled_thenTheSchedulerServiceIsReturned() throws Exception {
SchedulerDTO schedulerDTO = SchedulerDTO.builder()
.name("TEST_SCHEDULER")
.instanceId("testSchedulerId")
.status(SchedulerStatus.STOPPED)
.build();
Mockito.when(schedulerService.getScheduler()).thenReturn(schedulerDTO);
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(schedulerDTO)));
Mockito.verify(schedulerService).getScheduler();
}
@Test
void givenAScheduler_whenTheGetPausedIsCalled_then2xxReturned() throws Exception {
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/pause")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isNoContent())
.andExpect(MockMvcResultMatchers.content().string(""));
Mockito.verify(schedulerService).standby();
}
@Test
void givenAScheduler_whenTheGetResumedIsCalled_then2xxReturned() throws Exception {
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/resume")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isNoContent())
.andExpect(MockMvcResultMatchers.content().string(""));
Mockito.verify(schedulerService).start();
}
@Test
void givenAScheduler_whenTheGetRunIsCalled_then2xxReturned() throws Exception {
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/run")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isNoContent())
.andExpect(MockMvcResultMatchers.content().string(""));
Mockito.verify(schedulerService).start();
}
@Test
void givenAScheduler_whenTheGetStoppedIsCalled_then2xxReturned() throws Exception {
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/stop")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isNoContent())
.andExpect(MockMvcResultMatchers.content().string(""));
Mockito.verify(schedulerService).shutdown();
}
}

View File

@@ -0,0 +1,19 @@
package it.fabioformosa.quartzmanager.api.controllers;
import it.fabioformosa.quartzmanager.api.exceptions.ResourceConflictException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping(TestController.TEST_CONTROLLER_BASE_URL)
@RestController
public class TestController {
public static final String TEST_CONTROLLER_BASE_URL = "/test-controller";
@PostMapping("/test-conflict")
public void raiseConflictException(){
throw new ResourceConflictException(1000L, "another entity has found with the same ID");
}
}

View File

@@ -0,0 +1,71 @@
package it.fabioformosa.quartzmanager.api.converters;
import it.fabioformosa.quartzmanager.api.dto.SchedulerDTO;
import it.fabioformosa.quartzmanager.api.enums.SchedulerStatus;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.convert.ConversionService;
import org.springframework.test.annotation.DirtiesContext;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@SpringBootTest
class SchedulerToSchedulerDTOTest {
@Autowired
private Scheduler scheduler;
@Autowired
private ConversionService conversionService;
@Order(1)
@Test
void givenAnActiveScheduler_whenItIsConverted_thenADtoIsReturned () throws SchedulerException {
Assertions.assertThat(scheduler.isShutdown()).isFalse();
SchedulerDTO schedulerDTO = conversionService.convert(scheduler, SchedulerDTO.class);
Assertions.assertThat(schedulerDTO).isNotNull();
Assertions.assertThat(schedulerDTO.getName()).isEqualTo(scheduler.getSchedulerName());
Assertions.assertThat(schedulerDTO.getInstanceId()).isEqualTo(scheduler.getSchedulerInstanceId());
}
@Order(2)
@Test
void givenAnActiveScheduler_whenItIsConverted_thenADtoHasAStatus () throws SchedulerException {
scheduler.start();
Assertions.assertThat(scheduler.isStarted()).isTrue();
SchedulerDTO schedulerDTO = conversionService.convert(scheduler, SchedulerDTO.class);
Assertions.assertThat(schedulerDTO.getStatus()).isEqualTo(SchedulerStatus.RUNNING);
scheduler.standby();
Assertions.assertThat(scheduler.isInStandbyMode()).isTrue();
schedulerDTO = conversionService.convert(scheduler, SchedulerDTO.class);
Assertions.assertThat(schedulerDTO.getStatus()).isEqualTo(SchedulerStatus.PAUSED);
}
@DirtiesContext
@Order(3)
@Test
void givenASchedulerInShutdown_whenItIsConverted_thenADtoIsReturnedWithNoTriggers () throws SchedulerException {
Assertions.assertThat(scheduler.isShutdown()).isFalse();
scheduler.shutdown(false);
Assertions.assertThat(scheduler.isShutdown()).isTrue();
SchedulerDTO schedulerDTO = conversionService.convert(scheduler, SchedulerDTO.class);
Assertions.assertThat(schedulerDTO).isNotNull();
Assertions.assertThat(schedulerDTO.getName()).isEqualTo(scheduler.getSchedulerName());
Assertions.assertThat(schedulerDTO.getInstanceId()).isEqualTo(scheduler.getSchedulerInstanceId());
Assertions.assertThat(schedulerDTO.getTriggerKeys()).isNull();
Assertions.assertThat(schedulerDTO.getStatus()).isEqualTo(SchedulerStatus.STOPPED);
}
}

View File

@@ -0,0 +1,33 @@
package it.fabioformosa.quartzmanager.api.dto;
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;
import static org.junit.jupiter.api.Assertions.*;
class TriggerFiredBundleDTOTest {
@CsvSource({
"10, 100, 10",
"23, 1000, 2",
"26, 1000, 3"
})
@ParameterizedTest
void givenARepeatCount_whenTheTriggerHasFiredXTimes_thenThePercentageIsCalculatedAccordingly(int timesTriggered, int repeatCount, int expectedPercentage){
TriggerFiredBundleDTO triggerFiredBundleDTO = TriggerFiredBundleDTO.builder().build();
triggerFiredBundleDTO.setTimesTriggered(timesTriggered);
triggerFiredBundleDTO.setRepeatCount(repeatCount);
Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isEqualTo(expectedPercentage);
}
@Test
void givenAnInfiniteRecursion_whenTheTriggerHasFired10_thenThePercentageIsMinus1(){
TriggerFiredBundleDTO triggerFiredBundleDTO = TriggerFiredBundleDTO.builder().build();
triggerFiredBundleDTO.setTimesTriggered(10);
triggerFiredBundleDTO.setRepeatCount(-1);
Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isEqualTo(-1);
}
}

View File

@@ -0,0 +1,79 @@
package it.fabioformosa.quartzmanager.api.jobs;
import it.fabioformosa.quartzmanager.api.dto.TriggerFiredBundleDTO;
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
import it.fabioformosa.quartzmanager.api.websockets.WebhookSender;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.quartz.*;
import static org.mockito.ArgumentMatchers.argThat;
class SampleJobTest {
@InjectMocks
private SampleJob sampleJob;
@Mock
private WebhookSender<TriggerFiredBundleDTO> webSocketProgressNotifier;
@Mock
private WebhookSender<LogRecord> webSocketLogsNotifier;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void givenASampleJob_whenTheJobIsExecuted_thenTheWebhookSendersAreCalled() {
JobExecutionContext jobExecutionContext = Mockito.mock(JobExecutionContext.class);
ScheduleBuilder schedulerBuilder = SimpleScheduleBuilder.simpleSchedule()
.withRepeatCount(5)
.withIntervalInMilliseconds(1000L);
JobDetail jobDetail = JobBuilder
.newJob(SampleJob.class).withIdentity(JobKey.jobKey("test-job"))
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withSchedule(schedulerBuilder)
.build();
Mockito.when(jobExecutionContext.getTrigger()).thenReturn(trigger);
Mockito.when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail);
sampleJob.execute(jobExecutionContext);
Mockito.verify(webSocketLogsNotifier).send(argThat(actualLogRecord -> {
Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!");
Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO);
Assertions.assertThat(actualLogRecord.getDate()).isNotNull();
Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull();
return true;
}));
Mockito.verify(webSocketProgressNotifier).send(argThat(triggerFiredBundleDTO -> {
Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job");
Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6);
Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName());
Assertions.assertThat(triggerFiredBundleDTO.getTimesTriggered()).isZero();
Assertions.assertThat(triggerFiredBundleDTO.getNextFireTime()).isNull();
Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isZero();
Assertions.assertThat(triggerFiredBundleDTO.getFinalFireTime()).isNotNull();
Assertions.assertThat(triggerFiredBundleDTO.getPreviousFireTime()).isNull();
return true;
}));
}
@Test
void givenASampleJob_whenTheDoItMethodIsCalled_thenALogRecordIsReturned() {
JobExecutionContext jobExecutionContext = Mockito.mock(JobExecutionContext.class);
LogRecord logRecord = sampleJob.doIt(jobExecutionContext);
Assertions.assertThat(logRecord.getMessage()).isEqualTo("Hello!");
Assertions.assertThat(logRecord.getType()).isEqualTo(LogRecord.LogType.INFO);
Assertions.assertThat(logRecord.getDate()).isNotNull();
}
}

View File

@@ -0,0 +1,57 @@
package it.fabioformosa.quartzmanager.api.services;
import it.fabioformosa.quartzmanager.api.dto.SchedulerDTO;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@SpringBootTest
class SchedulerServiceIntegrationTest {
@Autowired
private SchedulerService schedulerService;
@Autowired
private Scheduler scheduler;
@Test
void givenASchedulerService_whenGetSchedulerIsCalled_thenReturnIt() throws SchedulerException {
SchedulerDTO schedulerDTO = schedulerService.getScheduler();
Assertions.assertThat(schedulerDTO).isNotNull();
Assertions.assertThat(schedulerDTO.getName()).isEqualTo(scheduler.getSchedulerName());
}
@Order(1)
@Test
void givenASchedulerService_whenTheStatusIsChange_thenTheSchedulerReflectsTheSame() throws SchedulerException {
Assertions.assertThat(scheduler.isStarted()).isFalse();
schedulerService.start();
Assertions.assertThat(scheduler.isStarted()).isTrue();
}
@Order(2)
@Test
void givenASchedulerService_whenStandByIsCalled_thenTheStandByIsPropagated() throws SchedulerException {
Assertions.assertThat(scheduler.isInStandbyMode()).isFalse();
schedulerService.standby();
Assertions.assertThat(scheduler.isInStandbyMode()).isTrue();
}
@Order(3)
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
@Test
void givenASchedulerService_whenShutdownIsCalled_thenTheShutdownIsPropagated() throws SchedulerException {
Assertions.assertThat(scheduler.isShutdown()).isFalse();
schedulerService.start();
schedulerService.shutdown();
Assertions.assertThat(scheduler.isShutdown()).isTrue();
}
}

View File

@@ -0,0 +1,63 @@
package it.fabioformosa.quartzmanager.api.services;
import it.fabioformosa.quartzmanager.api.dto.SchedulerDTO;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.core.convert.ConversionService;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.MockitoAnnotations.openMocks;
class SchedulerServiceTest {
@InjectMocks
private SchedulerService schedulerService;
@Mock
private Scheduler scheduler;
@Mock
private ConversionService conversionService;
@BeforeEach
void setUp() {
openMocks(this);
}
@Test
void givenASchedulerService_whenGetSchedulerIsCalled_thenReturnIt(){
Mockito.when(conversionService.convert(any(Scheduler.class), eq(SchedulerDTO.class))).thenReturn(SchedulerDTO.builder()
.name("testScheduler")
.build());
SchedulerDTO schedulerDTO = schedulerService.getScheduler();
Assertions.assertThat(schedulerDTO).isNotNull();
Assertions.assertThat(schedulerDTO.getName()).isEqualTo("testScheduler");
}
@Test
void givenASchedulerService_whenStandByIsCalled_thenTheStandByIsPropagated() throws SchedulerException {
schedulerService.standby();
Mockito.verify(scheduler).standby();
}
@Test
void givenASchedulerService_whenShutdownIsCalled_thenTheShutdownIsPropagated() throws SchedulerException {
schedulerService.shutdown();
Mockito.verify(scheduler).shutdown(true);
}
@Test
void givenASchedulerService_whenStarted_thenTheSchedulerIsStarted() throws SchedulerException {
schedulerService.start();
Mockito.verify(scheduler).start();
}
}

View File

@@ -9,9 +9,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.*;
import org.springframework.core.convert.ConversionService;
import java.util.Date;
@@ -45,6 +43,21 @@ class SimpleTriggerServiceTest {
Assertions.assertThat(throwable).isInstanceOf(TriggerNotFoundException.class);
}
@Test
void givenAnExistingTrigger_whenGetSimplerTriggerByNameIsCalled_thenTheDtoIsReturned() throws SchedulerException, TriggerNotFoundException {
String existing_trigger = "existing_trigger";
Mockito.when(scheduler.getTrigger(any(TriggerKey.class)))
.thenReturn(TriggerBuilder.newTrigger().withIdentity(existing_trigger).build());
Mockito.when(conversionService.convert(any(SimpleTrigger.class), eq(SimpleTriggerDTO.class)))
.thenReturn(SimpleTriggerDTO.builder()
.triggerKeyDTO(TriggerKeyDTO.builder().name(existing_trigger).build())
.build());
SimpleTriggerDTO simpleTriggerByName = simpleSchedulerService.getSimpleTriggerByName(existing_trigger);
Assertions.assertThat(simpleTriggerByName.getTriggerKeyDTO().getName()).isEqualTo(existing_trigger);
}
@Test
void givenASimpleTriggerCommandDTO_whenASimpleTriggerIsScheduled_thenATriggerDTOIsReturned() throws SchedulerException, ClassNotFoundException {
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
@@ -79,4 +92,41 @@ class SimpleTriggerServiceTest {
Assertions.assertThat(simpleTrigger).isEqualTo(expectedTriggerDTO);
}
@Test
void givenASimpleTriggerCommandDTO_whenASimpleTriggerIsRecheduled_thenATriggerDTOIsReturned() throws SchedulerException, ClassNotFoundException {
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
.jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob")
.startDate(new Date())
.repeatInterval(5000L).repeatCount(5)
.endDate(DateUtils.addHoursToNow(1))
.build();
String simpleTriggerName = "simpleTrigger";
SimpleTriggerDTO expectedTriggerDTO = SimpleTriggerDTO.builder()
.startTime(triggerInputDTO.getStartDate())
.repeatInterval(1000)
.repeatCount(10)
.mayFireAgain(true)
.finalFireTime(triggerInputDTO.getEndDate())
.jobKeyDTO(JobKeyDTO.builder().name("MyJob").build())
.misfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW)
.triggerKeyDTO(TriggerKeyDTO.builder().name(simpleTriggerName).build())
.build();
Mockito.when(scheduler.rescheduleJob(any(), any())).thenReturn(new Date());
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
.triggerName(simpleTriggerName)
.simpleTriggerInputDTO(triggerInputDTO)
.build();
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
Assertions.assertThat(simpleTrigger).isEqualTo(expectedTriggerDTO);
Mockito.verify(scheduler).rescheduleJob(any(), any());
Mockito.verify(conversionService).convert(any(), eq(SimpleTriggerDTO.class));
}
}

View File

@@ -0,0 +1,50 @@
package it.fabioformosa.quartzmanager.api.services;
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
class TriggerServiceTest {
@InjectMocks
private TriggerService triggerService;
@Mock
private Scheduler scheduler;
@Mock
private ConversionService conversionService;
@BeforeEach
void setUp(){
MockitoAnnotations.openMocks(this);
}
@Test
void givenATrigger_whenTheyAreFecthed_TheServiceReturnsTheDtos() throws SchedulerException {
String triggerTestName = "triggerTest";
Mockito.when(scheduler.getTriggerKeys(any())).thenReturn(Set.of(TriggerKey.triggerKey(triggerTestName)));
Mockito.when(conversionService.convert(any(Set.class), any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(List.of(TriggerKeyDTO.builder().name(triggerTestName).build()));
List<TriggerKeyDTO> triggerKeyDTOs = triggerService.fetchTriggers();
Assertions.assertThat(triggerKeyDTOs).hasSize(1);
Assertions.assertThat(triggerKeyDTOs.get(0).getName()).isEqualTo(triggerTestName);
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-persistence</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-security</artifactId>

View File

@@ -1,51 +0,0 @@
package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class AjaxAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public class AjaxLoginAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
response.setStatus(HttpServletResponse.SC_OK);
super.clearAuthenticationAttributes(request);
}
}
public AjaxAuthenticationFilter(AuthenticationManager authenticationManager) {
setAuthenticationManager(authenticationManager);
setUsernameParameter("ajaxUsername");
setPasswordParameter("ajaxPassword");
setPostOnly(true);
setFilterProcessesUrl("/ajaxLogin");
setAuthenticationSuccessHandler(new AjaxLoginAuthSuccessHandler());
}
/**
* Removes temporary authentication-related data which may have been stored
* in the session during the authentication process.
*/
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null)
return;
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}

View File

@@ -1,7 +1,9 @@
package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import lombok.EqualsAndHashCode;
import org.springframework.security.authentication.AbstractAuthenticationToken;
@EqualsAndHashCode
public class AnonAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
@@ -9,15 +11,6 @@ public class AnonAuthentication extends AbstractAuthenticationToken {
super( null );
}
@Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
return getClass() == obj.getClass();
}
@Override
public Object getCredentials() {
return null;
@@ -28,11 +21,6 @@ public class AnonAuthentication extends AbstractAuthenticationToken {
return null;
}
@Override
public int hashCode() {
return 7;
}
@Override
public boolean isAuthenticated() {
return true;

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-ui</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.5</version>
<version>4.0.6-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-web-showcase</artifactId>