From cb9a76eaafd37f921006822bcfe043655288bc63 Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Mon, 22 Jul 2019 13:02:19 +0200 Subject: Test Flow of DeliveryRequest from "End-To-End" & Fix Bugs Schema Changes: - Remove mzs:DeliveryRequest/TnvzMetaData because all metadata fields can be collected from DeliveryRequest and redundancy is not needed. Fixes and Refactoring in preprocess: - MzsDeliveryRequestValidator: Instead of returning false, throw an exception when a condition is not met, and explain which condition is not met / why it is not met in the exception's message. - Integrate interface change in ConfigProfileGenerator and DeliveryRequestAugmenter. - Rewrite and simplify DeliveryRequestAugmenter's augmentation. - ConfigUtil Fixes: Ensure that we do not override the wrong parameters while merging. This error appeared in tnvz / msg client, connection / receive timeout, key / trust store, and lax hostname verification / trust all. Fix Bugs in Interceptor / SoapUtils: - Problem: DOM access and information extraction was implemented somewhat sloppy. - SolutioN: Change DOM access interface to access DOM more efficiently. Add boundary checks and handle edge cases while extracting information from SOAP Messages. - Test those changes properly. Testing: - Implement Delivery Request Flow in ITEndToEndTest. - Start application on random port instead of fixed port when running integration tests. - Add assertions to tests in ITMzsServiceTest suite. Others Bug Fixes: - ServicesConfig: Ensure that mzs service and msg service run on different endpoint addresses (/msg and /mzs). - DeliveryRequestBackend: Throw exception when binary message is missing. Don't wrap the exception. - SaveResponseToFileSink: Wrap Response in JAXB Element (otherwise, marshaller does not recognize it) --- src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java | 259 +++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java (limited to 'src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java') diff --git a/src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java b/src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java new file mode 100644 index 0000000..fd2e629 --- /dev/null +++ b/src/test/java/at/gv/egiz/moazs/ITEndToEndTest.java @@ -0,0 +1,259 @@ +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.DeliveryAnswerType.deliveryAnswerTypeBuilder; +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.mockito.Mockito.*; + +@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.create(any(), same(TNVZServicePort.class))).thenReturn(tnvz); + when(factory.create(any(), same(App2ZusePort.class))).thenReturn(msg); + when(factory.create(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("*/*"), 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 = deliveryAnswerTypeBuilder() + .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); + Thread.sleep(100); + verify(APP).forwardStatus(any()); + assertStatusWrittenToFileSystem(saveSinkFolder, statusResponseID); + assertStatusWasLogged(); + + // 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); + Thread.sleep(100); + verify(APP).forwardNotification(any()); + assertStatusWrittenToFileSystem(saveSinkFolder, notificationResponseID); + assertStatusWasLogged(); + + } + + 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 void assertStatusWasLogged() { + //todo + } + + + 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()); + + } + +} -- cgit v1.2.3