From 8ae669cbf29a80718f90e7f43a3b6726a7dfb1f6 Mon Sep 17 00:00:00 2001 From: "ruth.bovell" Date: Mon, 20 Jul 2026 15:28:34 +0100 Subject: [PATCH 1/2] Add DB migration to add updated terms and conditions to database --- .../migration/V054__AddDynatraceToAppTermsAndConditions.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/resources/db/migration/V054__AddDynatraceToAppTermsAndConditions.sql diff --git a/src/main/resources/db/migration/V054__AddDynatraceToAppTermsAndConditions.sql b/src/main/resources/db/migration/V054__AddDynatraceToAppTermsAndConditions.sql new file mode 100644 index 000000000..9fd3a5af4 --- /dev/null +++ b/src/main/resources/db/migration/V054__AddDynatraceToAppTermsAndConditions.sql @@ -0,0 +1,6 @@ +INSERT into public.terms_and_conditions VALUES +( + gen_random_uuid(), + E'

Pre-Recorded Evidence (PRE) Service

\n

Terms & Conditions (including Acceptable Use)
(All Users)

\n

July 2026

\n\n

Introduction

\nThis PRE Service is provided by HM Courts and Tribunals Service (HMCTS) Unauthorised use is a criminal offence under the Computer Misuse Act 1990, and you should disconnect immediately if you have not been authorised to use this system. Unauthorised access is prevented by two factor authentication.\n

The PRE Service provides access to pre-recorded cross examination recordings. It is supplied to individual users (The User) in accordance with the following Terms & Conditions.

\n

The User understands that use of the Service will be taken as their acceptance of these Terms & Conditions and that they are fully aware of their responsibilities in relation to the use of the service as set out in the Terms & Conditions on the page below.

\n\n\n

Cookies

\n

We use cookies to help us understand how the service is being used and to improve it. Cookies we use:

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NamePurposeExpires
dtCookie dtPC dtSa rxVisitor rxtvPlease see the Dynatrace website for up-to-date information on the usage of each of these cookies.Please see the Dynatrace website for up-to-date information on the expiry of these cookies.
\n

Terms and Conditions

\n
    \n
  1. \n I have a legitimate need to use the PRE Service, and will only access recordings associated with media of cases where I have a business need to do so.\n
  2. \n
  3. \n I will comply with UK Data Protection Act 2018, relevant privacy regulations and all professional codes of conduct by which I am bound and will ensure all information accessed through the PRE Service is treated accordingly. I acknowledge that any breach of these provisions may result in my access to the PRE Service being suspended or terminated. Any breach may also result in disciplinary action.\n
  4. \n
  5. \n I will seek to prevent inadvertent disclosure of information by taking care when viewing the recording on the PRE Service. I will make every effort to ensure my screen is not visible to others who do not have a legitimate reason to see the recording.\n
  6. \n
  7. \n I agree to be accountable for any use of the PRE Service using my unique user credentials (user ID, password, log-in) and e-mail address. As such, I understand that:\n
      \n
    1. \n I must protect my PRE Service credentials for access to the service.\n
    2. \n
    3. \n I must not share my PRE Service credentials.\n
    4. \n
    5. \n I must report actual or suspected disclosure of this information to HMCTS through the local court.\n
    6. \n
    7. \n I will not use another person’s credentials to access the PRE Service.\n
    8. \n
    \n
  8. \n
  9. \n I will ensure that computing devices connected to the PRE Service will not be left unattended unless they are physically secure and have a clear password locked screen.\n
  10. \n
  11. \n I will take precautions to protect all computer media and portable computers/devices that will be used to access the PRE Service at all times (e.g., by not leaving a device unattended or on display in a public space).\n
  12. \n
  13. \n I will not share any video recording or other content accessed via the PRE Service with any other party or persons, unless they have a legal right to view the recording.\n
  14. \n
  15. \n I will not access the PRE Service from public shared devices (e.g. those in internet cafes).\n
  16. \n
  17. \n I will only access the PRE Service from devices which have appropriate security controls installed and which are maintained to be up to date (including, as appropriate, firewalls, anti-virus & spyware software and operating system security patches).\n
  18. \n
  19. \n I will not attempt to bypass or subvert system security controls.\n
  20. \n
  21. \n When using Wi-Fi, I will only access the PRE Service using secure internet connection or using secure internet service. I will not ‘trust’ or ‘accept’ invalid security for web site certificates.\n
  22. \n
  23. \n I confirm that I will only connect to the PRE Service from within the United Kingdom and will not attempt to access the PRE Service from a location that is outside the United Kingdom, without prior authorisation.\n
  24. \n
  25. \n I understand that HMCTS and the Ministry of Justice (MOJ) reserves the right to audit my usage and investigate security incidents and confirm that, should such an investigation be necessary, I will provide any necessary support to the best of my ability.\n
  26. \n
  27. \n I agree to report any data losses, breaches or security incidents by contacting the DTS Service Desk and Line Manager immediately.\n
  28. \n
  29. \n In the event of a suspected breach of these Terms & Conditions HMCTS reserves the right to investigate and if a breach has occurred, to impose appropriate sanctions. This can range from a warning and instructions to improve practice, to temporary suspension or reduction in the service availability, to the potential complete withdrawal of service should the breach impact adversely users of the PRE Service, and other associated services.\n
  30. \n
  31. \n I will use the PRE Service in accordance with the appropriate user guides and agree to notify the helpdesk and line manager immediately if there is any change in my circumstances or role that affect my access to the Pre Service. This includes (but is not limited to) changes to my circumstances or role so that certain levels of access are no longer appropriate. I will inform the helpdesk and line manager prior to leaving my role in order that my account may be deleted.\n
  32. \n
\n\n

Declaration:

\n

I declare that I am fully aware of my responsibilities in relation to the use of the Service as set out in the above Terms & Conditions.

', + 'APP' +); From b63c57a9822fd3ca40f4db3fe1a699f63ed19370 Mon Sep 17 00:00:00 2001 From: "ruth.bovell" Date: Mon, 20 Jul 2026 15:29:45 +0100 Subject: [PATCH 2/2] Add "new terms" feature flag --- .../controllers/TermsAndConditionsFT.java | 44 +++++++++ .../resources/application.properties | 3 + ...TermsAndConditionsServiceCutoffUsedIT.java | 83 ++++++++++++++++ .../services/TermsAndConditionsServiceIT.java | 5 + .../TermsAndConditionsRepository.java | 4 + .../services/TermsAndConditionsService.java | 30 +++++- src/main/resources/application.yaml | 3 + .../TermsAndConditionsServiceTest.java | 98 ++++++++++++++++++- 8 files changed, 263 insertions(+), 7 deletions(-) create mode 100644 src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/TermsAndConditionsFT.java create mode 100644 src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceCutoffUsedIT.java diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/TermsAndConditionsFT.java b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/TermsAndConditionsFT.java new file mode 100644 index 000000000..69467cb4c --- /dev/null +++ b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/TermsAndConditionsFT.java @@ -0,0 +1,44 @@ +package uk.gov.hmcts.reform.preapi.controllers; + +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.preapi.controllers.params.TestingSupportRoles; +import uk.gov.hmcts.reform.preapi.util.FunctionalTestBase; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TermsAndConditionsFT extends FunctionalTestBase { + + @Test + @DisplayName("Should return the latest APP terms when the feature flag is enabled") + void getLatestAppTerms() { + // Create an APP terms and conditions record for testing + Response created = doPostRequest("/testing-support/create-terms-and-conditions/APP", + TestingSupportRoles.SUPER_USER); + assertResponseCode(created, 200); + String createdId = created.body().jsonPath().getString("termsId"); + + // Get latest APP terms and conditions + Response response = doGetRequest("/app-terms-and-conditions/latest", TestingSupportRoles.SUPER_USER); + assertResponseCode(response, 200); + assertThat(response.body().jsonPath().getString("id")).isEqualTo(createdId); + assertThat(response.body().jsonPath().getString("type")).isEqualTo("APP"); + } + + @Test + @DisplayName("Should return the latest Portal terms") + void getLatestPortalTerms() { + // Create a Portal terms and conditions record for testing + Response created = doPostRequest("/testing-support/create-terms-and-conditions/PORTAL", + TestingSupportRoles.SUPER_USER); + assertResponseCode(created, 200); + String createdId = created.body().jsonPath().getString("termsId"); + + // Get latest Portal terms and conditions + Response response = doGetRequest("/portal-terms-and-conditions/latest", TestingSupportRoles.SUPER_USER); + assertResponseCode(response, 200); + assertThat(response.body().jsonPath().getString("id")).isEqualTo(createdId); + assertThat(response.body().jsonPath().getString("type")).isEqualTo("PORTAL"); + } +} diff --git a/src/functionalTest/resources/application.properties b/src/functionalTest/resources/application.properties index 1c0894c0e..3c5bf455b 100644 --- a/src/functionalTest/resources/application.properties +++ b/src/functionalTest/resources/application.properties @@ -24,6 +24,9 @@ mediakind.symmetricKey=${SYMMETRIC_KEY:} media-service=${MEDIA_SERVICE:MediaKind} editing.enable=true +feature-flags.dynatrace-terms-and-conditions.enabled=true +feature-flags.dynatrace-terms-and-conditions.cut-off-date=2026-07-15 + #logging.level.uk.gov.hmcts.reform.preapi.actuator.PreApiHealthIndicator: DEBUG #logging.level.uk.gov.hmcts.reform.preapi.email.StopLiveEventNotifierFlowClient: DEBUG #logging.level.uk.gov.hmcts.reform.preapi.email.CaseStateChangeNotifierFlowClient: DEBUG diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceCutoffUsedIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceCutoffUsedIT.java new file mode 100644 index 000000000..14c829210 --- /dev/null +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceCutoffUsedIT.java @@ -0,0 +1,83 @@ +package uk.gov.hmcts.reform.preapi.services; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; +import uk.gov.hmcts.reform.preapi.dto.TermsAndConditionsDTO; +import uk.gov.hmcts.reform.preapi.entities.TermsAndConditions; +import uk.gov.hmcts.reform.preapi.enums.TermsAndConditionsType; +import uk.gov.hmcts.reform.preapi.util.HelperFactory; +import uk.gov.hmcts.reform.preapi.utils.IntegrationTestBase; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestPropertySource(properties = { + "feature-flags.dynatrace-terms-and-conditions.enabled=false", + "feature-flags.dynatrace-terms-and-conditions.cut-off-date=2026-07-15" +}) +public class TermsAndConditionsServiceCutoffUsedIT extends IntegrationTestBase { + + @Autowired + private TermsAndConditionsService termsAndConditionsService; + + @Test + @Transactional + public void getLatestAppTermsAndConditionsCreatedBeforeCutoff() { + //Create an app terms that is before cutoff date + TermsAndConditions termsCreatedBeforeCutoff = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.APP, "

Terms and Conditions

"); + entityManager.persist(termsCreatedBeforeCutoff); + termsCreatedBeforeCutoff + .setCreatedAt(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 14).atStartOfDay())); + entityManager.flush(); + entityManager.clear(); + + //Create an app terms that is after cutoff date + TermsAndConditions termsCreatedAfterCutoff = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.APP, "

Terms and Conditions

"); + entityManager.persist(termsCreatedAfterCutoff); + termsCreatedAfterCutoff + .setCreatedAt(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 16).atStartOfDay())); + entityManager.flush(); + entityManager.clear(); + + TermsAndConditionsDTO result = + termsAndConditionsService.getLatestTermsAndConditions(TermsAndConditionsType.APP); + + assertThat(result.getId()).isEqualTo(termsCreatedBeforeCutoff.getId()); + assertThat(result.getCreatedAt()) + .isBefore(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 15).atStartOfDay())); + } + + @Test + @Transactional + public void getLatestPortalTermsAndConditionsIgnoresCutoff() { + + //Create a portal terms that is before cutoff date + TermsAndConditions termsCreatedBeforeCutoff = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.PORTAL, "# Terms and Conditions"); + entityManager.persist(termsCreatedBeforeCutoff); + termsCreatedBeforeCutoff + .setCreatedAt(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 14).atStartOfDay())); + entityManager.flush(); + entityManager.clear(); + + //Create a portal terms that is after cutoff date + TermsAndConditions termsCreatedAfterCutoff = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.PORTAL, "# Terms and Conditions"); + entityManager.persist(termsCreatedAfterCutoff); + termsCreatedAfterCutoff + .setCreatedAt(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 16).atStartOfDay())); + entityManager.flush(); + entityManager.clear(); + + TermsAndConditionsDTO result + = termsAndConditionsService.getLatestTermsAndConditions(TermsAndConditionsType.PORTAL); + + assertThat(result.getCreatedAt()) + .isAfter(java.sql.Timestamp.valueOf(LocalDate.of(2026, 7, 15).atStartOfDay())); + } +} diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceIT.java index 165be7dff..a01657992 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceIT.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceIT.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; import uk.gov.hmcts.reform.preapi.entities.TermsAndConditions; import uk.gov.hmcts.reform.preapi.enums.TermsAndConditionsType; import uk.gov.hmcts.reform.preapi.util.HelperFactory; @@ -11,6 +12,10 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestPropertySource(properties = { + "feature-flags.dynatrace-terms-and-conditions.enabled=true", + "feature-flags.dynatrace-terms-and-conditions.cut-off-date=2026-07-15" +}) class TermsAndConditionsServiceIT extends IntegrationTestBase { @Autowired private TermsAndConditionsService termsAndConditionsService; diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/TermsAndConditionsRepository.java b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/TermsAndConditionsRepository.java index 44d6d3ca3..7072b3528 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/TermsAndConditionsRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/TermsAndConditionsRepository.java @@ -5,10 +5,14 @@ import uk.gov.hmcts.reform.preapi.entities.TermsAndConditions; import uk.gov.hmcts.reform.preapi.enums.TermsAndConditionsType; +import java.sql.Timestamp; import java.util.Optional; import java.util.UUID; @Repository public interface TermsAndConditionsRepository extends JpaRepository { Optional findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType type); + + Optional findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + TermsAndConditionsType type, Timestamp timestamp); } diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsService.java index c071eab11..3a4268811 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsService.java @@ -2,24 +2,50 @@ import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import uk.gov.hmcts.reform.preapi.dto.TermsAndConditionsDTO; +import uk.gov.hmcts.reform.preapi.entities.TermsAndConditions; import uk.gov.hmcts.reform.preapi.enums.TermsAndConditionsType; import uk.gov.hmcts.reform.preapi.exception.NotFoundException; import uk.gov.hmcts.reform.preapi.repositories.TermsAndConditionsRepository; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.Optional; + @Service public class TermsAndConditionsService { private final TermsAndConditionsRepository termsAndConditionsRepository; + @Value("${feature-flags.dynatrace-terms-and-conditions.cut-off-date}") + private LocalDate cutOffDate; + + @Value("${feature-flags.dynatrace-terms-and-conditions.enabled}") + private boolean isDynatraceAppTermsEnabled; + + @Autowired public TermsAndConditionsService(TermsAndConditionsRepository termsAndConditionsRepository) { this.termsAndConditionsRepository = termsAndConditionsRepository; } + /** + * Returns the latest terms for the given type. + * For `PORTAL`, always returns the newest terms. + * Temporarily - For `APP`, returns either the newest terms or the newest terms before the configured cutoff date, + * depending on `feature-flags.dynatrace-terms-and-conditions.enabled` property. + * @param type terms and conditions type (`APP` or `PORTAL`) + * @return latest applicable terms and conditions + */ public TermsAndConditionsDTO getLatestTermsAndConditions(@NotNull TermsAndConditionsType type) { - return termsAndConditionsRepository.findFirstByTypeOrderByCreatedAtDesc(type) - .map(TermsAndConditionsDTO::new) + boolean useLatestTerms = type == TermsAndConditionsType.PORTAL || isDynatraceAppTermsEnabled; + Optional terms = useLatestTerms + ? termsAndConditionsRepository.findFirstByTypeOrderByCreatedAtDesc(type) + : termsAndConditionsRepository.findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + type, Timestamp.valueOf(cutOffDate.atStartOfDay())); + + return terms.map(TermsAndConditionsDTO::new) .orElseThrow(() -> new NotFoundException("Terms and conditions of type: " + type)); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b5a7b1f87..3e0018251 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -172,6 +172,9 @@ feature-flags: hide-reencoded-recordings: ${HIDE_REENCODED_RECORDINGS:true} cleanup-null-duration: upsert-enabled: ${ENABLE_NULL_DURATION_UPSERT:false} + dynatrace-terms-and-conditions: + enabled: ${ENABLE_TERMS_AND_CONDITIONS:false} + cut-off-date: ${TERMS_AND_CONDITIONS_CUTOFF_DATE:2026-07-15} #logging: # level: diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceTest.java index 1372ea339..63fc3a13d 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/TermsAndConditionsServiceTest.java @@ -1,10 +1,12 @@ package uk.gov.hmcts.reform.preapi.services; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.util.ReflectionTestUtils; import uk.gov.hmcts.reform.preapi.enums.TermsAndConditionsType; import uk.gov.hmcts.reform.preapi.exception.NotFoundException; import uk.gov.hmcts.reform.preapi.repositories.TermsAndConditionsRepository; @@ -12,15 +14,23 @@ import java.sql.Timestamp; import java.time.Instant; +import java.time.LocalDate; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +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; -@SpringBootTest(classes = TermsAndConditionsService.class) +@SpringBootTest(classes = TermsAndConditionsService.class, + properties = { + "feature-flags.dynatrace-terms-and-conditions.enabled=true", + "feature-flags.dynatrace-terms-and-conditions.cut-off-date=2026-07-15" + }) public class TermsAndConditionsServiceTest { @MockitoBean private TermsAndConditionsRepository termsAndConditionsRepository; @@ -28,10 +38,18 @@ public class TermsAndConditionsServiceTest { @Autowired private TermsAndConditionsService termsAndConditionsService; + @AfterEach + void resetTermsFeatureFlags() { + ReflectionTestUtils.setField(termsAndConditionsService, "isDynatraceAppTermsEnabled", true); + ReflectionTestUtils + .setField(termsAndConditionsService, "cutOffDate", LocalDate.of(2026, 7, 15)); + } + @Test @DisplayName("Should get the latest app terms and conditions and return model") public void getLatestAppTermsAndConditionsSuccess() { - var termsAndConditions = HelperFactory.createTermsAndConditions(TermsAndConditionsType.APP, "some content"); + var termsAndConditions = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.APP, "some content"); termsAndConditions.setCreatedAt(Timestamp.from(Instant.now())); when(termsAndConditionsRepository.findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP)) @@ -44,13 +62,15 @@ public void getLatestAppTermsAndConditionsSuccess() { assertThat(model.getHtml()).isEqualTo(termsAndConditions.getContent()); assertThat(model.getCreatedAt()).isEqualTo(termsAndConditions.getCreatedAt()); - verify(termsAndConditionsRepository, times(1)).findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP); + verify(termsAndConditionsRepository, + times(1)).findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP); } @Test @DisplayName("Should get the latest portal terms and conditions and return model") public void getLatestPortalTermsAndConditionsSuccess() { - var termsAndConditions = HelperFactory.createTermsAndConditions(TermsAndConditionsType.PORTAL, "some content"); + var termsAndConditions = + HelperFactory.createTermsAndConditions(TermsAndConditionsType.PORTAL, "some content"); termsAndConditions.setCreatedAt(Timestamp.from(Instant.now())); when(termsAndConditionsRepository.findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.PORTAL)) @@ -79,7 +99,75 @@ public void getLatestTermsAndConditionsNotFound() { ).getMessage(); assertThat(message).isEqualTo("Not found: Terms and conditions of type: APP"); - verify(termsAndConditionsRepository, times(1)).findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP); + verify(termsAndConditionsRepository, + times(1)).findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP); + + } + + @Test + @DisplayName("Should use terms before cutoff for APP when dynatrace terms flag is disabled") + void appUsesCutoffDateWhenFlagDisabled() { + ReflectionTestUtils.setField(termsAndConditionsService, "isDynatraceAppTermsEnabled", false); + ReflectionTestUtils + .setField(termsAndConditionsService, "cutOffDate", LocalDate.of(2026, 7, 15)); + + var terms = HelperFactory.createTermsAndConditions(TermsAndConditionsType.APP, "some content"); + terms.setCreatedAt(Timestamp.from(Instant.now())); + + when(termsAndConditionsRepository.findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + eq(TermsAndConditionsType.APP), + eq(Timestamp.valueOf(LocalDate.of(2026, 7, 15).atStartOfDay())))) + .thenReturn(Optional.of(terms)); + + termsAndConditionsService.getLatestTermsAndConditions(TermsAndConditionsType.APP); + + verify(termsAndConditionsRepository, times(1)) + .findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + eq(TermsAndConditionsType.APP), any(Timestamp.class)); + verify(termsAndConditionsRepository, never()) + .findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.APP); + } + + @Test + @DisplayName("Should throw not found exception when there are no APP terms before cutoff date " + + "and dynatrace terms flag is disabled") + void appThrowsNotFoundWhenNoTermsBeforeCutoffAndFlagDisabled() { + ReflectionTestUtils.setField(termsAndConditionsService, "isDynatraceAppTermsEnabled", false); + ReflectionTestUtils + .setField(termsAndConditionsService, "cutOffDate", LocalDate.of(2026, 7, 15)); + + when(termsAndConditionsRepository.findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + eq(TermsAndConditionsType.APP), + eq(Timestamp.valueOf(LocalDate.of(2026, 7, 15).atStartOfDay())))) + .thenReturn(Optional.empty()); + var message = assertThrows( + NotFoundException.class, + () -> termsAndConditionsService.getLatestTermsAndConditions(TermsAndConditionsType.APP) + ).getMessage(); + assertThat(message).isEqualTo("Not found: Terms and conditions of type: APP"); + } + + @Test + @DisplayName("Portal should ignore dynatrace terms flag and always return the latest terms") + void portalDoesNotUseCutoffDateWhenFlagDisabled() { + ReflectionTestUtils.setField(termsAndConditionsService, "isDynatraceAppTermsEnabled", false); + ReflectionTestUtils + .setField(termsAndConditionsService, "cutOffDate", LocalDate.of(2026, 7, 15)); + + var terms = HelperFactory.createTermsAndConditions(TermsAndConditionsType.PORTAL, "some content"); + terms.setCreatedAt(Timestamp.from(Instant.now())); + + when(termsAndConditionsRepository.findFirstByTypeOrderByCreatedAtDesc( + eq(TermsAndConditionsType.PORTAL))) + .thenReturn(Optional.of(terms)); + + termsAndConditionsService.getLatestTermsAndConditions(TermsAndConditionsType.PORTAL); + + verify(termsAndConditionsRepository, never()) + .findFirstByTypeAndCreatedAtBeforeOrderByCreatedAtDesc( + any(TermsAndConditionsType.class), any(Timestamp.class)); + verify(termsAndConditionsRepository, times(1)) + .findFirstByTypeOrderByCreatedAtDesc(TermsAndConditionsType.PORTAL); } }