import io.netty.handler.timeout.TimeoutException;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.exceptions.DataValidationException;
@Slf4j
public class ProvMnSController implements ProvMnS {
- private static final Pattern STANDARD_ATTRIBUTE_PATTERN = Pattern.compile("[^#]/attributes");
private static final String NO_AUTHORIZATION = null;
private static final String PROVMNS_NOT_SUPPORTED_ERROR_MESSAGE =
"Registered DMI does not support the ProvMnS interface.";
throw new ProvMnSException(httpServletRequest.getMethod(), HttpStatus.PAYLOAD_TOO_LARGE, title, NO_OP);
}
final RequestParameters requestParameters = parameterMapper.extractRequestParameters(httpServletRequest);
- for (final PatchItem patchItem : patchItems) {
- validateAttributeReference(httpServletRequest.getMethod(), httpServletRequest.getContentType(), patchItem);
- }
try {
final YangModelCmHandle yangModelCmHandle = getAndValidateYangModelCmHandle(requestParameters);
checkPermissionForEachPatchItem(requestParameters, patchItems, yangModelCmHandle);
return provMnSException;
}
- private void validateAttributeReference(final String httpMethodName,
- final String contentType,
- final PatchItem patchItem) {
- final String path = patchItem.getPath();
- boolean attributesReferenceIncorrect = false;
- if (path.contains("#/attributes") && "application/json-patch+json".equals(contentType)) {
- attributesReferenceIncorrect = true;
- } else {
- final Matcher matcher = STANDARD_ATTRIBUTE_PATTERN.matcher(path);
- if ("application/3gpp-json-patch+json".equals(contentType) && matcher.find()) {
- attributesReferenceIncorrect = true;
- }
- }
- if (attributesReferenceIncorrect) {
- throw new ProvMnSException(httpMethodName, HttpStatus.BAD_REQUEST,
- "Invalid path for content-type " + contentType, NO_OP);
- }
- }
-
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * Copyright (C) 2025-2026 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
assert response.contentAsString.contains('"title":"/myClass=id1 not found"')
}
- def 'Patch request with invalid attribute reference: #scenario.'() {
- given: 'provMns url'
- def provMnsUrl = "$provMnSBasePath/v1/myClass=id1"
- and: 'alternate Id can be matched'
- mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1', "/") >> 'ch-1'
- and: 'persistence service returns yangModelCmHandle'
- mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id:'ch-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
- and: 'dmi provides a response'
- mockDmiRestClient.synchronousPatchOperation(*_) >> new ResponseEntity<>('Response from DMI service', HttpStatus.OK)
- when: 'patch request is performed'
- def response = mvc.perform(patch(provMnsUrl)
- .contentType(contentType)
- .content('[{"op":"replace","path":"className' + attributeReference + '"}]'))
- .andReturn().response
- then: 'response status is BAD REQUEST (400)'
- assert response.status == HttpStatus.BAD_REQUEST.value()
- and: 'response contains expected message for error case'
- assert response.contentAsString.contains('"title":"Invalid path for content-type ' + contentType + '"')
- where: 'following scenarios'
- scenario | contentType | attributeReference
- '3gpp without hash' | 'application/3gpp-json-patch+json' | '/attributes/attr1'
- 'standard with hash' | "application/json-patch+json" | '#/attributes/attr1'
- }
}
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2026 OpenInfra Foundation Europe
* ================================================================================
* 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.rest.util
import org.mapstruct.factory.Mappers
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
import org.onap.cps.ncmp.api.inventory.models.CmHandleState
class CmHandleStateMapperSpec 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 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(CmHandleStateMapper)
def 'Composite State to CmHandleCompositeState'() {
assert result.dataSyncState.operational.getSyncState() != null
}
- def 'Handling null state.'() {
+ def 'Convert composite datastore that is null.'() {
expect: 'converting null returns null'
- CmHandleStateMapper.toDataStores(null) == null
+ assert CmHandleStateMapper.toDataStores(null) == null
}
- def 'Internal to External Lock Reason Mapping of #scenario'() {
+ def 'Convert empty composite datastore.'() {
+ when: 'converting a empty data stores object'
+ def result = CmHandleStateMapper.toDataStores(new CompositeState.DataStores())
+ then: 'result not null but both datastores are null'
+ assert result.operational == null
+ assert result.running == null
+ }
+
+ def 'Internal to External Lock Reason Mapping of #scenario.'() {
given: 'a LOCKED composite state with locked reason of #scenario'
- def compositeState = new CompositeStateBuilder()
- .withCmHandleState(CmHandleState.LOCKED)
- .withLockReason(lockReason, '').build()
+ def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED).withLockReason(lockReason, '').build()
when: 'the composite state is mapped to a CMHandle composite state'
- def result = objectUnderTest.toCmHandleCompositeStateExternalLockReason(compositeState)
+ def result = objectUnderTest.toCmHandleCompositeStateExternalLockReason(compositeState)
then: 'the composite state contains the expected lock Reason and details'
- result.getLockReason().getReason() == (expectedExternalLockReason as String)
- where:
- scenario | lockReason || expectedExternalLockReason
- 'MODULE_SYNC_FAILED' | MODULE_SYNC_FAILED || MODULE_SYNC_FAILED
- 'null value' | null || LOCKED_MISBEHAVING
+ assert result.getLockReason().getReason() == (expectedExternalLockReason as String)
+ where: 'following lock states are used'
+ scenario | lockReason || expectedExternalLockReason
+ 'MODULE_SYNC_FAILED' | MODULE_SYNC_FAILED || MODULE_SYNC_FAILED
+ 'null value' | null || LOCKED_MISBEHAVING
}
}