move lcm/common events classes
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / client / DmiRestClientSpec.groovy
index 389086c..bb73c68 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.api.impl.client
 
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-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 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.DmiProperties
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException
+import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpMethod
+import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
-import org.springframework.test.context.ContextConfiguration
-import org.springframework.web.client.RestTemplate
+import org.springframework.web.reactive.function.client.WebClient
+import org.springframework.web.reactive.function.client.WebClientRequestException
+import org.springframework.web.reactive.function.client.WebClientResponseException
+import reactor.core.publisher.Mono
 import spock.lang.Specification
 
-@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient])
+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 static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL
+
 class DmiRestClientSpec extends Specification {
 
-    @SpringBean
-    RestTemplate mockRestTemplate = Mock(RestTemplate)
+    static final NO_AUTH_HEADER = null
+    static final BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ='
+    static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token'
 
-    @Autowired
-    DmiRestClient objectUnderTest
-    def resourceUrl = 'some url'
+    def mockDataServicesWebClient = Mock(WebClient)
+    def mockModelServicesWebClient = Mock(WebClient)
+    def mockHealthChecksWebClient = Mock(WebClient)
 
-    def 'DMI POST operation'() {
-        given: 'the rest template returns a valid response entity'
-            def mockResponseEntity = Mock(ResponseEntity)
-            mockRestTemplate.exchange(resourceUrl, HttpMethod.POST, _ as HttpEntity, Object.class) >> mockResponseEntity
-        when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperation(resourceUrl, new HttpHeaders())
-        then: 'the output of the method is equal to the output from the rest template'
-            result == mockResponseEntity
+    def mockRequestBody = Mock(WebClient.RequestBodyUriSpec)
+    def mockResponse = Mock(WebClient.ResponseSpec)
+
+    def mockDmiProperties = Mock(DmiProperties)
+
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+    DmiRestClient objectUnderTest = new DmiRestClient(mockDmiProperties, jsonObjectMapper, mockDataServicesWebClient, mockModelServicesWebClient, mockHealthChecksWebClient)
+
+    def setup() {
+        mockRequestBody.uri(_) >> mockRequestBody
+        mockRequestBody.headers(_) >> mockRequestBody
+        mockRequestBody.body(_) >> mockRequestBody
+        mockRequestBody.retrieve() >> mockResponse
     }
 
-    def 'DMI POST operation with JSON.'() {
-        given: 'the rest template returns a valid response entity'
-            def mockResponseEntity = Mock(ResponseEntity)
-            mockRestTemplate.postForEntity(resourceUrl, _ as HttpEntity, Object.class) >> mockResponseEntity
-        when: 'POST operation is invoked'
-            def result = objectUnderTest.postOperationWithJsonData(resourceUrl, 'json-data', new HttpHeaders())
+    def 'DMI POST Operation with JSON for DMI Data Service '() {
+        given: 'the Data web client returns a valid response entity for the expected parameters'
+            mockDataServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT))
+        when: 'POST operation is invoked fro Data Service'
+            def response = objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER)
+        then: 'the output of the method is equal to the output from the test template'
+            assert response.statusCode == HttpStatus.I_AM_A_TEAPOT
+            assert response.body == 'from Data service'
+    }
+
+    def 'DMI POST Operation with JSON for DMI Model Service '() {
+        given: 'the Model web client returns a valid response entity for the expected parameters'
+            mockModelServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT))
+        when: 'POST operation is invoked for Model Service'
+            def response = objectUnderTest.synchronousPostOperationWithJsonData(MODEL, '/my/url', 'some json', READ, NO_AUTH_HEADER)
         then: 'the output of the method is equal to the output from the test template'
-            result == mockResponseEntity
+            assert response.statusCode == HttpStatus.I_AM_A_TEAPOT
+            assert response.body == 'from Model service'
     }
 
+    def 'Failing DMI POST operation due to invalid dmi resource url.'() {
+        when: 'POST operation is invoked with invalid dmi resource url'
+            objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/invalid dmi url', null, null, NO_AUTH_HEADER)
+        then: 'invalid dmi resource url exception is thrown'
+            def thrown = thrown(InvalidDmiResourceUrlException)
+        and: 'the exception has the relevant details from the error response'
+            thrown.httpStatus == 400
+            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'
+            mockDataServicesWebClient.post() >> mockRequestBody
+            mockResponse.toEntity(Object.class) >> Mono.error(exceptionType)
+        when: 'POST operation is invoked'
+            objectUnderTest.synchronousPostOperationWithJsonData(DATA, '/my/url', 'some json', READ, NO_AUTH_HEADER)
+        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 == expectedNcmpResponseStatusCode
+            assert thrown.httpStatusCode == httpStatusCode
+        where: 'the following errors occur'
+            scenario                  | httpStatusCode | exceptionType                                                                                    || expectedNcmpResponseStatusCode
+            'dmi service unavailable' | 503            | new WebClientRequestException(new RuntimeException('some-error'), null, null, new HttpHeaders()) || DMI_SERVICE_NOT_RESPONDING
+            'dmi request timeout'     | 408            | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null)        || DMI_SERVICE_NOT_RESPONDING
+            'dmi server error'        | 500            | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null)        || UNABLE_TO_READ_RESOURCE_DATA
+            'unknown error'           | 500            | new Throwable('message')                                                                         || UNKNOWN_ERROR
+    }
+
+    def 'Dmi trust level is determined by spring boot health status'() {
+        given: 'a health check response'
+            def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
+            def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
+            ((ObjectNode) jsonNode).put('status', 'my status')
+            mockHealthChecksWebClient.get() >> mockRequestBody
+            mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode)
+        when: 'get trust level of the dmi plugin'
+            def result = objectUnderTest.getDmiHealthStatus('some/url')
+        then: 'the status value from the json is returned'
+            assert result == 'my status'
+    }
+
+    def 'Failing to get dmi plugin health status #scenario'() {
+        given: 'rest template with #scenario'
+            mockHealthChecksWebClient.get() >> mockRequestBody
+            mockResponse.bodyToMono(_) >> healthStatusResponse
+        when: 'attempt to get health status of the dmi plugin'
+            def result = objectUnderTest.getDmiHealthStatus('some url')
+        then: 'result will be empty'
+            assert result == ''
+        where: 'the following responses are used'
+            scenario    | healthStatusResponse
+            'null'      | null
+            'exception' | { throw new Exception() }
+    }
+
+    def 'DMI auth header #scenario'() {
+        when: 'Specific dmi properties are provided'
+            mockDmiProperties.dmiBasicAuthEnabled >> authEnabled
+            mockDmiProperties.authUsername >> 'some user'
+            mockDmiProperties.authPassword >> 'some password'
+        then: 'http headers to conditionally have Authorization header'
+            def httpHeaders = new HttpHeaders()
+            objectUnderTest.configureHttpHeaders(httpHeaders, ncmpAuthHeader)
+            def outputAuthHeader = (httpHeaders.Authorization == null ? null : httpHeaders.Authorization[0])
+            assert outputAuthHeader == expectedAuthHeader
+        where: 'the following configurations are used'
+            scenario                                          | authEnabled | ncmpAuthHeader     || expectedAuthHeader
+            'DMI basic auth enabled, no NCMP bearer token'    | true        | NO_AUTH_HEADER     || BASIC_AUTH_HEADER
+            'DMI basic auth enabled, with NCMP bearer token'  | true        | BEARER_AUTH_HEADER || BASIC_AUTH_HEADER
+            'DMI basic auth disabled, no NCMP bearer token'   | false       | NO_AUTH_HEADER     || NO_AUTH_HEADER
+            '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
+    }
 }