package dev.fitko.fitconnect.integrationtests; import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.domain.model.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventState; import dev.fitko.fitconnect.api.domain.model.event.Status; import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag; import dev.fitko.fitconnect.api.domain.model.event.problems.other.TechnicalError; import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.sender.SendableSubmission; import dev.fitko.fitconnect.api.domain.subscriber.ReceivedSubmission; import dev.fitko.fitconnect.api.exceptions.client.FitConnectSubscriberException; import dev.fitko.fitconnect.api.exceptions.internal.RestApiException; import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.bootstrap.ClientFactory; import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isOneOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @EnableIfEnvironmentVariablesAreSet public class SubscriberClientIT extends IntegrationTestBase { @BeforeEach public void cleanup() { cleanupTestSubmissions(); } @Test void testListSubmissionsForDestination() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final var senderClient = ClientFactory.createSenderClient(config); final var subscriberClient = ClientFactory.createSubscriberClient(config); final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); final String leikaKey = "urn:de:fim:leika:leistung:99400048079000"; final String serviceName = "Test Service"; final String submissionData = getResourceAsString("/submission_data.json"); final URI submissionDataSchemaUri = URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json"); final var submissionOne = SendableSubmission.Builder() .setDestination(destinationId).setServiceType(leikaKey, serviceName) .setJsonData(submissionData, submissionDataSchemaUri) .build(); final var submissionTwo = SendableSubmission.Builder() .setDestination(destinationId).setServiceType(leikaKey, serviceName) .setJsonData(submissionData, submissionDataSchemaUri) .build(); final var sentSubmissionOne = senderClient.send(submissionOne); final var sentSubmissionTwo = senderClient.send(submissionTwo); assertNotNull(sentSubmissionOne); assertNotNull(sentSubmissionTwo); // When final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId); // Then Assertions.assertFalse(submissions.isEmpty()); final List<UUID> submissionIds = submissions.stream().map(SubmissionForPickup::getSubmissionId).collect(Collectors.toList()); assertThat(submissionIds, Matchers.hasItems(sentSubmissionOne.getSubmissionId(), sentSubmissionTwo.getSubmissionId())); } @Test void testReceiveSingleSubmission() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final var senderClient = ClientFactory.createSenderClient(config); final var subscriberClient = ClientFactory.createSubscriberClient(config); final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); final String leikaKey = "urn:de:fim:leika:leistung:99400048079000"; final String serviceName = "Test Service"; final String submissionData = getResourceAsString("/submission_data.json"); final var submission = SendableSubmission.Builder() .setDestination(destinationId).setServiceType(leikaKey, serviceName) .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromByteArray("foo".getBytes(), "plain/text")) .build(); final var sentSubmission = senderClient.send(submission); assertNotNull(sentSubmission); // When final ReceivedSubmission receivedSubmission = subscriberClient.requestSubmission(sentSubmission.getSubmissionId()); // Then assertNotNull(receivedSubmission); assertThat(receivedSubmission.getAttachments(), hasSize(1)); assertThat(receivedSubmission.getAttachments().get(0).getDataAsString(StandardCharsets.UTF_8), is("foo")); assertThat(receivedSubmission.getDataAsString(), is(submissionData)); assertThat(receivedSubmission.getServiceType(), is(new ServiceType(serviceName, leikaKey))); } @Test void testReceiveSubmissionWithUrnSchemaValidation(@TempDir final Path tempDir) throws IOException { // Given // Prepare locally set submission data schema for a urn schema in given path final String schemaResource = getResourceAsString("/submission-data-schema/submission_data_schema.json"); final Path schemaPath = Files.writeString(Path.of(tempDir.toString(), "submission_data_schema.json"), schemaResource); final String schemaUri = "urn:de:fitko:test:schema"; final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(true, Map.of(schemaUri, schemaPath.toString())); final String submissionData = getResourceAsString("/submission_data.json"); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079001", "Test Service") .setJsonData(submissionData, URI.create("urn:de:fitko:test:schema")) .build(); // When final var sentSubmission = ClientFactory.createSenderClient(config).send(submission); // Then assertNotNull(sentSubmission); // When final ReceivedSubmission receivedSubmission = ClientFactory.createSubscriberClient(config).requestSubmission(sentSubmission.getSubmissionId()); // Then assertNotNull(receivedSubmission); } @Test void testRejectEvent() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final SenderClient senderClient = ClientFactory.createSenderClient(config); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) .build(); final var sentSubmission = senderClient.send(submission); assertNotNull(sentSubmission); // When final var subscriberClient = ClientFactory.createSubscriberClient(config); final var sentSubmissionId = sentSubmission.getSubmissionId(); // reject and remove subscriberClient.requestSubmission(sentSubmissionId).rejectSubmission(List.of(new DataEncryptionIssue())); // check event log if reject event was sent final Status status = senderClient.getSubmissionStatus(sentSubmission); assertThat(status.getState(), is(EventState.REJECTED)); // second attempt to receive and reject the submission should return an empty result // since the submission is gone after being rejected final FitConnectSubscriberException exception = assertThrows(FitConnectSubscriberException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); assertThat(exception.getCause(), instanceOf(RestApiException.class)); assertThat(((RestApiException) exception.getCause()).getStatusCode(), is(404)); } @Test void testAcceptEvent() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final SenderClient senderClient = ClientFactory.createSenderClient(config); final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) .build(); final var sentSubmission = senderClient.send(submission); Assertions.assertNotNull(sentSubmission); // When final var sentSubmissionId = sentSubmission.getSubmissionId(); // accept and remove subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(); // check event log if accept event was sent final Status status = senderClient.getSubmissionStatus(sentSubmission); assertThat(status.getState(), is(EventState.ACCEPTED)); // second attempt to receive the submission should return an empty result // since the submission is gone after being accepted final FitConnectSubscriberException exception = assertThrows(FitConnectSubscriberException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); assertThat(exception.getCause(), instanceOf(RestApiException.class)); assertThat(((RestApiException) exception.getCause()).getStatusCode(), is(404)); } @Test void testAcceptEventWithProblem() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) .build(); final SenderClient senderClient = ClientFactory.createSenderClient(config); final var subscriberClient = ClientFactory.createSubscriberClient(config); final var sentSubmission = senderClient.send(submission); assertNotNull(sentSubmission); // When final var sentSubmissionId = sentSubmission.getSubmissionId(); // accept and remove subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(new IncorrectDataAuthenticationTag()); // check event log if accept event was sent and contains a problem // check event log if accept event was sent final Status status = senderClient.getSubmissionStatus(sentSubmission); assertThat(status.getState(), is(EventState.ACCEPTED)); // second attempt to receive the submission should return an empty result // since the submission is gone after being accepted final FitConnectSubscriberException exception = assertThrows(FitConnectSubscriberException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); assertThat(exception.getCause(), instanceOf(RestApiException.class)); assertThat(((RestApiException) exception.getCause()).getStatusCode(), is(404)); } @Test void testRejectEventViaClient() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final SenderClient senderClient = ClientFactory.createSenderClient(config); final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain")) .build(); final var sentSubmission = senderClient.send(submission); assertNotNull(sentSubmission); // When final Optional<SubmissionForPickup> submissionForPickup = subscriberClient.getAvailableSubmissionsForDestination(submission.getDestinationId()) .stream() .filter(s -> s.getSubmissionId().equals(sentSubmission.getSubmissionId())) .findFirst(); assertTrue(submissionForPickup.isPresent()); subscriberClient.rejectSubmission(submissionForPickup.get(), List.of(new InvalidEventLog())); // Then assertThat(senderClient.getSubmissionStatus(sentSubmission).getState(), is(EventState.REJECTED)); // second attempt to receive and reject the submission should return an empty result since the submission is gone after being rejected final FitConnectSubscriberException exception = assertThrows(FitConnectSubscriberException.class, () -> subscriberClient.requestSubmission(sentSubmission.getSubmissionId())); assertThat(exception.getCause(), instanceOf(RestApiException.class)); assertThat(((RestApiException) exception.getCause()).getStatusCode(), is(404)); } @Test void testReadSubmissionStatus() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final SenderClient senderClient = ClientFactory.createSenderClient(config); final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) .build(); final var sentSubmission = senderClient.send(submission); Assertions.assertNotNull(sentSubmission); final Status statusForSubmission = subscriberClient.getSubmissionStatus(sentSubmission.getDestinationId(), sentSubmission.getCaseId(), sentSubmission.getSubmissionId()); assertThat(statusForSubmission.getState(), isOneOf(Event.SUBMIT_SUBMISSION.getState(), Event.NOTIFY_SUBMISSION.getState())); } @Test void testReadAllSubmissionsOrderedByIssuedAtTimestamp() throws IOException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final SenderClient senderClient = ClientFactory.createSenderClient(config); final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config); final String submissionData = getResourceAsString("/submission_data.json"); final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); // ensure destination is empty subscriberClient.getAvailableSubmissionsForDestination(destinationId).forEach(s -> subscriberClient.rejectSubmission(s, List.of(new TechnicalError()))); assertThat(subscriberClient.getAvailableSubmissionsForDestination(destinationId), hasSize(0)); final var submission = SendableSubmission.Builder() .setDestination(destinationId) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .build(); IntStream.range(0, 5).forEach(index -> { try { senderClient.send(submission); // create some distance between submission dates Thread.sleep(1000); } catch (final InterruptedException e) { throw new RuntimeException(e); } }); final List<Date> submissionDates = subscriberClient.getAvailableSubmissionsForDestination(destinationId).stream() .map(subscriberClient::requestSubmission) // receivedSubmission implements comparable on issuedAt .sorted() .map(ReceivedSubmission::getSubmittedAt) .collect(Collectors.toList()); assertThat(submissionDates, hasSize(5)); boolean consecutiveSubmissionDates = false; for (int i = 0; i < submissionDates.size() - 1; i++) { consecutiveSubmissionDates = submissionDates.get(i + 1).toInstant().isAfter(submissionDates.get(i).toInstant()); } assertTrue(consecutiveSubmissionDates); } @Test void testMultiThreadExecution() throws IOException, InterruptedException { // Given final ApplicationConfig config = getConfigWithCredentialsFromEnvironment(); final String submissionData = getResourceAsString("/submission_data.json"); final SenderClient senderClient = ClientFactory.createSenderClient(config); final var submission = SendableSubmission.Builder() .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") .setJsonData(submissionData, URI.create("https://schema.fitko.de/fim/s00000114_1.1.schema.json")) .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) .build(); IntStream.range(0, 10).mapToObj(i -> submission).forEach(senderClient::send); final ExecutorService es = Executors.newCachedThreadPool(); final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config); for (final SubmissionForPickup submissionForPickup : subscriberClient.getAvailableSubmissionsForDestination(submission.getDestinationId())) { es.execute(new SubscriberRunner(subscriberClient, submissionForPickup, "Thread-" + submissionForPickup.getSubmissionId())); } es.shutdown(); es.awaitTermination(1, TimeUnit.MINUTES); } static class SubscriberRunner implements Runnable { private final SubscriberClient subscriberClient; private final SubmissionForPickup submissionForPickup; private final String threadName; SubscriberRunner(final SubscriberClient subscriberClient, final SubmissionForPickup submissionForPickup, final String threadName) { this.subscriberClient = subscriberClient; this.submissionForPickup = submissionForPickup; this.threadName = threadName; System.out.println("Creating " + threadName); } public void run() { System.out.println("Running " + threadName); assertNotNull(subscriberClient.requestSubmission(submissionForPickup)); System.out.println("Thread " + threadName + " exiting."); } } }