Replaced RestTemplate with WebClient in synchronous DMI calls
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / client / DmiRestClientSpec.groovy
index c8e34b1..547d08a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.ncmp.api.impl.client
 
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
+
 import com.fasterxml.jackson.databind.JsonNode
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.databind.node.ObjectNode
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
+import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration
+import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException
 import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.HttpEntity
 import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
 import org.springframework.web.client.HttpServerErrorException
-import org.springframework.web.client.RestTemplate
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.core.publisher.Mono
 import spock.lang.Specification
-
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import org.springframework.web.reactive.function.client.WebClientResponseException
 
 @SpringBootTest
-@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper])
+@ContextConfiguration(classes = [DmiWebClientConfiguration, DmiRestClient, ObjectMapper])
 class DmiRestClientSpec extends Specification {
 
     static final NO_AUTH_HEADER = null
     static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ='
     static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token'
 
-    @SpringBean
-    RestTemplate mockRestTemplate = Mock(RestTemplate)
-
     @Autowired
-    NcmpConfiguration.DmiProperties dmiProperties
+    DmiWebClientConfiguration.DmiProperties dmiProperties
 
     @Autowired
     DmiRestClient objectUnderTest
 
-    @Autowired
-    ObjectMapper objectMapper
+    @SpringBean
+    WebClient mockWebClient = Mock(WebClient);
 
-    def responseFromRestTemplate = Mock(ResponseEntity)
+    @SpringBean
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+    def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
+    def mockResponseSpec = Mock(WebClient.ResponseSpec)
+    def mockResponseEntity = Mock(ResponseEntity)
+
+    def setup() {
+        mockRequestBodyUriSpec.uri(_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.headers(_) >> mockRequestBodyUriSpec
+        mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
+    }
 
     def 'DMI POST operation with JSON.'() {
-        given: 'the rest template returns a valid response entity for the expected parameters'
-            mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate
+        given: 'the web client returns a valid response entity for the expected parameters'
+            mockWebClient.post() >> mockRequestBodyUriSpec
+            mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec
+            def monoSpec = Mono.just(mockResponseEntity)
+            mockResponseSpec.bodyToMono(Object.class) >>  monoSpec
+            monoSpec.block() >> mockResponseEntity
         when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ, null)
+            def response = objectUnderTest.postOperationWithJsonData('/my/url', 'some json', READ, null)
         then: 'the output of the method is equal to the output from the test template'
-            result == responseFromRestTemplate
+            assert response.statusCode.value() == 200
+            assert response.hasBody()
     }
 
-    def 'Failing DMI POST operation.'() {
-        given: 'the rest template returns a valid response entity'
-            def serverResponse = 'server response'.getBytes()
-            def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null)
-            mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException }
+    def 'Failing DMI POST operation for server error'() {
+        given: 'the web client throws an exception'
+            mockWebClient.post() >> { throw new HttpServerErrorException(SERVICE_UNAVAILABLE, null, null, null) }
         when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null)
-        then: 'a Http Client Exception is thrown'
-            def thrown = thrown(HttpClientRequestException)
+            objectUnderTest.postOperationWithJsonData('/some', 'some json', READ, null)
+        then: 'a http client exception is thrown'
+            def thrown = thrown(DmiClientRequestException)
+        and: 'the exception has the relevant details from the error response'
+            assert thrown.ncmpResponseStatus.code == '102'
+            assert thrown.httpStatusCode == 503
+    }
+
+    def 'Failing DMI POST operation due to invalid dmi resource url.'() {
+        when: 'POST operation is invoked with invalid dmi resource url'
+            objectUnderTest.postOperationWithJsonData('/invalid dmi url', null, null, null)
+        then: 'invalid dmi resource url exception is thrown'
+            def thrown = thrown(InvalidDmiResourceUrlException)
         and: 'the exception has the relevant details from the error response'
-            assert thrown.httpStatus == 403
-            assert thrown.message == "Unable to ${operation} resource data."
-            assert thrown.details == 'server response'
-        where: 'the following operation is executed'
+            assert thrown.httpStatus == 400
+            assert thrown.message == 'Invalid dmi resource url: /invalid dmi url'
+        where: 'the following operations are executed'
             operation << [CREATE, READ, PATCH]
     }
 
+    def 'Dmi service sends client error response when #scenario'() {
+        given: 'the web client unable to return response entity but error'
+            mockWebClient.post() >> mockRequestBodyUriSpec
+            mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec
+            def monoSpec = Mono.error(new WebClientResponseException('message', httpStatusCode, null, null, null, null))
+            mockResponseSpec.bodyToMono(Object.class) >>  monoSpec
+        when: 'POST operation is invoked'
+            objectUnderTest.postOperationWithJsonData('/my/url', 'some json', READ, null)
+        then: 'a http client exception is thrown'
+            def thrown = thrown(DmiClientRequestException)
+        and: 'the exception has the relevant details from the error response'
+            assert thrown.ncmpResponseStatus.code == expectedNcmpResponseStatusCode
+            assert thrown.httpStatusCode == httpStatusCode
+        where: 'the following errors occur'
+            scenario              | httpStatusCode | expectedNcmpResponseStatusCode
+            'dmi request timeout' | 408            | DMI_SERVICE_NOT_RESPONDING.code
+            'other error code'    | 500            | UNABLE_TO_READ_RESOURCE_DATA.code
+    }
+
     def 'Dmi trust level is determined by spring boot health status'() {
         given: 'a health check response'
             def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
-            def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
+            def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
             ((ObjectNode) jsonNode).put('status', 'my status')
-            mockRestTemplate.getForObject(*_) >> {jsonNode}
+            def monoResponse = Mono.just(jsonNode)
+            mockWebClient.get() >> mockRequestBodyUriSpec
+            mockResponseSpec.bodyToMono(_) >> monoResponse
+            monoResponse.block() >> jsonNode
         when: 'get trust level of the dmi plugin'
-            def result = objectUnderTest.getDmiHealthStatus('some url')
+            def result = objectUnderTest.getDmiHealthStatus('some/url')
         then: 'the status value from the json is return'
             assert result == 'my status'
     }
 
     def 'Failing to get dmi plugin health status #scenario'() {
         given: 'rest template with #scenario'
-            mockRestTemplate.getForObject(*_) >> healthStatusResponse
+            mockWebClient.get() >> healthStatusResponse
         when: 'attempt to get health status of the dmi plugin'
             def result = objectUnderTest.getDmiHealthStatus('some url')
         then: 'result will be empty'
@@ -132,5 +178,4 @@ class DmiRestClientSpec extends Specification {
             'DMI basic auth disabled, with NCMP bearer token' | false       | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER
             'DMI basic auth disabled, with NCMP basic auth'   | false       | BASIC_AUTH_HEADER  || NO_AUTH_HEADER
     }
-
 }