/******************************************************************************* * Copyright 2019 Graz University of Technology * MOA ZS has been developed in a cooperation between EGIZ * and Graz University of Technology. * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. *******************************************************************************/ package at.gv.egiz.moazs; import at.gv.egiz.moazs.client.ClientFactory; import at.gv.egiz.moazs.repository.DeliveryRepository; import at.gv.egiz.moazs.scheme.NotificationResponse; import at.gv.egiz.moazs.scheme.RequestStatusResponse; import at.gv.zustellung.app2mzs.xsd.DeliveryNotificationACKType; import at.gv.zustellung.app2mzs.xsd.DeliveryResponseType; import at.gv.zustellung.app2mzs.xsd.Mzs2AppPortType; import at.gv.zustellung.msg.xsd.App2ZusePort; import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; import at.gv.zustellung.tnvz.xsd.TNVZServicePort; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.junit4.SpringRunner; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.function.Consumer; import static at.gv.zustellung.app2mzs.xsd.DeliveryNotificationACKType.deliveryNotificationACKTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.persondata.IdentificationType.Value.valueBuilder; import static at.gv.zustellung.app2mzs.xsd.persondata.IdentificationType.identificationTypeBuilder; import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.PartialSuccess.partialSuccessBuilder; import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; import static java.net.http.HttpResponse.BodyHandlers.ofString; import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.*; /** * @author Christof Rabensteiner * */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ITEndToEndTest { private final static Logger log = LoggerFactory.getLogger(ITEndToEndTest.class); public static final String GZ_WATERMARK = "my-secret-watermark-string"; public static final String DELIVERY_SYSTEM = "http://localhost/url/to/msg/service"; @LocalServerPort public int port; private String mzsFrontendURL; private final String basePath = "src/test/resources/at/gv/egiz/moazs/ITEndToEndTest/"; private static Mzs2AppPortType APP; @Before public void setupFrontendURL() { this.mzsFrontendURL = "http://localhost:" + port + "/services"; } @TestConfiguration public static class MockClientsAndSignatureVerificationConfig { @Autowired private DeliveryRepository repository; @Bean @Primary public Consumer signatureVerifier() { return bytes -> {}; } @Bean @Primary public ClientFactory mockClientFactory() { var factory = mock(ClientFactory.class); var tnvz = mockTnvz(); var msg = mockMsg(); APP = mockApp(); when(factory.createSOAP12(any(), same(TNVZServicePort.class))).thenReturn(tnvz); when(factory.createSOAP12(any(), same(App2ZusePort.class))).thenReturn(msg); when(factory.createSOAP11(any(), same(Mzs2AppPortType.class))).thenReturn(APP); return factory; } private Mzs2AppPortType mockApp() { var app = mock(Mzs2AppPortType.class); when(app.forwardStatus(any())).thenAnswer(i -> ack(i.getArgument(0))); when(app.forwardNotification(any())).thenAnswer(i -> ack(i.getArgument(0))); return app; } private DeliveryNotificationACKType ack(DeliveryResponseType response) { return deliveryNotificationACKTypeBuilder() .withAppDeliveryID(response.getSuccess().getAppDeliveryID()) .build(); } private TNVZServicePort mockTnvz() { var tnvz = mock(TNVZServicePort.class); var value = valueBuilder().withId("id").withValue("value").build(); var receiverId = identificationTypeBuilder() .withValue(value).withId("id").withType("type").build(); var tnvzHelperTest = new TnvzHelperTest(); tnvzHelperTest.setup(); var tnvzSuccess = tnvzHelperTest.tnvzSuccess(List.of(""), true, receiverId); when(tnvz.queryPerson(any())).thenReturn(tnvzSuccess); return tnvz; } private App2ZusePort mockMsg() { var msg = mock(App2ZusePort.class); when(msg.delivery(any())).thenAnswer(i-> partialSuccess(i.getArgument(0))); return msg; } private DeliveryRequestStatusType partialSuccess(DeliveryRequestType request) { var appDeliveryID = request.getMetaData().getAppDeliveryID(); var zsDeliveryID = "ZSDID-" + appDeliveryID; var responseID = RequestStatusResponse.getResponseID(appDeliveryID); var answer = partialSuccessBuilder() .withDeliverySystem(DELIVERY_SYSTEM) .withAppDeliveryID(appDeliveryID) .withZSDeliveryID(zsDeliveryID) .withGZ(GZ_WATERMARK) .build(); repository.store(responseID, new byte[]{}); return deliveryRequestStatusTypeBuilder() .withPartialSuccess(answer) .build(); } } @Test public void testHappyPath() throws IOException, InterruptedException { //prepare var appDeliveryID = "delivery-request-id"; var zsDeliveryID = "ZSDID-" + appDeliveryID; var timestamp = ITMzsServiceTest.genTimeStamp().toXMLFormat(); var saveSinkFolder = "target/tmp/ITEndToEndTestOut"; delete(saveSinkFolder); //app sends delivery request to moazs and receives partial success var partialSuccess = sendMzsDeliveryRequest("mzs-delivery-request.xml"); assertThat(partialSuccess.statusCode()).isEqualTo(200); assertThat(partialSuccess.body()).contains(List.of(GZ_WATERMARK, "PartialSuccess")); // zusemsg sends async success var statusResponseID = RequestStatusResponse.getResponseID(appDeliveryID); var msgStatus = formatFile("msg-delivery-request-status.xml", new String[]{ DELIVERY_SYSTEM, zsDeliveryID, appDeliveryID, GZ_WATERMARK, timestamp }); sendMsgResponse(msgStatus); await().untilAsserted(() -> { verify(APP).forwardStatus(any()); assertStatusWrittenToFileSystem(saveSinkFolder, statusResponseID); }); // zusemsg sends async notification var notificationResponseID = NotificationResponse.getResponseID(appDeliveryID); var notification = formatFile("msg-delivery-notification.xml", new String[]{ DELIVERY_SYSTEM, zsDeliveryID, appDeliveryID, GZ_WATERMARK, timestamp, timestamp }); sendMsgResponse(notification); await().untilAsserted(() -> { verify(APP).forwardNotification(any()); assertStatusWrittenToFileSystem(saveSinkFolder, notificationResponseID); }); } private void assertStatusWrittenToFileSystem(String folder, String pathSubString) { var rootFolder = new File(folder); Collection files = FileUtils.listFiles(rootFolder, null, true); assertThat(rootFolder.exists()).isTrue(); assertThat(rootFolder.isDirectory()).isTrue(); assertThat(files).isNotEmpty(); long count = files.stream() .map(File::getAbsolutePath) .filter(path -> path.contains(pathSubString)) .count(); assertThat(count).isEqualTo(2); } private String readFile(File file) { try { return readFileToString(file, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } } private void delete(String file) { try { FileUtils.deleteDirectory(new File(file)); } catch (IOException e) { log.warn("Could not delete {}", file); } } private String formatFile(String templateFile, String... values) throws IOException { var path = basePath + templateFile; var templateString = FileUtils.readFileToString(new File(path), StandardCharsets.UTF_8); return String.format(templateString, values); } private HttpResponse sendMsgResponse(String bodyString) throws IOException, InterruptedException { var body = HttpRequest.BodyPublishers.ofString(bodyString); var client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); var request = HttpRequest.newBuilder() .uri(URI.create(mzsFrontendURL + "/msg/")) .header("Content-Type", "text/xml;charset=UTF-8") .header("SOAPAction", "\"\"") .POST(body) .build(); return client.send(request, ofString()); } private HttpResponse sendMzsDeliveryRequest(String fileName) throws IOException, InterruptedException { var path = basePath + fileName; var client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); var request = HttpRequest.newBuilder() .uri(URI.create(mzsFrontendURL + "/mzs/")) .header("Content-Type", "text/xml;charset=UTF-8") .header("SOAPAction", "\"\"") .POST(HttpRequest.BodyPublishers.ofFile(Paths.get(path))) .build(); return client.send(request, ofString()); } }