From 79d0288b0d3986240ed3bfb04b1a7002edcfcaef Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 19:12:46 +0000 Subject: [PATCH 1/7] adds missing endpoints and polish --- scheduler/boot-scheduler-quartz/README.md | 2 +- scheduler/boot-scheduler-quartz/pom.xml | 4 +++ .../scheduler/quartz/config/WebMvcConfig.java | 3 +- .../job/SchedulerRegistrationService.java | 2 +- .../scheduler/quartz/service/JobsService.java | 26 +++++++++++++---- .../quartz/web/controller/JobsController.java | 28 +++++++++++++++++++ .../src/main/resources/application.properties | 2 ++ .../db/changelog/migration/01-init.xml | 2 +- .../src/main/resources/logback-spring.xml | 1 + .../src/main/resources/templates/index.html | 15 +++++++--- 10 files changed, 72 insertions(+), 13 deletions(-) diff --git a/scheduler/boot-scheduler-quartz/README.md b/scheduler/boot-scheduler-quartz/README.md index ab5d5f363..92e064e07 100644 --- a/scheduler/boot-scheduler-quartz/README.md +++ b/scheduler/boot-scheduler-quartz/README.md @@ -32,4 +32,4 @@ You can also run the application using Maven as follows: * Swagger UI: http://localhost:8080/swagger-ui.html * Actuator Endpoint: http://localhost:8080/actuator * PGAdmin : http://localhost:5050 - +* UI : http://localhost:8080/index diff --git a/scheduler/boot-scheduler-quartz/pom.xml b/scheduler/boot-scheduler-quartz/pom.xml index 9f7ca401b..c154a3a6f 100644 --- a/scheduler/boot-scheduler-quartz/pom.xml +++ b/scheduler/boot-scheduler-quartz/pom.xml @@ -55,6 +55,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/config/WebMvcConfig.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/config/WebMvcConfig.java index 9e49d0845..415963323 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/config/WebMvcConfig.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/config/WebMvcConfig.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -11,7 +12,7 @@ public class WebMvcConfig implements WebMvcConfigurer { private final ApplicationProperties properties; @Override - public void addCorsMappings(CorsRegistry registry) { + public void addCorsMappings(@NonNull CorsRegistry registry) { registry.addMapping(properties.getCors().getPathPattern()) .allowedMethods(properties.getCors().getAllowedMethods()) .allowedHeaders(properties.getCors().getAllowedHeaders()) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java index bf4622112..31824f96e 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java @@ -36,7 +36,7 @@ Trigger triggerOddJob(JobDetail registerOddJob) { .forJob(registerOddJob.getKey()) .startAt(futureDate(10, DateBuilder.IntervalUnit.SECOND)) .withSchedule(simpleSchedule() - .withIntervalInSeconds(60) // Run every 2 seconds + .withIntervalInSeconds(120) // Run every 120 seconds .repeatForever() .withMisfireHandlingInstructionFireNow()) .build(); diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java index 6c5783152..a8a82d830 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java @@ -36,11 +36,6 @@ public JobsService(Scheduler scheduler) { this.scheduler = scheduler; } - public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException { - JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); - scheduler.deleteJob(jobKey); - } - public List getJobs() { List jobList = new ArrayList<>(); try { @@ -113,8 +108,11 @@ private void updateJobCronExpression(ScheduleJob scheduleJob) throws SchedulerEx } private void addJob(ScheduleJob scheduleJob) throws SchedulerException { + // Create TriggerKey for the job TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.jobName(), GROUP_NAME); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + + // Throw exception if the job already exists if (trigger != null) { throw new SchedulerException("job already exists!"); } @@ -123,13 +121,16 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { ScheduleJob withJobId = scheduleJob.withJobId(String.valueOf(SampleJob.JOB_LIST.size() + 1)); SampleJob.JOB_LIST.add(withJobId); + // Build the JobDetail with recovery and durability JobDetail jobDetail = JobBuilder.newJob(SampleJob.class) .withIdentity(withJobId.jobName(), GROUP_NAME) + .withDescription(scheduleJob.desc()) .storeDurably() .requestRecovery() .build(); jobDetail.getJobDataMap().put("scheduleJob", withJobId.jobId()); + // Build the Trigger with Cron expression and associate it with the job CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(withJobId.cronExpression()); trigger = TriggerBuilder.newTrigger() .withIdentity(withJobId.jobName() + "-trigger", GROUP_NAME) @@ -143,4 +144,19 @@ public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException { JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); scheduler.pauseJob(jobKey); } + + public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException { + JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + scheduler.resumeJob(jobKey); + } + + public void runJob(ScheduleJob job) throws SchedulerException { + JobKey jobKey = JobKey.jobKey(job.jobName(), job.jobGroup()); + scheduler.triggerJob(jobKey); + } + + public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException { + JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + scheduler.deleteJob(jobKey); + } } diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java index 735a33dbc..d70ae790c 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java @@ -62,6 +62,34 @@ public Message pauseJob(ScheduleJob job) { return message; } + @PostMapping(value = "/resumeJob") + public Message resumeJob(ScheduleJob job) { + log.info("resumeJob params = {}", job); + Message message = Message.failure(); + try { + jobsService.resumeJob(job); + message = Message.success(); + } catch (Exception e) { + message.setMsg(e.getMessage()); + log.error("resumeJob ex:", e); + } + return message; + } + + @PostMapping(value = "/runJob") + public Message runJob(ScheduleJob job) { + log.info("runJob params = {}", job); + Message message = Message.failure(); + try { + jobsService.runJob(job); + message = Message.success(); + } catch (Exception e) { + message.setMsg(e.getMessage()); + log.error("runJob ex:", e); + } + return message; + } + @DeleteMapping(value = "/deleteJob") public Message deleteJob(ScheduleJob job) { log.info("deleteJob params : {}", job); diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties index de09bdfa6..2aca129e6 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties +++ b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties @@ -14,6 +14,8 @@ spring.jpa.show-sql=false spring.jpa.open-in-view=false spring.data.jpa.repositories.bootstrap-mode=deferred spring.datasource.hikari.auto-commit=false +spring.datasource.hikari.pool-name=HikariPool-${spring.application.name} +spring.datasource.hikari.data-source-properties.ApplicationName=${spring.application.name} spring.jpa.hibernate.ddl-auto=none #spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.jdbc.time_zone=UTC diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/db/changelog/migration/01-init.xml b/scheduler/boot-scheduler-quartz/src/main/resources/db/changelog/migration/01-init.xml index 9d3b8b9b3..5b45d59dd 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/db/changelog/migration/01-init.xml +++ b/scheduler/boot-scheduler-quartz/src/main/resources/db/changelog/migration/01-init.xml @@ -4,6 +4,6 @@ xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd"> + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd"> diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/logback-spring.xml b/scheduler/boot-scheduler-quartz/src/main/resources/logback-spring.xml index c53f88b80..66308a3db 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/logback-spring.xml +++ b/scheduler/boot-scheduler-quartz/src/main/resources/logback-spring.xml @@ -20,5 +20,6 @@ + diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/templates/index.html b/scheduler/boot-scheduler-quartz/src/main/resources/templates/index.html index 26ce47311..dd5b9d5c4 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/templates/index.html +++ b/scheduler/boot-scheduler-quartz/src/main/resources/templates/index.html @@ -10,7 +10,9 @@ .input-group {margin-bottom: 5px;} .title {text-align:center; font-size:30px; margin-top:15px;} .btnCreate {text-align:right; margin:5px 15px;} - .head {border:solid; border-color:#8080805c; border-width:1px;} + .head { + border: 1px solid #8080805c; + } .line { border: 0; height: 1px; @@ -78,21 +80,25 @@
job name +
job group +
cron expression + + id="edit_cron"/>
job status - @@ -100,6 +106,7 @@
job description +
From 55cb4d7ea3e8079bd139c50c9971d2288a504956 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 20:40:42 +0000 Subject: [PATCH 2/7] fix : issue with communicating with API --- scheduler/boot-scheduler-quartz/README.md | 2 +- .../quartz/model/response/ScheduleJob.java | 8 ++- .../scheduler/quartz/service/JobsService.java | 8 +-- .../quartz/web/controller/JobsController.java | 27 ++++++---- .../src/main/resources/application.properties | 16 +++++- .../src/main/resources/static/js/index.js | 53 +++++++++++-------- 6 files changed, 76 insertions(+), 38 deletions(-) diff --git a/scheduler/boot-scheduler-quartz/README.md b/scheduler/boot-scheduler-quartz/README.md index 92e064e07..8defcc552 100644 --- a/scheduler/boot-scheduler-quartz/README.md +++ b/scheduler/boot-scheduler-quartz/README.md @@ -32,4 +32,4 @@ You can also run the application using Maven as follows: * Swagger UI: http://localhost:8080/swagger-ui.html * Actuator Endpoint: http://localhost:8080/actuator * PGAdmin : http://localhost:5050 -* UI : http://localhost:8080/index +* UI: [http://localhost:8080/index](http://localhost:8080/index) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java index 4d104dc08..3a1bffb25 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java @@ -1,8 +1,14 @@ package com.scheduler.quartz.model.response; +import jakarta.validation.constraints.NotBlank; import java.io.Serializable; import lombok.With; public record ScheduleJob( - @With String jobId, String jobName, String jobGroup, String jobStatus, String cronExpression, String desc) + @With String jobId, + @NotBlank(message = "Job Name cant be blank") String jobName, + String jobGroup, + String jobStatus, + String cronExpression, + String desc) implements Serializable {} diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java index a8a82d830..41349d3cd 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java @@ -22,6 +22,7 @@ import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -32,8 +33,8 @@ public class JobsService { private final Scheduler scheduler; public static final String GROUP_NAME = "sample-group"; - public JobsService(Scheduler scheduler) { - this.scheduler = scheduler; + public JobsService(SchedulerFactoryBean schedulerFactoryBean) { + this.scheduler = schedulerFactoryBean.getScheduler(); } public List getJobs() { @@ -134,7 +135,8 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(withJobId.cronExpression()); trigger = TriggerBuilder.newTrigger() .withIdentity(withJobId.jobName() + "-trigger", GROUP_NAME) - .withSchedule(cronScheduleBuilder) + .withDescription(scheduleJob.desc()) + .withSchedule(cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires()) .build(); scheduler.scheduleJob(jobDetail, trigger); diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java index d70ae790c..df968e41b 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java @@ -4,14 +4,13 @@ import com.scheduler.quartz.model.response.JobStatus; import com.scheduler.quartz.model.response.ScheduleJob; import com.scheduler.quartz.service.JobsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.validation.Valid; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.quartz.SchedulerException; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") @@ -35,7 +34,7 @@ List getJobsStatuses() throws SchedulerException { } @PostMapping(value = "/saveOrUpdate") - public Message saveOrUpdate(ScheduleJob job) { + public Message saveOrUpdate(@RequestBody @Valid ScheduleJob job) { log.info("saveOrUpdateJob params : {}", job); Message message = Message.failure(); try { @@ -49,7 +48,7 @@ public Message saveOrUpdate(ScheduleJob job) { } @PostMapping(value = "/pauseJob") - public Message pauseJob(ScheduleJob job) { + public Message pauseJob(@RequestBody @Valid ScheduleJob job) { log.info("pauseJob params = {}", job); Message message = Message.failure(); try { @@ -62,8 +61,12 @@ public Message pauseJob(ScheduleJob job) { return message; } + @Operation(summary = "Resume a scheduled job") + @ApiResponse(responseCode = "200", description = "Job resumed successfully") + @ApiResponse(responseCode = "400", description = "Invalid job parameters") + @ApiResponse(responseCode = "500", description = "Internal server error") @PostMapping(value = "/resumeJob") - public Message resumeJob(ScheduleJob job) { + public Message resumeJob(@RequestBody @Valid ScheduleJob job) { log.info("resumeJob params = {}", job); Message message = Message.failure(); try { @@ -76,8 +79,12 @@ public Message resumeJob(ScheduleJob job) { return message; } + @Operation(summary = "Trigger immediate execution of a job") + @ApiResponse(responseCode = "200", description = "Job triggered successfully") + @ApiResponse(responseCode = "400", description = "Invalid job parameters") + @ApiResponse(responseCode = "500", description = "Internal server error") @PostMapping(value = "/runJob") - public Message runJob(ScheduleJob job) { + public Message runJob(@RequestBody @Valid ScheduleJob job) { log.info("runJob params = {}", job); Message message = Message.failure(); try { @@ -91,7 +98,7 @@ public Message runJob(ScheduleJob job) { } @DeleteMapping(value = "/deleteJob") - public Message deleteJob(ScheduleJob job) { + public Message deleteJob(@RequestBody @Valid ScheduleJob job) { log.info("deleteJob params : {}", job); Message message = Message.failure(); try { diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties index 2aca129e6..b1c0ab8d7 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties +++ b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties @@ -1,9 +1,10 @@ spring.application.name=boot-scheduler-quartz server.port=8080 server.shutdown=graceful -spring.main.allow-bean-definition-overriding=true spring.jmx.enabled=false + spring.mvc.problemdetails.enabled=true +spring.threads.virtual.enabled=true ################ Actuator ##################### management.endpoints.web.exposure.include=configprops,env,health,info,logfile,loggers,metrics,prometheus @@ -36,4 +37,15 @@ spring.quartz.properties.org.quartz.scheduler.instanceName=${spring.application. spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO #spring.quartz.properties.org.quartz.jobStore.useProperties=true spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate -spring.threads.virtual.enabled=true +## Default is RamJobStore, if you want to use JDBC Job Store, you need to set the following properties +spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX +spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ +spring.quartz.properties.org.quartz.jobStore.dataSource=quartzDataSource +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.provider=hikaricp +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.driver=${spring.datasource.driver-class-name} +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.URL=${spring.datasource.url} +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.user=${spring.datasource.username} +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.password=${spring.datasource.password} +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.maxConnections=10 +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.validationQuery=select 1 + diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/static/js/index.js b/scheduler/boot-scheduler-quartz/src/main/resources/static/js/index.js index 9b525d479..6b745e34b 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/static/js/index.js +++ b/scheduler/boot-scheduler-quartz/src/main/resources/static/js/index.js @@ -6,10 +6,11 @@ $(function() { $.ajax({ url: "/api/runJob?t=" + new Date().getTime(), type: "POST", - data: { - "jobName": $("#name_"+jobId).text(), - "jobGroup": $("#group_"+jobId).text() - }, + contentType: "application/json", + data: JSON.stringify({ + jobName: $("#name_"+jobId).text(), + jobGroup: $("#group_"+jobId).text() + }), success: function(res) { if (res.valid) { alert("run success!"); @@ -26,10 +27,11 @@ $(function() { $.ajax({ url: "/api/pauseJob?t=" + new Date().getTime(), type: "POST", - data: { - "jobName": $("#name_"+jobId).text(), - "jobGroup": $("#group_"+jobId).text() - }, + contentType: "application/json", + data: JSON.stringify({ + jobName: $("#name_"+jobId).text(), + jobGroup: $("#group_"+jobId).text() + }), success: function(res) { if (res.valid) { alert("pause success!"); @@ -43,14 +45,15 @@ $(function() { //resume job $(".btnResume").click(function() { - var jobId = $(this).parent().data("id"); + const jobId = $(this).parent().data("id"); $.ajax({ url: "/api/resumeJob?t=" + new Date().getTime(), type: "POST", - data: { - "jobName": $("#name_"+jobId).text(), - "jobGroup": $("#group_"+jobId).text() - }, + contentType: "application/json", + data: JSON.stringify({ + jobName: $("#name_"+jobId).text(), + jobGroup: $("#group_"+jobId).text() + }), success: function(res) { if (res.valid) { alert("resume success!"); @@ -68,10 +71,11 @@ $(function() { $.ajax({ url: "/api/deleteJob?t=" + new Date().getTime(), type: "DELETE", - data: { - "jobName": $("#name_"+jobId).text(), - "jobGroup": $("#group_"+jobId).text() - }, + contentType: "application/json", + data: JSON.stringify({ + jobName: $("#name_"+jobId).text(), + jobGroup: $("#group_"+jobId).text() + }), success: function(res) { if (res.valid) { alert("delete success!"); @@ -107,7 +111,15 @@ $(function() { $.ajax({ url: "/api/saveOrUpdate?t=" + new Date().getTime(), type: "POST", - data: $('#mainForm').serialize(), + contentType: "application/json", + data: JSON.stringify({ + jobId: $("#jobId").val(), + jobName: $("#edit_name").val(), + jobGroup: $("#edit_group").val(), + cronExpression: $("#edit_cron").val(), + jobStatus: $("#edit_status").val(), + desc: $("#edit_desc").val() + }), success: function(res) { if (res.valid) { alert("success!"); @@ -138,6 +150,5 @@ $(function() { $("#myModal").modal("show"); }); - - -}); \ No newline at end of file + +}); From d9138748fe382177f581d29f6228f3916182988e Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 21:11:07 +0000 Subject: [PATCH 3/7] fix : test cases --- .../java/com/scheduler/quartz/job/SampleJob.java | 5 ----- .../quartz/job/SchedulerRegistrationService.java | 4 ++-- .../quartz/model/response/ScheduleJob.java | 6 ++---- .../scheduler/quartz/service/JobsService.java | 16 ++++++++++++---- .../src/main/resources/application.properties | 1 - .../quartz/common/ContainersConfig.java | 16 ++++++++++++++++ .../test/resources/application-test.properties | 1 + 7 files changed, 33 insertions(+), 16 deletions(-) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SampleJob.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SampleJob.java index f32ac281e..788e34553 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SampleJob.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SampleJob.java @@ -1,9 +1,6 @@ package com.scheduler.quartz.job; -import com.scheduler.quartz.model.response.ScheduleJob; import com.scheduler.quartz.service.OddEvenService; -import java.util.ArrayList; -import java.util.List; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; @@ -15,8 +12,6 @@ public class SampleJob implements Job { private final OddEvenService oddEvenService; - public static final List JOB_LIST = new ArrayList<>(); - public SampleJob(OddEvenService oddEvenService) { this.oddEvenService = oddEvenService; } diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java index 31824f96e..2ec5d0f70 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/job/SchedulerRegistrationService.java @@ -6,6 +6,7 @@ import static org.quartz.TriggerBuilder.newTrigger; import com.scheduler.quartz.model.response.ScheduleJob; +import java.util.UUID; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -22,10 +23,9 @@ JobDetail registerOddJob() { .storeDurably() .requestRecovery() .build(); - String jobId = String.valueOf(SampleJob.JOB_LIST.size() + 1); + String jobId = UUID.randomUUID().toString(); ScheduleJob scheduleJob = new ScheduleJob(jobId, "oddEvenJob", GROUP_NAME, null, null, "Sample OddEvenJob"); jobDetail.getJobDataMap().put("scheduleJob", jobId); - SampleJob.JOB_LIST.add(scheduleJob); return jobDetail; } diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java index 3a1bffb25..1f3052b83 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/response/ScheduleJob.java @@ -1,14 +1,12 @@ package com.scheduler.quartz.model.response; import jakarta.validation.constraints.NotBlank; -import java.io.Serializable; import lombok.With; public record ScheduleJob( @With String jobId, - @NotBlank(message = "Job Name cant be blank") String jobName, + @NotBlank(message = "Job Name can't be blank") String jobName, String jobGroup, String jobStatus, String cronExpression, - String desc) - implements Serializable {} + String desc) {} diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java index 41349d3cd..a1b5058e9 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java @@ -10,6 +10,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; @@ -119,13 +120,13 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { } // simulate job info db persist operation - ScheduleJob withJobId = scheduleJob.withJobId(String.valueOf(SampleJob.JOB_LIST.size() + 1)); - SampleJob.JOB_LIST.add(withJobId); + ScheduleJob withJobId = scheduleJob.withJobId(UUID.randomUUID().toString()); // Build the JobDetail with recovery and durability JobDetail jobDetail = JobBuilder.newJob(SampleJob.class) .withIdentity(withJobId.jobName(), GROUP_NAME) - .withDescription(scheduleJob.desc()) + .withDescription( + StringUtils.hasText(scheduleJob.desc()) ? scheduleJob.desc() : "No description provided") .storeDurably() .requestRecovery() .build(); @@ -135,7 +136,8 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(withJobId.cronExpression()); trigger = TriggerBuilder.newTrigger() .withIdentity(withJobId.jobName() + "-trigger", GROUP_NAME) - .withDescription(scheduleJob.desc()) + .withDescription( + StringUtils.hasText(scheduleJob.desc()) ? scheduleJob.desc() : "No description provided") .withSchedule(cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires()) .build(); @@ -149,11 +151,17 @@ public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException { public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException { JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + if (!scheduler.checkExists(jobKey)) { + throw new SchedulerException("Job does not exist"); + } scheduler.resumeJob(jobKey); } public void runJob(ScheduleJob job) throws SchedulerException { JobKey jobKey = JobKey.jobKey(job.jobName(), job.jobGroup()); + if (!scheduler.checkExists(jobKey)) { + throw new SchedulerException("Job does not exist"); + } scheduler.triggerJob(jobKey); } diff --git a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties index b1c0ab8d7..49e523d98 100644 --- a/scheduler/boot-scheduler-quartz/src/main/resources/application.properties +++ b/scheduler/boot-scheduler-quartz/src/main/resources/application.properties @@ -39,7 +39,6 @@ spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate ## Default is RamJobStore, if you want to use JDBC Job Store, you need to set the following properties spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX -spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ spring.quartz.properties.org.quartz.jobStore.dataSource=quartzDataSource spring.quartz.properties.org.quartz.dataSource.quartzDataSource.provider=hikaricp spring.quartz.properties.org.quartz.dataSource.quartzDataSource.driver=${spring.datasource.driver-class-name} diff --git a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/ContainersConfig.java b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/ContainersConfig.java index 690bbfec8..a9c2c9164 100644 --- a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/ContainersConfig.java +++ b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/ContainersConfig.java @@ -3,6 +3,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; +import org.springframework.test.context.DynamicPropertyRegistrar; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.utility.DockerImageName; @@ -14,4 +15,19 @@ public class ContainersConfig { PostgreSQLContainer postgreSQLContainer() { return new PostgreSQLContainer<>(DockerImageName.parse("postgres").withTag("17.2-alpine")); } + + @Bean + DynamicPropertyRegistrar dynamicPropertyRegistrar(PostgreSQLContainer postgreSQLContainer) { + return (registrar) -> { + registrar.add( + "spring.quartz.properties.org.quartz.dataSource.quartzDataSource.URL", + postgreSQLContainer::getJdbcUrl); + registrar.add( + "spring.quartz.properties.org.quartz.dataSource.quartzDataSource.user", + postgreSQLContainer::getUsername); + registrar.add( + "spring.quartz.properties.org.quartz.dataSource.quartzDataSource.password", + postgreSQLContainer::getPassword); + }; + } } diff --git a/scheduler/boot-scheduler-quartz/src/test/resources/application-test.properties b/scheduler/boot-scheduler-quartz/src/test/resources/application-test.properties index e69de29bb..041818925 100644 --- a/scheduler/boot-scheduler-quartz/src/test/resources/application-test.properties +++ b/scheduler/boot-scheduler-quartz/src/test/resources/application-test.properties @@ -0,0 +1 @@ +spring.datasource.driver-class-name=org.postgresql.Driver From b9f6a0dc4ca86d4eb1b461f3e4e1800d7feaf3cf Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 21:13:36 +0000 Subject: [PATCH 4/7] removes wild card import --- .../scheduler/quartz/web/controller/JobsController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java index df968e41b..818b15b8a 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/web/controller/JobsController.java @@ -10,7 +10,12 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; import org.quartz.SchedulerException; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") From 335065476861e78ca25ec9b98065daaf7fa2c1e6 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 21:18:43 +0000 Subject: [PATCH 5/7] implements nitpick comments --- .../scheduler/quartz/service/JobsService.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java index a1b5058e9..8bfae614c 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/service/JobsService.java @@ -116,7 +116,8 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { // Throw exception if the job already exists if (trigger != null) { - throw new SchedulerException("job already exists!"); + throw new SchedulerException( + "Job already exists with name '" + scheduleJob.jobName() + "' in group '" + GROUP_NAME + "'"); } // simulate job info db persist operation @@ -142,31 +143,41 @@ private void addJob(ScheduleJob scheduleJob) throws SchedulerException { .build(); scheduler.scheduleJob(jobDetail, trigger); + JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + log.info("Scheduled job with key: {}", jobKey); } public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException { JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + validateJobExists(jobKey); scheduler.pauseJob(jobKey); + log.info("Paused job with key: {}", jobKey); } public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException { JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); - if (!scheduler.checkExists(jobKey)) { - throw new SchedulerException("Job does not exist"); - } + validateJobExists(jobKey); scheduler.resumeJob(jobKey); + log.info("Resumed job with key: {}", jobKey); } public void runJob(ScheduleJob job) throws SchedulerException { JobKey jobKey = JobKey.jobKey(job.jobName(), job.jobGroup()); - if (!scheduler.checkExists(jobKey)) { - throw new SchedulerException("Job does not exist"); - } + validateJobExists(jobKey); scheduler.triggerJob(jobKey); + log.info("Triggered job with key: {}", jobKey); } public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException { JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup()); + validateJobExists(jobKey); scheduler.deleteJob(jobKey); + log.info("Deleted job with key: {}", jobKey); + } + + private void validateJobExists(JobKey jobKey) throws SchedulerException { + if (!scheduler.checkExists(jobKey)) { + throw new SchedulerException("Job does not exist with key: " + jobKey); + } } } From 101a7c0e84a1e32bf6a100e35c08b4e689c683c4 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 21:32:59 +0000 Subject: [PATCH 6/7] adds integrationTests --- .../common/AbstractIntegrationTest.java | 4 +- .../web/controller/JobsControllerIntTest.java | 209 ++++++++++++++++++ 2 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java diff --git a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/AbstractIntegrationTest.java b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/AbstractIntegrationTest.java index f4d27c1e8..24d714fe7 100644 --- a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/AbstractIntegrationTest.java +++ b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/common/AbstractIntegrationTest.java @@ -8,7 +8,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; @ActiveProfiles({PROFILE_TEST}) @SpringBootTest( @@ -18,7 +18,7 @@ public abstract class AbstractIntegrationTest { @Autowired - protected MockMvc mockMvc; + protected MockMvcTester mockMvcTester; @Autowired protected ObjectMapper objectMapper; diff --git a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java new file mode 100644 index 000000000..3b379b968 --- /dev/null +++ b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java @@ -0,0 +1,209 @@ +package com.scheduler.quartz.web.controller; + +import com.scheduler.quartz.common.AbstractIntegrationTest; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +class JobsControllerIntTest extends AbstractIntegrationTest { + + @Test + void testGetJobs() { + mockMvcTester.get().uri("/api").assertThat().hasStatusOk().hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testGetJobsStatuses() { + mockMvcTester.get().uri("/api/statuses").assertThat().hasStatusOk().hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testSaveOrUpdate() { + String requestBody = + """ + { + "jobName": "SampleJob", + "cronExpression": "0/5 * * * * ?", + "jobId": "12345", + "description": "Test job description" + } + """; + + mockMvcTester + .post() + .uri("/api/saveOrUpdate") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testSaveOrUpdateWithInvalidCronExpression() { + String requestBody = + """ + { + "jobName": "SampleJob", + "cronExpression": "0/5 * * * *", + "jobId": "12345", + "description": "Test job description" + } + """; + + mockMvcTester + .post() + .uri("/api/saveOrUpdate") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testPauseJob() { + String requestBody = + """ + { + "jobName": "SampleJob", + "jobId": "12345" + } + """; + + mockMvcTester + .post() + .uri("/api/pauseJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testPauseJobWithInvalidJobName() { + String requestBody = + """ + { + "jobName": "InvalidJob", + "jobId": "12345" + } + """; + + mockMvcTester + .post() + .uri("/api/pauseJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testResumeJob() { + String requestBody = + """ + { + "jobName": "SampleJob", + "jobId": "12345" + } + """; + mockMvcTester + .post() + .uri("/api/resumeJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testResumeJobWithInvalidJobName() { + String requestBody = + """ + { + "jobName": "InvalidJob", + "jobId": "12345" + } + """; + mockMvcTester + .post() + .uri("/api/resumeJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testRunJob() { + String requestBody = + """ + { + "jobName": "SampleJob", + "jobId": "12345" + } + """; + + mockMvcTester + .post() + .uri("/api/runJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testRunJobWithInvalidJobName() { + String requestBody = + """ + { + "jobName": "InvalidJob", + "jobId": "12345" + } + """; + + mockMvcTester + .post() + .uri("/api/runJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } + + @Test + void testDeleteJob() { + String requestBody = + """ + { + "jobName": "SampleJob", + "jobId": "12345" + } + """; + + mockMvcTester + .delete() + .uri("/api/deleteJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON); + } +} From 4ba0017ca6c74ba1fc2aa6480f782de752bce15c Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 21:57:21 +0000 Subject: [PATCH 7/7] adds assertions --- .../quartz/model/common/Message.java | 4 + .../web/controller/JobsControllerIntTest.java | 99 ++++++++++++++++--- 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/common/Message.java b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/common/Message.java index 07514099e..85c0df633 100644 --- a/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/common/Message.java +++ b/scheduler/boot-scheduler-quartz/src/main/java/com/scheduler/quartz/model/common/Message.java @@ -31,6 +31,10 @@ public static Message success(String msg) { return new Message(true, msg); } + public Message() { + super(); + } + public Message(boolean valid, String msg) { super(); this.valid = valid; diff --git a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java index 3b379b968..1651c13de 100644 --- a/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java +++ b/scheduler/boot-scheduler-quartz/src/test/java/com/scheduler/quartz/web/controller/JobsControllerIntTest.java @@ -1,6 +1,10 @@ package com.scheduler.quartz.web.controller; +import static org.assertj.core.api.Assertions.assertThat; + import com.scheduler.quartz.common.AbstractIntegrationTest; +import com.scheduler.quartz.model.common.Message; +import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -8,25 +12,39 @@ class JobsControllerIntTest extends AbstractIntegrationTest { @Test void testGetJobs() { - mockMvcTester.get().uri("/api").assertThat().hasStatusOk().hasContentType(MediaType.APPLICATION_JSON); + mockMvcTester + .get() + .uri("/api") + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(List.class); } @Test void testGetJobsStatuses() { - mockMvcTester.get().uri("/api/statuses").assertThat().hasStatusOk().hasContentType(MediaType.APPLICATION_JSON); + mockMvcTester + .get() + .uri("/api/statuses") + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(List.class); } @Test void testSaveOrUpdate() { String requestBody = """ - { - "jobName": "SampleJob", - "cronExpression": "0/5 * * * * ?", - "jobId": "12345", - "description": "Test job description" - } - """; + { + "jobName": "SampleJob", + "cronExpression": "0/5 * * * * ?", + "jobId": "12345", + "description": "Test job description" + } + """; mockMvcTester .post() @@ -36,7 +54,14 @@ void testSaveOrUpdate() { .accept(MediaType.APPLICATION_JSON) .assertThat() .hasStatusOk() - .hasContentType(MediaType.APPLICATION_JSON); + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isNull(); + assertThat(message.isValid()).isEqualTo(true); + assertThat(message.getData()).isNull(); + }); } @Test @@ -59,7 +84,12 @@ void testSaveOrUpdateWithInvalidCronExpression() { .accept(MediaType.APPLICATION_JSON) .assertThat() .hasStatusOk() - .hasContentType(MediaType.APPLICATION_JSON); + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isEqualTo("CronExpression '0/5 * * * *' is invalid."); + }); } @Test @@ -101,7 +131,12 @@ void testPauseJobWithInvalidJobName() { .accept(MediaType.APPLICATION_JSON) .assertThat() .hasStatusOk() - .hasContentType(MediaType.APPLICATION_JSON); + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isEqualTo("Job does not exist with key: DEFAULT.InvalidJob"); + }); } @Test @@ -141,7 +176,12 @@ void testResumeJobWithInvalidJobName() { .accept(MediaType.APPLICATION_JSON) .assertThat() .hasStatusOk() - .hasContentType(MediaType.APPLICATION_JSON); + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isEqualTo("Job does not exist with key: DEFAULT.InvalidJob"); + }); } @Test @@ -183,7 +223,12 @@ void testRunJobWithInvalidJobName() { .accept(MediaType.APPLICATION_JSON) .assertThat() .hasStatusOk() - .hasContentType(MediaType.APPLICATION_JSON); + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isEqualTo("Job does not exist with key: DEFAULT.InvalidJob"); + }); } @Test @@ -206,4 +251,30 @@ void testDeleteJob() { .hasStatusOk() .hasContentType(MediaType.APPLICATION_JSON); } + + @Test + void testDeleteJobWithInvalidJobName() { + String requestBody = + """ + { + "jobName": "InvalidJob", + "jobId": "12345" + } + """; + + mockMvcTester + .delete() + .uri("/api/deleteJob") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(Message.class) + .satisfies(message -> { + assertThat(message.getMsg()).isEqualTo("Job does not exist with key: DEFAULT.InvalidJob"); + }); + } }