Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

feat(): On startup, reschedule missed campaign #1252

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.task.TaskExecutorBuilder;
DelaunayAlex marked this conversation as resolved.
Show resolved Hide resolved
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -78,6 +79,11 @@ public TaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.threadNamePrefix("app-task-exec").build();
}

@Bean
public ApplicationRunner scheduledMissedCampaignToExecute(CampaignScheduler campaignScheduler) {
return arg -> campaignScheduler.scheduledMissedCampaignToExecute();
}

/**
* @see ScheduleCampaign#executeScheduledCampaign()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class SchedulingCampaignFileRepository implements PeriodicScheduledCampai
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
private final ReadWriteLock rwLock;

SchedulingCampaignFileRepository(@Value(CONFIGURATION_FOLDER_SPRING_VALUE) String storeFolderPath) throws UncheckedIOException {
public SchedulingCampaignFileRepository(@Value(CONFIGURATION_FOLDER_SPRING_VALUE) String storeFolderPath) throws UncheckedIOException {
DelaunayAlex marked this conversation as resolved.
Show resolved Hide resolved
this.rwLock = new ReentrantReadWriteLock(true);
this.storeFolderPath = Paths.get(storeFolderPath).resolve(ROOT_DIRECTORY_NAME);
this.resolvedFilePath = this.storeFolderPath.resolve(SCHEDULING_CAMPAIGNS_FILE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ private Callable<Void> executeScheduledCampaignById(List<Long> campaignsId) {
};
}

public void scheduledMissedCampaignToExecute() {
scheduledCampaignIdsToExecute().toList();
DelaunayAlex marked this conversation as resolved.
Show resolved Hide resolved
}

synchronized private Stream<List<Long>> scheduledCampaignIdsToExecute() {
try {
return periodicScheduledCampaignRepository.getALl().stream()
Expand All @@ -99,7 +103,11 @@ synchronized private Stream<List<Long>> scheduledCampaignIdsToExecute() {
private void prepareScheduledCampaignForNextExecution(PeriodicScheduledCampaign periodicScheduledCampaign) {
try {
if (!Frequency.EMPTY.equals(periodicScheduledCampaign.frequency)) {
periodicScheduledCampaignRepository.add(periodicScheduledCampaign.nextScheduledExecution());
PeriodicScheduledCampaign periodicScheduledCampaignWithNextSchedule = periodicScheduledCampaign;
while (periodicScheduledCampaignWithNextSchedule.nextExecutionDate.isBefore(LocalDateTime.now(clock))) {
periodicScheduledCampaignWithNextSchedule = periodicScheduledCampaignWithNextSchedule.nextScheduledExecution();
}
periodicScheduledCampaignRepository.add(periodicScheduledCampaignWithNextSchedule);
LOGGER.info("Next execution of scheduled campaign(s) {} with frequency [{}] has been added", periodicScheduledCampaign.campaignsId, periodicScheduledCampaign.frequency);
}
periodicScheduledCampaignRepository.removeById(periodicScheduledCampaign.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
Expand All @@ -32,17 +34,26 @@
import com.chutneytesting.campaign.domain.Frequency;
import com.chutneytesting.campaign.domain.PeriodicScheduledCampaign;
import com.chutneytesting.campaign.domain.PeriodicScheduledCampaignRepository;
import com.chutneytesting.campaign.infra.SchedulingCampaignFileRepository;
import com.chutneytesting.execution.domain.campaign.CampaignExecutionEngine;
import com.chutneytesting.tools.file.FileUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.InOrder;
import org.mockito.Mock;

public class CampaignSchedulerTest {

Expand All @@ -51,11 +62,16 @@ public class CampaignSchedulerTest {
private final CampaignExecutionEngine campaignExecutionEngine = mock(CampaignExecutionEngine.class);
private final PeriodicScheduledCampaignRepository periodicScheduledCampaignRepository = mock(PeriodicScheduledCampaignRepository.class);

private Clock clock;
private static Path SCHEDULING_CAMPAIGN_FILE;
@TempDir
private static Path temporaryFolder;
private final Clock clock = mock(Clock.class);
DelaunayAlex marked this conversation as resolved.
Show resolved Hide resolved

@BeforeEach
public void setUp() {
clock = Clock.systemDefaultZone();
Clock fixedClock = Clock.fixed(LocalDateTime.of(2024, 3, 15, 15, 0, 0).toInstant(ZoneOffset.UTC), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
sut = new CampaignScheduler(campaignExecutionEngine, clock, periodicScheduledCampaignRepository, Executors.newFixedThreadPool(2));
}

Expand Down Expand Up @@ -107,6 +123,69 @@ void should_add_next_execution_when_executing_periodic_scheduled_campaign_except
}
}

@Test
void should_reschedule_missed_campaign() {
String tmpConfDir = temporaryFolder.toFile().getAbsolutePath();
SCHEDULING_CAMPAIGN_FILE = Paths.get(tmpConfDir + "/scheduling/schedulingCampaigns.json");
CampaignScheduler campaignScheduler = new CampaignScheduler(campaignExecutionEngine, clock, new SchedulingCampaignFileRepository(tmpConfDir), Executors.newFixedThreadPool(2));
DelaunayAlex marked this conversation as resolved.
Show resolved Hide resolved

String initialSchedules = """
{
"1" : {
"id" : "1",
"campaignsId" : [ 11 ],
"campaignsTitle" : [ "campaign title 1" ],
"schedulingDate" : [ 2024, 1, 1, 14, 0 ],
"frequency" : "Weekly"
},
"2" : {
"id" : "2",
"campaignsId" : [ 22 ],
"campaignsTitle" : [ "campaign title 2" ],
"schedulingDate" : [ 2023, 3, 4, 7, 10 ],
"frequency" : "Hourly"
},
"3" : {
"id" : "3",
"campaignsId" : [ 33 ],
"campaignsTitle" : [ "campaign title 3" ],
"schedulingDate" : [ 2024, 2, 2, 14, 0 ]
}
}
""";
FileUtils.initFolder(SCHEDULING_CAMPAIGN_FILE.getParent());
FileUtils.writeContent(SCHEDULING_CAMPAIGN_FILE, initialSchedules);


String expectedSchedules =
"""
{
"4" : {
"id" : "4",
"campaignsId" : [ 11 ],
"campaignsTitle" : [ "campaign title 1" ],
"schedulingDate" : [ 2024, 3, 18, 14, 0 ],
"frequency" : "Weekly"
},
"5" : {
"id" : "5",
"campaignsId" : [ 22 ],
"campaignsTitle" : [ "campaign title 2" ],
"schedulingDate" : [ 2024, 3, 15, 16, 10 ],
"frequency" : "Hourly"
}
}
""";

// WHEN
campaignScheduler.scheduledMissedCampaignToExecute();

// THEN
String result = FileUtils.readContent(SCHEDULING_CAMPAIGN_FILE);
assertThat(result).isEqualToIgnoringNewLines(expectedSchedules);

}

@Test
void should_not_explode_when_runtime_exceptions_occur_retrieving_campaigns_to_execute() {
when(periodicScheduledCampaignRepository.getALl())
Expand Down
Loading