Skip to content
Snippets Groups Projects
Commit 7d98b038 authored by OZGCloud's avatar OZGCloud
Browse files

Merge pull request 'OZG-4428_enterprise_adapter' (#76) from OZG-4428_enterprise_adapter into master

parents ff0ba2f9 74fecb89
No related branches found
No related tags found
No related merge requests found
Showing
with 786 additions and 3 deletions
......@@ -37,9 +37,9 @@ public class AntragstellerTestFactory {
public static final String SUB_VERBOTENE_VEREINIGUNG_KEY = "MitgliedschaftInVerboternerVereinigung";
public static final String SUB_VERBOTENE_VEREINIGUNG_VALUE = "true";
public static final String VORNAME = "Helge";
public static final String NACHNAME = "Schneider";
public static final String GEBURTSNAME = "Schneider";
public static final String VORNAME = "Theo";
public static final String NACHNAME = "Test";
public static final String GEBURTSNAME = "Toast";
public static final String GEBURTSDATUM = "30.8.1955";
public static final String GEBURTSORT = "Mülheim an der Ruhr";
public static final String EMAIL = "schneider@helgeschneider.local";
......
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.itvsh.kop.eingangsadapter</groupId>
<artifactId>parent</artifactId>
<version>1.13.0-SNAPSHOT</version>
</parent>
<artifactId>enterprise-adapter</artifactId>
<name>EM - Enterprise Interface Adapter</name>
<properties>
<spring-boot.build-image.imageName>
docker.ozg-sh.de/enterprise-interface-adapter:build-latest
</spring-boot.build-image.imageName>
</properties>
<dependencies>
<!--ozg-Cloud-->
<dependency>
<groupId>de.itvsh.kop.eingangsadapter</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>de.itvsh.kop.eingangsadapter</groupId>
<artifactId>semantik-adapter</artifactId>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--dev tools-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<!--test -->
<dependency>
<groupId>de.itvsh.kop.eingangsadapter</groupId>
<artifactId>common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>ci-build</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-image</id>
<phase>
install</phase>
<goals>
<goal>build-image</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
\ No newline at end of file
package de.ozgcloud.eingang.enterprise.entry;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import de.ozgcloud.eingang.semantik.SemantikAdapter;
@Controller
@ResponseBody
@RequestMapping("antrag")
public class EntryController {
@Autowired
private EntryDataMapper mapper;
@Autowired
private SemantikAdapter semantikAdapter;
@ResponseStatus(HttpStatus.ACCEPTED)
@PostMapping(consumes = "multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE)
public void receiveAntrag(@RequestPart Resource formData) throws IOException {
var mapped = mapper.mapEntryData(formData.getInputStream());
semantikAdapter.processFormData(mapped);
}
}
package de.ozgcloud.eingang.enterprise.entry;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
@Builder
@Getter
@Jacksonized
public class EntryData {
private ControlData control;
private List<EntryFormDataItem> formData;
@Builder
@Getter
@Jacksonized
public static class ControlData {
private String transactionId;
private String zustaendigeStelle;
private String[] leikaIds;
private ResultEndpoint resultEndpoint;
private String formId;
@JsonProperty("name")
private String formName;
@Builder
@Getter
@Jacksonized
public static class ResultEndpoint {
private String address;
}
}
}
package de.ozgcloud.eingang.enterprise.entry;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ozgcloud.eingang.common.formdata.FormData;
@Component
class EntryDataMapper {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private FormDataMapper formDataMapper;
public FormData mapEntryData(InputStream request) {
return formDataMapper.mapEntryData(readRequest(request));
}
EntryData readRequest(InputStream request) {
try {
return objectMapper.readValue(request, EntryData.class);
} catch (IOException e) {
throw new ReadingRequestException(e);
}
}
}
package de.ozgcloud.eingang.enterprise.entry;
import java.time.LocalDate;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
@Builder
@Getter
@Jacksonized
@ToString(onlyExplicitlyIncluded = true)
public class EntryFormDataField implements EntryFormDataItem {
private String name;
@ToString.Include
private String label;
private String stringValue;
private Boolean booleanValue;
private Number numberValue;
private LocalDate dateValue;
@Override
public boolean isFormField() {
return true;
}
public Object getValue() {
if (StringUtils.isNotBlank(stringValue)) {
return stringValue;
}
if (Objects.nonNull(booleanValue)) {
return booleanValue;
}
if (Objects.nonNull(numberValue)) {
return numberValue;
}
if (Objects.nonNull(dateValue)) {
return dateValue;
}
return null;
}
}
package de.ozgcloud.eingang.enterprise.entry;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(EntryFormDataField.class), @Type(EntryFormDataSubForm.class) })
public interface EntryFormDataItem {
String getName();
String getLabel();
default boolean isSubForm() {
return false;
}
default boolean isFormField() {
return false;
}
}
package de.ozgcloud.eingang.enterprise.entry;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
@Builder
@Getter
@Jacksonized
@ToString(onlyExplicitlyIncluded = true)
public class EntryFormDataSubForm implements EntryFormDataItem {
private String name;
@ToString.Include
private String label;
@Singular
private List<EntryFormDataItem> formItems;
@Override
public boolean isSubForm() {
return true;
}
}
package de.ozgcloud.eingang.enterprise.entry;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import de.ozgcloud.eingang.common.formdata.FormData;
import de.ozgcloud.eingang.common.formdata.FormHeader;
import de.ozgcloud.eingang.common.formdata.ZustaendigeStelle;
@Mapper
public interface FormDataMapper {
public static final String VALUE_KEY = "value";
public static final String LABEL_KEY = "label";
@Mapping(target = "antragsteller", ignore = true)
@Mapping(target = "attachment", ignore = true)
@Mapping(target = "attachments", ignore = true)
@Mapping(target = "numberOfAttachments", ignore = true)
@Mapping(target = "representation", ignore = true)
@Mapping(target = "representations", ignore = true)
@Mapping(target = "numberOfRepresentations", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "header", source = "control")
@Mapping(target = "zustaendigeStelle", source = "control.zustaendigeStelle")
FormData mapEntryData(EntryData entryData);
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "formEngineName", constant = "EnterpriseSoftware")
@Mapping(target = "requestId", source = "transactionId")
@Mapping(target = "sender", ignore = true) // TODO fill from authentication
@Mapping(target = "serviceKonto", ignore = true)
FormHeader mapHeader(EntryData.ControlData controlData);
default ZustaendigeStelle fromId(String organisationsEinheitenId) {
return ZustaendigeStelle.builder().organisationseinheitenId(organisationsEinheitenId).build();
}
default Map<String, Object> mapFormItems(List<EntryFormDataItem> items) {
return items.stream().map(item -> Pair.of(item.getName(),
item.isFormField() ? mapFormField((EntryFormDataField) item) : mapSubForm((EntryFormDataSubForm) item)))
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}
default Map<String, Object> mapFormField(EntryFormDataField field) {
return Map.of(LABEL_KEY, field.getLabel(), VALUE_KEY, field.getValue());
}
default Map<String, Object> mapSubForm(EntryFormDataSubForm subForm) {
return Map.of(LABEL_KEY, subForm.getLabel(), VALUE_KEY, mapFormItems(subForm.getFormItems()));
}
}
package de.ozgcloud.eingang.enterprise.entry;
import de.itvsh.kop.common.errorhandling.TechnicalException;
public class ReadingRequestException extends TechnicalException {
private static final String MESSAGE = "Error reading Request.";
public ReadingRequestException(Exception cause) {
super(MESSAGE, cause);
}
}
logging:
config: classpath:log4j2-local.xml
server:
port: 9294
error:
include-stacktrace: always
kop:
adapter:
targetPlutoName: local
fallbackStrategy: DENY
grpc:
client:
pluto-local:
address: static://127.0.0.1:9090
negotiationType: PLAINTEXT
\ No newline at end of file
logging:
level:
ROOT: WARN
'[de.itvsh]': INFO
spring:
servlet:
multipart:
max-file-size: 124MB
max-request-size: 256MB
file-size-threshold: 10MB
server:
http2:
enabled: true
error:
include-stacktrace: never
management:
server:
port: 8081
health:
livenessState:
enabled: true
readinessState:
enabled: true
endpoint:
health:
group:
exploratory:
include: livenessState,readinessState,ping
show-details: always
probes:
enabled: true
prometheus:
enabled: true
endpoints:
web:
exposure:
include: health,prometheus
kop:
adapter:
routingStrategy: SINGLE
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5level] %c{1.} %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="WARN">
<appender-ref ref="CONSOLE" />
</Root>
</Loggers>
</configuration>
\ No newline at end of file
package de.ozgcloud.eingang.enterprise.entry;
import de.ozgcloud.eingang.enterprise.entry.EntryData.ControlData;
import de.ozgcloud.eingang.enterprise.entry.EntryData.ControlData.ResultEndpoint;
public class ControlDataTestFactory {
public static final String TRANSACTION_ID = "4e7a6ae7-4d0f-444d-8971-7cfc051c9924";
public static final String ZUSTAENDIGE_STELLE = "248240886";
public static final String[] LEIKA_IDS = new String[] { "99108011000000", "99108011153000" };
public static final String RESULT_ENDPOIN_ADDRESS = "https://idalabs.de/backend/api";
public static final String FORM_ID = "KFAS_LIVE_KI_10_Haltverbot_befristet";
public static final String NAME = "Anmeldung zur Einrichtung einer zeitlich befristeten Haltverbotszone gem. § 45 Abs. 1 Straßenverkehrsordnung (StVO)";
public static ControlData create() {
return createBuilder().build();
}
public static ControlData.ControlDataBuilder createBuilder() {
return ControlData.builder()
.transactionId(TRANSACTION_ID)
.zustaendigeStelle(ZUSTAENDIGE_STELLE)
.leikaIds(LEIKA_IDS)
.resultEndpoint(ResultEndpoint.builder().address(RESULT_ENDPOIN_ADDRESS).build())
.formId(FORM_ID)
.formName(NAME);
}
}
package de.ozgcloud.eingang.enterprise.entry;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import de.itvsh.kop.common.test.TestUtils;
import de.ozgcloud.eingang.Application;
import de.ozgcloud.eingang.router.VorgangService;
import lombok.SneakyThrows;
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class EnterpriseEntryITCase {
@MockBean
private VorgangService vorgangService;
@Autowired
private MockMvc mockMvc;
@Test
void shouldCallVorgangService() {
doPostRequest();
verify(vorgangService).createVorgang(any());
}
@SneakyThrows
private ResultActions doPostRequest() {
return mockMvc.perform(multipart("/antrag")
.file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/simple.json").getBytes())))
.andExpect(status().is2xxSuccessful());
}
}
package de.ozgcloud.eingang.enterprise.entry;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import de.itvsh.kop.common.test.TestUtils;
import de.ozgcloud.eingang.common.formdata.FormData;
import de.ozgcloud.eingang.common.formdata.FormDataTestFactory;
import de.ozgcloud.eingang.semantik.SemantikAdapter;
import lombok.SneakyThrows;
class EntryControllerTest {
@InjectMocks
private EntryController controller;
@Mock
private EntryDataMapper mapper;
@Mock
private SemantikAdapter semantikAdapter;
private MockMvc mockMvc;
@BeforeEach
void init() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Nested
class ReceiveAntrag {
@Captor
private ArgumentCaptor<InputStream> streamCaptor;
@Test
@SneakyThrows
void shouldReturnAccepted() {
doPostRequest().andExpect(status().isAccepted());
}
@Test
@SneakyThrows
void shouldCallMapper() {
var request = TestUtils.loadTextFile("request/simple.json");
doPostRequest();
verify(mapper).mapEntryData(streamCaptor.capture());
var inputBytes = IOUtils.readFully(streamCaptor.getValue(), request.getBytes().length);
assertThat(inputBytes).hasSameSizeAs(request.getBytes()).isEqualTo(request.getBytes());
}
@Test
void shouldCallSemantikAdapter() {
FormData formData = FormDataTestFactory.create();
when(mapper.mapEntryData(any())).thenReturn(formData);
doPostRequest();
verify(semantikAdapter).processFormData(formData);
}
@SneakyThrows
private ResultActions doPostRequest() {
return mockMvc.perform(multipart("/antrag")
.file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/simple.json").getBytes())))
.andExpect(status().is2xxSuccessful());
}
}
}
package de.ozgcloud.eingang.enterprise.entry;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.InputStream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.itvsh.kop.common.test.TestUtils;
import de.ozgcloud.eingang.common.formdata.FormData;
import de.ozgcloud.eingang.common.formdata.FormDataTestFactory;
import lombok.SneakyThrows;
class EntryDataMapperTest {
@Spy
@InjectMocks
private EntryDataMapper mapper;
@Mock
private FormDataMapper formDataMapper;
@Spy
private ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
@Nested
class TestMappingEntryData {
private InputStream jsonInput = TestUtils.loadFile("request/simple.json");
@Nested
class TestReadRequest {
@Test
void shouldReadJson() {
var read = mapper.readRequest(jsonInput);
assertThat(read).usingRecursiveComparison().isEqualTo(EntryDataTestFactory.create());
}
}
@Test
void shouldReadRequest() {
mapper.mapEntryData(jsonInput);
verify(mapper).readRequest(jsonInput);
}
@Test
@SneakyThrows
void shouldCallFormDataMapper() {
var entryData = EntryDataTestFactory.create();
doReturn(entryData).when(objectMapper).readValue(any(InputStream.class), Mockito.<Class<EntryData>>any());
mapper.mapEntryData(jsonInput);
verify(formDataMapper).mapEntryData(entryData);
}
@Test
void shouldReturnMappedResult() {
FormData formData = FormDataTestFactory.create();
when(formDataMapper.mapEntryData(any())).thenReturn(formData);
var result = mapper.mapEntryData(jsonInput);
assertThat(result).isSameAs(formData);
}
}
}
package de.ozgcloud.eingang.enterprise.entry;
public class EntryDataTestFactory {
public static EntryData create() {
return createBuilder().build();
}
public static EntryData.EntryDataBuilder createBuilder() {
return EntryData.builder()
.control(ControlDataTestFactory.create())
.formData(EntryFormDataTestFactory.create());
}
}
package de.ozgcloud.eingang.enterprise.entry;
import static de.ozgcloud.eingang.enterprise.entry.FormDataMapper.*;
import java.util.List;
import java.util.Map;
public class EntryFormDataTestFactory {
public static final String FORM_FIELD_NAME = "field_name";
public static final String FORM_FIELD_VALUE = "field_string_value";
public static final String FORM_FIELD_LABEL = "field_label";
public static final String SUB_FORM_NAME = "antragsteller";
public static final String SUB_FORM_LABEL = "Antragstellende Person";
public static final String SUB_FORM_FIELD_NAME = "lastname";
public static final String SUB_FORM_FIELD_LABEL = "Nachname";
public static final String SUB_FORM_FIELD_VALUE = "Täst";
public static List<EntryFormDataItem> create() {
return List.of(
EntryFormDataField.builder().name(FORM_FIELD_NAME).label(FORM_FIELD_LABEL).stringValue(FORM_FIELD_VALUE).build(),
EntryFormDataSubForm.builder().name(SUB_FORM_NAME).label(SUB_FORM_LABEL)
.formItem(
EntryFormDataField.builder()
.name(SUB_FORM_FIELD_NAME)
.label(SUB_FORM_FIELD_LABEL)
.stringValue(SUB_FORM_FIELD_VALUE)
.build())
.build());
}
public static Map<String, Object> createAsFormDataMap() {
return Map.of(
FORM_FIELD_NAME, Map.of(LABEL_KEY, FORM_FIELD_LABEL, VALUE_KEY, FORM_FIELD_VALUE),
SUB_FORM_NAME, Map.of(LABEL_KEY, SUB_FORM_LABEL, VALUE_KEY, Map.of(
SUB_FORM_FIELD_NAME, Map.of(LABEL_KEY, SUB_FORM_FIELD_LABEL, VALUE_KEY, SUB_FORM_FIELD_VALUE))));
}
}
package de.ozgcloud.eingang.enterprise.entry;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
class FormDataMapperTest {
@InjectMocks
private FormDataMapper mapper = Mappers.getMapper(FormDataMapper.class);
@Nested
class TestMapFormItems {
@Test
void shouldMapFormItems() {
var mapped = mapper.mapFormItems(EntryFormDataTestFactory.create());
assertThat(mapped).usingRecursiveComparison().isEqualTo(EntryFormDataTestFactory.createAsFormDataMap());
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment