diff --git a/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/RestTemplateConfiguration.java b/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/RestTemplateConfiguration.java new file mode 100644 index 00000000..e7e987a0 --- /dev/null +++ b/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/RestTemplateConfiguration.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 the qc-atlas contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.planqk.atlas.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfiguration { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} + diff --git a/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/WineryService.java b/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/WineryService.java index ba5da1fa..13b29032 100644 --- a/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/WineryService.java +++ b/org.planqk.atlas.core/src/main/java/org/planqk/atlas/core/WineryService.java @@ -19,8 +19,11 @@ package org.planqk.atlas.core; +import java.net.URI; +import java.net.URISyntaxException; import java.util.InputMismatchException; +import org.apache.http.client.utils.URIBuilder; import org.planqk.atlas.core.model.ToscaApplication; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; @@ -48,19 +51,24 @@ public class WineryService { // API Endpoints - private final String baseAPIEndpoint; + private final URIBuilder baseAPIEndpoint; private final ObjectMapper mapper = new ObjectMapper(); + private final RestTemplate restTemplate; + public WineryService( @Value("${org.planqk.atlas.winery.protocol}") String protocol, @Value("${org.planqk.atlas.winery.hostname}") String hostname, - @Value("${org.planqk.atlas.winery.port}") String port - ) { + @Value("${org.planqk.atlas.winery.port}") int port, + RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.baseAPIEndpoint = new URIBuilder(); + this.baseAPIEndpoint.setHost(hostname).setPort(port); if ("".equals(protocol)) { - this.baseAPIEndpoint = String.format("http://%s:%s/", hostname, port); + this.baseAPIEndpoint.setScheme("http"); } else { - this.baseAPIEndpoint = String.format("%s://%s:%s/", protocol, hostname, port); + this.baseAPIEndpoint.setScheme(protocol); } } @@ -68,20 +76,22 @@ public String get(@NonNull String route) { return this.get(route, String.class); } - public T get(String route, Class responseType) { - final RestTemplate restTemplate = new RestTemplate(); + public T get(String route, Class responseType) { try { - final ResponseEntity response = restTemplate.getForEntity(this.baseAPIEndpoint + route, responseType); + final ResponseEntity response = restTemplate.getForEntity(this.baseAPIEndpoint.setPath(route).build(), responseType); if (!response.getStatusCode().equals(HttpStatus.OK)) { throw new ResponseStatusException(response.getStatusCode()); } return response.getBody(); } catch (HttpClientErrorException.NotFound notFound) { throw new ResponseStatusException(notFound.getStatusCode()); + } catch (URISyntaxException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); } } + public ToscaApplication uploadCsar(@NonNull Resource file, String name) { final HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); @@ -90,18 +100,26 @@ public ToscaApplication uploadCsar(@NonNull Resource file, String name) { body.add("name", name); final HttpEntity> postRequestEntity = new HttpEntity<>(body, headers); - final RestTemplate restTemplate = new RestTemplate(); - final ResponseEntity response = restTemplate - .postForEntity(this.baseAPIEndpoint + "/winery/", postRequestEntity, String.class); + final ResponseEntity response; + try { + response = this.restTemplate + .postForEntity(this.baseAPIEndpoint.setPath("/winery/").build(), postRequestEntity, String.class); + } catch (URISyntaxException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } if (!response.getStatusCode().equals(HttpStatus.CREATED)) { throw new ResponseStatusException(response.getStatusCode()); } - if (response.getHeaders().getLocation() == null) { + final URI location = response.getHeaders().getLocation(); + if (location == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND); } - final String path = response.getHeaders().getLocation().getPath(); - log.info(path); - final String jsonResponse = restTemplate.getForObject(this.baseAPIEndpoint + path, String.class); + final String jsonResponse; + try { + jsonResponse = this.restTemplate.getForObject(baseAPIEndpoint.setPath(location.getPath()).build(), String.class); + } catch (URISyntaxException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } final JsonNode node; try { node = this.mapper.readTree(jsonResponse).get("serviceTemplateOrNodeTypeOrNodeTypeImplementation"); @@ -114,7 +132,7 @@ public ToscaApplication uploadCsar(@NonNull Resource file, String name) { toscaApplication.setToscaNamespace(firstElement.get("targetNamespace").asText()); toscaApplication.setToscaName(firstElement.get("name").asText()); toscaApplication.setName(name); - toscaApplication.setWineryLocation(path); + toscaApplication.setWineryLocation(location.getPath()); return toscaApplication; } catch (JsonProcessingException e) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); @@ -123,8 +141,13 @@ public ToscaApplication uploadCsar(@NonNull Resource file, String name) { public void delete(@NonNull ToscaApplication toscaApplication) { final String path = toscaApplication.getWineryLocation(); - final RestTemplate restTemplate = new RestTemplate(); - restTemplate.delete(this.baseAPIEndpoint + path); + try { + this.restTemplate.delete(this.baseAPIEndpoint.setPath(path).build()); + } catch (URISyntaxException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } } + + } diff --git a/org.planqk.atlas.core/src/test/java/org/planqk/atlas/core/WineryServiceTest.java b/org.planqk.atlas.core/src/test/java/org/planqk/atlas/core/WineryServiceTest.java new file mode 100644 index 00000000..b06c23b4 --- /dev/null +++ b/org.planqk.atlas.core/src/test/java/org/planqk/atlas/core/WineryServiceTest.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2022 the qc-atlas contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.planqk.atlas.core; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import lombok.SneakyThrows; +import org.apache.http.client.utils.URIBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; +import org.planqk.atlas.core.model.ToscaApplication; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.server.ResponseStatusException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; + +@ExtendWith(MockitoExtension.class) +@RunWith(MockitoJUnitRunner.class) +class WineryServiceTest { + + @Mock + private RestTemplate restTemplate; + + private WineryService wineryService; + + private URIBuilder wineryEndPoint; + + @BeforeEach + void init(){ + wineryService = new WineryService("http", "localhost", 8091, restTemplate); + wineryEndPoint = new URIBuilder(); + wineryEndPoint.setScheme("http").setHost("localhost").setPort(8091); + } + + @SneakyThrows + @Test + public void get_returnOK() { + var testRoute = "this/is/the/test/route"; + var expectedResult = "Test-Body"; + Mockito.when(restTemplate.getForEntity(eq(wineryEndPoint.setPath(testRoute).build()), eq(String.class))).thenReturn(new ResponseEntity<>(expectedResult, HttpStatus.OK)); + String result = wineryService.get(testRoute); + assertEquals(expectedResult, result); + } + + @SneakyThrows + @Test + public void get_notFound() { + var testRoute = "this/is/the/test/route"; + Mockito.when(restTemplate.getForEntity(eq(wineryEndPoint.setPath(testRoute).build()), eq(String.class))).thenReturn(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + ResponseStatusException responseStatusException = assertThrows(ResponseStatusException.class, () -> wineryService.get(testRoute)); + assertEquals(HttpStatus.NOT_FOUND, responseStatusException.getStatus()); + } + + @Test + @SneakyThrows + public void uploadCsar_notFound(){ + var uploadRoute = "/winery/"; + Resource file = new InputStreamResource(new ByteArrayInputStream("test input stream".getBytes())); + String name = "test-name"; + + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + final MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", file); + body.add("name", name); + final HttpEntity> postRequestEntity = new HttpEntity<>(body, headers); + + Mockito.when(restTemplate.postForEntity(eq(wineryEndPoint.setPath(uploadRoute).build()), eq(postRequestEntity), eq(String.class))).thenReturn(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + ResponseStatusException responseStatusException = assertThrows(ResponseStatusException.class, () -> wineryService.uploadCsar(file, name)); + assertEquals(HttpStatus.NOT_FOUND, responseStatusException.getStatus()); + } + + @Test + @SneakyThrows + public void uploadCsar_created(){ + var uploadRoute = "/winery/"; + var locationRoute = "/this/is/the/serviceTemplate/location"; + Resource file = new InputStreamResource(new ByteArrayInputStream("test input stream".getBytes())); + String name = "test-name"; + + InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("winery_response.json"); + assert resourceAsStream != null; + String wineryJsonResponse = new String(resourceAsStream.readAllBytes(), StandardCharsets.UTF_8); + + + ToscaApplication expectedToscaApplication = new ToscaApplication(); + expectedToscaApplication.setToscaID("Java_Web_Application__MySQL"); + expectedToscaApplication.setToscaName("Java_Web_Application__MySQL"); + expectedToscaApplication.setToscaNamespace("http://opentosca.org/servicetemplates"); + expectedToscaApplication.setName(name); + expectedToscaApplication.setWineryLocation(locationRoute); + + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + final MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", file); + body.add("name", name); + final HttpEntity> postRequestEntity = new HttpEntity<>(body, headers); + + + ResponseEntity responseEntity = ResponseEntity.created(new URI(locationRoute)).build(); + + Mockito.when(restTemplate.postForEntity(eq(wineryEndPoint.setPath(uploadRoute).build()), eq(postRequestEntity), eq(String.class))).thenReturn(responseEntity); + Mockito.when(restTemplate.getForObject(eq(wineryEndPoint.setPath(locationRoute).build()), eq(String.class))).thenReturn(wineryJsonResponse); + + ToscaApplication toscaApplication = wineryService.uploadCsar(file, name); + assertEquals(expectedToscaApplication, toscaApplication); + } + + @SneakyThrows + @Test + public void delete() { + var wineryLocation = "this/is/the/winery/location"; + ToscaApplication toscaApplication = new ToscaApplication(); + toscaApplication.setWineryLocation(wineryLocation); + wineryService.delete(toscaApplication); + Mockito.verify(restTemplate).delete(wineryEndPoint.setPath(wineryLocation).build()); + } + + +} diff --git a/org.planqk.atlas.core/src/test/resources/application.properties b/org.planqk.atlas.core/src/test/resources/application.properties index dd47e7c2..47575f30 100644 --- a/org.planqk.atlas.core/src/test/resources/application.properties +++ b/org.planqk.atlas.core/src/test/resources/application.properties @@ -1,5 +1,5 @@ ################################################################################ -# Copyright (c) 2020 the qc-atlas contributors. +# Copyright (c) 2020-2022 the qc-atlas contributors. # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -17,3 +17,6 @@ # limitations under the License. ################################################################################# cloud.storage.implementation-files-bucket-name=${IMPLEMENTATION_FILES_BUCKET_NAME:planqk-algo-artifacts} +org.planqk.atlas.winery.protocol=http +org.planqk.atlas.winery.hostname=localhost +org.planqk.atlas.winery.port=8080 diff --git a/org.planqk.atlas.core/src/test/resources/winery_response.json b/org.planqk.atlas.core/src/test/resources/winery_response.json new file mode 100644 index 00000000..ec08d33d --- /dev/null +++ b/org.planqk.atlas.core/src/test/resources/winery_response.json @@ -0,0 +1,524 @@ +{ + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "id": "otsteIgeneral-Java_Web_Application__MySQL", + "serviceTemplateOrNodeTypeOrNodeTypeImplementation": [ + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "id": "Java_Web_Application__MySQL", + "boundaryDefinitions": { + }, + "topologyTemplate": { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "relationshipTemplates": [ + { + "id": "con_71", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}DependsOn", + "sourceElement": { + "ref": "Tomcat8" + }, + "targetElement": { + "ref": "Java8" + }, + "name": "con_71" + }, + { + "id": "con_171", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "Java_Shop_Application" + }, + "targetElement": { + "ref": "Tomcat8" + }, + "name": "con_171" + }, + { + "id": "con_183", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}ConnectsTo", + "sourceElement": { + "ref": "Java_Shop_Application" + }, + "targetElement": { + "ref": "MySQL-DB" + }, + "name": "con_183" + }, + { + "id": "con_HostedOn_0", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "Tomcat8" + }, + "targetElement": { + "ref": "Ubuntu-VM_18.04-w1" + }, + "name": "HostedOn" + }, + { + "id": "con_HostedOn_2", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "Ubuntu-VM_18.04-w1" + }, + "targetElement": { + "ref": "OpenStack-Liberty-12" + }, + "name": "HostedOn" + }, + { + "id": "con_HostedOn_3", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "Java8" + }, + "targetElement": { + "ref": "Ubuntu-VM_18.04-w1" + }, + "name": "HostedOn" + }, + { + "id": "con_HostedOn_1", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "MySQL-DB" + }, + "targetElement": { + "ref": "MySQL-DBMS_5.7-w1" + }, + "name": "HostedOn" + }, + { + "id": "con_HostedOn_4", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", + "sourceElement": { + "ref": "MySQL-DBMS_5.7-w1" + }, + "targetElement": { + "ref": "Ubuntu-VM_18.04-w1" + }, + "name": "HostedOn" + } + ], + "nodeTemplates": [ + { + "id": "MySQL-DB", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}location": "undefined", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "1034", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "113" + }, + "properties": { + "propertyType": "KV", + "namespace": "http://www.example.org", + "elementName": "Properties", + "kvproperties": { + "DBName": "shop", + "DBUser": "app", + "DBPassword": "installed" + } + }, + "type": "{http://opentosca.org/nodetypes}MySQL-DB", + "deploymentArtifacts": [ + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "name": "schema", + "artifactType": "{http://opentosca.org/artifacttypes}SQLArtifact", + "artifactRef": "{http://opentosca.org/artifacttemplates}Petclinic-Schema-DA_w1-wip1" + } + ], + "name": "Web Application DB", + "minInstances": 1, + "maxInstances": "1", + "x": "1034", + "y": "113" + }, + { + "id": "Java8", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}location": "undefined", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "242", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "523" + }, + "type": "{http://opentosca.org/nodetypes}Java8", + "name": "JRE", + "minInstances": 1, + "maxInstances": "1", + "x": "242", + "y": "523" + }, + { + "id": "Tomcat8", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}location": "undefined", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "390", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "296" + }, + "type": "{http://opentosca.org/nodetypes}Tomcat8", + "name": "Tomcat", + "minInstances": 1, + "maxInstances": "1", + "x": "390", + "y": "296" + }, + { + "id": "Java_Shop_Application", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}location": "undefined", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "393", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "86" + }, + "type": "{http://opentosca.org/nodetypes}Java_Shop_Application", + "deploymentArtifacts": [ + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "name": "shop", + "artifactType": "{http://opentosca.org/artifacttypes}WAR", + "artifactRef": "{http://opentosca.org/artifacttemplates}Java_Shop_Application_DA" + } + ], + "name": "Java_Shop_Application", + "minInstances": 1, + "maxInstances": "1", + "x": "393", + "y": "86" + }, + { + "id": "OpenStack-Liberty-12", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "704", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "703" + }, + "properties": { + "propertyType": "KV", + "namespace": "http://www.example.org", + "elementName": "Properties", + "kvproperties": { + "HypervisorEndpoint": "asflexsm.informatik.uni-stuttgart.de", + "HypervisorTenantID": "SmartServices", + "HypervisorUserName": "get_input: OpenStackUser", + "HypervisorUserPassword": "get_input: OpenStackUserPassword" + } + }, + "type": "{http://opentosca.org/nodetypes}OpenStack-Liberty-12", + "name": "OpenStack-Liberty-12", + "minInstances": 1, + "maxInstances": "1", + "x": "704", + "y": "703" + }, + { + "id": "Ubuntu-VM_18.04-w1", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "703", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "515" + }, + "properties": { + "propertyType": "KV", + "namespace": "http://www.example.org", + "elementName": "Properties", + "kvproperties": { + "VMIP": "", + "VMInstanceID": "", + "VMType": "m1.small", + "VMUserName": "ubuntu", + "VMUserPassword": "N/A", + "VMPrivateKey": "get_input: VMPrivateKey", + "VMPublicKey": "N/A", + "VMKeyPairName": "get_input: VMKeyPair" + } + }, + "type": "{http://opentosca.org/nodetypes}Ubuntu-VM_18.04-w1", + "name": "Ubuntu-VM_18.04-w1", + "minInstances": 1, + "maxInstances": "1", + "x": "703", + "y": "515" + }, + { + "id": "MySQL-DBMS_5.7-w1", + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "1039", + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "395" + }, + "properties": { + "propertyType": "KV", + "namespace": "http://www.example.org", + "elementName": "Properties", + "kvproperties": { + "DBMSUser": "root", + "DBMSPassword": "installed", + "DBMSPort": "3306" + } + }, + "type": "{http://opentosca.org/nodetypes}MySQL-DBMS_5.7-w1", + "name": "MySQL-DBMS_5.7-w1", + "minInstances": 1, + "maxInstances": "1", + "x": "1039", + "y": "395" + } + ] + }, + "name": "Java_Web_Application__MySQL", + "targetNamespace": "http://opentosca.org/servicetemplates" + } + ], + "targetNamespace": "http://opentosca.org/servicetemplates", + "import": [ + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__MySQL-DBMS_5.7-w1.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__OpenStack-Liberty-12.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/artifacttypes", + "location": "artifacttypes__SQLArtifact.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__Java8.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes", + "location": "ToscaBaseTypes__DependsOn.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__Tomcat8.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes", + "location": "ToscaBaseTypes__ConnectsTo.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes", + "location": "ToscaBaseTypes__HostedOn.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/artifacttemplates", + "location": "artifacttemplates__Petclinic-Schema-DA_w1-wip1.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__Java_Shop_Application.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__MySQL-DB.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/artifacttypes", + "location": "artifacttypes__WAR.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/nodetypes", + "location": "nodetypes__Ubuntu-VM_18.04-w1.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + }, + { + "documentation": [ + ], + "any": [ + ], + "otherAttributes": { + }, + "namespace": "http://opentosca.org/artifacttemplates", + "location": "artifacttemplates__Java_Shop_Application_DA.tosca", + "importType": "http://docs.oasis-open.org/tosca/ns/2011/12" + } + ] +} diff --git a/org.planqk.atlas.web/src/main/resources/application.properties b/org.planqk.atlas.web/src/main/resources/application.properties index 4d1a0941..2bc5db63 100644 --- a/org.planqk.atlas.web/src/main/resources/application.properties +++ b/org.planqk.atlas.web/src/main/resources/application.properties @@ -51,4 +51,4 @@ cloud.storage.implementation-files-bucket-name=${IMPLEMENTATION_FILES_BUCKET_NAM # Winery configuration org.planqk.atlas.winery.protocol=http org.planqk.atlas.winery.hostname=localhost -org.planqk.atlas.winery.port=8091 +org.planqk.atlas.winery.port=8080 diff --git a/org.planqk.atlas.web/src/test/java/org/planqk/atlas/web/controller/ToscaApplicationControllerTest.java b/org.planqk.atlas.web/src/test/java/org/planqk/atlas/web/controller/ToscaApplicationControllerTest.java new file mode 100644 index 00000000..601da3f5 --- /dev/null +++ b/org.planqk.atlas.web/src/test/java/org/planqk/atlas/web/controller/ToscaApplicationControllerTest.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2022 the qc-atlas contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.planqk.atlas.web.controller; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.planqk.atlas.core.model.ToscaApplication; +import org.planqk.atlas.core.services.ToscaApplicationService; +import org.planqk.atlas.web.controller.util.ObjectMapperUtils; +import org.planqk.atlas.web.dtos.ToscaApplicationDto; +import org.planqk.atlas.web.linkassembler.EnableLinkAssemblers; +import org.planqk.atlas.web.linkassembler.LinkBuilderService; +import org.planqk.atlas.web.utils.ListParameters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ToscaApplicationController.class) +@ExtendWith(MockitoExtension.class) +@EnableLinkAssemblers +@AutoConfigureMockMvc +class ToscaApplicationControllerTest { + private final ObjectMapper mapper = ObjectMapperUtils.newTestMapper(); + + private final int page = 0; + + private final int size = 2; + + private final Pageable pageable = PageRequest.of(page, size); + + @MockBean + private ToscaApplicationService toscaApplicationService; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private LinkBuilderService linkBuilderService; + + @Test + @SneakyThrows + public void createApplication_returnCreated() { + + UUID uuid = UUID.randomUUID(); + String name = "Test Name"; + + var returnedResource = new ToscaApplication(); + returnedResource.setName(name); + returnedResource.setId(uuid); + + doReturn(returnedResource).when(toscaApplicationService).createFromFile(any(), any()); + + MockMultipartFile file + = new MockMultipartFile( + "file", + "hello.txt", + MediaType.TEXT_PLAIN_VALUE, + "Hello, World!".getBytes() + ); + MockPart namePart = new MockPart("name", "name", name.getBytes()); + namePart.getHeaders().setContentType(MediaType.TEXT_PLAIN); + + mockMvc.perform( + multipart( + linkBuilderService.urlStringTo( + methodOn(ToscaApplicationController.class).createApplication(null, null) + ) + ).file(file).part(namePart) + ).andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(uuid.toString())) + .andExpect(jsonPath("$.name").value(name)); + } + + @Test + @SneakyThrows + void getApplications_EmptyList_returnOk() { + doReturn(new PageImpl(List.of())).when(toscaApplicationService).findAll(any()); + + var url = linkBuilderService.urlStringTo(methodOn(ToscaApplicationController.class) + .getApplications(ListParameters.getDefault())); + MvcResult mvcResult = mockMvc + .perform(get(url).accept(APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + assertEquals(ObjectMapperUtils.mapResponseToList(mvcResult, ToscaApplicationDto.class).size(), 0); + } + + @Test + @SneakyThrows + void getApplications_SingleElement_returnOk() { + var toscaApplication = new ToscaApplication(); + toscaApplication.setId(UUID.randomUUID()); + toscaApplication.setName("name"); + toscaApplication.setToscaName("toscaName"); + toscaApplication.setToscaID("toscaID"); + toscaApplication.setToscaNamespace("toscaNameSpace"); + + doReturn(new PageImpl<>(List.of(toscaApplication))).when(toscaApplicationService).findAll(any()); + + var url = linkBuilderService.urlStringTo(methodOn(ToscaApplicationController.class) + .getApplications(ListParameters.getDefault())); + MvcResult mvcResult = mockMvc.perform(get(url).accept(APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + ToscaApplicationDto toscaApplicationDto = ObjectMapperUtils.mapResponseToList(mvcResult, ToscaApplicationDto.class).get(0); + assertEquals(toscaApplicationDto.getId(), toscaApplication.getId()); + assertEquals(toscaApplicationDto.getToscaID(), toscaApplication.getToscaID()); + assertEquals(toscaApplicationDto.getToscaNamespace(), toscaApplication.getToscaNamespace()); + assertEquals(toscaApplicationDto.getToscaName(), toscaApplication.getToscaName()); + } + + @Test + @SneakyThrows + void getApplication_returnOk() { + var toscaApplication = new ToscaApplication(); + toscaApplication.setId(UUID.randomUUID()); + toscaApplication.setName("name"); + toscaApplication.setToscaName("toscaName"); + toscaApplication.setToscaID("toscaID"); + toscaApplication.setToscaNamespace("toscaNameSpace"); + + doReturn(toscaApplication).when(toscaApplicationService).findById(any()); + + var url = linkBuilderService.urlStringTo(methodOn(ToscaApplicationController.class) + .getApplication(toscaApplication.getId())); + + mockMvc.perform(get(url).accept(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(toscaApplication.getId().toString())) + .andExpect(jsonPath("$.name").value(toscaApplication.getName())) + .andExpect(jsonPath("$.toscaName").value(toscaApplication.getToscaName())) + .andExpect(jsonPath("$.toscaID").value(toscaApplication.getToscaID())) + .andExpect(jsonPath("$.toscaNamespace").value(toscaApplication.getToscaNamespace())); + } + + @Test + @SneakyThrows + public void updateApplication_returnNotFound() { + var resource = new ToscaApplicationDto(); + resource.setId(UUID.randomUUID()); + resource.setName("Hello World"); + + doThrow(new NoSuchElementException()).when(toscaApplicationService).update(any()); + + mockMvc.perform( + put( + linkBuilderService.urlStringTo( + methodOn(ToscaApplicationController.class).updateApplication(UUID.randomUUID(), null) + ) + ).content(mapper.writeValueAsString(resource)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ).andExpect(status().isNotFound()); + } + + @Test + @SneakyThrows + public void updateApplication_returnBadRequest() { + var resource = new ToscaApplicationDto(); + resource.setId(UUID.randomUUID()); + + mockMvc.perform( + put( + linkBuilderService.urlStringTo( + methodOn(ToscaApplicationController.class).updateApplication(UUID.randomUUID(), null) + ) + ).content(mapper.writeValueAsString(resource)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ).andExpect(status().isBadRequest()); + } + + @Test + @SneakyThrows + public void updateApplication_returnOk() { + var resource = new ToscaApplicationDto(); + resource.setId(UUID.randomUUID()); + resource.setName("Hello World"); + + var returnedResource = new ToscaApplication(); + returnedResource.setName(resource.getName()); + returnedResource.setId(resource.getId()); + + doReturn(returnedResource).when(toscaApplicationService).update(any()); + + mockMvc.perform( + put( + linkBuilderService.urlStringTo( + methodOn(ToscaApplicationController.class).updateApplication(UUID.randomUUID(), null) + ) + ).content(mapper.writeValueAsString(resource)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(resource.getId().toString())) + .andExpect(jsonPath("$.name").value(resource.getName())); + } + + @Test + @SneakyThrows + void deleteApplication_returnNoContent() { + doNothing().when(toscaApplicationService).delete(any()); + mockMvc.perform( + delete( + linkBuilderService.urlStringTo( + methodOn(ToscaApplicationController.class).deleteApplication(UUID.randomUUID()) + ) + ).accept(MediaType.APPLICATION_JSON) + ).andExpect(status().isNoContent()); + } +}