From 4cf4962b74765a5afe234aa258a9143ea6936f73 Mon Sep 17 00:00:00 2001 From: mpriyank Date: Fri, 20 May 2022 15:25:15 +0100 Subject: [PATCH] Enhanced response with Complex State in API - Introduced RestOutputCmHandleState in API specs of retrieveCmHandleDetailsById - Mapper to map CompositeState to RestOutputCmHandleState - Enhanced existing test cases and introduced new one to test the mapping result Issue-ID: CPS-1047 Change-Id: I34fa198287e5d920bc0cea312ee4e368f3be2b90 Signed-off-by: mpriyank --- cps-ncmp-rest/docs/openapi/components.yaml | 46 +++++++++++++ .../rest/controller/NetworkCmProxyController.java | 4 ++ .../rest/mapper/RestOutputCmHandleStateMapper.java | 79 ++++++++++++++++++++++ .../controller/NetworkCmProxyControllerSpec.groovy | 46 +++++++++++-- .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 4 ++ .../RestOutputCmHandleStateMapperTest.groovy | 75 ++++++++++++++++++++ .../api/impl/yangmodels/YangModelCmHandle.java | 1 + .../cps/ncmp/api/models/NcmpServiceCmHandle.java | 4 ++ .../ncmp/api/inventory/CompositeStateSpec.groovy | 3 +- .../src/test/resources/expectedStateModel.json | 6 +- 10 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java create mode 100644 cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 32d25e395..5fe47e4b0 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -209,6 +209,8 @@ components: example: my-cm-handle1 publicCmHandleProperties: $ref: '#/components/schemas/CmHandlePublicProperties' + state: + $ref: '#/components/schemas/RestOutputCmHandleState' CmHandlePublicProperties: type: array items: @@ -216,6 +218,50 @@ components: additionalProperties: type: string example: Book Type + RestOutputCmHandleState: + type: object + properties: + cmHandleState: + type: string + example: ADVISED + lockReason: + $ref: '#/components/schemas/lock-reason' + lastUpdateTime: + type: string + example: 2022-12-31T20:30:40.000+0000 + dataSyncEnabled: + type: boolean + example: false + dataSyncState: + $ref: '#/components/schemas/dataStores' + + lock-reason: + type: object + properties: + reason: + type: string + example: LOCKED_OTHER + details: + type: string + example: locked due to module sync + + dataStores: + type: object + properties: + operational: + $ref: '#/components/schemas/sync-state' + running: + $ref: '#/components/schemas/sync-state' + + sync-state: + type: object + properties: + state: + type: string + example: NONE_REQUESTED + lastSyncTime: + type: string + example: 2022-12-31T20:30:40.000+0000 RestOutputCmHandlePublicProperties: type: object diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index ca7e258bc..cedc94672 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -46,6 +46,7 @@ import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; +import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper; import org.onap.cps.ncmp.rest.model.CmHandleProperties; import org.onap.cps.ncmp.rest.model.CmHandleProperty; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; @@ -79,6 +80,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { private final NetworkCmProxyDataService networkCmProxyDataService; private final JsonObjectMapper jsonObjectMapper; private final NcmpRestInputMapper ncmpRestInputMapper; + private final RestOutputCmHandleStateMapper restOutputCmHandleStateMapper; /** * Get resource data from operational datastore. @@ -312,6 +314,8 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { restOutputCmHandle.setCmHandle(ncmpServiceCmHandle.getCmHandleId()); cmHandlePublicProperties.add(ncmpServiceCmHandle.getPublicProperties()); restOutputCmHandle.setPublicCmHandleProperties(cmHandlePublicProperties); + restOutputCmHandle.setState(restOutputCmHandleStateMapper.toRestOutputCmHandleState( + ncmpServiceCmHandle.getCompositeState())); return restOutputCmHandle; } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java new file mode 100644 index 000000000..89015cca9 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java @@ -0,0 +1,79 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.onap.cps.ncmp.api.inventory.CmHandleState; +import org.onap.cps.ncmp.api.inventory.CompositeState; +import org.onap.cps.ncmp.rest.model.DataStores; +import org.onap.cps.ncmp.rest.model.RestOutputCmHandleState; +import org.onap.cps.ncmp.rest.model.SyncState; + +@Mapper(componentModel = "spring") +public interface RestOutputCmHandleStateMapper { + + @Mapping(target = "dataSyncState", source = "dataStores", qualifiedByName = "dataStoreToDataSyncState") + @Mapping(target = "cmHandleState", source = "cmhandleState", qualifiedByName = "cmHandleStateEnumToString") + RestOutputCmHandleState toRestOutputCmHandleState(CompositeState compositeState); + + /** + * Convert from CompositeState datastore to RestOutput Datastores. + * + * @param compositeStateDataStore Composite State data stores + * @return DataStores + */ + @Named("dataStoreToDataSyncState") + static DataStores toDataStores(CompositeState.DataStores compositeStateDataStore) { + + final DataStores dataStores = new DataStores(); + + if (compositeStateDataStore.getRunningDataStore() != null) { + final SyncState runningSyncState = new SyncState(); + runningSyncState.setState(compositeStateDataStore.getRunningDataStore().getSyncState()); + runningSyncState.setLastSyncTime(compositeStateDataStore.getRunningDataStore().getLastSyncTime()); + dataStores.setRunning(runningSyncState); + } + + if (compositeStateDataStore.getOperationalDataStore() != null) { + final SyncState operationalSyncState = new SyncState(); + operationalSyncState.setState(compositeStateDataStore.getOperationalDataStore().getSyncState()); + operationalSyncState.setLastSyncTime(compositeStateDataStore.getOperationalDataStore().getLastSyncTime()); + dataStores.setOperational(operationalSyncState); + } + + + return dataStores; + + } + + /** + * Converts cmHandleState enum value to equivalent string. + * @param cmHandleState cm handle state enum + * @return cm handle state as string + */ + @Named("cmHandleStateEnumToString") + static String toCmHandleState(final CmHandleState cmHandleState) { + return cmHandleState.name(); + } + +} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index ba49321d8..6cf150668 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -24,10 +24,21 @@ package org.onap.cps.ncmp.rest.controller import org.mapstruct.factory.Mappers +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper import spock.lang.Shared +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH +import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores +import static org.onap.cps.ncmp.api.inventory.CompositeState.LockReason +import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational +import static org.onap.cps.ncmp.api.inventory.CompositeState.Running 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.patch @@ -69,6 +80,12 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper) + @SpringBean + RestOutputCmHandleStateMapper restOutputCmHandleStateMapper = Mappers.getMapper(RestOutputCmHandleStateMapper) + + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + @Value('${rest.api.ncmp-base-path}/v1') def ncmpBasePathV1 @@ -225,24 +242,32 @@ class NetworkCmProxyControllerSpec extends Specification { response.contentAsString == '{"cmHandles":[{"cmHandleId":"some-cmhandle-id1"},{"cmHandleId":"some-cmhandle-id2"}]}' } - def 'Get Cm Handle details by Cm Handle id.' () { + def 'Get Cm Handle details by Cm Handle id.'() { given: 'an endpoint and a cm handle' def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle" and: 'an existing ncmp service cm handle' def cmHandleId = 'some-cm-handle' def dmiProperties = [ prop:'some DMI property' ] def publicProperties = [ "public prop":'some public property' ] - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties) + def compositeState = new CompositeState(cmhandleState: CmHandleState.ADVISED, + lockReason: LockReason.builder().reason('LOCKED_OTHER').details("lock-misbehaving-details").build(), + lastUpdateTime: formattedDateAndTime.toString(), + dataSyncEnabled: false, + dataStores: dataStores()) + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) and: 'the service method is invoked with the cm handle id' 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() - and: 'the response returns public properties and the correct properties' + and: 'the response returns public properties and the correct cm handle states' response.contentAsString.contains('publicCmHandleProperties') - response.contentAsString.contains('public prop') - response.contentAsString.contains('some public property') + response.contentAsString.contains('LOCKED_OTHER') + response.contentAsString.contains('lock-misbehaving-details') + response.contentAsString.contains('ADVISED') + response.contentAsString.contains('NONE_REQUESTED') + response.contentAsString.contains('2022-12-31T20:30:40.000+0000') and: 'the content does not contain dmi properties' !response.contentAsString.contains("some DMI property") } @@ -348,5 +373,16 @@ class NetworkCmProxyControllerSpec extends Specification { ':passthrough-running' | 'passthrough-running' } + def dataStores() { + DataStores.builder() + .operationalDataStore(Operational.builder() + .syncState('NONE_REQUESTED') + .lastSyncTime(formattedDateAndTime.toString()).build()) + .runningDataStore(Running.builder() + .syncState('NONE_REQUESTED') + .lastSyncTime(formattedDateAndTime.toString()).build()) + .build() + } + } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy index 751fdcd8b..1258e6e1c 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -29,6 +29,7 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper +import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException @@ -66,6 +67,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper) + @SpringBean + RestOutputCmHandleStateMapper restOutputCmHandleStateMapper = Mappers.getMapper(RestOutputCmHandleStateMapper) + @Value('${rest.api.ncmp-base-path}') def basePathNcmp diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy new file mode 100644 index 000000000..4560ae481 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.mapper + +import org.mapstruct.factory.Mappers +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeState +import org.onap.cps.ncmp.rest.model.RestOutputCmHandleState +import spock.lang.Specification + +import java.time.OffsetDateTime +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.LockReason +import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational + +class RestOutputCmHandleStateMapperTest extends Specification { + + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + def objectUnderTest = Mappers.getMapper(RestOutputCmHandleStateMapper) + + def 'Composite State to Rest Output CmHandleState'() { + given: 'a composite state model' + def compositeState = new CompositeState(cmhandleState: CmHandleState.ADVISED, + lockReason: LockReason.builder().reason('LOCKED_OTHER').details('locked-other-details').build(), + lastUpdateTime: formattedDateAndTime.toString(), + dataSyncEnabled: false, + dataStores: dataStores()) + when: 'mapper is called' + def result = objectUnderTest.toRestOutputCmHandleState(compositeState) + then: 'result is of the correct type' + assert result.class == RestOutputCmHandleState.class + and: 'mapped result should have correct values' + assert !result.dataSyncEnabled + assert result.lastUpdateTime == formattedDateAndTime + assert result.lockReason.reason == 'LOCKED_OTHER' + assert result.lockReason.details == 'locked-other-details' + assert result.cmHandleState == CmHandleState.ADVISED.name() + assert result.dataSyncState.operational != null + assert result.dataSyncState.running != null + } + + def dataStores() { + + return DataStores.builder() + .operationalDataStore(Operational.builder() + .syncState('NONE_REQUESTED') + .lastSyncTime(formattedDateAndTime.toString()).build()) + .runningDataStore(CompositeState.Running.builder() + .syncState('NONE_REQUESTED') + .lastSyncTime(formattedDateAndTime.toString()).build()) + .build() + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java index d4c64eac9..65e03f1f9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java @@ -90,6 +90,7 @@ public class YangModelCmHandle { yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(ncmpServiceCmHandle.getDmiProperties())); yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties( ncmpServiceCmHandle.getPublicProperties())); + yangModelCmHandle.setCompositeState(ncmpServiceCmHandle.getCompositeState()); return yangModelCmHandle; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java index 6811b59e0..963b484ed 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java @@ -27,6 +27,7 @@ import java.util.Map; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.onap.cps.ncmp.api.inventory.CompositeState; import org.springframework.validation.annotation.Validated; /** @@ -47,4 +48,7 @@ public class NcmpServiceCmHandle { @JsonSetter(nulls = Nulls.AS_EMPTY) private Map publicProperties = Collections.emptyMap(); + @JsonSetter(nulls = Nulls.AS_EMPTY) + private CompositeState compositeState; + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy index 4f4cccfe7..59c9951d4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy @@ -36,7 +36,8 @@ import static org.springframework.util.StringUtils.trimAllWhitespace class CompositeStateSpec extends Specification { - def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 1, 1, 1, 1, 1, 1, ZoneOffset.MIN)) + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) def objectMapper = new ObjectMapper() def "Composite State Specification"() { diff --git a/cps-ncmp-service/src/test/resources/expectedStateModel.json b/cps-ncmp-service/src/test/resources/expectedStateModel.json index a41619434..f68d725ed 100644 --- a/cps-ncmp-service/src/test/resources/expectedStateModel.json +++ b/cps-ncmp-service/src/test/resources/expectedStateModel.json @@ -4,16 +4,16 @@ "reason" : "lock-reason", "details" : "lock-misbehaving-details" }, - "last-update-time" : "2022-01-01T01:01:01.000-1800", + "last-update-time" : "2022-12-31T20:30:40.000+0000", "data-sync-enabled" : false, "datastores" : { "operational" : { "sync-state" : "NONE_REQUESTED", - "last-sync-time" : "2022-01-01T01:01:01.000-1800" + "last-sync-time" : "2022-12-31T20:30:40.000+0000" }, "running" : { "sync-state" : "NONE_REQUESTED", - "last-sync-time" : "2022-01-01T01:01:01.000-1800" + "last-sync-time" : "2022-12-31T20:30:40.000+0000" } } } \ No newline at end of file -- 2.16.6