diff --git a/src/integration/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferServiceTest.java b/src/integration/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferServiceTest.java index 302b1562..1144cb7e 100644 --- a/src/integration/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferServiceTest.java +++ b/src/integration/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferServiceTest.java @@ -10,7 +10,6 @@ import uk.nhs.prm.repo.ehrtransferservice.activemq.ForceXercesParserExtension; import uk.nhs.prm.repo.ehrtransferservice.configuration.LocalStackAwsConfig; import uk.nhs.prm.repo.ehrtransferservice.database.model.ConversationRecord; -import uk.nhs.prm.repo.ehrtransferservice.exceptions.base.DatabaseException; import uk.nhs.prm.repo.ehrtransferservice.exceptions.database.ConversationNotPresentException; import uk.nhs.prm.repo.ehrtransferservice.exceptions.database.ConversationUpdateException; import uk.nhs.prm.repo.ehrtransferservice.exceptions.database.QueryReturnedNoItemsException; @@ -124,13 +123,13 @@ void updateConversationTransferStatus_ValidInboundConversationIdAndConversationT } @Test - void updateConversationTransferStatus_NonExistingInboundConversationIdAndConversationTransferStatus_ShouldThrowConversationNotPresentException() { + void updateConversationTransferStatus_NonExistingInboundConversationIdAndExistingConversationTransferStatus_ShouldThrowConversationUpdateException() { // given final UUID inboundConversationId = UUID.randomUUID(); - // when - assertThrows(ConversationNotPresentException.class, () -> - transferService.updateConversationTransferStatus(inboundConversationId, INBOUND_FAILED)); + // then + assertThrows(ConversationUpdateException.class, () -> + transferService.updateConversationTransferStatus(inboundConversationId, INBOUND_FAILED)); } @Test @@ -154,13 +153,13 @@ void updateConversationTransferStatusWithFailure_ValidInboundConversationIdAndFa } @Test - void updateConversationTransferStatusWithFailure_NonExistingInboundConversationIdAndFailureCode_ShouldThrowConversationNotPresentException() { + void updateConversationTransferStatusWithFailure_NonExistingInboundConversationIdAndFailureCode_ShouldThrowConversationUpdateException() { // given final UUID inboundConversationId = UUID.randomUUID(); final String failureCode = "19"; // when - assertThrows(ConversationNotPresentException.class, () -> + assertThrows(ConversationUpdateException.class, () -> transferService.updateConversationTransferStatusWithFailure(inboundConversationId, failureCode)); } diff --git a/src/main/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepository.java b/src/main/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepository.java index 7d10df54..16877b55 100644 --- a/src/main/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepository.java +++ b/src/main/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepository.java @@ -136,12 +136,7 @@ ConversationRecord findConversationByInboundConversationId(UUID inboundConversat } void updateConversationStatus(UUID inboundConversationId, ConversationTransferStatus conversationTransferStatus) { - if (!isInboundConversationPresent(inboundConversationId)) { - throw new ConversationNotPresentException(inboundConversationId); - } - final Map keyItems = new HashMap<>(); - final String updateTimestamp = getIsoTimestamp(); keyItems.put(INBOUND_CONVERSATION_ID.name, AttributeValue.builder() .s(inboundConversationId.toString().toUpperCase()) @@ -151,22 +146,23 @@ void updateConversationStatus(UUID inboundConversationId, ConversationTransferSt .s(CONVERSATION.name()) .build()); - final Map updateItems = new HashMap<>(); - - updateItems.put(TRANSFER_STATUS.name, AttributeValueUpdate.builder() - .value(AttributeValue.builder().s(conversationTransferStatus.name()).build()) - .action(AttributeAction.PUT) - .build()); - - updateItems.put(UPDATED_AT.name, AttributeValueUpdate.builder() - .value(AttributeValue.builder().s(updateTimestamp).build()) - .action(AttributeAction.PUT) - .build()); - final UpdateItemRequest itemRequest = UpdateItemRequest.builder() .tableName(config.transferTrackerDbTableName()) .key(keyItems) - .attributeUpdates(updateItems) + .updateExpression("SET #TransferStatus = :tsValue, #UpdatedAt = :uaValue") + .expressionAttributeNames(Map.of( + "#TransferStatus", TRANSFER_STATUS.name, + "#UpdatedAt", UPDATED_AT.name + )) + .expressionAttributeValues(Map.of( + ":tsValue", AttributeValue.builder() + .s(conversationTransferStatus.name()) + .build(), + ":uaValue", AttributeValue.builder() + .s(getIsoTimestamp()) + .build() + )) + .conditionExpression("attribute_exists(CreatedAt)") .build(); try { @@ -177,12 +173,7 @@ void updateConversationStatus(UUID inboundConversationId, ConversationTransferSt } void updateConversationStatusWithFailure(UUID inboundConversationId, String failureCode) { - if (!isInboundConversationPresent(inboundConversationId)) { - throw new ConversationNotPresentException(inboundConversationId); - } - final Map keyItems = new HashMap<>(); - final String updateTimestamp = getIsoTimestamp(); keyItems.put(INBOUND_CONVERSATION_ID.name, AttributeValue.builder() .s(inboundConversationId.toString().toUpperCase()) @@ -192,27 +183,27 @@ void updateConversationStatusWithFailure(UUID inboundConversationId, String fail .s(CONVERSATION.name()) .build()); - final Map updateItems = new HashMap<>(); - - updateItems.put(TRANSFER_STATUS.name, AttributeValueUpdate.builder() - .value(AttributeValue.builder().s(INBOUND_FAILED.name()).build()) - .action(AttributeAction.PUT) - .build()); - - updateItems.put(FAILURE_CODE.name, AttributeValueUpdate.builder() - .value(AttributeValue.builder().s(failureCode).build()) - .action(AttributeAction.PUT) - .build()); - - updateItems.put(UPDATED_AT.name, AttributeValueUpdate.builder() - .value(AttributeValue.builder().s(updateTimestamp).build()) - .action(AttributeAction.PUT) - .build()); - final UpdateItemRequest itemRequest = UpdateItemRequest.builder() .tableName(config.transferTrackerDbTableName()) .key(keyItems) - .attributeUpdates(updateItems) + .updateExpression("SET #TransferStatus = :tsValue, #FailureCode = :fcValue, #UpdatedAt = :uaValue") + .expressionAttributeNames(Map.of( + "#TransferStatus", TRANSFER_STATUS.name, + "#UpdatedAt", UPDATED_AT.name, + "#FailureCode", FAILURE_CODE.name + )) + .expressionAttributeValues(Map.of( + ":tsValue", AttributeValue.builder() + .s(INBOUND_FAILED.name()) + .build(), + ":fcValue", AttributeValue.builder() + .s(failureCode) + .build(), + ":uaValue", AttributeValue.builder() + .s(getIsoTimestamp()) + .build() + )) + .conditionExpression("attribute_exists(CreatedAt)") .build(); try { diff --git a/src/test/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepositoryTest.java b/src/test/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepositoryTest.java new file mode 100644 index 00000000..6b4afae5 --- /dev/null +++ b/src/test/java/uk/nhs/prm/repo/ehrtransferservice/database/TransferRepositoryTest.java @@ -0,0 +1,125 @@ +package uk.nhs.prm.repo.ehrtransferservice.database; + +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.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import uk.nhs.prm.repo.ehrtransferservice.config.AppConfig; +import uk.nhs.prm.repo.ehrtransferservice.database.enumeration.ConversationTransferStatus; +import uk.nhs.prm.repo.ehrtransferservice.exceptions.database.ConversationUpdateException; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; +import static uk.nhs.prm.repo.ehrtransferservice.database.enumeration.TransferTableAttribute.INBOUND_CONVERSATION_ID; + +@ExtendWith(MockitoExtension.class) +class TransferRepositoryTest { + @Mock + private AppConfig appConfig; + + @Mock + private DynamoDbClient dynamoDbClient; + + @InjectMocks + private TransferRepository transferRepository; + + private static final String TABLE_NAME = "ehr-transfer-tracker"; + private static final String CREATED_AT_CONDITION_EXPRESSION = "attribute_exists(CreatedAt)"; + private static final String UPDATE_STATUS_CODE_EXPRESSION = "SET #TransferStatus = :tsValue, #UpdatedAt = :uaValue"; + private static final String UPDATE_WITH_FAILURE_CODE_EXPRESSION = "SET #TransferStatus = :tsValue, #FailureCode = :fcValue, #UpdatedAt = :uaValue"; + private static final ArgumentCaptor updateItemRequestCaptor = ArgumentCaptor.forClass(UpdateItemRequest.class); + + @BeforeEach + void beforeEach() { + doReturn(TABLE_NAME) + .when(appConfig) + .transferTrackerDbTableName(); + } + + @Test + void updateConversationStatus_ValidInboundConversationIdAndStatus_ShouldCallUpdateItem() { + // given + final String inboundConversationId = "F68FE779-5A2A-490B-81FE-6976543EFC06"; + final ConversationTransferStatus status = ConversationTransferStatus.INBOUND_COMPLETE; + + // when + transferRepository.updateConversationStatus(UUID.fromString(inboundConversationId), status); + + // then + verify(appConfig).transferTrackerDbTableName(); + verify(dynamoDbClient).updateItem(updateItemRequestCaptor.capture()); + assertEquals(inboundConversationId, updateItemRequestCaptor.getValue().key().get(INBOUND_CONVERSATION_ID.name).s()); + assertEquals(status.name(), updateItemRequestCaptor.getValue().expressionAttributeValues().get(":tsValue").s()); + assertEquals(CREATED_AT_CONDITION_EXPRESSION, updateItemRequestCaptor.getValue().conditionExpression()); + assertEquals(UPDATE_STATUS_CODE_EXPRESSION, updateItemRequestCaptor.getValue().updateExpression()); + } + + @Test + void updateConversationStatus_ValidInboundConversationIdAndStatusWithNoCreatedAtDate_ShouldThrowConversationUpdateException() { + // given + final String inboundConversationId = "C68082F9-EFB9-4144-BAA0-3A2F2E2A88B9"; + final ConversationTransferStatus status = ConversationTransferStatus.INBOUND_COMPLETE; + + // when + doThrow(ConditionalCheckFailedException.class) + .when(dynamoDbClient) + .updateItem(any(UpdateItemRequest.class)); + + // then + assertThrows( + ConversationUpdateException.class, + () -> transferRepository.updateConversationStatus(UUID.fromString(inboundConversationId), status) + ); + + verify(dynamoDbClient).updateItem(updateItemRequestCaptor.capture()); + assertEquals(inboundConversationId, updateItemRequestCaptor.getValue().key().get(INBOUND_CONVERSATION_ID.name).s()); + } + + @Test + void updateConversationStatusWithFailure_ValidInboundConversationIdAndFailureCode_ShouldCallUpdateItem() { + // given + final String inboundConversationId = "0A1B2C3D-4E5F-6789-ABCD-EF0123456789"; + final String failureCode = "19"; + + // when + transferRepository.updateConversationStatusWithFailure(UUID.fromString(inboundConversationId), failureCode); + + // then + verify(appConfig).transferTrackerDbTableName(); + verify(dynamoDbClient).updateItem(updateItemRequestCaptor.capture()); + assertEquals(inboundConversationId, updateItemRequestCaptor.getValue().key().get(INBOUND_CONVERSATION_ID.name).s()); + assertEquals(failureCode, updateItemRequestCaptor.getValue().expressionAttributeValues().get(":fcValue").s()); + assertEquals(UPDATE_WITH_FAILURE_CODE_EXPRESSION, updateItemRequestCaptor.getValue().updateExpression()); + assertEquals(CREATED_AT_CONDITION_EXPRESSION, updateItemRequestCaptor.getValue().conditionExpression()); + } + + @Test + void updateConversationStatusWithFailure_ValidInboundConversationIdAndFailureCodeWithNoCreatedAtDate_ShouldThrowConversationUpdateException() { + // given + final String inboundConversationId = "8A9B0CAD-2E3F-4567-ABCD-EF8901234567"; + final String failureCode = "19"; + + // when + doThrow(ConditionalCheckFailedException.class) + .when(dynamoDbClient) + .updateItem(any(UpdateItemRequest.class)); + + // then + assertThrows( + ConversationUpdateException.class, + () -> transferRepository.updateConversationStatusWithFailure(UUID.fromString(inboundConversationId), failureCode) + ); + + verify(dynamoDbClient).updateItem(updateItemRequestCaptor.capture()); + assertEquals(inboundConversationId, updateItemRequestCaptor.getValue().key().get(INBOUND_CONVERSATION_ID.name).s()); + } +} \ No newline at end of file