From 811f316deab26fc6579129278ff3423587f25ec9 Mon Sep 17 00:00:00 2001 From: Gavin Cook Date: Tue, 23 Apr 2024 10:48:37 +0100 Subject: [PATCH 1/5] replacing synchronous calls to publish adverts into opensearch with a queue --- .../config/OpenSearchSqsProperties.java | 18 +++ .../adminbackend/config/WebClientConfig.java | 25 ---- .../adminbackend/dtos/SendAdvertToSqsDto.java | 4 + .../services/GrantAdvertService.java | 32 ++++- .../services/OpenSearchService.java | 96 -------------- .../services/GrantAdvertServiceTest.java | 76 ++++++++--- .../services/OpenSearchServiceTest.java | 121 ------------------ 7 files changed, 107 insertions(+), 265 deletions(-) create mode 100644 src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchSqsProperties.java delete mode 100644 src/main/java/gov/cabinetoffice/gap/adminbackend/config/WebClientConfig.java create mode 100644 src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java delete mode 100644 src/main/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchService.java delete mode 100644 src/test/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchServiceTest.java diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchSqsProperties.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchSqsProperties.java new file mode 100644 index 00000000..7c2b59be --- /dev/null +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchSqsProperties.java @@ -0,0 +1,18 @@ +package gov.cabinetoffice.gap.adminbackend.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Configuration("openSearchSqsProperties") +@ConfigurationProperties(prefix = "open-search-sqs") +public class OpenSearchSqsProperties { + private String queueUrl; +} diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/config/WebClientConfig.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/config/WebClientConfig.java deleted file mode 100644 index b1882ff4..00000000 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/config/WebClientConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package gov.cabinetoffice.gap.adminbackend.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.netty.http.client.HttpClient; -import reactor.netty.resources.ConnectionProvider; - -import java.time.Duration; - -@Configuration -public class WebClientConfig { - - @Bean - public WebClient getWebClient() { - final ConnectionProvider provider = ConnectionProvider.builder("fixed") - .maxIdleTime(Duration.ofSeconds(30)) - .build(); - - return WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(HttpClient.create(provider))) - .build(); - } -} diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java new file mode 100644 index 00000000..8fad0bc1 --- /dev/null +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java @@ -0,0 +1,4 @@ +package gov.cabinetoffice.gap.adminbackend.dtos; + +public record SendAdvertToSqsDto(String contentfulEntryId, String action) { +} diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java index 9eeece9f..70d41448 100644 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java @@ -1,5 +1,7 @@ package gov.cabinetoffice.gap.adminbackend.services; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; import com.contentful.java.cda.CDAArray; import com.contentful.java.cda.CDAClient; import com.contentful.java.cda.CDAEntry; @@ -7,8 +9,11 @@ import com.contentful.java.cma.CMAClient; import com.contentful.java.cma.model.CMAEntry; import com.contentful.java.cma.model.rich.CMARichDocument; +import com.fasterxml.jackson.databind.ObjectMapper; import gov.cabinetoffice.gap.adminbackend.config.ContentfulConfigProperties; import gov.cabinetoffice.gap.adminbackend.config.FeatureFlagsConfigurationProperties; +import gov.cabinetoffice.gap.adminbackend.config.OpenSearchSqsProperties; +import gov.cabinetoffice.gap.adminbackend.dtos.SendAdvertToSqsDto; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertPageResponseDTO; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertPublishingInformationResponseDTO; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertStatusResponseDTO; @@ -62,14 +67,15 @@ public class GrantAdvertService { private final CMAClient contentfulManagementClient; private final CDAClient contentfulDeliveryClient; private final UserService userService; - private final OpenSearchService openSearchService; private final WebClient.Builder webClientBuilder; - - private final WebClient webClient; + private final AmazonSQS amazonSqs; + private final ObjectMapper mapper; private final Clock clock; private final ContentfulConfigProperties contentfulProperties; private final FeatureFlagsConfigurationProperties featureFlagsProperties; + private final OpenSearchSqsProperties openSearchSqsProperties; + public GrantAdvert save(GrantAdvert advert) { final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Optional.ofNullable(auth) @@ -320,7 +326,7 @@ public GrantAdvert publishAdvert(UUID advertId) { if (Boolean.FALSE.equals(contentfulAdvert.isPublished())) { final CMAEntry publishedAdvert = contentfulManagementClient.entries().publish(contentfulAdvert); - openSearchService.indexEntry(publishedAdvert); + sendMessageToQueue(new SendAdvertToSqsDto(publishedAdvert.getId(), "CREATE")); } updateGrantAdvertApplicationDates(advert); @@ -333,7 +339,7 @@ public void unpublishAdvert(UUID advertId) { if (Boolean.TRUE.equals(contentfulAdvert.isPublished())) { final CMAEntry unpublishedAd = contentfulManagementClient.entries().unPublish(contentfulAdvert); - openSearchService.removeIndexEntry(unpublishedAd); + sendMessageToQueue(new SendAdvertToSqsDto(unpublishedAd.getId(), "DELETE")); } advert.setStatus(GrantAdvertStatus.DRAFT); @@ -343,6 +349,19 @@ public void unpublishAdvert(UUID advertId) { save(advert); } + public void sendMessageToQueue(final SendAdvertToSqsDto advertDto) { + final UUID messageId = UUID.randomUUID(); + final String messageBody = mapper.valueToTree(advertDto).toString(); + final SendMessageRequest messageRequest = new SendMessageRequest() + .withQueueUrl(openSearchSqsProperties.getQueueUrl()) + .withMessageGroupId(messageId.toString()) + .withMessageBody(messageBody) + .withMessageDeduplicationId(messageId.toString()); + + amazonSqs.sendMessage(messageRequest); + log.info("Message sent to queue for advert with contentful ID {}"); + } + private CMAEntry createAdvertInContentful(final GrantAdvert grantAdvert) { final CMAEntry contentfulAdvert = new CMAEntry(); @@ -438,7 +457,8 @@ private void createRichTextQuestionsInContentful(final GrantAdvert advert, final } final Instant now = Instant.now(); - webClient.patch() + webClientBuilder.build() + .patch() .uri(contentfulUrl) .headers(h -> { h.set("Authorization", String.format("Bearer %s", contentfulProperties.getAccessToken())); diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchService.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchService.java deleted file mode 100644 index adff5511..00000000 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchService.java +++ /dev/null @@ -1,96 +0,0 @@ -package gov.cabinetoffice.gap.adminbackend.services; - -import com.contentful.java.cma.model.CMAEntry; -import gov.cabinetoffice.gap.adminbackend.config.ContentfulConfigProperties; -import gov.cabinetoffice.gap.adminbackend.config.OpenSearchConfig; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import static org.apache.http.HttpHeaders.AUTHORIZATION; -import static org.apache.http.HttpHeaders.CONTENT_TYPE; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -@Service -@RequiredArgsConstructor -@Slf4j -public class OpenSearchService { - - private final WebClient webClient; - private final OpenSearchConfig openSearchConfig; - private final ContentfulConfigProperties contentfulProperties; - - public void indexEntry(final CMAEntry contentfulEntry) { - final String body = getContentfulAdvertAsJson(contentfulEntry.getId()); - webClient.put() - .uri(createUrl(contentfulEntry)) - .body(Mono.just(body), String.class) - .header(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + "; " + StandardCharsets.UTF_8.name()) - .header(AUTHORIZATION, createAuthHeader()) - .retrieve() - .bodyToMono(void.class) - .doOnError(e -> log.error("Failed to create an index entry for ad " + contentfulEntry.getId() + "in open search: {}", e.getMessage())) - .block(); - } - - public void removeIndexEntry(final CMAEntry contentfulEntry) { - final String body = getContentfulAdvertAsJson(contentfulEntry.getId()); - webClient.method(HttpMethod.DELETE) - .uri(createUrl(contentfulEntry)) - .body(Mono.just(body), String.class) - .header(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + "; " + StandardCharsets.UTF_8.name()) - .header(AUTHORIZATION, createAuthHeader()) - .retrieve() - .bodyToMono(void.class) - .doOnError(e -> log.error("Failed to delete an index entry for ad " + contentfulEntry.getId() + "in open search: {}", e.getMessage())) - .block(); - } - - private String createUrl(final CMAEntry contentfulEntry) { - return openSearchConfig.getUrl() + "/" + openSearchConfig.getDomain() + "/_doc/" + contentfulEntry.getId(); - } - - private String createAuthHeader() { - final String auth = openSearchConfig.getUsername() + ":" + openSearchConfig.getPassword(); - return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()); - } - - private String getContentfulAdvertAsJson(String entryId) { - final String contentfulUrl = String.format( - "https://api.contentful.com/spaces/%1$s/environments/%2$s/entries/%3$s", - contentfulProperties.getSpaceId(), - contentfulProperties.getEnvironmentId(), - entryId - ); - - return webClient.get() - .uri(contentfulUrl) - .headers(h -> - h.set("Authorization", String.format("Bearer %s", contentfulProperties.getAccessToken())) - ) - .retrieve() - .onStatus(HttpStatus::isError, response -> { - log.error("Contentful response -------------------"); - log.error(response.statusCode().toString()); - log.error(response.bodyToMono(String.class).toString()); - log.error("End Contentful response ---------------"); - - return Mono.empty(); - }) - .bodyToMono(String.class) - .doOnError(exception -> - log.error( - "getContentfulAdvertAsJson failed on GET to {}, with message: {}", - contentfulUrl, - exception - ) - ) - .block(); - } -} diff --git a/src/test/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertServiceTest.java b/src/test/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertServiceTest.java index 5f7728b8..a1e3e7c6 100644 --- a/src/test/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertServiceTest.java +++ b/src/test/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertServiceTest.java @@ -1,14 +1,18 @@ package gov.cabinetoffice.gap.adminbackend.services; +import com.amazonaws.services.sqs.AmazonSQS; import com.contentful.java.cda.CDAArray; import com.contentful.java.cda.CDAClient; import com.contentful.java.cda.FetchQuery; import com.contentful.java.cma.CMAClient; import com.contentful.java.cma.ModuleEntries; import com.contentful.java.cma.model.CMAEntry; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import gov.cabinetoffice.gap.adminbackend.annotations.WithAdminSession; import gov.cabinetoffice.gap.adminbackend.config.ContentfulConfigProperties; import gov.cabinetoffice.gap.adminbackend.config.FeatureFlagsConfigurationProperties; +import gov.cabinetoffice.gap.adminbackend.config.OpenSearchSqsProperties; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertPageResponseDTO; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertPublishingInformationResponseDTO; import gov.cabinetoffice.gap.adminbackend.dtos.grantadvert.GetGrantAdvertStatusResponseDTO; @@ -72,19 +76,30 @@ class GrantAdvertServiceTest { @Mock private CDAClient contentfulDeliveryClient; + @Mock + private ObjectMapper mapper; + + @Mock + private AmazonSQS amazonSqs; + @Spy private GrantAdvertMapper grantAdvertMapper = new GrantAdvertMapperImpl(); @Spy private ContentfulConfigProperties contentfulConfigProperties = ContentfulConfigProperties.builder() - .accessToken("an-access-token").environmentId("dev").spaceId("a-space-id") - .deliveryAPIAccessToken("a-delivery-access-token").build(); + .accessToken("an-access-token") + .environmentId("dev") + .spaceId("a-space-id") + .deliveryAPIAccessToken("a-delivery-access-token") + .build(); - @Mock - private ModuleEntries contentfulEntries; + @Spy + private OpenSearchSqsProperties openSearchSqsProperties = OpenSearchSqsProperties.builder() + .queueUrl("a-url") + .build(); @Mock - private ModuleEntries.Async async; + private ModuleEntries contentfulEntries; @Mock private FetchQuery mockedFetchQuery; @@ -104,12 +119,6 @@ class GrantAdvertServiceTest { @Mock private UserService userService; - @Mock - private OpenSearchService openSearchService; - - @Mock - private WebClient webClient; - @InjectMocks @Spy private GrantAdvertService grantAdvertService; @@ -850,14 +859,14 @@ void publishAdvert_successfullyPublishedAdvert() { when(contentfulEntries.fetchOne(contentfulAdvertId)).thenReturn(publishedContentfulAdvert); - //when(contentfulEntries.async()).thenReturn(async); - doReturn(mockGrantAdvert).when(grantAdvertService).save(any()); + final WebClient webClient = mock(WebClient.class); final WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); final WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); final WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); + when(webClientBuilder.build()).thenReturn(webClient); when(webClient.patch()).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.headers(any())).thenReturn(requestBodyUriSpec); @@ -865,6 +874,14 @@ void publishAdvert_successfullyPublishedAdvert() { when(requestHeadersSpec.retrieve()).thenReturn(responseSpec); when(responseSpec.bodyToMono(Void.class)).thenReturn(Mono.empty()); + when(contentfulEntries.publish(any())).thenReturn(publishedContentfulAdvert); + + final JsonNode mockJsonNode = mock(JsonNode.class); + + when(mapper.valueToTree(any())).thenReturn(mockJsonNode); + + when(mockJsonNode.toString()).thenReturn("{contentfulEntryId: \"entry-id\", action: \"CREATE\"}"); + final ArgumentCaptor entryCaptor = ArgumentCaptor.forClass(CMAEntry.class); final ArgumentCaptor grantAdvertArgumentCaptor = ArgumentCaptor.forClass(GrantAdvert.class); @@ -925,19 +942,27 @@ void publishAdvert_updatesExistingAdvert_IfFirstPublishedDateHasBeenSet() { when(contentfulEntries.update(Mockito.any())).thenReturn(publishedContentfulAdvert); when(contentfulEntries.fetchOne(contentfulAdvertId)).thenReturn(publishedContentfulAdvert, publishedContentfulAdvert); - //when(contentfulEntries.async()).thenReturn(async); doReturn(grantAvertInDatabase).when(grantAdvertService).save(any()); + final WebClient webClient = mock(WebClient.class); final WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); final WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); final WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); + when(webClientBuilder.build()).thenReturn(webClient); when(webClient.patch()).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.headers(any())).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.bodyValue(any())).thenReturn(requestHeadersSpec); when(requestHeadersSpec.retrieve()).thenReturn(responseSpec); when(responseSpec.bodyToMono(Void.class)).thenReturn(Mono.empty()); + when(contentfulEntries.publish(any())).thenReturn(publishedContentfulAdvert); + + final JsonNode mockJsonNode = mock(JsonNode.class); + + when(mapper.valueToTree(any())).thenReturn(mockJsonNode); + + when(mockJsonNode.toString()).thenReturn("{contentfulEntryId: \"entry-id\", action: \"CREATE\"}"); final ArgumentCaptor grantAdvertArgumentCaptor = ArgumentCaptor.forClass(GrantAdvert.class); @@ -976,10 +1001,12 @@ void publishAdvertThroughLambda_successfullyPublishedAdvert() { .grantAdvertName("Grant Advert Name").response(response).grantAdvertName("Homelessness Grant") .build(); + final WebClient webClient = mock(WebClient.class); final WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); final WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); final WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); + when(webClientBuilder.build()).thenReturn(webClient); when(webClient.patch()).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.headers(any())).thenReturn(requestBodyUriSpec); @@ -1007,7 +1034,13 @@ void publishAdvertThroughLambda_successfullyPublishedAdvert() { when(contentfulEntries.fetchOne(contentfulAdvertId)).thenReturn(publishedContentfulAdvert); - //when(contentfulEntries.async()).thenReturn(async); + when(contentfulEntries.publish(any())).thenReturn(publishedContentfulAdvert); + + final JsonNode mockJsonNode = mock(JsonNode.class); + + when(mapper.valueToTree(any())).thenReturn(mockJsonNode); + + when(mockJsonNode.toString()).thenReturn("{contentfulEntryId: \"entry-id\", action: \"CREATE\"}"); final ArgumentCaptor entryCaptor = ArgumentCaptor.forClass(CMAEntry.class); @@ -1076,6 +1109,12 @@ void unpublishAdvert_UnpublishesAdvertFromContentful_AndSetsStatusToDraftInDb() doReturn(advert).when(grantAdvertService).save(any()); when(contentfulAdvert.isPublished()).thenReturn(true); + final JsonNode mockJsonNode = mock(JsonNode.class); + + when(mapper.valueToTree(any())).thenReturn(mockJsonNode); + + when(mockJsonNode.toString()).thenReturn("{contentfulEntryId: \"entry-id\", action: \"CREATE\"}"); + final ArgumentCaptor advertCaptor = ArgumentCaptor.forClass(GrantAdvert.class); // maybe overkill to check this here but ensures we can be sure the state has changed @@ -1084,7 +1123,6 @@ void unpublishAdvert_UnpublishesAdvertFromContentful_AndSetsStatusToDraftInDb() grantAdvertService.unpublishAdvert(grantAdvertId); verify(contentfulEntries).unPublish(contentfulAdvert); - verify(openSearchService).removeIndexEntry(contentfulAdvert); verify(grantAdvertService).save(advertCaptor.capture()); assertThat(advertCaptor.getValue().getId()).isEqualTo(grantAdvertId); @@ -1107,6 +1145,11 @@ void unpublishAdvertThroughLambda_successfullyUnpublishedAdvert() { doReturn(advert).when(grantAdvertService).save(any()); when(contentfulAdvert.isPublished()).thenReturn(true); + final JsonNode mockJsonNode = mock(JsonNode.class); + when(mapper.valueToTree(any())).thenReturn(mockJsonNode); + + when(mockJsonNode.toString()).thenReturn("{contentfulEntryId: \"entry-id\", action: \"CREATE\"}"); + final ArgumentCaptor advertCaptor = ArgumentCaptor.forClass(GrantAdvert.class); // maybe overkill to check this here but ensures we can be sure the state has changed @@ -1115,7 +1158,6 @@ void unpublishAdvertThroughLambda_successfullyUnpublishedAdvert() { grantAdvertService.unpublishAdvert(grantAdvertId); verify(contentfulEntries).unPublish(contentfulAdvert); - verify(openSearchService).removeIndexEntry(contentfulAdvert); verify(grantAdvertService).save(advertCaptor.capture()); assertThat(advertCaptor.getValue().getId()).isEqualTo(grantAdvertId); diff --git a/src/test/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchServiceTest.java b/src/test/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchServiceTest.java deleted file mode 100644 index af556dc9..00000000 --- a/src/test/java/gov/cabinetoffice/gap/adminbackend/services/OpenSearchServiceTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package gov.cabinetoffice.gap.adminbackend.services; - -import com.contentful.java.cma.model.CMAEntry; -import com.contentful.java.cma.model.CMASystem; -import gov.cabinetoffice.gap.adminbackend.config.ContentfulConfigProperties; -import gov.cabinetoffice.gap.adminbackend.config.OpenSearchConfig; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import static org.mockito.Mockito.*; -import org.mockito.Spy; -import org.springframework.http.HttpMethod; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -@SpringJUnitConfig -class OpenSearchServiceTest { - @Spy - @InjectMocks - private OpenSearchService openSearchService; - - @Mock - private OpenSearchConfig openSearchConfig; - - @Mock - private WebClient webClient; - - @Mock - private ContentfulConfigProperties contentfulProperties; - - private CMAEntry contentfulEntry; - - @BeforeEach - void beforeEach() { - when(openSearchConfig.getUrl()).thenReturn("testUrl"); - when(openSearchConfig.getDomain()).thenReturn("testDomain"); - when(openSearchConfig.getUsername()).thenReturn("testUsername"); - when(openSearchConfig.getPassword()).thenReturn("testPassword"); - - contentfulEntry = new CMAEntry(); - final CMASystem system = new CMASystem(); - system.setId("testId"); - contentfulEntry.setSystem(system); - } - - @Test - void indexEntry() { - final WebClient.RequestBodyUriSpec mockRequestBodyUriSpec = Mockito.mock(WebClient.RequestBodyUriSpec.class); - final WebClient.RequestHeadersSpec mockRequestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); - final WebClient.RequestHeadersUriSpec mockRequestHeadersUriSpec = mock(WebClient.RequestHeadersUriSpec.class); - final WebClient.ResponseSpec mockResponseSpec = mock(WebClient.ResponseSpec.class); - - when(contentfulProperties.getSpaceId()).thenReturn("Space"); - when(contentfulProperties.getEnvironmentId()).thenReturn("environment"); - when(contentfulProperties.getAccessToken()).thenReturn("accessToken"); - when(contentfulProperties.getDeliveryAPIAccessToken()).thenReturn("deliveryAccessToken"); - - when(webClient.get()).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.uri(anyString())).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.headers(any())).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.retrieve()).thenReturn(mockResponseSpec); - when(mockResponseSpec.onStatus(any(), any())).thenReturn(mockResponseSpec); - when(mockResponseSpec.bodyToMono(String.class)).thenReturn(Mono.just("{\"system\":{\"id\":\"testId\"},\"environmentId\":\"master\",\"id\":\"testId\",\"published\":false,\"archived\":false}")); - - when(webClient.put()).thenReturn(mockRequestBodyUriSpec); - when(mockRequestBodyUriSpec.uri("testUrl/testDomain/_doc/testId")).thenReturn(mockRequestBodyUriSpec); - when(mockRequestBodyUriSpec.body(any(), eq(String.class))).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.header("Content-Type", "application/json; UTF-8")).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.header("Authorization", "Basic dGVzdFVzZXJuYW1lOnRlc3RQYXNzd29yZA==")).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.retrieve()).thenReturn(mockResponseSpec); - when(mockResponseSpec.bodyToMono(void.class)).thenReturn(Mono.empty()); - - openSearchService.indexEntry(contentfulEntry); - - verify(webClient.get(), times(1)) - .uri("https://api.contentful.com/spaces/Space/environments/environment/entries/testId"); - - verify(webClient.put(), times(1)) - .uri("testUrl/testDomain/_doc/testId"); - } - - @Test - void removeIndexEntry() { - final WebClient.RequestBodyUriSpec mockRequestBodyUriSpec = Mockito.mock(WebClient.RequestBodyUriSpec.class); - final WebClient.RequestHeadersSpec mockRequestHeadersSpec = mock(WebClient.RequestHeadersSpec.class); - final WebClient.ResponseSpec mockResponseSpec = mock(WebClient.ResponseSpec.class); - final WebClient.RequestHeadersUriSpec mockRequestHeadersUriSpec = mock(WebClient.RequestHeadersUriSpec.class); - - when(contentfulProperties.getSpaceId()).thenReturn("Space"); - when(contentfulProperties.getEnvironmentId()).thenReturn("environment"); - when(contentfulProperties.getAccessToken()).thenReturn("accessToken"); - when(contentfulProperties.getDeliveryAPIAccessToken()).thenReturn("deliveryAccessToken"); - - - when(webClient.get()).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.uri(anyString())).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.headers(any())).thenReturn(mockRequestHeadersUriSpec); - when(mockRequestHeadersUriSpec.retrieve()).thenReturn(mockResponseSpec); - when(mockResponseSpec.onStatus(any(), any())).thenReturn(mockResponseSpec); - when(mockResponseSpec.bodyToMono(String.class)).thenReturn(Mono.just("{\"system\":{\"id\":\"testId\"},\"environmentId\":\"master\",\"id\":\"testId\",\"published\":false,\"archived\":false}")); - - when(webClient.method(HttpMethod.DELETE)).thenReturn(mockRequestBodyUriSpec); - when(mockRequestBodyUriSpec.uri("testUrl/testDomain/_doc/testId")).thenReturn(mockRequestBodyUriSpec); - when(mockRequestBodyUriSpec.body(any(), eq(String.class))).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.header("Content-Type", "application/json; UTF-8")).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.header("Authorization", "Basic dGVzdFVzZXJuYW1lOnRlc3RQYXNzd29yZA==")).thenReturn(mockRequestHeadersSpec); - when(mockRequestHeadersSpec.retrieve()).thenReturn(mockResponseSpec); - when(mockResponseSpec.bodyToMono(void.class)).thenReturn(Mono.empty()); - - openSearchService.removeIndexEntry(contentfulEntry); - - verify(webClient.get(), times(1)) - .uri("https://api.contentful.com/spaces/Space/environments/environment/entries/testId"); - - verify(webClient.method(HttpMethod.DELETE), times(1)) - .uri("testUrl/testDomain/_doc/testId"); - } -} From 0e3440f68cfdd3f1038c5524f114e1b4372c5662 Mon Sep 17 00:00:00 2001 From: Gavin Cook Date: Tue, 23 Apr 2024 13:22:22 +0100 Subject: [PATCH 2/5] updating code to match lambda + cleanup --- .../adminbackend/config/OpenSearchConfig.java | 21 ------------------- .../adminbackend/dtos/SendAdvertToSqsDto.java | 2 +- src/main/resources/application.properties | 5 +---- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchConfig.java diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchConfig.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchConfig.java deleted file mode 100644 index 82f5df8e..00000000 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/config/OpenSearchConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package gov.cabinetoffice.gap.adminbackend.config; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Configuration -@ConfigurationProperties(prefix = "open-search") -public class OpenSearchConfig { - private String url; - private String domain; - private String username; - private String password; -} diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java index 8fad0bc1..991aa945 100644 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/SendAdvertToSqsDto.java @@ -1,4 +1,4 @@ package gov.cabinetoffice.gap.adminbackend.dtos; -public record SendAdvertToSqsDto(String contentfulEntryId, String action) { +public record SendAdvertToSqsDto(String contentfulEntryId, String type) { } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4ae4e4fe..6d4948a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -78,7 +78,4 @@ aws.kms.stage=stage spring.jpa.properties.hibernate.order_by.default_null_ordering=last -open-search.url=https://search-url -open-search.domain=domain -open-search.username=username -open-search.password=password +open-search-sqs.queueUrl=an-sqs-url From 481ab286c498e1703528939c6ae4b652485d98ed Mon Sep 17 00:00:00 2001 From: Gavin Cook Date: Wed, 24 Apr 2024 08:48:07 +0100 Subject: [PATCH 3/5] updating queue action --- .../gap/adminbackend/services/GrantAdvertService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java index 70d41448..a5d3ff9c 100644 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java @@ -326,7 +326,7 @@ public GrantAdvert publishAdvert(UUID advertId) { if (Boolean.FALSE.equals(contentfulAdvert.isPublished())) { final CMAEntry publishedAdvert = contentfulManagementClient.entries().publish(contentfulAdvert); - sendMessageToQueue(new SendAdvertToSqsDto(publishedAdvert.getId(), "CREATE")); + sendMessageToQueue(new SendAdvertToSqsDto(publishedAdvert.getId(), "ADD")); } updateGrantAdvertApplicationDates(advert); From 5fc5b7ae63ec73412ff2154352baa30dbe5ff377 Mon Sep 17 00:00:00 2001 From: Gavin Cook Date: Wed, 24 Apr 2024 09:40:57 +0100 Subject: [PATCH 4/5] updating queue action (again) --- .../gap/adminbackend/services/GrantAdvertService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java index a5d3ff9c..d2fa1fae 100644 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java @@ -339,7 +339,7 @@ public void unpublishAdvert(UUID advertId) { if (Boolean.TRUE.equals(contentfulAdvert.isPublished())) { final CMAEntry unpublishedAd = contentfulManagementClient.entries().unPublish(contentfulAdvert); - sendMessageToQueue(new SendAdvertToSqsDto(unpublishedAd.getId(), "DELETE")); + sendMessageToQueue(new SendAdvertToSqsDto(unpublishedAd.getId(), "REMOVE")); } advert.setStatus(GrantAdvertStatus.DRAFT); From 03eb29ffd0d890d5424ffad572d9066346b805a1 Mon Sep 17 00:00:00 2001 From: Gavin Cook Date: Thu, 25 Apr 2024 10:15:19 +0100 Subject: [PATCH 5/5] adding to the queue after saving --- .../services/GrantAdvertService.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java index d2fa1fae..7f3e4a53 100644 --- a/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java +++ b/src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantAdvertService.java @@ -320,26 +320,29 @@ public GrantAdvert publishAdvert(UUID advertId) { } contentfulAdvert = contentfulManagementClient.entries().fetchOne(contentfulAdvert.getId()); + final boolean isPublished = Boolean.TRUE.equals(contentfulAdvert.isPublished()); advert.setStatus(GrantAdvertStatus.PUBLISHED); advert.setContentfulSlug(contentfulAdvert.getField("label", CONTENTFUL_LOCALE)); advert.setContentfulEntryId(contentfulAdvert.getId()); - if (Boolean.FALSE.equals(contentfulAdvert.isPublished())) { - final CMAEntry publishedAdvert = contentfulManagementClient.entries().publish(contentfulAdvert); - sendMessageToQueue(new SendAdvertToSqsDto(publishedAdvert.getId(), "ADD")); + if (!isPublished) { + contentfulManagementClient.entries().publish(contentfulAdvert); } updateGrantAdvertApplicationDates(advert); - return save(advert); + final GrantAdvert savedAdvert = save(advert); + sendMessageToQueue(new SendAdvertToSqsDto(contentfulAdvert.getId(), "ADD")); + + return savedAdvert; } public void unpublishAdvert(UUID advertId) { final GrantAdvert advert = this.getAdvertById(advertId); final CMAEntry contentfulAdvert = contentfulManagementClient.entries().fetchOne(advert.getContentfulEntryId()); + final boolean isPublished = Boolean.TRUE.equals(contentfulAdvert.isPublished()); - if (Boolean.TRUE.equals(contentfulAdvert.isPublished())) { - final CMAEntry unpublishedAd = contentfulManagementClient.entries().unPublish(contentfulAdvert); - sendMessageToQueue(new SendAdvertToSqsDto(unpublishedAd.getId(), "REMOVE")); + if (isPublished) { + contentfulManagementClient.entries().unPublish(contentfulAdvert); } advert.setStatus(GrantAdvertStatus.DRAFT); @@ -347,6 +350,7 @@ public void unpublishAdvert(UUID advertId) { advert.setUnpublishedDate(Instant.now()); save(advert); + sendMessageToQueue(new SendAdvertToSqsDto(contentfulAdvert.getId(), "REMOVE")); } public void sendMessageToQueue(final SendAdvertToSqsDto advertDto) { @@ -359,7 +363,7 @@ public void sendMessageToQueue(final SendAdvertToSqsDto advertDto) { .withMessageDeduplicationId(messageId.toString()); amazonSqs.sendMessage(messageRequest); - log.info("Message sent to queue for advert with contentful ID {}"); + log.info("Message sent to queue for advert with contentful ID {}", advertDto.contentfulEntryId()); } private CMAEntry createAdvertInContentful(final GrantAdvert grantAdvert) {