diff --git a/service/pom.xml b/service/pom.xml index 887042e..d0d6381 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -64,6 +64,12 @@ de.fraunhofer.iosb.ilt.faaast.service model ${faaast.service.version} + + + com.vaadin.external.google + android-json + + info.picocli @@ -80,6 +86,12 @@ postgresql ${postgres.version} + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + diff --git a/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/ShellRegistryControllerIT.java b/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/ShellRegistryControllerIT.java new file mode 100644 index 0000000..f3ae72c --- /dev/null +++ b/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/ShellRegistryControllerIT.java @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.ilt.faaast.registry.service; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetKind; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeIec61360; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.SecurityTypeEnum; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAdministrativeInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultDataSpecificationIec61360; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEmbeddedDataSpecification; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEndpoint; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultExtension; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringDefinitionTypeIec61360; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringNameType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringPreferredNameTypeIec61360; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringShortNameTypeIec61360; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProtocolInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSecurityAttributeObject; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelDescriptor; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultValueList; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultValueReferencePair; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = App.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = "classpath:application-integrationtest.properties") +public class ShellRegistryControllerIT { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void testGetAASs() { + ResponseEntity> response = restTemplate.exchange( + createURLWithPort(""), HttpMethod.GET, null, new ParameterizedTypeReference>() {}); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + var list = response.getBody().getContent(); + Assert.assertNotNull(list); + } + + + @Test + public void testCreateAas() { + AssetAdministrationShellDescriptor expected = getAas(); + createAas(expected); + checkGetAas(expected); + + ResponseEntity> response2 = restTemplate.exchange( + createURLWithPort(""), HttpMethod.GET, null, new ParameterizedTypeReference>() {}); + + Assert.assertNotNull(response2); + Assert.assertEquals(HttpStatus.OK, response2.getStatusCode()); + Assert.assertNotNull(response2.getBody()); + var list = response2.getBody().getContent(); + Assert.assertNotNull(list); + Assert.assertTrue(list.size() >= 0); + + Optional actual = list.stream().filter(x -> expected.getId().equals(x.getId())).findFirst(); + Assert.assertTrue(actual.isPresent()); + Assert.assertEquals(expected, actual.get()); + + // check that an error is reposrted, when an AAS shall be created, that already exists + checkCreateAasError(expected, HttpStatus.CONFLICT); + } + + + @Test + public void testCreateInvalidAas() { + AssetAdministrationShellDescriptor expected = getAasInvalid(); + checkCreateAasError(expected, HttpStatus.BAD_REQUEST); + } + + + @Test + public void testUpdateDeleteAas() { + // create AAS + AssetAdministrationShellDescriptor original = getAasUpdate(); + createAas(original); + + // update AAS + AssetAdministrationShellDescriptor expected = getAasUpdate(); + expected.setIdShort("IntegrationTest100A"); + expected.getDisplayName().add(new DefaultLangStringNameType.Builder().text("Integration Test 100 Name Updated").language("en-US").build()); + + HttpEntity entity = new HttpEntity<>(expected); + ResponseEntity responsePut = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.PUT, entity, Void.class); + Assert.assertNotNull(responsePut); + Assert.assertEquals(HttpStatus.NO_CONTENT, responsePut.getStatusCode()); + + checkGetAas(expected); + + // delete AAS + ResponseEntity responseDelete = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.DELETE, entity, Void.class); + Assert.assertNotNull(responseDelete); + Assert.assertEquals(HttpStatus.NO_CONTENT, responsePut.getStatusCode()); + + checkGetAasNotExist(expected.getId()); + } + + + @Test + public void testInvalidLimit() { + ResponseEntity response = restTemplate.exchange(createURLWithPort("?limit=0"), HttpMethod.GET, null, Void.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + + @Test + public void testPageCursorWithAssetType() { + Map expectedMap = new HashMap<>(); + String assetType = "PageCursorTest"; + AssetAdministrationShellDescriptor aas1 = getAas(); + aas1.setId("http://iosb.fraunhofer.de/IntegrationTest/PageCursor/AAS1"); + aas1.setAssetType(assetType); + createAas(aas1); + expectedMap.put(aas1.getId(), aas1); + + AssetAdministrationShellDescriptor aas2 = getAas(); + aas2.setId("http://iosb.fraunhofer.de/IntegrationTest/PageCursor/AAS2"); + aas2.setAssetType(assetType); + createAas(aas2); + expectedMap.put(aas2.getId(), aas2); + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("?limit=1&assetType=" + EncodingHelper.base64UrlEncode(assetType)), HttpMethod.GET, null, + new ParameterizedTypeReference>() {}); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + var list = response.getBody().getContent(); + Assert.assertNotNull(list); + Assert.assertEquals(1, list.size()); + var metadata = response.getBody().getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertNotNull(metadata.getCursor()); + Assert.assertFalse(metadata.getCursor().isEmpty()); + + AssetAdministrationShellDescriptor actual = list.get(0); + Assert.assertTrue(expectedMap.containsKey(actual.getId())); + Assert.assertEquals(expectedMap.get(actual.getId()), actual); + expectedMap.remove(actual.getId()); + + ResponseEntity> response2 = restTemplate.exchange( + createURLWithPort("?limit=1&assetType=" + EncodingHelper.base64UrlEncode(assetType) + "&cursor=" + metadata.getCursor()), HttpMethod.GET, null, + new ParameterizedTypeReference>() {}); + Assert.assertNotNull(response2); + Assert.assertEquals(HttpStatus.OK, response2.getStatusCode()); + Assert.assertNotNull(response2.getBody()); + list = response2.getBody().getContent(); + Assert.assertNotNull(list); + Assert.assertEquals(1, list.size()); + + actual = list.get(0); + Assert.assertTrue(expectedMap.containsKey(actual.getId())); + Assert.assertEquals(expectedMap.get(actual.getId()), actual); + expectedMap.remove(actual.getId()); + Assert.assertTrue(expectedMap.isEmpty()); + } + + + @Test + public void testAddUpdateDeleteSubmodel() { + // create AAS + AssetAdministrationShellDescriptor aas = getAas101(); + createAas(aas); + + SubmodelDescriptor newSubmodel = getSubmodel2A(); + checkGetSubmodelError(aas.getId(), newSubmodel.getId(), HttpStatus.NOT_FOUND); + + // add Submodel + HttpEntity entity = new HttpEntity<>(newSubmodel); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(aas.getId()) + "/submodel-descriptors"), + HttpMethod.POST, entity, SubmodelDescriptor.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(HttpStatus.CREATED, responsePost.getStatusCode()); + Assert.assertEquals(newSubmodel, responsePost.getBody()); + + checkGetSubmodel(aas.getId(), newSubmodel); + + // update Submodel + newSubmodel.setIdShort("Submodel-101-2 updated"); + newSubmodel.getDescription().add(new DefaultLangStringTextType.Builder().language("en-US").text("Submodel 101-2 new Description").build()); + entity = new HttpEntity<>(newSubmodel); + ResponseEntity responsePut = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(aas.getId()) + "/submodel-descriptors/" + EncodingHelper.base64UrlEncode(newSubmodel.getId())), + HttpMethod.PUT, entity, Void.class); + Assert.assertNotNull(responsePut); + Assert.assertEquals(HttpStatus.NO_CONTENT, responsePut.getStatusCode()); + checkGetSubmodel(aas.getId(), newSubmodel); + + // delete Submodel + ResponseEntity responseDelete = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(aas.getId()) + "/submodel-descriptors/" + EncodingHelper.base64UrlEncode(newSubmodel.getId())), + HttpMethod.DELETE, null, Void.class); + Assert.assertNotNull(responseDelete); + Assert.assertEquals(HttpStatus.NO_CONTENT, responseDelete.getStatusCode()); + checkGetSubmodelError(aas.getId(), newSubmodel.getId(), HttpStatus.NOT_FOUND); + } + + + @Test + public void testAddInvalidSubmodel() { + AssetAdministrationShellDescriptor aas = getAas(); + aas.setId("http://iosb.fraunhofer.de/IntegrationTest/Invalid/AAS1"); + createAas(aas); + + HttpEntity entity = new HttpEntity<>(getSubmodelInvalid()); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(aas.getId()) + "/submodel-descriptors"), + HttpMethod.POST, entity, Void.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(HttpStatus.BAD_REQUEST, responsePost.getStatusCode()); + } + + + private void checkGetAas(AssetAdministrationShellDescriptor expected) { + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.GET, null, AssetAdministrationShellDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + Assert.assertEquals(expected, response.getBody()); + } + + + private void checkGetAasNotExist(String id) { + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(id)), HttpMethod.GET, null, AssetAdministrationShellDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + + private void checkGetSubmodel(String aasId, SubmodelDescriptor submodel) { + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(aasId) + "/submodel-descriptors/" + EncodingHelper.base64UrlEncode(submodel.getId())), HttpMethod.GET, null, + SubmodelDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + Assert.assertEquals(submodel, response.getBody()); + } + + + private void checkGetSubmodelError(String aasId, String submodelId, HttpStatusCode statusCode) { + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(aasId) + "/submodel-descriptors/" + EncodingHelper.base64UrlEncode(submodelId)), HttpMethod.GET, null, + SubmodelDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(statusCode, response.getStatusCode()); + } + + + private String createURLWithPort(String uri) { + return "http://localhost:" + port + "/api/v3.0/shell-descriptors" + uri; + } + + + private void createAas(AssetAdministrationShellDescriptor aas) { + HttpEntity entity = new HttpEntity<>(aas); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort(""), HttpMethod.POST, entity, + AssetAdministrationShellDescriptor.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(HttpStatus.CREATED, responsePost.getStatusCode()); + Assert.assertEquals(aas, responsePost.getBody()); + } + + + private void checkCreateAasError(AssetAdministrationShellDescriptor aas, HttpStatusCode statusCode) { + HttpEntity entity = new HttpEntity<>(aas); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort(""), HttpMethod.POST, entity, + AssetAdministrationShellDescriptor.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(statusCode, responsePost.getStatusCode()); + } + + + private static AssetAdministrationShellDescriptor getAas() { + return new DefaultAssetAdministrationShellDescriptor.Builder() + .idShort("IntegrationTest99") + .id("http://iosb.fraunhofer.de/IntegrationTest/AAS99") + .displayName(new DefaultLangStringNameType.Builder().text("Integration Test 99 Name").language("de-DE").build()) + .description(new DefaultLangStringTextType.Builder() + .language("en-US") + .text("AAS 99 Integration Test") + .build()) + .description(new DefaultLangStringTextType.Builder() + .language("de-DE") + .text("AAS 99 Integrationstest") + .build()) + .globalAssetId("http://iosb.fraunhofer.de/GlobalAssetId/IntegrationTest99") + .assetType("AssetType99") + .assetKind(AssetKind.INSTANCE) + .administration(new DefaultAdministrativeInformation.Builder() + .creator(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://anydomain.com/users/User99-1") + .build()) + .build()) + .version("12") + .revision("25") + .embeddedDataSpecifications(new DefaultEmbeddedDataSpecification.Builder() + .dataSpecification(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/AAS99/DataSpecificationIEC61360") + .build()) + .build()) + .dataSpecificationContent(new DefaultDataSpecificationIec61360.Builder() + .preferredName(Arrays.asList( + new DefaultLangStringPreferredNameTypeIec61360.Builder().text("AAS 99 Spezifikation").language("de").build(), + new DefaultLangStringPreferredNameTypeIec61360.Builder().text("AAS 99 Specification").language("en-us").build())) + .dataType(DataTypeIec61360.REAL_MEASURE) + .definition(new DefaultLangStringDefinitionTypeIec61360.Builder().text("Dies ist eine Data Specification fuer Integration Test") + .language("de").build()) + .definition( + new DefaultLangStringDefinitionTypeIec61360.Builder().text("This is a DataSpecification for integration testing purposes") + .language("en-us").build()) + .shortName(new DefaultLangStringShortNameTypeIec61360.Builder().text("Test Spezifikation").language("de").build()) + .shortName(new DefaultLangStringShortNameTypeIec61360.Builder().text("Test Spec").language("en-us").build()) + .unit("SpaceUnit") + .unitId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Units/TestUnit") + .build()) + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .build()) + .sourceOfDefinition("http://iosb.fraunhofer.de/IntegrationTest/AAS99/DataSpec/ExampleDef") + .symbol("SU") + .valueFormat("string") + .value("TEST") + .valueList(new DefaultValueList.Builder() + .valueReferencePairs(new DefaultValueReferencePair.Builder() + .value("http://iosb.fraunhofer.de/IntegrationTest/ValueId/ExampleValueId") + .valueId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/ExampleValueId") + .build()) + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .build()) + .build()) + .valueReferencePairs(new DefaultValueReferencePair.Builder() + .value("http://iosb.fraunhofer.de/IntegrationTest/ValueId/ExampleValueId2") + .valueId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/ValueId/ExampleValueId2") + .build()) + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .build()) + .build()) + .build()) + .build()) + .build()) + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/IntegrationTest/Endpoints/AAS99") + .endpointProtocolVersion(List.of("2.1")) + .subprotocol("https") + .subprotocolBody("any body") + .subprotocolBodyEncoding("UTF-8") + .securityAttributes(new DefaultSecurityAttributeObject.Builder() + .type(SecurityTypeEnum.NONE) + .key("") + .value("") + .build()) + .build()) + .build()) + .extensions(new DefaultExtension.Builder() + .name("AAS99 Extension Name") + .value("AAS99 Extension Value") + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Extension99/SemanticId1") + .build()) + .build()) + .refersTo(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Extension99/RefersTo1") + .build()) + .build()) + .valueType(DataTypeDefXsd.STRING) + .supplementalSemanticIds(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Extension99/SupplementalSemanticId1") + .build()) + .build()) + .build()) + .submodelDescriptors(new DefaultSubmodelDescriptor.Builder() + .id("http://iosb.fraunhofer.de/IntegrationTest/Submodel99-1") + .idShort("Submodel-99-1") + .administration(new DefaultAdministrativeInformation.Builder() + .version("1") + .revision("12") + .build()) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Submodel99-1/SemanticId") + .build()) + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/Endpoints/Submodel99-1") + .endpointProtocolVersion(List.of("2.0")) + .build()) + .build()) + .build()) + .build(); + } + + + private static AssetAdministrationShellDescriptor getAasUpdate() { + return new DefaultAssetAdministrationShellDescriptor.Builder() + .idShort("IntegrationTest100") + .id("http://iosb.fraunhofer.de/IntegrationTest/AAS100") + .displayName(new DefaultLangStringNameType.Builder().text("Integration Test 100 Name aktualisiert").language("de-DE").build()) + .globalAssetId("http://iosb.fraunhofer.de/GlobalAssetId/IntegrationTest100") + .assetType("AssetType100") + .build(); + } + + + private static AssetAdministrationShellDescriptor getAas101() { + return new DefaultAssetAdministrationShellDescriptor.Builder() + .idShort("IntegrationTest99") + .id("http://iosb.fraunhofer.de/IntegrationTest/AAS101") + .displayName(new DefaultLangStringNameType.Builder().text("Integration Test 101 Name").language("de-DE").build()) + .globalAssetId("http://iosb.fraunhofer.de/GlobalAssetId/IntegrationTest101") + .assetType("AssetType101") + .submodelDescriptors(new DefaultSubmodelDescriptor.Builder() + .id("http://iosb.fraunhofer.de/IntegrationTest/Submodel101-1") + .idShort("Submodel-101-1") + .administration(new DefaultAdministrativeInformation.Builder() + .version("2") + .revision("15") + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/Endpoints/Submodel101-1") + .endpointProtocolVersion(List.of("2.0")) + .build()) + .build()) + .displayName(new DefaultLangStringNameType.Builder() + .language("de-DE") + .text("Submodel 101-1") + .build()) + .build()) + .build(); + } + + + private static SubmodelDescriptor getSubmodel2A() { + return new DefaultSubmodelDescriptor.Builder() + .id("http://iosb.fraunhofer.de/IntegrationTest/Submodel101-2") + .idShort("Submodel-101-2") + .administration(new DefaultAdministrativeInformation.Builder() + .version("2") + .revision("18") + .templateId("Template101-2") + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/Endpoints/Submodel101-2") + .endpointProtocolVersion(List.of("2.1")) + .build()) + .build()) + .displayName(new DefaultLangStringNameType.Builder() + .language("de-DE") + .text("Submodel 101-2") + .build()) + .description(new DefaultLangStringTextType.Builder() + .language("de-DE") + .text("Submodel 101-2 Beschreibung") + .build()) + .build(); + } + + + private static AssetAdministrationShellDescriptor getAasInvalid() { + return new DefaultAssetAdministrationShellDescriptor.Builder() + .idShort("AasInvalid") + .displayName(new DefaultLangStringNameType.Builder().text("AAS Invalid Name").language("en-US").build()) + .globalAssetId("http://iosb.fraunhofer.de/GlobalAssetId/AasInvalid") + .assetType("AssetTypeInvalid") + .build(); + } + + + private static SubmodelDescriptor getSubmodelInvalid() { + return new DefaultSubmodelDescriptor.Builder() + .idShort("Submodel-Invalid") + .administration(new DefaultAdministrativeInformation.Builder() + .version("1") + .revision("A") + .build()) + .build(); + } +} diff --git a/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/SubmodelRegistryControllerIT.java b/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/SubmodelRegistryControllerIT.java new file mode 100644 index 0000000..737ae7c --- /dev/null +++ b/service/src/test/java/de/fraunhofer/iosb/ilt/faaast/registry/service/SubmodelRegistryControllerIT.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2021 Fraunhofer IOSB, eine rechtlich nicht selbstaendige + * Einrichtung der Fraunhofer-Gesellschaft zur Foerderung der angewandten + * Forschung e.V. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.iosb.ilt.faaast.registry.service; + +import de.fraunhofer.iosb.ilt.faaast.service.model.api.paging.Page; +import de.fraunhofer.iosb.ilt.faaast.service.util.EncodingHelper; +import java.util.List; +import java.util.Optional; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAdministrativeInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEndpoint; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringNameType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProtocolInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelDescriptor; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = App.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = "classpath:application-integrationtest.properties") +public class SubmodelRegistryControllerIT { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void testGetSubmodels() { + ResponseEntity> response = restTemplate.exchange( + createURLWithPort(""), HttpMethod.GET, null, new ParameterizedTypeReference>() {}); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + var list = response.getBody().getContent(); + Assert.assertNotNull(list); + } + + + @Test + public void testCreateSubmodel() { + SubmodelDescriptor expected = getSubmodel(); + createSubmodel(expected); + checkGetSubmodel(expected); + + ResponseEntity> response = restTemplate.exchange(createURLWithPort(""), HttpMethod.GET, null, + new ParameterizedTypeReference>() {}); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + var list = response.getBody().getContent(); + Assert.assertNotNull(list); + Assert.assertTrue(list.size() >= 0); + + Optional actual = list.stream().filter(x -> expected.getId().equals(x.getId())).findFirst(); + Assert.assertTrue(actual.isPresent()); + Assert.assertEquals(expected, actual.get()); + + // check that an error is reposrted, when a Submodel shall be created, that already exists + checkCreateSubmodelError(expected, HttpStatus.CONFLICT); + } + + + @Test + public void testCreateInvalidSubmodel() { + SubmodelDescriptor expected = getSubmodelInvalid(); + checkCreateSubmodelError(expected, HttpStatus.BAD_REQUEST); + } + + + @Test + public void testUpdateDeleteSubmodel() { + // create Submodel + SubmodelDescriptor original = getSubmodelUpdate(); + createSubmodel(original); + + // update Submodel + SubmodelDescriptor expected = getSubmodelUpdate(); + expected.setDescription(List.of(new DefaultLangStringTextType.Builder().language("en-US").text("Submodel 201 updated description").build())); + expected.getDisplayName().get(0).setText("Submodel 201 Name"); + + HttpEntity entity = new HttpEntity<>(expected); + ResponseEntity responsePut = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.PUT, entity, Void.class); + Assert.assertNotNull(responsePut); + Assert.assertEquals(HttpStatus.NO_CONTENT, responsePut.getStatusCode()); + + checkGetSubmodel(expected); + + // delete Submodel + ResponseEntity responseDelete = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.DELETE, entity, Void.class); + Assert.assertNotNull(responseDelete); + Assert.assertEquals(HttpStatus.NO_CONTENT, responsePut.getStatusCode()); + + checkGetAasNotExist(expected.getId()); + } + + + @Test + public void testInvalidLimit() { + ResponseEntity response = restTemplate.exchange(createURLWithPort("?limit=0"), HttpMethod.GET, null, Void.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + + private void checkGetSubmodel(SubmodelDescriptor expected) { + ResponseEntity response = restTemplate.exchange(createURLWithPort("/" + EncodingHelper.base64UrlEncode(expected.getId())), HttpMethod.GET, null, + SubmodelDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assert.assertNotNull(response.getBody()); + Assert.assertEquals(expected, response.getBody()); + } + + + private void checkGetAasNotExist(String id) { + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/" + EncodingHelper.base64UrlEncode(id)), HttpMethod.GET, null, SubmodelDescriptor.class); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + + private void createSubmodel(SubmodelDescriptor submodel) { + HttpEntity entity = new HttpEntity<>(submodel); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort(""), HttpMethod.POST, entity, SubmodelDescriptor.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(HttpStatus.CREATED, responsePost.getStatusCode()); + Assert.assertEquals(submodel, responsePost.getBody()); + } + + + private void checkCreateSubmodelError(SubmodelDescriptor submodel, HttpStatusCode statusCode) { + HttpEntity entity = new HttpEntity<>(submodel); + ResponseEntity responsePost = restTemplate.exchange(createURLWithPort(""), HttpMethod.POST, entity, SubmodelDescriptor.class); + Assert.assertNotNull(responsePost); + Assert.assertEquals(statusCode, responsePost.getStatusCode()); + } + + + private SubmodelDescriptor getSubmodel() { + return new DefaultSubmodelDescriptor.Builder() + .id("http://iosb.fraunhofer.de/IntegrationTest/Submodel200") + .idShort("Submodel-200") + .administration(new DefaultAdministrativeInformation.Builder() + .version("2") + .revision("27") + .build()) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Submodel200/SemanticId") + .build()) + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/Endpoints/Submodel200") + .endpointProtocolVersion(List.of("2.1")) + .build()) + .build()) + .build(); + } + + + private SubmodelDescriptor getSubmodelUpdate() { + return new DefaultSubmodelDescriptor.Builder() + .id("http://iosb.fraunhofer.de/IntegrationTest/Submodel201") + .idShort("Submodel-201") + .displayName(new DefaultLangStringNameType.Builder().language("de-DE").text("Submodel 201 Name").build()) + .administration(new DefaultAdministrativeInformation.Builder() + .version("3") + .revision("31") + .build()) + .semanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("http://iosb.fraunhofer.de/IntegrationTest/Submodel201/SemanticId") + .build()) + .build()) + .endpoints(new DefaultEndpoint.Builder() + ._interface("http") + .protocolInformation(new DefaultProtocolInformation.Builder() + .endpointProtocol("http") + .href("http://iosb.fraunhofer.de/Endpoints/Submodel201") + .endpointProtocolVersion(List.of("2.2")) + .build()) + .build()) + .build(); + } + + + private SubmodelDescriptor getSubmodelInvalid() { + return new DefaultSubmodelDescriptor.Builder() + .idShort("Submodel-Invalid") + .displayName(new DefaultLangStringNameType.Builder().language("de-DE").text("Submodel Invalid Name").build()) + .build(); + } + + + private String createURLWithPort(String uri) { + return "http://localhost:" + port + "/api/v3.0/submodel-descriptors" + uri; + } +} diff --git a/service/src/test/resources/application-integrationtest.properties b/service/src/test/resources/application-integrationtest.properties new file mode 100644 index 0000000..3de338c --- /dev/null +++ b/service/src/test/resources/application-integrationtest.properties @@ -0,0 +1,35 @@ +server.error.include-message=always +###### RDBS via JPA ##### +spring.jpa.orm=orm.xml +#spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.open-in-view=true +server.ssl.enabled=false +######################### + +###### SSL Bundle ####### +#server.ssl.bundle=service +#spring.ssl.bundle.jks.service.key.alias=server +#spring.ssl.bundle.jks.service.keystore.location=classpath:keystore.p12 +#spring.ssl.bundle.jks.service.keystore.password=password +#spring.ssl.bundle.jks.service.keystore.type=PKCS12 +######################### + +##### RDBS via JPA (in-memory H2) ##### +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +#spring.profiles.active=jpa +#spring.datasource.driver=org.h2.Driver +#spring.datasource.url=jdbc:h2:mem:testdb +#spring.datasource.username=sa +#spring.datasource.password= +####################################### + +###### JPA (e.g. PostgresDB) ##### +#spring.profiles.active=jpa +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +#spring.datasource.driver=org.postgresql.Driver +#spring.datasource.url=jdbc:postgresql://localhost:5432/fa3st-registry +#spring.datasource.username=fa3st-registry +#spring.datasource.password=ChangeMe +###############################################