diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js
index 8f57c37c..ada8cbdb 100644
--- a/src/main/resources/static/main.js
+++ b/src/main/resources/static/main.js
@@ -142,6 +142,7 @@ function registerListeners() {
document.getElementById("postTrainingBodyTrustJob").addEventListener("click", runJob);
document.getElementById("personElasticSearchSyncJob").addEventListener("click", runJob);
document.getElementById("personRecordStatusJob").addEventListener("click", runPersonStatusSyncJob);
+ document.getElementById("postFundingSyncJob").addEventListener("click", runJob);
document.getElementById("runAllJobs").addEventListener("click", runAllJobs);
document.getElementById("getStatus").addEventListener("click", getStatus);
document.getElementById("revalCurrentPmJob").addEventListener("click", runJob);
diff --git a/src/test/java/uk/nhs/tis/sync/api/JobResourceTest.java b/src/test/java/uk/nhs/tis/sync/api/JobResourceTest.java
index 929f5a59..8d51d28a 100644
--- a/src/test/java/uk/nhs/tis/sync/api/JobResourceTest.java
+++ b/src/test/java/uk/nhs/tis/sync/api/JobResourceTest.java
@@ -31,6 +31,7 @@
import uk.nhs.tis.sync.job.PersonPlacementTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.PersonRecordStatusJob;
import uk.nhs.tis.sync.job.PostEmployingBodyTrustJob;
+import uk.nhs.tis.sync.job.PostFundingSyncJob;
import uk.nhs.tis.sync.job.PostTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.person.PersonElasticSearchSyncJob;
import uk.nhs.tis.sync.job.reval.RevalCurrentPlacementSyncJob;
@@ -66,6 +67,9 @@ class JobResourceTest {
@MockBean
private RevalCurrentPlacementSyncJob revalCurrentPlacementSyncJob;
+ @MockBean
+ PostFundingSyncJob postFundingSyncJob;
+
private MockMvc mockMvc;
private JobResource jobResource;
@@ -81,6 +85,7 @@ void setup() {
personRecordStatusJob);
jobResource.setRevalCurrentPmSyncJob(revalCurrentPmSyncJob);
jobResource.setRevalCurrentPlacementSyncJob((revalCurrentPlacementSyncJob));
+ jobResource.setPostFundingSyncJob(postFundingSyncJob);
mockMvc = MockMvcBuilders.standaloneSetup(jobResource).build();
}
@@ -103,6 +108,8 @@ void shouldReturnAllStatusWhenGetStatus() throws Exception {
.thenReturn(false);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(false);
+ when(postFundingSyncJob.isCurrentlyRunning())
+ .thenReturn(false);
mockMvc.perform(get("/api/jobs/status")
.contentType(MediaType.APPLICATION_JSON))
@@ -114,6 +121,7 @@ void shouldReturnAllStatusWhenGetStatus() throws Exception {
.andExpect(jsonPath("$.personOwnerRebuildJob").value(false))
.andExpect(jsonPath("$.personRecordStatusJob").value(false))
.andExpect(jsonPath("$.revalCurrentPmJob").value(false))
+ .andExpect(jsonPath("$.postFundingSyncJob").value(false))
.andExpect(status().isOk());
}
@@ -159,7 +167,8 @@ void shouldNotReturnStatusForRevalCurrentSyncWhenJobNotAvailable() throws Except
"personElasticSearchSyncJob",
"personOwnerRebuildJob",
"personRecordStatusJob",
- "revalCurrentPmJob"
+ "revalCurrentPmJob",
+ "postFundingSyncJob"
})
void shouldReturnJustStartedWhenAJobTriggered(String name) throws Exception {
when(personPlacementTrainingBodyTrustJob.isCurrentlyRunning())
@@ -178,6 +187,8 @@ void shouldReturnJustStartedWhenAJobTriggered(String name) throws Exception {
.thenReturn(false);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(false);
+ when(postFundingSyncJob.isCurrentlyRunning())
+ .thenReturn(false);
mockMvc.perform(put("/api/job/" + name)
.contentType(MediaType.APPLICATION_JSON))
@@ -261,7 +272,8 @@ void shouldReturnErrorWhenPersonRecordStatusJobWithInvalidArg(String arg)
"personElasticSearchSyncJob",
"personOwnerRebuildJob",
"personRecordStatusJob",
- "revalCurrentPmJob"
+ "revalCurrentPmJob",
+ "postFundingSyncJob"
})
void shouldReturnAlreadyRunningWhenTriggerARunningJob(String name) throws Exception {
@@ -281,6 +293,8 @@ void shouldReturnAlreadyRunningWhenTriggerARunningJob(String name) throws Except
.thenReturn(true);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(true);
+ when(postFundingSyncJob.isCurrentlyRunning())
+ .thenReturn(true);
mockMvc.perform(put("/api/job/" + name)
.contentType(MediaType.APPLICATION_JSON))
diff --git a/src/test/java/uk/nhs/tis/sync/event/listener/JobRunningListenerTest.java b/src/test/java/uk/nhs/tis/sync/event/listener/JobRunningListenerTest.java
index 28cd8ebc..480529a2 100644
--- a/src/test/java/uk/nhs/tis/sync/event/listener/JobRunningListenerTest.java
+++ b/src/test/java/uk/nhs/tis/sync/event/listener/JobRunningListenerTest.java
@@ -10,6 +10,7 @@
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import uk.nhs.tis.sync.job.PersonOwnerRebuildJob;
+import uk.nhs.tis.sync.job.PostFundingSyncJob;
import uk.nhs.tis.sync.job.person.PersonElasticSearchSyncJob;
import uk.nhs.tis.sync.job.reval.RevalCurrentPmSyncJob;
@@ -26,6 +27,8 @@ public class JobRunningListenerTest {
private PersonOwnerRebuildJob personOwnerRebuildJob;
@MockBean
private RevalCurrentPmSyncJob revalCurrentPmSyncJob;
+ @MockBean
+ private PostFundingSyncJob postFundingSyncJob;
@Autowired
JobRunningListener testClass;
@@ -35,11 +38,14 @@ public void testRunJobs() {
when(personOwnerRebuildJob.isCurrentlyRunning()).thenReturn(false);
when(personElasticSearchSyncJob.isCurrentlyRunning()).thenReturn(false);
when(revalCurrentPmSyncJob.isCurrentlyRunning()).thenReturn(false);
+ when(postFundingSyncJob.isCurrentlyRunning()).thenReturn(false);
testClass.runJobs();
verify(personOwnerRebuildJob).personOwnerRebuildJob();
verify(personOwnerRebuildJob).isCurrentlyRunning();
verify(personElasticSearchSyncJob).personElasticSearchSync();
verify(personElasticSearchSyncJob).isCurrentlyRunning();
verify(revalCurrentPmSyncJob).isCurrentlyRunning();
+ verify(postFundingSyncJob).postFundingSyncJob();
+ verify(postFundingSyncJob).isCurrentlyRunning();
}
}
diff --git a/src/test/java/uk/nhs/tis/sync/job/PostFundingSyncJobTest.java b/src/test/java/uk/nhs/tis/sync/job/PostFundingSyncJobTest.java
new file mode 100644
index 00000000..2979140a
--- /dev/null
+++ b/src/test/java/uk/nhs/tis/sync/job/PostFundingSyncJobTest.java
@@ -0,0 +1,200 @@
+package uk.nhs.tis.sync.job;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.transformuk.hee.tis.tcs.api.enumeration.Status;
+import com.transformuk.hee.tis.tcs.service.model.Post;
+import com.transformuk.hee.tis.tcs.service.model.PostFunding;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Query;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ApplicationEventPublisher;
+import uk.nhs.tis.sync.event.JobExecutionEvent;
+
+@ExtendWith(MockitoExtension.class)
+class PostFundingSyncJobTest {
+
+ @Mock
+ private EntityManager entityManager;
+ @Mock
+ ApplicationEventPublisher applicationEventPublisher;
+ @Mock
+ private EntityManagerFactory entityManagerFactory;
+ @Mock
+ private EntityTransaction transaction;
+ @Mock
+ private Query query;
+
+ private PostFundingSyncJob postFundingSyncJob;
+
+ private static final int DEFAULT_PAGE_SIZE = 5000;
+
+ @BeforeEach
+ void setUp() {
+ postFundingSyncJob = new PostFundingSyncJob();
+ postFundingSyncJob.entityManagerFactory = entityManagerFactory;
+ }
+
+ @Test
+ void testShouldWorkWithBuildQueryForDateMethod() {
+ LocalDate dateOfChange = LocalDate.now();
+ String expectedQuery = " SELECT postId "
+ + " FROM PostFunding "
+ + " WHERE postId > :lastPostId "
+ + " AND startDate IS NOT NULL "
+ + " AND (endDate = '" + dateOfChange.minusDays(1)
+ .format(DateTimeFormatter.ISO_LOCAL_DATE) + "' OR endDate IS NULL) "
+ + " GROUP BY postId "
+ + " ORDER BY postId LIMIT " + DEFAULT_PAGE_SIZE + " ";
+
+ String actualQuery = postFundingSyncJob.buildQueryForDate();
+ assertThat(expectedQuery, is(actualQuery));
+ }
+
+ @Test
+ void testShouldConvertDataWithNoPostsAndFundings() {
+ List
entityData = new ArrayList<>();
+ Set entitiesToSave = new HashSet<>();
+
+ int result = postFundingSyncJob.convertData(entitiesToSave, entityData, entityManager);
+
+ assertThat(result, is(0));
+ assertThat(entitiesToSave.size(), is(0));
+ }
+
+ @Test
+ void testShouldPassWhenAPostHasMultiplePostFundings() {
+ Post post = new Post();
+ post.setId(1L);
+ PostFunding postFunding1 = new PostFunding();
+ postFunding1.setId(999L);
+ PostFunding postFunding2 = new PostFunding();
+ postFunding2.setId(1000L);
+ post.fundingStatus(Status.CURRENT);
+ Set postFundingSet = new HashSet<>(Arrays.asList(postFunding1, postFunding2));
+ post.setFundings(postFundingSet);
+
+ when(entityManager.find(Post.class, post.getId())).thenReturn(post);
+
+ List entityData = new ArrayList<>();
+ entityData.add(1L);
+ Set entitiesToSave = new HashSet<>();
+
+ int result = postFundingSyncJob.convertData(entitiesToSave, entityData, entityManager);
+
+ assertThat(entitiesToSave.size(), is(1));
+ assertThat(result, is(0));
+ assertThat(entitiesToSave.contains(post), is(true));
+ assertThat(post.getFundingStatus(), is(Status.CURRENT));
+ }
+
+ @Test
+ void testJobShouldChangeCurrentPostFundingToInactive() {
+ Post post = new Post();
+ post.setId(1L);
+ PostFunding postFunding = new PostFunding();
+ postFunding.setId(999L);
+ post.fundingStatus(Status.CURRENT);
+ Set postFundingSet = Collections.singleton(postFunding);
+ post.setFundings(postFundingSet);
+
+ when(entityManager.find(Post.class, post.getId())).thenReturn(post);
+
+ List entityData = Arrays.asList(1L);
+
+ Set entitiesToSave = new HashSet<>();
+ int result = postFundingSyncJob.convertData(entitiesToSave, entityData, entityManager);
+
+ assertThat(result, is(0));
+ assertThat(entitiesToSave.contains(post), is(true));
+ assertThat(post.getFundingStatus(), is(Status.INACTIVE));
+ }
+
+ @Test
+ void testShouldBeSuccessfulWithHandleDataMethod() {
+ Post post = new Post();
+ Set dataToSave = new HashSet<>();
+ dataToSave.add(post);
+
+ postFundingSyncJob.handleData(dataToSave, entityManager);
+
+ verify(entityManager, times(1)).persist(post);
+ verify(entityManager, times(1)).flush();
+ }
+
+ @Test
+ void testDoDataSync()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ when(entityManagerFactory.createEntityManager()).thenReturn(entityManager);
+ when(entityManager.getTransaction()).thenReturn(transaction);
+ when(transaction.isActive()).thenReturn(false);
+ when(entityManager.createNativeQuery(ArgumentMatchers.any())).thenReturn(query);
+ when(query.setParameter(eq("lastPostId"), ArgumentMatchers.any())).thenReturn(query);
+ when(query.getResultList())
+ .thenReturn(Arrays.asList(BigInteger.ONE, BigInteger.valueOf(2), BigInteger.valueOf(3)))
+ .thenReturn(new ArrayList<>());
+
+ Method method = PostFundingSyncJob.class.getDeclaredMethod("doDataSync");
+ method.setAccessible(true);
+ method.invoke(postFundingSyncJob);
+
+ verify(query, times(2)).getResultList();
+ }
+
+ @Test
+ void testPublishJobExecutionEvent()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ postFundingSyncJob.applicationEventPublisher = applicationEventPublisher;
+
+ JobExecutionEvent event = new JobExecutionEvent(this, "Post funding sync job");
+
+ Method method = PostFundingSyncJob.class
+ .getDeclaredMethod("publishJobexecutionEvent", JobExecutionEvent.class);
+ method.setAccessible(true);
+ method.invoke(postFundingSyncJob, event);
+
+ ArgumentCaptor eventCaptor = ArgumentCaptor
+ .forClass(JobExecutionEvent.class);
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture());
+
+ JobExecutionEvent capturedEvent = eventCaptor.getValue();
+ assertThat(capturedEvent.getMessage(), containsString("Post funding sync job"));
+ }
+
+ @Test
+ public void testRun() {
+ PostFundingSyncJob spyJob = spy(postFundingSyncJob);
+ doNothing().when(spyJob).postFundingSyncJob();
+
+ spyJob.run(null);
+
+ verify(spyJob).postFundingSyncJob();
+ }
+}
diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml
index f49d76a8..df1dc8a3 100644
--- a/src/test/resources/config/application.yml
+++ b/src/test/resources/config/application.yml
@@ -96,6 +96,7 @@ application:
revalCurrentPmJob: ${APPLICATION_CRON_REVALCURRENTPMJOB:0 39 1 * * *}
revalCurrentPlacementJob: ${APPLICATION_CRON_REVALCURRENTPLACEMENTJOB:0 49 1 * * *}
recordResendingJob: ${APPLICATION_CRON_RECORDRESENDINGJOB:0 * * * * *}
+ postFundingSyncJob: ${APPLICATION_CRON_POSTFUNDINGSYNCJOB:0 0 3 * * *}
jobs:
runOnStartup:
earliest: ${APPLICATION_JOBS_RUNONSTARTUP_EARLIEST:05:01}