From 997983b3ea2dce3d18233c1d8c0c1910944c96e8 Mon Sep 17 00:00:00 2001 From: Jan Zickermann <jan.zickermann@dataport.de> Date: Mon, 14 Oct 2024 10:37:56 +0200 Subject: [PATCH] OZG-6748 KOP-2734 Implement fetchMessage --- .../xta/client/FetchMessageParameter.java | 36 + .../client/FetchMessageParameterFactory.java | 16 + .../de/ozgcloud/xta/client/XtaClient.java | 195 +++--- .../ozgcloud/xta/client/XtaClientFactory.java | 10 +- .../xta/client/config/XtaClientConfig.java | 5 + .../xta/client/core/XtaClientService.java | 142 ++++ .../client/core/XtaClientServiceFactory.java | 26 + .../exception/ClientRuntimeException.java | 12 + .../FetchMessageParameterFactoryTest.java | 13 + .../xta/client/FetchMessageParameterTest.java | 108 +++ .../xta/client/XtaClientFactoryTest.java | 10 +- .../ozgcloud/xta/client/XtaClientITCase.java | 119 +--- .../xta/client/XtaClientRemoteITCase.java | 132 +--- .../de/ozgcloud/xta/client/XtaClientTest.java | 641 +++++++++++++++--- .../xta/client/core/XtaClientServiceTest.java | 427 ++++++++++++ .../XtaServerSetupExtensionTestUtil.java | 41 +- .../XtaMessageMetaDataListingTestFactory.java | 31 + .../XtaTransportReportTestFactory.java | 21 + 18 files changed, 1527 insertions(+), 458 deletions(-) create mode 100644 src/main/java/de/ozgcloud/xta/client/FetchMessageParameter.java create mode 100644 src/main/java/de/ozgcloud/xta/client/FetchMessageParameterFactory.java create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java create mode 100644 src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java create mode 100644 src/main/java/de/ozgcloud/xta/client/exception/ClientRuntimeException.java create mode 100644 src/test/java/de/ozgcloud/xta/client/FetchMessageParameterFactoryTest.java create mode 100644 src/test/java/de/ozgcloud/xta/client/FetchMessageParameterTest.java create mode 100644 src/test/java/de/ozgcloud/xta/client/core/XtaClientServiceTest.java create mode 100644 src/test/java/de/ozgcloud/xta/client/factory/XtaMessageMetaDataListingTestFactory.java create mode 100644 src/test/java/de/ozgcloud/xta/client/factory/XtaTransportReportTestFactory.java diff --git a/src/main/java/de/ozgcloud/xta/client/FetchMessageParameter.java b/src/main/java/de/ozgcloud/xta/client/FetchMessageParameter.java new file mode 100644 index 0000000..b2ae07f --- /dev/null +++ b/src/main/java/de/ozgcloud/xta/client/FetchMessageParameter.java @@ -0,0 +1,36 @@ +package de.ozgcloud.xta.client; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.ozgcloud.xta.client.model.XtaIdentifier; +import de.ozgcloud.xta.client.model.XtaMessage; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; + +record FetchMessageParameter( + XtaIdentifier clientIdentifier, + Consumer<XtaMessage> processMessage, + Set<String> viewedMessageIds +) { + + public FetchMessageParameter withViewedMessageIdsFrom(List<XtaMessageMetaData> messageMetaData) { + return new FetchMessageParameter(this.clientIdentifier, this.processMessage, + collectSetOfViewedMessageIds(messageMetaData, this.viewedMessageIds)); + } + + private Set<String> collectSetOfViewedMessageIds(List<XtaMessageMetaData> messageMetaData, Set<String> processedMessageIds) { + return Stream.concat( + processedMessageIds.stream(), + messageMetaData.stream() + .map(XtaMessageMetaData::messageId) + ).collect(Collectors.toSet()); + } + + public boolean hasMessageAlreadyBeenViewed(XtaMessageMetaData messageMetaData) { + return viewedMessageIds.contains(messageMetaData.messageId()); + } + +} diff --git a/src/main/java/de/ozgcloud/xta/client/FetchMessageParameterFactory.java b/src/main/java/de/ozgcloud/xta/client/FetchMessageParameterFactory.java new file mode 100644 index 0000000..34f3297 --- /dev/null +++ b/src/main/java/de/ozgcloud/xta/client/FetchMessageParameterFactory.java @@ -0,0 +1,16 @@ +package de.ozgcloud.xta.client; + +import java.util.Collections; +import java.util.function.Consumer; + +import de.ozgcloud.xta.client.model.XtaIdentifier; +import de.ozgcloud.xta.client.model.XtaMessage; +import lombok.Builder; + +@Builder +class FetchMessageParameterFactory { + + public FetchMessageParameter create(XtaIdentifier clientIdentifier, Consumer<XtaMessage> processMessage) { + return new FetchMessageParameter(clientIdentifier, processMessage, Collections.emptySet()); + } +} diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java index a22c002..b487ce9 100644 --- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java +++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java @@ -1,113 +1,142 @@ package de.ozgcloud.xta.client; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Stream; + import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import de.ozgcloud.xta.client.config.XtaClientConfig; -import de.ozgcloud.xta.client.core.WrappedXtaService; +import de.ozgcloud.xta.client.core.XtaClientService; +import de.ozgcloud.xta.client.exception.ClientRuntimeException; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; -import de.ozgcloud.xta.client.model.XtaMessageAndTransportReport; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; import de.ozgcloud.xta.client.model.XtaMessageMetaDataListing; +import de.ozgcloud.xta.client.model.XtaMessageStatus; import de.ozgcloud.xta.client.model.XtaTransportReport; -import genv3.de.xoev.transport.xta.x211.InvalidMessageIDException; -import genv3.de.xoev.transport.xta.x211.MessageSchemaViolationException; -import genv3.de.xoev.transport.xta.x211.MessageVirusDetectionException; -import genv3.de.xoev.transport.xta.x211.ParameterIsNotValidException; -import genv3.de.xoev.transport.xta.x211.PermissionDeniedException; -import genv3.de.xoev.transport.xta.x211.SyncAsyncException; -import genv3.de.xoev.transport.xta.x211.XTAWSTechnicalProblemException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor(access = AccessLevel.MODULE) @Builder(access = AccessLevel.MODULE) @Getter(AccessLevel.MODULE) +@Slf4j public class XtaClient { - private final WrappedXtaService service; + private final XtaClientService service; private final XtaClientConfig config; + private final FetchMessageParameterFactory fetchMessageParameterFactory; - /** - * Fetch metadata of pending messages sent to the {@code xtaIdentifier}. The returned listing contains at most - * {@link de.ozgcloud.xta.client.config.XtaClientConfig#getMaxListItems() maxListItems} messages. To fetch the next messages, use - * {@link #getNextMessagesMetadata(String)}. Note that {@code xtaIdentifier} has to be configured as a - * {@link de.ozgcloud.xta.client.config.XtaClientConfig#getClientIdentifiers() clientIdentifiers}. - * - * @param clientIdentifier the client identifier value to fetch messages for - * @return the listing result with metadata of messages - */ - public XtaMessageMetaDataListing getMessagesMetadata(@NotBlank String clientIdentifier) - throws XTAWSTechnicalProblemException, PermissionDeniedException { - var identifier = deriveIdentifier(clientIdentifier); - service.checkAccountActive(identifier); - return getStatusList(identifier); - } - - /** - * Fetch metadata of pending messages sent to the {@code xtaIdentifier}. This method skips checks but otherwise behaves exactly as - * {@link #getMessagesMetadata(String)}. - */ - public XtaMessageMetaDataListing getNextMessagesMetadata(@NotBlank String clientIdentifier) - throws XTAWSTechnicalProblemException, PermissionDeniedException { - return getStatusList(deriveIdentifier(clientIdentifier)); - } - - private XtaMessageMetaDataListing getStatusList(XtaIdentifier clientIdentifier) throws XTAWSTechnicalProblemException, PermissionDeniedException { - return service.getStatusList(clientIdentifier, config.getMaxListItems()); - } - - /** - * Fetch the message content, close the message, and then fetch the transport report for the given {@code messageId} and reader identifier - * {@code clientIdentifier}. - * - * @param clientIdentifier Identifier of the reading client - * @param messageId Identifier of the message to fetch - * @return The message and transport report - */ - public XtaMessageAndTransportReport getMessage(@NotBlank String clientIdentifier, @NotBlank String messageId) - throws XTAWSTechnicalProblemException, PermissionDeniedException, InvalidMessageIDException { - var identifier = deriveIdentifier(clientIdentifier); - - var message = service.getMessage(messageId, identifier); - service.close(messageId, identifier); - - var transportReport = service.getTransportReport(messageId, identifier); - return XtaMessageAndTransportReport.builder() - .message(message) - .transportReport(transportReport) - .build(); - } - - public XtaTransportReport sendMessage(@Valid XtaMessage messageWithoutMessageId) - throws XTAWSTechnicalProblemException, PermissionDeniedException, InvalidMessageIDException, SyncAsyncException, - MessageVirusDetectionException, MessageSchemaViolationException, ParameterIsNotValidException { + public List<XtaTransportReport> fetchMessages(@NotNull Consumer<XtaMessage> processMessage) { + return config.getClientIdentifiers().stream() + .filter(service::checkAccountActive) + .map(clientIdentifier -> fetchMessageParameterFactory.create(clientIdentifier, processMessage)) + .flatMap(this::fetchMessagesForClientIdentifier) + .toList(); + } - var metaData = messageWithoutMessageId.metaData(); - var authorIdentifier = metaData.authorIdentifier(); - service.checkAccountActive(authorIdentifier); - service.lookupService(metaData.service(), metaData.readerIdentifier(), authorIdentifier); + Stream<XtaTransportReport> fetchMessagesForClientIdentifier(FetchMessageParameter parameter) { + return service.getStatusList(parameter.clientIdentifier()) + .map(listing -> { + var transportReports = fetchMessagesForListing(listing.messages(), parameter); + return Stream.concat(transportReports.stream(), + checkMoreMessagesAvailable(listing, transportReports) + ? fetchMessagesForClientIdentifier(parameter.withViewedMessageIdsFrom(listing.messages())) + : Stream.empty()); + }) + .orElse(Stream.empty()); + } - var messageId = service.createMessageId(authorIdentifier); - service.sendMessage(createXtaMessageWithMessageId(messageWithoutMessageId, messageId)); + boolean checkMoreMessagesAvailable(XtaMessageMetaDataListing listing, List<XtaTransportReport> transportReports) { + return checkExtraPendingMessagesAvailable(listing) && checkSomeMessageHasBeenClosed(transportReports); + } - return service.getTransportReport(messageId, authorIdentifier); + boolean checkExtraPendingMessagesAvailable(XtaMessageMetaDataListing listing) { + return listing.messages().size() < listing.pendingMessageCount().intValue(); } - XtaMessage createXtaMessageWithMessageId(XtaMessage messageWithoutMessageId, String messageId) { - return messageWithoutMessageId.toBuilder() - .metaData(messageWithoutMessageId.metaData().toBuilder() - .messageId(messageId) - .build()) - .build(); + boolean checkSomeMessageHasBeenClosed(List<XtaTransportReport> transportReports) { + var someTransportReportHasGreenStatus = transportReports.stream() + .anyMatch(t -> t.status().equals(XtaMessageStatus.GREEN)); + if (!someTransportReportHasGreenStatus) { + logWarnForNoMessageClosed(); + } + return someTransportReportHasGreenStatus; } - XtaIdentifier deriveIdentifier(String xtaIdentifier) { - return config.getClientIdentifiers().stream() - .filter(id -> id.value().equals(xtaIdentifier)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Unknown identifier: " + xtaIdentifier)); + void logWarnForNoMessageClosed() { + log.warn("No message has been closed although more are pending. Try increasing max list items."); + } + + List<XtaTransportReport> fetchMessagesForListing( + List<XtaMessageMetaData> messageMetaDataItems, + FetchMessageParameter parameter + ) { + return messageMetaDataItems.stream() + .filter(metaData -> checkMessageShouldBeFetched(metaData, parameter)) + .map(metaData -> fetchMessage(metaData, parameter)) + .flatMap(Optional::stream) + .toList(); + } + + boolean checkMessageShouldBeFetched(XtaMessageMetaData messageMetaData, FetchMessageParameter parameter) { + return !parameter.hasMessageAlreadyBeenViewed(messageMetaData) && isMessageSupported(messageMetaData); + } + + boolean isMessageSupported(XtaMessageMetaData messageMetaData) { + return Optional.ofNullable(config.getIsMessageSupported()) + .map(predicate -> predicate.test(messageMetaData)) + .orElse(true); + } + + Optional<XtaTransportReport> fetchMessage(XtaMessageMetaData messageMetaData, FetchMessageParameter parameter) { + return service.getMessage(messageMetaData) + .flatMap(message -> processMesssageAndFetchTransportReport(message, parameter)); } + + Optional<XtaTransportReport> processMesssageAndFetchTransportReport(XtaMessage message, FetchMessageParameter parameter) { + processMessageAndCloseIfNoException(message, parameter.processMessage()); + return service.getTransportReport(message.metaData()); + } + + void processMessageAndCloseIfNoException(XtaMessage message, Consumer<XtaMessage> processMessage) { + var messageId = message.metaData().messageId(); + try { + processMessage.accept(message); + log.debug("Processing of message '{}' succeeded! Closing message.", messageId); + service.closeMessage(message); + } catch (RuntimeException exception) { + logErrorForMessageProcessingFailure(messageId, exception); + } + } + + void logErrorForMessageProcessingFailure(String messageId, RuntimeException exception) { + log.error("Processing of message '%s' failed! Not closing message.".formatted(messageId), exception); + } + + public XtaTransportReport sendMessage(@Valid XtaMessage messageWithoutMessageId) { + var metaData = messageWithoutMessageId.metaData(); + throwExceptionIfAccountInactive(metaData.authorIdentifier()); + throwExceptionIfServiceNotAvailable(metaData); + return service.sendMessage(messageWithoutMessageId); + } + + void throwExceptionIfServiceNotAvailable(XtaMessageMetaData metaData) { + if (!service.lookupService(metaData)) { + throw new ClientRuntimeException("Service %s is not available!".formatted(metaData.service())); + } + } + + void throwExceptionIfAccountInactive(XtaIdentifier clientIdentifier) { + if (!service.checkAccountActive(clientIdentifier)) { + throw new ClientRuntimeException("Account %s is not active!".formatted(clientIdentifier.value())); + } + } + } diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java index 8d9c3ec..4cc93a3 100644 --- a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java +++ b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java @@ -2,7 +2,7 @@ package de.ozgcloud.xta.client; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.config.XtaConfigValidator; -import de.ozgcloud.xta.client.core.WrappedXtaServiceFactory; +import de.ozgcloud.xta.client.core.XtaClientServiceFactory; import de.ozgcloud.xta.client.exception.ClientInitializationException; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -12,14 +12,16 @@ import lombok.RequiredArgsConstructor; public class XtaClientFactory { private final XtaConfigValidator configValidator; - private final WrappedXtaServiceFactory wrappedXtaServiceFactory; + private final XtaClientServiceFactory xtaClientServiceFactory; private final XtaClientConfig config; + private final FetchMessageParameterFactory fetchMessageParameterFactory; public static XtaClientFactory from(final XtaClientConfig config) { return XtaClientFactory.builder() .config(config) .configValidator(XtaConfigValidator.builder().build()) - .wrappedXtaServiceFactory(WrappedXtaServiceFactory.from(config)) + .xtaClientServiceFactory(XtaClientServiceFactory.from(config)) + .fetchMessageParameterFactory(FetchMessageParameterFactory.builder().build()) .build(); } @@ -27,7 +29,7 @@ public class XtaClientFactory { configValidator.validate(config); return XtaClient.builder() .config(config) - .service(wrappedXtaServiceFactory.create()) + .service(xtaClientServiceFactory.create()) .build(); } } diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java index 184ce5b..b2b8603 100644 --- a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java +++ b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java @@ -12,6 +12,7 @@ package de.ozgcloud.xta.client.config; import java.util.List; +import java.util.function.Predicate; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -20,6 +21,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import de.ozgcloud.xta.client.model.XtaIdentifier; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -34,6 +36,9 @@ public class XtaClientConfig { @NotEmpty(message = "at least one client identifier is required") private final List<@Valid XtaIdentifier> clientIdentifiers; + @Builder.Default + private final Predicate<XtaMessageMetaData> isMessageSupported = null; + @NotBlank private final String managementServiceUrl; @NotBlank diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java b/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java new file mode 100644 index 0000000..3c1aacc --- /dev/null +++ b/src/main/java/de/ozgcloud/xta/client/core/XtaClientService.java @@ -0,0 +1,142 @@ +package de.ozgcloud.xta.client.core; + +import java.util.Optional; + +import de.ozgcloud.xta.client.config.XtaClientConfig; +import de.ozgcloud.xta.client.exception.ClientRuntimeException; +import de.ozgcloud.xta.client.model.XtaIdentifier; +import de.ozgcloud.xta.client.model.XtaMessage; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; +import de.ozgcloud.xta.client.model.XtaMessageMetaDataListing; +import de.ozgcloud.xta.client.model.XtaTransportReport; +import genv3.de.xoev.transport.xta.x211.InvalidMessageIDException; +import genv3.de.xoev.transport.xta.x211.MessageSchemaViolationException; +import genv3.de.xoev.transport.xta.x211.MessageVirusDetectionException; +import genv3.de.xoev.transport.xta.x211.ParameterIsNotValidException; +import genv3.de.xoev.transport.xta.x211.PermissionDeniedException; +import genv3.de.xoev.transport.xta.x211.SyncAsyncException; +import genv3.de.xoev.transport.xta.x211.XTAWSTechnicalProblemException; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Builder +@RequiredArgsConstructor +@Slf4j +public class XtaClientService { + + private final WrappedXtaService service; + private final XtaClientConfig config; + + public Optional<XtaTransportReport> getTransportReport(XtaMessageMetaData messageMetaData) { + try { + return Optional.of(getTransportReportOrThrowException(messageMetaData)); + } catch (ClientRuntimeException e) { + logError("Failed to get transport report!", e); + return Optional.empty(); + } + } + + public void closeMessage(XtaMessage message) { + var messageId = message.metaData().messageId(); + var readerClientIdentifier = message.metaData().readerIdentifier(); + try { + service.close(messageId, readerClientIdentifier); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) { + logError("Failed to close message! '%s' (reader: %s)".formatted(messageId, readerClientIdentifier), e); + } + } + + public Optional<XtaMessage> getMessage(XtaMessageMetaData messageMetaData) { + var messageId = messageMetaData.messageId(); + var readerClientIdentifier = messageMetaData.readerIdentifier(); + try { + return Optional.of(service.getMessage(messageId, readerClientIdentifier)); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) { + logError("Failed to get message by id ! '%s' (reader: %s)".formatted(messageId, readerClientIdentifier.value()), e); + return Optional.empty(); + } + } + + public Optional<XtaMessageMetaDataListing> getStatusList(XtaIdentifier clientIdentifier) { + try { + return Optional.of(service.getStatusList(clientIdentifier, config.getMaxListItems())); + } catch (PermissionDeniedException | XTAWSTechnicalProblemException e) { + logError("Failed to get status list!", e); + return Optional.empty(); + } + } + + void logError(String message, Exception e) { + log.error(message, e); + } + + public boolean checkAccountActive(XtaIdentifier clientIdentifier) { + try { + service.checkAccountActive(clientIdentifier); + return true; + } catch (XTAWSTechnicalProblemException e) { + throw new ClientRuntimeException("Failed to check account active!", e); + } catch (PermissionDeniedException e) { + return false; + } + } + + public XtaTransportReport sendMessage(XtaMessage messageWithoutMessageId) { + var message = getXtaMessageWithMessageId(messageWithoutMessageId); + try { + service.sendMessage(message); + return getTransportReportOrThrowException(message.metaData()); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException | SyncAsyncException | ParameterIsNotValidException | + MessageVirusDetectionException | MessageSchemaViolationException e) { + throw new ClientRuntimeException("Failed to send message!", e); + } + } + + XtaMessage getXtaMessageWithMessageId(XtaMessage messageWithoutMessageId) { + var authorIdentifier = messageWithoutMessageId.metaData().authorIdentifier(); + try { + var messageId = service.createMessageId(authorIdentifier); + return createXtaMessageWithMessageId(messageWithoutMessageId, messageId); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException e) { + throw new ClientRuntimeException("Failed to create message id!", e); + } + } + + XtaMessage createXtaMessageWithMessageId(XtaMessage messageWithoutMessageId, String messageId) { + return messageWithoutMessageId.toBuilder() + .metaData(messageWithoutMessageId.metaData().toBuilder() + .messageId(messageId) + .build()) + .build(); + } + + XtaTransportReport getTransportReportOrThrowException(XtaMessageMetaData messageMetaData) { + var messageId = messageMetaData.messageId(); + var authorId = messageMetaData.authorIdentifier(); + try { + return service.getTransportReport(messageId, authorId); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException | InvalidMessageIDException e) { + throw new ClientRuntimeException( + "Failed to get transport report! (messageId: %s, reader: %s)".formatted(messageId, authorId.value()), e); + } + } + + public boolean lookupService(XtaMessageMetaData messageMetaData) { + try { + return service.lookupService( + messageMetaData.service(), + messageMetaData.readerIdentifier(), + messageMetaData.authorIdentifier() + ); + } catch (XTAWSTechnicalProblemException | PermissionDeniedException | ParameterIsNotValidException exception) { + logWarning("Failed to lookup service for message '%s'!".formatted(messageMetaData.messageId()), exception); + return false; + } + } + + void logWarning(String message, Exception exception) { + log.warn(message, exception); + } + +} diff --git a/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java b/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java new file mode 100644 index 0000000..61a95de --- /dev/null +++ b/src/main/java/de/ozgcloud/xta/client/core/XtaClientServiceFactory.java @@ -0,0 +1,26 @@ +package de.ozgcloud.xta.client.core; + +import de.ozgcloud.xta.client.config.XtaClientConfig; +import de.ozgcloud.xta.client.exception.ClientInitializationException; +import lombok.Builder; + +@Builder +public class XtaClientServiceFactory { + + private final WrappedXtaServiceFactory wrappedXtaServiceFactory; + private final XtaClientConfig config; + + public static XtaClientServiceFactory from(XtaClientConfig config) { + return XtaClientServiceFactory.builder() + .config(config) + .wrappedXtaServiceFactory(WrappedXtaServiceFactory.from(config)) + .build(); + } + + public XtaClientService create() throws ClientInitializationException { + return XtaClientService.builder() + .config(config) + .service(wrappedXtaServiceFactory.create()) + .build(); + } +} diff --git a/src/main/java/de/ozgcloud/xta/client/exception/ClientRuntimeException.java b/src/main/java/de/ozgcloud/xta/client/exception/ClientRuntimeException.java new file mode 100644 index 0000000..d2e32e5 --- /dev/null +++ b/src/main/java/de/ozgcloud/xta/client/exception/ClientRuntimeException.java @@ -0,0 +1,12 @@ +package de.ozgcloud.xta.client.exception; + +public class ClientRuntimeException extends RuntimeException { + + public ClientRuntimeException(String message) { + super(message); + } + + public ClientRuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterFactoryTest.java new file mode 100644 index 0000000..b7984c3 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterFactoryTest.java @@ -0,0 +1,13 @@ +package de.ozgcloud.xta.client; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; + +class FetchMessageParameterFactoryTest { + + @DisplayName("create") + @Nested + class TestCreate { + + } +} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterTest.java b/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterTest.java new file mode 100644 index 0000000..4ae4671 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/FetchMessageParameterTest.java @@ -0,0 +1,108 @@ +package de.ozgcloud.xta.client; + +import static de.ozgcloud.xta.client.factory.MessageMetaDataTestFactory.*; +import static de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory; +import de.ozgcloud.xta.client.model.XtaMessage; + +class FetchMessageParameterTest { + + private Consumer<XtaMessage> processMessage; + + private FetchMessageParameter parameter; + + @BeforeEach + void setup() { + processMessage = message -> { + }; + parameter = new FetchMessageParameter(SELF_IDENTIFIER, processMessage, Set.of(MESSAGE_ID2)); + + } + + @DisplayName("with viewed message ids from") + @Nested + class TestWithViewedMessageIdsFrom { + + @DisplayName("should keep client identifier") + @Test + void shouldKeepClientIdentifier() { + var result = parameter.withViewedMessageIdsFrom(Collections.emptyList()); + + assertThat(result.clientIdentifier()).isEqualTo(SELF_IDENTIFIER); + } + + @DisplayName("should keep process message") + @Test + void shouldKeepProcessMessage() { + var result = parameter.withViewedMessageIdsFrom(Collections.emptyList()); + + assertThat(result.processMessage()).isEqualTo(processMessage); + } + + @DisplayName("should return copy") + @Test + void shouldReturnCopy() { + var result = parameter.withViewedMessageIdsFrom(Collections.emptyList()); + + assertThat(result.viewedMessageIds()).containsExactly(MESSAGE_ID2); + } + + @DisplayName("should return copy with additional viewed message ids") + @Test + void shouldReturnCopyWithAdditionalViewedMessageIds() { + var result = parameter.withViewedMessageIdsFrom(List.of(XtaMessageMetaDataTestFactory.create())); + + assertThat(result.viewedMessageIds()).containsExactlyInAnyOrder(MESSAGE_ID2, MESSAGE_ID); + } + + @DisplayName("should not change original with additional viewed message ids") + @Test + void shouldNotChangeOriginalWithAdditionalViewedMessageIds() { + parameter.withViewedMessageIdsFrom(List.of(XtaMessageMetaDataTestFactory.create())); + + assertThat(parameter.viewedMessageIds()).containsExactly(MESSAGE_ID2); + } + } + + @DisplayName("has message already been viewed") + @Nested + class TestHasMessageAlreadyBeenViewed { + + @DisplayName("should return true if message id is in viewed message ids") + @Test + void shouldReturnTrueIfMessageIdIsInViewedMessageIds() { + var messageMetaData = XtaMessageMetaDataTestFactory.createBuilder() + .messageId(MESSAGE_ID2) + .build(); + + var result = parameter.hasMessageAlreadyBeenViewed(messageMetaData); + + assertThat(result).isTrue(); + } + + @DisplayName("should return false if message id is not in viewed message ids") + @Test + void shouldReturnFalseIfMessageIdIsNotInViewedMessageIds() { + var messageMetaData = XtaMessageMetaDataTestFactory.createBuilder() + .messageId(MESSAGE_ID3) + .build(); + + var result = parameter.hasMessageAlreadyBeenViewed(messageMetaData); + + assertThat(result).isFalse(); + } + } + +} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java index 3077166..a5fecab 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java @@ -12,8 +12,8 @@ import org.mockito.Mock; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.config.XtaConfigValidator; -import de.ozgcloud.xta.client.core.WrappedXtaService; -import de.ozgcloud.xta.client.core.WrappedXtaServiceFactory; +import de.ozgcloud.xta.client.core.XtaClientService; +import de.ozgcloud.xta.client.core.XtaClientServiceFactory; import lombok.SneakyThrows; class XtaClientFactoryTest { @@ -21,7 +21,7 @@ class XtaClientFactoryTest { @Mock private XtaConfigValidator configValidator; @Mock - private WrappedXtaServiceFactory wrappedXtaServiceFactory; + private XtaClientServiceFactory xtaClientServiceFactory; @Mock private XtaClientConfig config; @@ -33,12 +33,12 @@ class XtaClientFactoryTest { class TestCreate { @Mock - private WrappedXtaService service; + private XtaClientService service; @BeforeEach @SneakyThrows void mock() { - when(wrappedXtaServiceFactory.create()).thenReturn(service); + when(xtaClientServiceFactory.create()).thenReturn(service); } @DisplayName("should have service") diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java index cf0c0bc..547ab02 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java @@ -10,11 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader; -import de.ozgcloud.xta.client.extension.XtaServerSetupExtensionTestUtil; import de.ozgcloud.xta.client.extension.XtaTestServerSetupExtension; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageStatus; -import genv3.de.xoev.transport.xta.x211.InvalidMessageIDException; import lombok.SneakyThrows; class XtaClientITCase { @@ -30,121 +28,10 @@ class XtaClientITCase { client = XTA_TEST_SERVER_SETUP_EXTENSION.getClient(); } - @DisplayName("get messages metadata") + @DisplayName("fetch messages") @Nested - class TestGetMessagesMetadata { - - @DisplayName("with no messages") - @Nested - class TestWithNoMessages { - - @DisplayName("should return zero pending messages") - @Test - @SneakyThrows - void shouldReturnZeroPendingMessages() { - var result = client.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - - assertThat(result.pendingMessageCount()).isZero(); - } - } - - @DisplayName("with one message") - @Nested - class TestWithOneMessage { - - @BeforeEach - void setup() { - XTA_TEST_SERVER_SETUP_EXTENSION.sendTestMessage(); - } - - @DisplayName("should return one pending message for client") - @Test - @SneakyThrows - void shouldReturnOnePendingMessageClient() { - var result = client.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - - assertThat(result.pendingMessageCount()).isOne(); - } - - @DisplayName("should return no pending message for another client") - @Test - @SneakyThrows - void shouldReturnNoPendingMessageForAnotherClient() { - var result = client.getMessagesMetadata(READER_CLIENT_IDENTIFIER2.value()); - - assertThat(result.pendingMessageCount()).isZero(); - } - - } - - } - - @DisplayName("get message") - @Nested - class TestGetMessage { - - private String messageId; - private XtaMessage message; - - @BeforeEach - @SneakyThrows - void setup() { - var messageConfig = XtaMessageExampleLoader.MessageExampleConfig.builder() - .messageLabel("dfoerdermittel") - .reader(READER_CLIENT_IDENTIFIER1) - .author(AUTHOR_CLIENT_IDENTIFIER) - .build(); - message = XtaMessageExampleLoader.load(messageConfig); - messageId = XtaServerSetupExtensionTestUtil.sendTestMessage(client, message); - } - - @DisplayName("should return message with green status") - @Test - @SneakyThrows - void shouldReturnMessageWithGreenStatus() { - var result = client.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId); - - assertThat(result.message().metaData().messageId()).isEqualTo(messageId); - assertThat(result.transportReport().metaData().messageId()).isEqualTo(messageId); - assertThat(result.transportReport().status()).isEqualTo(XtaMessageStatus.GREEN); - } - - @DisplayName("should return message with correct message file content") - @Test - @SneakyThrows - void shouldReturnMessageWithCorrectMessageFileContent() { - var messageContent = extractMessageFileContent(message); - - var result = client.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId); - var resultContent = extractMessageFileContent(result.message()); - - assertThat(messageContent).isEqualTo(resultContent); - } - - @DisplayName("should not show message id for a closed message in status list") - @Test - @SneakyThrows - void shouldNotShowMessageIdForClosedMessageInStatusList() { - assertThatNoException().isThrownBy(() -> client.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId)); - var metadataResult = client.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - if (!metadataResult.messages().isEmpty()) { - assertThat(metadataResult.messages()).allMatch(metadata -> !messageId.equals(metadata.messageId())); - } - } - - @DisplayName("should throw invalid message id exception for modified message id") - @Test - void shouldThrowInvalidMessageIdExceptionForModifiedMessageId() { - assertThatThrownBy(() -> client.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId + "1")) - .isInstanceOf(InvalidMessageIDException.class); - } - - @DisplayName("should throw invalid message id exception for other client") - @Test - void shouldThrowInvalidMessageIdExceptionForOtherClient() { - assertThatThrownBy(() -> client.getMessage(READER_CLIENT_IDENTIFIER2.value(), messageId)) - .isInstanceOf(InvalidMessageIDException.class); - } + class TestFetchMessages { + // TODO KOP-2733 } @DisplayName("send message") diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java index 42f008a..5aa5fc7 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java @@ -3,11 +3,11 @@ package de.ozgcloud.xta.client; import static de.ozgcloud.xta.client.extension.XtaServerSetupExtensionTestUtil.*; import static org.assertj.core.api.Assertions.*; +import org.junit.Ignore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; @@ -19,8 +19,6 @@ import de.ozgcloud.xta.client.model.XtaFile; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageStatus; import de.ozgcloud.xta.client.xdomea.XdomeaXtaMessageCreatorFactory; -import genv3.de.xoev.transport.xta.x211.InvalidMessageIDException; -import genv3.de.xoev.transport.xta.x211.MessageSchemaViolationException; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -32,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; "This test requires the path KOP_SH_KIEL_{DEV,TEST}_PATH and password KOP_SH_KIEL_{DEV,TEST}_PASSWORD of KOP_SH_KIEL_DEV.p12 and OZG-CLOUD_SH_KIEL_TEST.pfx. " + "Additionally, the endpoint of the DEV-xta-server at li33-0005.dp.dsecurecloud.de must be reachable." ) +@Ignore class XtaClientRemoteITCase { @RegisterExtension @@ -47,121 +46,10 @@ class XtaClientRemoteITCase { readerClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getReaderClient(); } - @DisplayName("get messages metadata") + @DisplayName("fetch messages") @Nested - class TestGetMessagesMetadata { - - @DisplayName("with no messages") - @Nested - class TestWithNoMessages { - - @DisplayName("should return zero pending messages") - @Test - @SneakyThrows - void shouldReturnZeroPendingMessages() { - var result = readerClient.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - - assertThat(result.pendingMessageCount()).isZero(); - } - } - - @DisplayName("with one message") - @Nested - class TestWithOneMessage { - - @BeforeEach - void setup() { - XTA_REMOTE_SERVER_SETUP_EXTENSION.sendTestMessage(); - } - - @DisplayName("should return one pending message for client") - @Test - @SneakyThrows - void shouldReturnOnePendingMessageClient() { - var result = readerClient.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - - assertThat(result.pendingMessageCount()).isOne(); - } - - @DisplayName("should return no pending message for another client") - @Test - @SneakyThrows - void shouldReturnNoPendingMessageForAnotherClient() { - var result = readerClient.getMessagesMetadata(READER_CLIENT_IDENTIFIER2.value()); - - assertThat(result.pendingMessageCount()).isZero(); - } - } - - } - - @DisplayName("get message") - @Nested - class TestGetMessage { - - private String messageId; - private XtaMessage message; - - @BeforeEach - @SneakyThrows - void setup() { - var messageConfig = XtaMessageExampleLoader.MessageExampleConfig.builder() - .messageLabel("dfoerdermittel") - .reader(READER_CLIENT_IDENTIFIER1) - .author(AUTHOR_CLIENT_IDENTIFIER) - .build(); - message = XtaMessageExampleLoader.load(messageConfig); - messageId = XTA_REMOTE_SERVER_SETUP_EXTENSION.sendTestMessage(message); - } - - @DisplayName("should return message with green status") - @Test - @SneakyThrows - void shouldReturnMessageWithGreenStatus() { - var result = readerClient.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId); - - assertThat(result.message().metaData().messageId()).isEqualTo(messageId); - assertThat(result.transportReport().metaData().messageId()).isEqualTo(messageId); - assertThat(result.transportReport().status()).isEqualTo(XtaMessageStatus.GREEN); - } - - @DisplayName("should return message with correct message file content") - @Test - @SneakyThrows - void shouldReturnMessageWithCorrectMessageFileContent() { - var messageContent = extractMessageFileContent(message); - - var result = readerClient.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId); - var resultContent = extractMessageFileContent(result.message()); - - assertThat(messageContent).isEqualTo(resultContent); - } - - @DisplayName("should not show message id for a closed message in status list") - @Test - @SneakyThrows - void shouldNotShowMessageIdForClosedMessageInStatusList() { - assertThatNoException().isThrownBy(() -> readerClient.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId)); - var metadataResult = readerClient.getMessagesMetadata(READER_CLIENT_IDENTIFIER1.value()); - if (!metadataResult.messages().isEmpty()) { - assertThat(metadataResult.messages()).allMatch(metadata -> !messageId.equals(metadata.messageId())); - } - } - - @DisplayName("should throw invalid message id exception for modified message id") - @Test - void shouldThrowInvalidMessageIdExceptionForModifiedMessageId() { - assertThatThrownBy(() -> readerClient.getMessage(READER_CLIENT_IDENTIFIER1.value(), messageId + "1")) - .isInstanceOf(InvalidMessageIDException.class); - } - - @DisplayName("should throw invalid message id exception for other client") - @Test - void shouldThrowInvalidMessageIdExceptionForOtherClient() { - assertThatThrownBy(() -> readerClient.getMessage(READER_CLIENT_IDENTIFIER2.value(), messageId)) - .isInstanceOf(InvalidMessageIDException.class); - } - + class TestFetchMessages { + // TODO KOP-2733 } @DisplayName("send message") @@ -181,15 +69,9 @@ class XtaClientRemoteITCase { void shouldReturn(String messageLabel) { XtaMessage xtaMessage = createXdomeaMessage(messageLabel); - try { - var result = authorClient.sendMessage(xtaMessage); - - assertThat(result.status()).isEqualTo(XtaMessageStatus.OPEN); - } catch (MessageSchemaViolationException exception) { - log.error(exception.getFaultInfo().getErrorCode().toString(), exception); - fail(exception.getFaultInfo().getErrorCode().toString()); - } + var result = authorClient.sendMessage(xtaMessage); + assertThat(result.status()).isEqualTo(XtaMessageStatus.OPEN); } @SneakyThrows diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java index 76f2e92..37c7ef4 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientTest.java @@ -5,165 +5,577 @@ import static de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.math.BigInteger; import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import de.ozgcloud.xta.client.config.XtaClientConfig; -import de.ozgcloud.xta.client.core.WrappedXtaService; +import de.ozgcloud.xta.client.core.XtaClientService; +import de.ozgcloud.xta.client.exception.ClientRuntimeException; +import de.ozgcloud.xta.client.factory.XtaMessageMetaDataListingTestFactory; +import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory; import de.ozgcloud.xta.client.factory.XtaMessageTestFactory; +import de.ozgcloud.xta.client.factory.XtaTransportReportTestFactory; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageMetaData; import de.ozgcloud.xta.client.model.XtaMessageMetaDataListing; +import de.ozgcloud.xta.client.model.XtaMessageStatus; import de.ozgcloud.xta.client.model.XtaTransportReport; import lombok.SneakyThrows; class XtaClientTest { @Mock - private WrappedXtaService service; + private XtaClientService service; @Mock private XtaClientConfig config; + @Mock + private FetchMessageParameterFactory fetchMessageParameterFactory; + @Spy @InjectMocks private XtaClient client; - @DisplayName("get messages metadata") + private XtaMessageMetaDataListing listing; + + private List<XtaTransportReport> transportReports; + + private XtaMessage message; + + @BeforeEach + void setup() { + message = XtaMessageTestFactory.create(); + listing = XtaMessageMetaDataListingTestFactory.create(); + transportReports = listing.messages().stream() + .map(messageMetaData -> XtaTransportReportTestFactory.createBuilder() + .metaData(messageMetaData) + .build()) + .toList(); + } + + @DisplayName("fetch messages") @Nested - class TestGetMessagesMetadata { + class TestFetchMessages { + + @Mock + private Consumer<XtaMessage> processMessage; @Mock - XtaMessageMetaDataListing xtaMessageMetaDataListing; + private FetchMessageParameter parameter; + + @Mock + private XtaTransportReport transportReport; @BeforeEach - @SneakyThrows void mock() { - doReturn(SELF_IDENTIFIER).when(client).deriveIdentifier(SELF_IDENTIFIER_VALUE); - when(service.getStatusList(SELF_IDENTIFIER, MAX_LIST_ITEMS)).thenReturn(xtaMessageMetaDataListing); - when(config.getMaxListItems()).thenReturn(MAX_LIST_ITEMS); + doReturn(List.of(SELF_IDENTIFIER)).when(config).getClientIdentifiers(); } - @DisplayName("should call checkAccountActive") + @DisplayName("with active account") + @Nested + class TestWithActiveAccount { + + @DisplayName("should fetch messages for client identifier") + @Test + void shouldFetchMessagesForClientIdentifier() { + doReturn(true).when(service).checkAccountActive(SELF_IDENTIFIER); + when(fetchMessageParameterFactory.create(SELF_IDENTIFIER, processMessage)).thenReturn(parameter); + doReturn(Stream.of(transportReport)).when(client).fetchMessagesForClientIdentifier(parameter); + + var result = client.fetchMessages(processMessage); + + assertThat(result).containsExactly(transportReport); + } + } + + @DisplayName("with inactive account") + @Nested + class TestWithInactiveAccount { + + @DisplayName("should fetch no messages") + @Test + void shouldFetchNoMessages() { + doReturn(false).when(service).checkAccountActive(SELF_IDENTIFIER); + + var result = client.fetchMessages(processMessage); + + assertThat(result).isEmpty(); + } + } + } + + @DisplayName("fetch messages for client identifier") + @Nested + class TestFetchMessagesForClientIdentifier { + + @Mock + private FetchMessageParameter parameter; + + @BeforeEach + void mock() { + when(parameter.clientIdentifier()).thenReturn(SELF_IDENTIFIER); + } + + @DisplayName("with successful listing") + @Nested + class TestWithSuccessfulListing { + + @Mock + private FetchMessageParameter nextParameter; + + @BeforeEach + void mock() { + when(service.getStatusList(SELF_IDENTIFIER)).thenReturn(Optional.of(listing)); + doReturn(transportReports).when(client).fetchMessagesForListing(listing.messages(), parameter); + } + + @DisplayName("should return transport reports if no more messages available\"") + @Test + void shouldReturnTransportReportsIfNoMoreMessagesAvailable() { + doReturn(false).when(client).checkMoreMessagesAvailable(listing, transportReports); + + var result = client.fetchMessagesForClientIdentifier(parameter).toList(); + + assertThat(result) + .extracting(XtaTransportReport::metaData) + .extracting(XtaMessageMetaData::messageId) + .containsExactly(MESSAGE_ID, MESSAGE_ID2, MESSAGE_ID3); + } + + @DisplayName("should return concatenated transport reports if more messages available") + @Test + void shouldReturnConcatenatedTransportReportsIfMoreMessagesAvailable() { + var messageId4 = "messageId4"; + var additionalTransportReport = XtaTransportReportTestFactory.createBuilder() + .metaData(XtaMessageMetaDataTestFactory.createBuilder() + .messageId(messageId4) + .build()) + .build(); + doReturn(true).when(client).checkMoreMessagesAvailable(listing, transportReports); + doReturn(nextParameter).when(parameter).withViewedMessageIdsFrom(listing.messages()); + doReturn(Stream.of(additionalTransportReport)).when(client).fetchMessagesForClientIdentifier(nextParameter); + + var result = client.fetchMessagesForClientIdentifier(parameter).toList(); + + assertThat(result) + .extracting(XtaTransportReport::metaData) + .extracting(XtaMessageMetaData::messageId) + .containsExactly(MESSAGE_ID, MESSAGE_ID2, MESSAGE_ID3, messageId4); + } + } + + @DisplayName("with missing listing") + @Nested + class TestWithMissingListing { + + @BeforeEach + void mock() { + when(service.getStatusList(SELF_IDENTIFIER)).thenReturn(Optional.empty()); + } + + @DisplayName("should return empty stream") + @Test + void shouldReturnEmptyStream() { + var result = client.fetchMessagesForClientIdentifier(parameter).toList(); + + assertThat(result).isEmpty(); + } + } + } + + @DisplayName("check more messages available") + @Nested + class TestCheckMoreMessagesAvailable { + + @DisplayName("with no extra pending messages") + @Nested + class TestWithNoExtraPendingMessages { + @DisplayName("should return false") + @Test + void shouldReturnFalse() { + doReturn(false).when(client).checkExtraPendingMessagesAvailable(listing); + + var result = client.checkMoreMessagesAvailable(listing, transportReports); + + assertThat(result).isFalse(); + } + } + + @DisplayName("with extra pending messages but no message closed") + @Nested + class TestWithExtraPendingMessagesButNoMessageClosed { + @DisplayName("should return false") + @Test + void shouldReturnFalse() { + doReturn(true).when(client).checkExtraPendingMessagesAvailable(listing); + doReturn(false).when(client).checkSomeMessageHasBeenClosed(transportReports); + + var result = client.checkMoreMessagesAvailable(listing, transportReports); + + assertThat(result).isFalse(); + } + } + } + + @DisplayName("check extra pending messages available") + @Nested + class TestCheckExtraPendingMessagesAvailable { + @DisplayName("should return true if more pending than received") @Test - @SneakyThrows - void shouldCallCheckAccountActive() { - client.getMessagesMetadata(SELF_IDENTIFIER_VALUE); + void shouldReturnTrueIfMorePendingThanReceived() { + var result = client.checkExtraPendingMessagesAvailable(listing); - verify(service).checkAccountActive(SELF_IDENTIFIER); + assertThat(result).isTrue(); } - @DisplayName("should return get status list response") + @DisplayName("should return false if no more pending than received") @Test - @SneakyThrows - void shouldReturnGetStatusListResponse() { - var result = client.getMessagesMetadata(SELF_IDENTIFIER_VALUE); + void shouldReturnFalseIfNoMorePendingThanReceived() { + var listingWithNoExtraPendingMessages = XtaMessageMetaDataListingTestFactory.createBuilder() + .pendingMessageCount(BigInteger.valueOf(listing.messages().size())) + .build(); + + var result = client.checkExtraPendingMessagesAvailable(listingWithNoExtraPendingMessages); - assertThat(result).isEqualTo(xtaMessageMetaDataListing); + assertThat(result).isFalse(); } + } + + @DisplayName("check some message has been closed") + @Nested + class TestCheckSomeMessageHasBeenClosed { + @DisplayName("should return false if no message has green status") + @Test + void shouldReturnFalseIfNoMessageHasGreenStatus() { + var result = client.checkSomeMessageHasBeenClosed(transportReports); + + assertThat(result).isFalse(); + } + + @DisplayName("should log warning if no message has been closed") + @Test + void shouldLogWarningIfNoMessageHasBeenClosed() { + client.checkSomeMessageHasBeenClosed(transportReports); + + verify(client).logWarnForNoMessageClosed(); + } + + @DisplayName("should return true if some message has green status") + @Test + void shouldReturnTrueIfSomeMessageHasGreenStatus() { + var transportReportsWithGreenStatus = Stream.concat( + transportReports.stream(), + Stream.of(XtaTransportReportTestFactory.createBuilder() + .status(XtaMessageStatus.GREEN) + .build()) + ).toList(); + var result = client.checkSomeMessageHasBeenClosed(transportReportsWithGreenStatus); + + assertThat(result).isTrue(); + } } - @DisplayName("get next messages meta data") + @DisplayName("fetch messages for listing") @Nested - class TestGetNextMessagesMetaData { + class TestFetchMessagesForListing { @Mock - XtaMessageMetaDataListing xtaMessageMetaDataListing; + private FetchMessageParameter parameter; - @BeforeEach - @SneakyThrows - void mock() { - doReturn(SELF_IDENTIFIER).when(client).deriveIdentifier(SELF_IDENTIFIER_VALUE); - when(service.getStatusList(SELF_IDENTIFIER, MAX_LIST_ITEMS)).thenReturn(xtaMessageMetaDataListing); - when(config.getMaxListItems()).thenReturn(MAX_LIST_ITEMS); + @Captor + private ArgumentCaptor<XtaMessageMetaData> messageMetaDataArgumentCaptor; + + @DisplayName("with messages which should not be fetched") + @Nested + class TestWithMessagesWhichShouldNotBeFetched { + + @BeforeEach + void mock() { + doReturn(false).when(client).checkMessageShouldBeFetched(any(), any()); + } + + @DisplayName("should check if message has already been viewed") + @Test + void shouldCheckIfMessageHasAlreadyBeenViewed() { + client.fetchMessagesForListing(listing.messages(), parameter); + + verify(client, times(3)).checkMessageShouldBeFetched(messageMetaDataArgumentCaptor.capture(), eq(parameter)); + assertThat(messageMetaDataArgumentCaptor.getAllValues()) + .containsExactlyElementsOf(listing.messages()); + } } - @DisplayName("should return get status list response") + @DisplayName("with messages which should be fetched") + @Nested + class TestWithMessagesWhichShouldBeFetched { + + @BeforeEach + void mock() { + doReturn(true).when(client).checkMessageShouldBeFetched(any(), any()); + doAnswer(args -> { + XtaMessageMetaData metaData = args.getArgument(0); + assertThat(metaData).isNotNull(); + return getTestTransportReportByMessageId(metaData.messageId()); + }).when(client).fetchMessage(any(), any()); + } + + private Optional<XtaTransportReport> getTestTransportReportByMessageId(String messageId) { + assertThat(messageId).isNotNull(); + return transportReports.stream().filter(d -> { + assertThat(d.metaData().messageId()).isNotNull(); + return d.metaData().messageId().equals(messageId); + }).findFirst(); + } + + @DisplayName("should return transport reports") + @Test + void shouldReturnTransportReports() { + var result = client.fetchMessagesForListing(listing.messages(), parameter); + + assertThat(result).containsExactlyElementsOf(transportReports); + } + } + } + + @DisplayName("check message should be fetched") + @Nested + class TestCheckMessageShouldBeFetched { + @Mock + private FetchMessageParameter parameter; + + @Mock + private XtaMessageMetaData messageMetaData; + + @DisplayName("should return false if message already viewed") @Test - @SneakyThrows - void shouldReturnGetStatusListResponse() { - var result = client.getNextMessagesMetadata(SELF_IDENTIFIER_VALUE); + void shouldReturnFalseIfMessageAlreadyViewed() { + when(parameter.hasMessageAlreadyBeenViewed(messageMetaData)).thenReturn(true); - assertThat(result).isEqualTo(xtaMessageMetaDataListing); + var result = checkMessageShouldBeFetched(); + + assertThat(result).isFalse(); + } + + @DisplayName("with message not viewed") + @Nested + class TestWithMessageNotViewed { + + @BeforeEach + void mock() { + when(parameter.hasMessageAlreadyBeenViewed(messageMetaData)).thenReturn(false); + } + + @DisplayName("should return false if message is not supported") + @Test + void shouldReturnFalseIfMessageIsNotSupported() { + doReturn(false).when(client).isMessageSupported(messageMetaData); + + var result = checkMessageShouldBeFetched(); + + assertThat(result).isFalse(); + } + + @DisplayName("should return true if message is supported") + @Test + void shouldReturnTrueIfMessageIsSupported() { + doReturn(true).when(client).isMessageSupported(messageMetaData); + + var result = checkMessageShouldBeFetched(); + + assertThat(result).isTrue(); + } + } + + private boolean checkMessageShouldBeFetched() { + return client.checkMessageShouldBeFetched(messageMetaData, parameter); } } - @DisplayName("derive identifier") + @DisplayName("is message supported") @Nested - class TestDeriveIdentifier { + class TestIsMessageSupported { + + @Mock + private Predicate<XtaMessageMetaData> isSupportedPredicate; - @DisplayName("should use value") + @Mock + private XtaMessageMetaData messageMetaData; + + @DisplayName("should return true if predicate is null") @Test - void shouldUseValue() { - when(config.getClientIdentifiers()).thenReturn(List.of(SELF_IDENTIFIER2, SELF_IDENTIFIER)); + void shouldReturnTrueIfPredicateIsNull() { + when(config.getIsMessageSupported()).thenReturn(null); - var result = client.deriveIdentifier(SELF_IDENTIFIER_VALUE); + var result = client.isMessageSupported(messageMetaData); - assertThat(result.value()).isEqualTo(SELF_IDENTIFIER_VALUE); + assertThat(result).isTrue(); } - @DisplayName("should throw when unknown") + @DisplayName("should return predicate value") + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldReturnPredicateValue(boolean value) { + when(config.getIsMessageSupported()).thenReturn(isSupportedPredicate); + when(isSupportedPredicate.test(messageMetaData)).thenReturn(value); + + var result = client.isMessageSupported(messageMetaData); + + assertThat(result).isEqualTo(value); + } + } + + @DisplayName("fetch message") + @Nested + class TestFetchMessage { + + @Mock + private XtaMessageMetaData messageMetaData; + + @Mock + private FetchMessageParameter parameter; + + @Mock + private XtaMessage message; + + @Mock + private XtaTransportReport transportReport; + + @DisplayName("should return transport report") @Test - void shouldThrowWhenUnknown() { - when(config.getClientIdentifiers()).thenReturn(List.of(SELF_IDENTIFIER2)); + void shouldReturnTransportReport() { + when(service.getMessage(messageMetaData)).thenReturn(Optional.of(message)); + doReturn(Optional.of(transportReport)).when(client).processMesssageAndFetchTransportReport(message, parameter); - assertThatThrownBy(() -> client.deriveIdentifier(SELF_IDENTIFIER_VALUE)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Unknown identifier: " + SELF_IDENTIFIER_VALUE); + var result = client.fetchMessage(messageMetaData, parameter); + + assertThat(result).contains(transportReport); } } - @DisplayName("get message") + @DisplayName("process message and fetch transport report") @Nested - class TestGetMessage { + class TestProcessMessageAndFetchTransportReport { + + @Mock + private XtaMessageMetaData messageMetaData; @Mock - XtaMessage xtaMessage; + private FetchMessageParameter parameter; @Mock - XtaTransportReport xtaTransportReport; + private Consumer<XtaMessage> processMessageConsumer; + + @Mock + private XtaMessage message; + + @Mock + private XtaTransportReport transportReport; @BeforeEach - @SneakyThrows void mock() { - doReturn(SELF_IDENTIFIER).when(client).deriveIdentifier(SELF_IDENTIFIER_VALUE); - when(service.getMessage(MESSAGE_ID, SELF_IDENTIFIER)).thenReturn(xtaMessage); - when(service.getTransportReport(MESSAGE_ID, SELF_IDENTIFIER)).thenReturn(xtaTransportReport); + when(message.metaData()).thenReturn(messageMetaData); + when(parameter.processMessage()).thenReturn(processMessageConsumer); + doNothing().when(client).processMessageAndCloseIfNoException(any(), any()); + when(service.getTransportReport(messageMetaData)).thenReturn(Optional.of(transportReport)); } - @DisplayName("should call close") + @DisplayName("should call process message and close if no exception") @Test - @SneakyThrows - void shouldCallClose() { - client.getMessage(SELF_IDENTIFIER_VALUE, MESSAGE_ID); + void shouldCallProcessMessageAndCloseIfNoException() { + client.processMesssageAndFetchTransportReport(message, parameter); - verify(service).close(MESSAGE_ID, SELF_IDENTIFIER); + verify(client).processMessageAndCloseIfNoException(message, processMessageConsumer); } - @DisplayName("should return with message") + @DisplayName("should return") @Test - @SneakyThrows void shouldReturn() { - var result = client.getMessage(SELF_IDENTIFIER_VALUE, MESSAGE_ID); + var result = client.processMesssageAndFetchTransportReport(message, parameter); - assertThat(result.message()).isEqualTo(xtaMessage); + assertThat(result).contains(transportReport); } + } + + @DisplayName("process message and close if no exception") + @Nested + class TestProcessMessageAndCloseIfNoException { + + @Mock + private Consumer<XtaMessage> processMessageConsumer; - @DisplayName("should return with transport report") + @Mock + private XtaMessage message; + + @BeforeEach + void mock() { + when(message.metaData()).thenReturn(listing.messages().getFirst()); + } + + @DisplayName("should consume message") @Test - @SneakyThrows - void shouldReturnWithTransportReport() { - var result = client.getMessage(SELF_IDENTIFIER_VALUE, MESSAGE_ID); + void shouldConsumeMessage() { + processMessageAndCloseIfNoException(); + + verify(processMessageConsumer).accept(message); + } + + @DisplayName("should close message") + @Test + void shouldCloseMessage() { + processMessageAndCloseIfNoException(); - assertThat(result.transportReport()).isEqualTo(xtaTransportReport); + verify(service).closeMessage(message); + } + + @DisplayName("with runtime exception") + @Nested + class TestWithRuntimeException { + + @Mock + private RuntimeException exception; + + @BeforeEach + void mock() { + doThrow(exception).when(processMessageConsumer).accept(message); + } + + @DisplayName("should log error") + @Test + void shouldLogError() { + processMessageAndCloseIfNoException(); + + verify(client).logErrorForMessageProcessingFailure(MESSAGE_ID, exception); + } + + @DisplayName("should not close message") + @Test + void shouldNotCloseMessage() { + processMessageAndCloseIfNoException(); + + verify(service, times(0)).closeMessage(any()); + } + } + + private void processMessageAndCloseIfNoException() { + client.processMessageAndCloseIfNoException(message, processMessageConsumer); } } @@ -172,80 +584,91 @@ class XtaClientTest { class TestSendMessage { @Mock - XtaMessage xtaMessageWithoutMessageId; - - @Mock - XtaMessageMetaData xtaMessageMetaDataWithoutMessageId; - - @Mock - XtaMessage xtaMessage; - - @Mock - XtaTransportReport xtaTransportReport; + private XtaTransportReport transportReport; @BeforeEach - @SneakyThrows void mock() { - when(xtaMessageWithoutMessageId.metaData()).thenReturn(xtaMessageMetaDataWithoutMessageId); - when(xtaMessageMetaDataWithoutMessageId.authorIdentifier()).thenReturn(AUTHOR_IDENTIFIER); - when(xtaMessageMetaDataWithoutMessageId.readerIdentifier()).thenReturn(READER_IDENTIFIER); - when(xtaMessageMetaDataWithoutMessageId.service()).thenReturn(MESSAGE_SERVICE); - - when(service.createMessageId(AUTHOR_IDENTIFIER)).thenReturn(MESSAGE_ID); - doReturn(xtaMessage).when(client).createXtaMessageWithMessageId(xtaMessageWithoutMessageId, MESSAGE_ID); - when(service.getTransportReport(MESSAGE_ID, AUTHOR_IDENTIFIER)).thenReturn(xtaTransportReport); + doNothing().when(client).throwExceptionIfAccountInactive(any()); + doNothing().when(client).throwExceptionIfServiceNotAvailable(any()); + when(service.sendMessage(message)).thenReturn(transportReport); } @DisplayName("should call checkAccountActive") @Test @SneakyThrows void shouldCallCheckAccountActive() { - client.sendMessage(xtaMessageWithoutMessageId); + sendMessage(); - verify(service).checkAccountActive(AUTHOR_IDENTIFIER); + verify(client).throwExceptionIfAccountInactive(AUTHOR_IDENTIFIER); } @DisplayName("should call lookup service") @Test @SneakyThrows void shouldCallLookupService() { - client.sendMessage(xtaMessageWithoutMessageId); + sendMessage(); - verify(service).lookupService(MESSAGE_SERVICE, READER_IDENTIFIER, AUTHOR_IDENTIFIER); + verify(client).throwExceptionIfServiceNotAvailable(message.metaData()); } - @DisplayName("should call send message") + @DisplayName("should return") @Test - @SneakyThrows - void shouldCallSendMessage() { - client.sendMessage(xtaMessageWithoutMessageId); + void shouldReturn() { + var result = sendMessage(); - verify(service).sendMessage(xtaMessage); + assertThat(result).isEqualTo(transportReport); } - @DisplayName("should return with transport report") + private XtaTransportReport sendMessage() { + return client.sendMessage(message); + } + } + + @DisplayName("throw exception if service not available") + @Nested + class TestThrowExceptionIfServiceNotAvailable { + + @DisplayName("should call lookupService") @Test - @SneakyThrows - void shouldReturnWithTransportReport() { - var result = client.sendMessage(xtaMessageWithoutMessageId); + void shouldCallLookupService() { + doReturn(true).when(service).lookupService(any()); + + client.throwExceptionIfServiceNotAvailable(message.metaData()); - assertThat(result).isEqualTo(xtaTransportReport); + verify(service).lookupService(message.metaData()); + } + + @DisplayName("should throw exception if service not available") + @Test + void shouldThrowExceptionIfServiceNotAvailable() { + doReturn(false).when(service).lookupService(message.metaData()); + + assertThatThrownBy(() -> client.throwExceptionIfServiceNotAvailable(message.metaData())) + .isInstanceOf(ClientRuntimeException.class); } } - @DisplayName("create xta message with message id") + @DisplayName("throw exception if account inactive") @Nested - class TestCreateXtaMessageWithMessageId { - private final XtaMessage xtaMessage = XtaMessageTestFactory.create(); + class TestThrowExceptionIfAccountInactive { - @DisplayName("should set message id") + @DisplayName("should call checkAccountActive") @Test - void shouldSetMessageId() { - var newMessageId = "newMessageId"; + void shouldCallCheckAccountActive() { + doReturn(true).when(service).checkAccountActive(any()); - var result = client.createXtaMessageWithMessageId(xtaMessage, newMessageId); + client.throwExceptionIfAccountInactive(SELF_IDENTIFIER); + + verify(service).checkAccountActive(SELF_IDENTIFIER); + } + + @DisplayName("should throw exception if account inactive") + @Test + void shouldThrowExceptionIfAccountInactive() { + doReturn(false).when(service).checkAccountActive(AUTHOR_IDENTIFIER); - assertThat(result.metaData().messageId()).isEqualTo(newMessageId); + assertThatThrownBy(() -> client.throwExceptionIfAccountInactive(AUTHOR_IDENTIFIER)) + .isInstanceOf(ClientRuntimeException.class); } } diff --git a/src/test/java/de/ozgcloud/xta/client/core/XtaClientServiceTest.java b/src/test/java/de/ozgcloud/xta/client/core/XtaClientServiceTest.java new file mode 100644 index 0000000..266ffd9 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/core/XtaClientServiceTest.java @@ -0,0 +1,427 @@ +package de.ozgcloud.xta.client.core; + +import static de.ozgcloud.xta.client.factory.MessageMetaDataTestFactory.*; +import static de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.xta.client.config.XtaClientConfig; +import de.ozgcloud.xta.client.exception.ClientRuntimeException; +import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory; +import de.ozgcloud.xta.client.factory.XtaMessageTestFactory; +import de.ozgcloud.xta.client.factory.XtaTransportReportTestFactory; +import de.ozgcloud.xta.client.model.XtaMessage; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; +import de.ozgcloud.xta.client.model.XtaTransportReport; +import genv3.de.xoev.transport.xta.x211.PermissionDeniedException; +import genv3.de.xoev.transport.xta.x211.XTAWSTechnicalProblemException; +import lombok.SneakyThrows; + +class XtaClientServiceTest { + + @Mock + private WrappedXtaService wrapper; + + @Mock + private XtaClientConfig config; + + @Spy + @InjectMocks + private XtaClientService service; + + private final XtaMessageMetaData messageMetaData = XtaMessageMetaDataTestFactory.create(); + private final XtaMessage message = XtaMessageTestFactory.create(); + private final XtaTransportReport transportReport = XtaTransportReportTestFactory.create(); + + @DisplayName("get transport report") + @Nested + class TestGetTransportReport { + + @Mock + private ClientRuntimeException exception; + + @DisplayName("should return") + @Test + void shouldReturn() { + doReturn(transportReport).when(service).getTransportReportOrThrowException(messageMetaData); + + var result = service.getTransportReport(messageMetaData); + + assertThat(result).contains(transportReport); + } + + @DisplayName("should return empty if exception") + @Test + void shouldReturnEmptyIfException() { + doThrow(exception).when(service).getTransportReportOrThrowException(messageMetaData); + + var result = service.getTransportReport(messageMetaData); + + assertThat(result).isEmpty(); + } + + @DisplayName("should log error if exception") + @Test + void shouldLogErrorIfException() { + doThrow(exception).when(service).getTransportReportOrThrowException(messageMetaData); + + service.getTransportReport(messageMetaData); + + verify(service).logError(anyString(), eq(exception)); + } + } + + @DisplayName("close message") + @Nested + class TestCloseMessage { + @Mock + private XTAWSTechnicalProblemException xtaWSTechnicalProblemException; + + @DisplayName("should call close") + @Test + @SneakyThrows + void shouldCallClose() { + service.closeMessage(message); + + verify(wrapper).close(MESSAGE_ID, READER_IDENTIFIER); + } + + @DisplayName("should log error if exception") + @Test + @SneakyThrows + void shouldLogErrorIfException() { + doThrow(xtaWSTechnicalProblemException).when(wrapper).close(MESSAGE_ID, READER_IDENTIFIER); + + service.closeMessage(message); + + verify(service).logError(anyString(), xtaWSTechnicalProblemException); + } + + } + + @DisplayName("get message") + @Nested + class TestGetMessage { + + @Mock + private XTAWSTechnicalProblemException xtaWSTechnicalProblemException; + + @DisplayName("should return") + @Test + @SneakyThrows + void shouldReturn() { + doReturn(message).when(wrapper).getMessage(MESSAGE_ID, READER_IDENTIFIER); + + var result = service.getMessage(messageMetaData); + + assertThat(result).contains(message); + } + + @DisplayName("should return empty if exception") + @Test + @SneakyThrows + void shouldReturnEmptyIfException() { + doThrow(xtaWSTechnicalProblemException).when(wrapper).getMessage(MESSAGE_ID, READER_IDENTIFIER); + + var result = service.getMessage(messageMetaData); + + assertThat(result).isEmpty(); + } + + @DisplayName("should log error if exception") + @Test + @SneakyThrows + void shouldLogErrorIfException() { + doThrow(xtaWSTechnicalProblemException).when(wrapper).getMessage(MESSAGE_ID, READER_IDENTIFIER); + + service.getMessage(messageMetaData); + + verify(service).logError(anyString(), xtaWSTechnicalProblemException); + } + } + + @DisplayName("get status list") + @Nested + class TestGetStatusList { + + @Mock + private XTAWSTechnicalProblemException xtaWSTechnicalProblemException; + + @BeforeEach + void mock() { + when(config.getMaxListItems()).thenReturn(MAX_LIST_ITEMS); + } + + @DisplayName("should return") + @Test + @SneakyThrows + void shouldReturn() { + doReturn(messageMetaData).when(wrapper).getStatusList(SELF_IDENTIFIER, MAX_LIST_ITEMS); + + var result = service.getStatusList(SELF_IDENTIFIER); + + assertThat(result).isEqualTo(messageMetaData); + } + + @DisplayName("should return empty if exception") + @Test + @SneakyThrows + void shouldReturnEmptyIfException() { + doThrow(xtaWSTechnicalProblemException).when(wrapper).getStatusList(SELF_IDENTIFIER, MAX_LIST_ITEMS); + + var result = service.getStatusList(SELF_IDENTIFIER); + + assertThat(result).isEmpty(); + } + + @DisplayName("should log error if exception") + @Test + @SneakyThrows + void shouldLogErrorIfException() { + doThrow(xtaWSTechnicalProblemException).when(wrapper).getStatusList(SELF_IDENTIFIER, MAX_LIST_ITEMS); + + service.getStatusList(SELF_IDENTIFIER); + + verify(service).logError(anyString(), xtaWSTechnicalProblemException); + } + } + + @DisplayName("check account active") + @Nested + class TestCheckAccountActive { + + @DisplayName("should call check account active") + @Test + @SneakyThrows + void shouldCallCheckAccountActive() { + checkAccountActive(); + + verify(wrapper).checkAccountActive(SELF_IDENTIFIER); + } + + @DisplayName("should return true if no exception") + @Test + void shouldReturnTrueIfNoException() { + var result = checkAccountActive(); + + assertThat(result).isTrue(); + } + + @DisplayName("should return false if permission denied exception") + @Test + @SneakyThrows + void shouldReturnFalseIfPermissionDeniedException() { + doThrow(new PermissionDeniedException()).when(wrapper).checkAccountActive(any()); + + var result = checkAccountActive(); + + assertThat(result).isFalse(); + } + + @DisplayName("should throw on technical problem exception") + @Test + @SneakyThrows + void shouldThrowOnTechnicalProblemException() { + var technicalException = new XTAWSTechnicalProblemException(); + doThrow(technicalException).when(wrapper).checkAccountActive(any()); + + assertThatThrownBy(this::checkAccountActive) + .isInstanceOf(ClientRuntimeException.class) + .hasCause(technicalException); + } + + private boolean checkAccountActive() { + return service.checkAccountActive(SELF_IDENTIFIER); + } + } + + @DisplayName("send message") + @Nested + class TestSendMessage { + + @Mock + private XtaMessage messageWithoutMessageId; + + @Mock + private XTAWSTechnicalProblemException exception; + + @BeforeEach + @SneakyThrows + void mock() { + doReturn(message).when(service).getXtaMessageWithMessageId(message); + } + + @DisplayName("should call send message") + @Test + @SneakyThrows + void shouldCallSendMessage() { + sendMessage(); + + verify(wrapper).sendMessage(message); + } + + @DisplayName("should return with transport report") + @Test + @SneakyThrows + void shouldReturnWithTransportReport() { + doReturn(transportReport).when(service).getTransportReportOrThrowException(message.metaData()); + + var result = sendMessage(); + + assertThat(result).isEqualTo(transportReport); + } + + @DisplayName("should throw client runtime exception if send message fails") + @Test + @SneakyThrows + void shouldThrowClientRuntimeExceptionIfSendMessageFails() { + doThrow(exception).when(wrapper).sendMessage(message); + + assertThatThrownBy(this::sendMessage) + .isInstanceOf(ClientRuntimeException.class) + .hasCause(exception); + } + + private XtaTransportReport sendMessage() { + return service.sendMessage(messageWithoutMessageId); + } + } + + @DisplayName("get xta message with message id") + @Nested + class TestGetXtaMessageWithMessageId { + + @Mock + private XtaMessage messageWithMessageId; + + @Mock + private XTAWSTechnicalProblemException exception; + + @DisplayName("should return message") + @Test + @SneakyThrows + void shouldReturnMessage() { + doReturn(MESSAGE_ID2).when(wrapper).createMessageId(AUTHOR_IDENTIFIER); + doReturn(messageWithMessageId).when(service).createXtaMessageWithMessageId(message, MESSAGE_ID2); + + var result = service.getXtaMessageWithMessageId(message); + + assertThat(result).isEqualTo(messageWithMessageId); + } + + @DisplayName("should throw client runtime exception if create message fails") + @Test + @SneakyThrows + void shouldThrowClientRuntimeExceptionIfCreateMessageFails() { + doThrow(exception).when(wrapper).createMessageId(AUTHOR_IDENTIFIER); + + assertThatThrownBy(() -> service.getXtaMessageWithMessageId(message)) + .isInstanceOf(ClientRuntimeException.class) + .hasCause(exception); + } + } + + @DisplayName("get transport report or throw exception") + @Nested + class TestGetTransportReportOrThrowException { + @Mock + private XTAWSTechnicalProblemException exception; + + @DisplayName("should return") + @Test + @SneakyThrows + void shouldReturn() { + when(wrapper.getTransportReport(MESSAGE_ID, AUTHOR_IDENTIFIER)).thenReturn(transportReport); + + var result = service.getTransportReportOrThrowException(messageMetaData); + + assertThat(result).isEqualTo(transportReport); + } + + @DisplayName("should throw client exception if exception") + @Test + @SneakyThrows + void shouldThrowClientExceptionIfException() { + when(wrapper.getTransportReport(MESSAGE_ID, AUTHOR_IDENTIFIER)).thenThrow(exception); + + assertThatThrownBy(() -> service.getTransportReportOrThrowException(messageMetaData)) + .isInstanceOf(ClientRuntimeException.class) + .hasCause(exception); + } + } + + @DisplayName("create xta message with message id") + @Nested + class TestCreateXtaMessageWithMessageId { + private final XtaMessage xtaMessage = XtaMessageTestFactory.create(); + + @DisplayName("should set message id") + @Test + void shouldSetMessageId() { + var newMessageId = "newMessageId"; + + var result = service.createXtaMessageWithMessageId(xtaMessage, newMessageId); + + assertThat(result.metaData().messageId()).isEqualTo(newMessageId); + } + } + + @DisplayName("lookup service") + @Nested + class TestLookupService { + @Mock + private XTAWSTechnicalProblemException exception; + + @DisplayName("should return lookup result") + @ParameterizedTest + @ValueSource(booleans = { true, false }) + @SneakyThrows + void shouldReturnLookupResult(boolean lookupResult) { + when(wrapper.lookupService(MESSAGE_SERVICE, READER_IDENTIFIER, AUTHOR_IDENTIFIER)).thenReturn(lookupResult); + + var result = lookupService(); + + assertThat(result).isEqualTo(lookupResult); + } + + @DisplayName("should return false if exception") + @Test + @SneakyThrows + void shouldReturnFalseIfException() { + when(wrapper.lookupService(MESSAGE_SERVICE, READER_IDENTIFIER, AUTHOR_IDENTIFIER)).thenThrow(exception); + + var result = lookupService(); + + assertThat(result).isFalse(); + } + + @DisplayName("should log warning if exception") + @Test + @SneakyThrows + void shouldLogWarningIfException() { + when(wrapper.lookupService(MESSAGE_SERVICE, READER_IDENTIFIER, AUTHOR_IDENTIFIER)).thenThrow(exception); + + lookupService(); + + verify(service).logWarning(anyString(), eq(exception)); + } + + private boolean lookupService() { + return service.lookupService(messageMetaData); + } + } + +} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java index 45888f6..4ebb6c6 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java @@ -5,6 +5,8 @@ import java.util.List; import de.ozgcloud.xta.client.XtaClient; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.core.WrappedXtaService; +import de.ozgcloud.xta.client.core.XtaClientService; +import de.ozgcloud.xta.client.exception.ClientRuntimeException; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageMetaData; @@ -54,17 +56,17 @@ public class XtaServerSetupExtensionTestUtil { log.info("Sending from author {} to reader {}.", message.metaData().authorIdentifier(), message.metaData().readerIdentifier()); var transportReport = client.sendMessage(message); return transportReport.metaData().messageId(); - } catch (ParameterIsNotValidException e) { - logCodeFehlerNummer(e.getFaultInfo().getErrorCode()); - throw e; - } catch (PermissionDeniedException e) { - logCodeFehlerNummer(e.getFaultInfo().getErrorCode()); - throw e; - } catch (MessageSchemaViolationException e) { - logCodeFehlerNummer(e.getFaultInfo().getErrorCode()); - throw e; - } catch (XTAWSTechnicalProblemException e) { - logCodeFehlerNummer(e.getFaultInfo().getErrorCode()); + } catch (ClientRuntimeException e) { + var cause = e.getCause(); + if (cause instanceof ParameterIsNotValidException) { + logCodeFehlerNummer(((ParameterIsNotValidException) cause).getFaultInfo().getErrorCode()); + } else if (cause instanceof PermissionDeniedException) { + logCodeFehlerNummer(((PermissionDeniedException) cause).getFaultInfo().getErrorCode()); + } else if (cause instanceof MessageSchemaViolationException) { + logCodeFehlerNummer(((MessageSchemaViolationException) cause).getFaultInfo().getErrorCode()); + } else if (cause instanceof XTAWSTechnicalProblemException) { + logCodeFehlerNummer(((XTAWSTechnicalProblemException) cause).getFaultInfo().getErrorCode()); + } throw e; } } @@ -75,19 +77,26 @@ public class XtaServerSetupExtensionTestUtil { @SneakyThrows public static void closeAllMessages(XtaClient client, XtaIdentifier clientId) { - var field = XtaClient.class.getDeclaredField("service"); - field.setAccessible(true); - var service = (WrappedXtaService) field.get(client); + XtaClientService clientService = getPrivateFieldValue(client, "service"); + WrappedXtaService wrappedService = getPrivateFieldValue(clientService, "service"); - var result = service.getStatusList(clientId, 100); + var result = wrappedService.getStatusList(clientId, 100); var messageIds = result.messages().stream() .map(XtaMessageMetaData::messageId) .toList(); for (var messageId : messageIds) { - service.close(messageId, clientId); + wrappedService.close(messageId, clientId); } } + @SuppressWarnings("unchecked") + @SneakyThrows + private static <T> T getPrivateFieldValue(Object object, String fieldName) { + var field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(object); + } + @SneakyThrows public static byte[] extractMessageFileContent(XtaMessage xtaMessage) { return xtaMessage.messageFile().content().getInputStream().readAllBytes(); diff --git a/src/test/java/de/ozgcloud/xta/client/factory/XtaMessageMetaDataListingTestFactory.java b/src/test/java/de/ozgcloud/xta/client/factory/XtaMessageMetaDataListingTestFactory.java new file mode 100644 index 0000000..3da8ca5 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/factory/XtaMessageMetaDataListingTestFactory.java @@ -0,0 +1,31 @@ +package de.ozgcloud.xta.client.factory; + +import static de.ozgcloud.xta.client.factory.MessageMetaDataTestFactory.*; + +import java.math.BigInteger; +import java.util.List; + +import de.ozgcloud.xta.client.model.XtaMessageMetaDataListing; + +public class XtaMessageMetaDataListingTestFactory { + + public final static BigInteger PENDING_MESSAGES_COUNT = BigInteger.valueOf(7); + + public static XtaMessageMetaDataListing create() { + return createBuilder().build(); + } + + public static XtaMessageMetaDataListing.XtaMessageMetaDataListingBuilder createBuilder() { + return XtaMessageMetaDataListing.builder() + .messages(List.of(XtaMessageMetaDataTestFactory.create(), + XtaMessageMetaDataTestFactory.createBuilder() + .messageId(MESSAGE_ID2) + .messageTypeCode(MESSAGE_TYPE_CODE2) + .build(), + XtaMessageMetaDataTestFactory.createBuilder() + .messageId(MESSAGE_ID3) + .messageTypeCode(MESSAGE_TYPE_CODE3) + .build())) + .pendingMessageCount(PENDING_MESSAGES_COUNT); + } +} diff --git a/src/test/java/de/ozgcloud/xta/client/factory/XtaTransportReportTestFactory.java b/src/test/java/de/ozgcloud/xta/client/factory/XtaTransportReportTestFactory.java new file mode 100644 index 0000000..33d54a7 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/factory/XtaTransportReportTestFactory.java @@ -0,0 +1,21 @@ +package de.ozgcloud.xta.client.factory; + +import static de.ozgcloud.xta.client.factory.TransportReportTestFactory.*; + + +import de.ozgcloud.xta.client.model.XtaMessageStatus; +import de.ozgcloud.xta.client.model.XtaTransportReport; + +public class XtaTransportReportTestFactory { + + public static XtaTransportReport create() { + return createBuilder().build(); + } + + public static XtaTransportReport.XtaTransportReportBuilder createBuilder() { + return XtaTransportReport.builder() + .reportTime(REPORT_TIME) + .metaData(XtaMessageMetaDataTestFactory.create()) + .status(XtaMessageStatus.OPEN); + } +} -- GitLab