package org.onap.cps.ncmp.rest.controller
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+
import com.fasterxml.jackson.databind.ObjectMapper
import org.mapstruct.factory.Mappers
import org.onap.cps.TestUtils
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
-import org.onap.cps.ncmp.api.inventory.CmHandleState
-import org.onap.cps.ncmp.api.inventory.CompositeState
-import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.inventory.LockReasonCategory
+import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
+import org.onap.cps.ncmp.api.impl.inventory.CompositeState
+import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.rest.model.DataOperationRequest
+import org.onap.cps.ncmp.rest.model.DataOperationDefinition
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
import org.onap.cps.ncmp.rest.util.DeprecationHelper
import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.model.ModuleDefinition
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
-import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
-import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-
-
@WebMvcTest(NetworkCmProxyController)
class NetworkCmProxyControllerSpec extends Specification {
CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
@SpringBean
- CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
+ DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
+
+ @SpringBean
+ CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
@SpringBean
DeprecationHelper stubbedDeprecationHelper = Stub()
@SpringBean
- NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(spiedCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
+ NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
@SpringBean
- NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(spiedCpsTaskExecutor, mockNetworkCmProxyDataService)
+ NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
@Value('${rest.api.ncmp-base-path}/v1')
def ncmpBasePathV1
def requestBody = '{"some-key":"some-value"}'
- def bulkRequestBody = '["testCmHandle"]'
def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
response.status == HttpStatus.OK.value()
}
- def 'Get Resource Data Async Topic Handling with #scenario.'() {
- given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&${topicQueryParam}"
- when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
- then: 'task executor is called appropriate number of times'
- expectedNumberOfTaskExecutions * spiedCpsTaskExecutor.executeTask(_, TIMOUT_FOR_TEST)
- and: 'response status is OK'
- response.status == HttpStatus.OK.value()
- where: 'the following parameters are used'
- scenario | datastoreInUrl | topicQueryParam || expectedNumberOfTaskExecutions
- 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 1
- 'no topic in url' | 'passthrough-operational' | '' || 0
- 'null topic in url' | 'passthrough-operational' | '&topic=null' || 1
- }
-
def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
given: 'resource data url'
def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
}
- def 'Get Resource Data with invalid topic parameter: #scenario.'() {
- given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
- when: 'get data resource (async) request is performed'
- def response = mvc.perform(
- get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
- then: 'abad request is returned'
- response.status == HttpStatus.BAD_REQUEST.value()
- where: 'the following parameters are used'
- scenario | datastoreInUrl | topicQueryParam
- 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"'
- 'missing topic in url' | 'passthrough-operational' | '&topic='
- 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"'
- 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
- }
-
- def 'Get (async) bulk resource data from dmi service.'() {
- given: 'bulk resource data url'
- def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastore.datastoreName}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
- when: 'post data resource request is performed'
+ def 'Execute (async) data operation to read data from dmi service.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
+ when: 'post data operation request is performed'
def response = mvc.perform(
post(getUrl)
.contentType(MediaType.APPLICATION_JSON)
- .content(bulkRequestBody)
+ .content(dataOperationRequestJsonData)
).andReturn().response
then: 'response status is Ok'
response.status == HttpStatus.OK.value()
and: 'async request id is generated'
- assert response.contentAsString.contains("requestId")
- then: 'wait a little to allow execution of service method by task executor (on separate thread)'
- Thread.sleep(100);
- then: 'the service has been invoked with the correct parameters '
- 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastore.datastoreName, ['testCmHandle'],
- 'parent/child',
- '(a=1,b=2)',
- 'myTopic',
- _)
+ assert response.contentAsString.contains('requestId')
+ then: 'the request is handled asynchronously'
+ 1 * mockCpsTaskExecutor.executeTask(*_)
where: 'the following data stores are used'
datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
}
- def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
- given: 'bulk resource data url'
- def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+ def 'Execute (async) data operation with some validation error.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
+ getDataOperationRequest('read', 'invalid datastore'))
when: 'post data resource request is performed'
def response = mvc.perform(
post(getUrl)
.contentType(MediaType.APPLICATION_JSON)
- .content(bulkRequestBody)
+ .content(dataOperationRequestJsonData)
).andReturn().response
- then: 'response status code is 501 not implemented'
- response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ then: 'response status is BAD_REQUEST'
+ response.status == HttpStatus.BAD_REQUEST.value()
+ }
+
+ def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
+ getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
+ ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
+ when: 'post data resource request is performed'
+ def response = mvc.perform(
+ post(getUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(dataOperationRequestJsonData)
+ ).andReturn().response
+ then: 'response status is Ok'
+ response.status == HttpStatus.OK.value()
+ and: 'async request id is unavailable'
+ assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
}
def 'Query Resource Data from operational.'() {
response.status == HttpStatus.OK.value()
}
- def 'Query Resource Data using datastore of #datastore'() {
+ def 'Query Resource Data with unsupported datastore'() {
given: 'the query resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastore}/query" +
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
"?cps-path=/cps/path"
when: 'the query data resource request is performed'
def response = mvc.perform(
).andReturn().response
then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
response.status == 400
- and: 'the error message is that datastore #datastore is not supported'
- response.contentAsString.contains("ncmp-datastore:${datastore} is not supported")
- where: 'the following datastore is used'
- datastore << ["passthrough-running", "passthrough-operational"]
+ and: 'the error message is that the datastore is not supported'
+ response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
}
def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
def 'Get Resource Data from operational with or without descendants'() {
given: 'resource data url with descendants #enabled'
def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child&include-descendants=${enabled}"
+ "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
when: 'get data resource request is performed'
def response = mvc.perform(
get(getUrl)
and: 'response status is Ok'
response.status == HttpStatus.OK.value()
where: 'the following parameters are used'
- enabled | descendantsOption
- false | FetchDescendantsOption.OMIT_DESCENDANTS
- true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+ booleanValue | descendantsOption
+ false | OMIT_DESCENDANTS
+ true | INCLUDE_ALL_DESCENDANTS
}
def 'Attempt execute #operation rest operation on resource data with #scenario'() {
def compositeStateTestObject() {
new CompositeState(cmHandleState: CmHandleState.ADVISED,
- lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
+ lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
lastUpdateTime: formattedDateAndTime.toString(),
dataSyncEnabled: false,
dataStores: dataStores())
def expectedContent = [
'"state":',
'"cmHandleState":"ADVISED"',
- '"reason":"LOCKED_MISBEHAVING"',
- '"details":"lock details"',
+ '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
'"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
'"dataSyncEnabled":false',
'"dataSyncState":',
return assertContainsAll(response, expectedContent)
}
+ def getDataOperationRequest(operation, datastore) {
+ def dataOperationRequest = new DataOperationRequest()
+ def dataOperationDefinitions = new ArrayList()
+ dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
+ dataOperationRequest.addOperationsItem(dataOperationDefinitions)
+ return dataOperationRequest
+ }
+
+ def getDataOperationDefinition(operation, datastore) {
+ def dataOperationDefinition = new DataOperationDefinition()
+ dataOperationDefinition.setOperation(operation)
+ dataOperationDefinition.setOperationId("operational-12")
+ dataOperationDefinition.setDatastore(datastore)
+ dataOperationDefinition.setOptions("some option")
+ dataOperationDefinition.setResourceIdentifier("some resource identifier")
+ dataOperationDefinition.addTargetIdsItem("some-cm-handle")
+ return dataOperationDefinition
+ }
+
}