Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FACT-2041 added changes for cron to delete old letters DO NOT MERGE #2667

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion charts/rpe-send-letter-service/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: rpe-send-letter-service
apiVersion: v2
home: https://github.com/hmcts/send-letter-service
version: 0.4.25
version: 0.4.26
description: HMCTS Send letter service
maintainers:
- name: HMCTS BSP Team
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ private Flags() {
}

public static final String SEND_LETTER_SERVICE_TEST = "send-letter-service-test";
public static final String SEND_LETTER_SERVICE_DELETE_LETTERS_CRON = "send-letter-service-delete-letters-cron";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package uk.gov.hmcts.reform.sendletter.tasks;

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import uk.gov.hmcts.reform.sendletter.launchdarkly.LaunchDarklyClient;

import java.util.Optional;

import static uk.gov.hmcts.reform.sendletter.launchdarkly.Flags.SEND_LETTER_SERVICE_DELETE_LETTERS_CRON;
import static uk.gov.hmcts.reform.sendletter.util.TimeZones.EUROPE_LONDON;

/**
* Deletes old letters from the database based on a hardcoded SQL query.
* This task runs in all environments.
*/
@Component
public class DeleteOldLettersTask {

private static final Logger logger = LoggerFactory.getLogger(DeleteOldLettersTask.class);

private static final String TASK_NAME = "DeleteOldLetters";

private final JdbcTemplate jdbcTemplate;

private final LaunchDarklyClient launchDarklyClient;

@Value("${delete-old-letters.batch-size:1000}")
private int batchSize;

@Value("${delete-old-letters.civil-general-applications-interval:6 years}")
private String civilGeneralApplicationsInterval;

@Value("${delete-old-letters.civil-service-interval:6 years}")
private String civilServiceInterval;

@Value("${delete-old-letters.cmc-claim-store-interval:2 years}")
private String cmcClaimStoreInterval;

@Value("${delete-old-letters.divorce-frontend-interval:3 months}")
private String divorceFrontendInterval;

@Value("${delete-old-letters.finrem-case-orchestration-interval:3 months}")
private String finremCaseOrchestrationInterval;

@Value("${delete-old-letters.finrem-document-generator-interval:3 months}")
private String finremDocumentGeneratorInterval;

@Value("${delete-old-letters.fpl-case-service-interval:2 years}")
private String fplCaseServiceInterval;

@Value("${delete-old-letters.nfdiv-case-api-interval:3 months}")
private String nfdivCaseApiInterval;

@Value("${delete-old-letters.prl-cos-api-interval:18 years}")
private String prlCosApiInterval;

@Value("${delete-old-letters.probate-backend-interval:1 year}")
private String probateBackendInterval;

@Value("${delete-old-letters.send-letter-tests-interval:2 years}")
private String sendLetterTestsInterval;

@Value("${delete-old-letters.sscs-interval:3 months}")
private String sscsInterval;

// See V028__Add_batch_delete_letters_function.sql for sql function
private final String deleteQuery = """
SELECT batch_delete_letters(
?, -- Batch size
CAST(? AS INTERVAL), -- civil_general_applications_interval
CAST(? AS INTERVAL), -- civil_service_interval
CAST(? AS INTERVAL), -- cmc_claim_store_interval
CAST(? AS INTERVAL), -- divorce_frontend_interval
CAST(? AS INTERVAL), -- finrem_case_orchestration_interval
CAST(? AS INTERVAL), -- finrem_document_generator_interval
CAST(? AS INTERVAL), -- fpl_case_service_interval
CAST(? AS INTERVAL), -- nfdiv_case_api_interval
CAST(? AS INTERVAL), -- prl_cos_api_interval
CAST(? AS INTERVAL), -- probate_backend_interval
CAST(? AS INTERVAL), -- send_letter_tests_interval
CAST(? AS INTERVAL) -- sscs_interval
);
""";

/**
* Constructor for the DeleteOldLettersTask.
* @param jdbcTemplate The JDBC template for running SQL queries.
*/
public DeleteOldLettersTask(JdbcTemplate jdbcTemplate, LaunchDarklyClient launchDarklyClient) {
this.jdbcTemplate = jdbcTemplate;
this.launchDarklyClient = launchDarklyClient;
}

/**
* Deletes old letters from the database in batches based on the batch_delete_letters query.
* default cron: Every Saturday at 5 PM
*/
@SchedulerLock(name = TASK_NAME)
@Scheduled(cron = "${delete-old-letters.cron:0 0 17 ? * SAT}", zone = EUROPE_LONDON)
public void run() {
logger.info("Starting {} task", TASK_NAME);
if (launchDarklyClient.isFeatureEnabled(SEND_LETTER_SERVICE_DELETE_LETTERS_CRON)) {
logger.info("Flag enabled. Task {} running", TASK_NAME);

int totalRowsDeleted = 0;
int rowsDeleted;

try {
do {
rowsDeleted = Optional.ofNullable(
jdbcTemplate.queryForObject(
deleteQuery,
new Object[]{
batchSize,
civilGeneralApplicationsInterval, // civil_general_applications_interval
civilServiceInterval, // civil_service_interval
cmcClaimStoreInterval, // cmc_claim_store_interval
divorceFrontendInterval, // divorce_frontend_interval
finremCaseOrchestrationInterval, // finrem_case_orchestration_interval
finremDocumentGeneratorInterval, // finrem_document_generator_interval
fplCaseServiceInterval, // fpl_case_service_interval
nfdivCaseApiInterval, // nfdiv_case_api_interval
prlCosApiInterval, // prl_cos_api_interval
probateBackendInterval, // probate_backend_interval
sendLetterTestsInterval, // send_letter_tests_interval
sscsInterval // sscs_interval
},
Integer.class
)
).orElse(0);
totalRowsDeleted += rowsDeleted;
logger.info("Batch deleted: {} rows, Total deleted: {} rows", rowsDeleted, totalRowsDeleted);
} while (rowsDeleted > 0);
logger.info("{} task completed. Total rows deleted: {}", TASK_NAME, totalRowsDeleted);
} catch (Exception e) {
logger.error("Error occurred during {} task", TASK_NAME, e);
}
} else {
logger.info("Flag disabled. Task {} did not run", TASK_NAME);
}

}
}
19 changes: 19 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,22 @@ documents:
launchdarkly:
sdk-key: ${LAUNCH_DARKLY_SDK_KEY:XXXXX}
offline-mode: ${LAUNCH_DARKLY_OFFLINE_MODE:false}

#(0 */2 * * * ?) replace cron with this to test, will run every 2 minutes
delete-old-letters:
cron: ${DELETE_OLD_LETTERS_CRON:0 0 17 ? * SAT}
batch-size: ${BATCH_SIZE:1000}
civil-general-applications-interval: ${CIVIL_GENERAL_APPLICATIONS_INTERVAL:6 years}
civil-service-interval: ${CIVIL_SERVICE_INTERVAL:6 years}
cmc-claim-store-interval: ${CMC_CLAIM_STORE_INTERVAL:2 years}
divorce-frontend-interval: ${DIVORCE_FRONTEND_INTERVAL:3 months}
finrem-case-orchestration-interval: ${FINREM_CASE_ORCHESTRATION_INTERVAL:3 months}
finrem-document-generator-interval: ${FINREM_DOCUMENT_GENERATOR_INTERVAL:3 months}
fpl-case-service-interval: ${FPL_CASE_SERVICE_INTERVAL:2 years}
nfdiv-case-api-interval: ${NFDIV_CASE_API_INTERVAL:3 months}
prl-cos-api-interval: ${PRL_COS_API_INTERVAL:18 years}
probate-backend-interval: ${PROBATE_BACKEND_INTERVAL:1 year}
send-letter-tests-interval: ${SEND_LETTER_TESTS_INTERVAL:2 years}
sscs-interval: ${SSCS_INTERVAL:3 months}


Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package uk.gov.hmcts.reform.sendletter.tasks;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.jdbc.core.JdbcTemplate;
import uk.gov.hmcts.reform.sendletter.launchdarkly.LaunchDarklyClient;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static uk.gov.hmcts.reform.sendletter.launchdarkly.Flags.SEND_LETTER_SERVICE_DELETE_LETTERS_CRON;

class DeleteOldLettersTaskTest {

@Mock
private JdbcTemplate jdbcTemplate;

@Mock
private LaunchDarklyClient launchDarklyClient;

private DeleteOldLettersTask deleteOldLettersTask;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
deleteOldLettersTask = new DeleteOldLettersTask(jdbcTemplate, launchDarklyClient);
}

@Test
void testRunWhenFeatureFlagIsEnabled() {
// Arrange: Mock LaunchDarkly feature flag to return true
when(launchDarklyClient.isFeatureEnabled(SEND_LETTER_SERVICE_DELETE_LETTERS_CRON)).thenReturn(true);

// Mock JdbcTemplate behavior for the deletion loop
when(jdbcTemplate.queryForObject(anyString(), any(Object[].class), eq(Integer.class)))
.thenReturn(100)
.thenReturn(50)
.thenReturn(0); // Simulate 3 batches with 100, 50 rows deleted, and then no more rows

// Act: Call the run method
deleteOldLettersTask.run();

// Assert: Check if the correct number of deletions were made
verify(jdbcTemplate, times(3)).queryForObject(anyString(), any(Object[].class), eq(Integer.class));
}

@Test
void testRunWhenFeatureFlagIsDisabled() {
// Arrange: Mock LaunchDarkly feature flag to return false
when(launchDarklyClient.isFeatureEnabled(SEND_LETTER_SERVICE_DELETE_LETTERS_CRON)).thenReturn(false);

// Act: Call the run method
deleteOldLettersTask.run();

// Assert: Verify no deletion attempt is made
verify(jdbcTemplate, never()).queryForObject(anyString(), any(Object[].class), eq(Integer.class));
}

@Test
void testRunWhenErrorOccurs() {
// Arrange: Mock LaunchDarkly feature flag to return true
when(launchDarklyClient.isFeatureEnabled(SEND_LETTER_SERVICE_DELETE_LETTERS_CRON)).thenReturn(true);

// Mock JdbcTemplate to throw an exception
when(jdbcTemplate.queryForObject(anyString(), any(Object[].class), eq(Integer.class)))
.thenThrow(new RuntimeException("Database error"));

// Act: Call the run method
deleteOldLettersTask.run();

}
}