</exclusion>
</exclusions>
</dependency>
+ <!-- T E S T D E P E N D E N C I E S -->
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-json</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
</snapshotRepository>
</distributionManagement>
-</project>
\ No newline at end of file
+</project>
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.
+ * 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.dmi.rest.stub.controller
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.dmi.provmns.model.ResourceOneOf
+import org.onap.cps.ncmp.dmi.rest.stub.utils.Sleeper
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.validation.Validator
+import spock.lang.Specification
+
+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
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+
+@WebMvcTest(controllers = ProvMnsStubController)
+class ProvMnsStubControllerSpec extends Specification {
+
+ @Autowired
+ MockMvc mockMvc
+
+ @SpringBean
+ Sleeper sleeper = Mock()
+
+ @Autowired
+ ObjectMapper objectMapper
+
+ @SpringBean
+ Validator validator = Mock()
+
+ def 'ProvMnS GET request with #scenario.'() {
+ given: 'url'
+ def url = "/ProvMnS/v1/someSegment=1/otherSegment=2/${segmentName}=${segmentValue}/finalSegment=3"
+ when: 'get request is executed'
+ def response = mockMvc.perform(get(url)).andReturn().response
+ then: 'response status is #expectedHttpStatus'
+ assert response.status == expectedHttpStatus.value()
+ and: 'content contains the expected json (snippet)'
+ assert response.getContentAsString().contains(expectedContentSnippet)
+ where: 'following simulations are applied'
+ scenario | segmentName | segmentValue || expectedHttpStatus || expectedContentSnippet
+ 'no simulation' | 'anotherSegment' | 'some value' || HttpStatus.OK || '"objectClass":"dummyClass"'
+ 'delay 1' | 'dmiSimulation' | 'slowResponse_1' || HttpStatus.OK || '"objectClass":"dummyClass"'
+ 'http error' | 'dmiSimulation' | 'httpError_418' || HttpStatus.I_AM_A_TEAPOT || '"status":"418"'
+ }
+
+ def 'ProvMnS PUT request with #scenario.'() {
+ given: 'url and some resource as body'
+ def url = "/ProvMnS/v1/someSegment=1/otherSegment=2/${segmentName}=${segmentValue}/finalSegment=3"
+ def requestBody = objectMapper.writeValueAsString(new ResourceOneOf('myId'))
+ when: 'put request is executed'
+ def response = mockMvc.perform(put(url)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andReturn().response
+ then: 'response status is #expectedHttpStatus'
+ assert response.status == expectedHttpStatus.value()
+ and: 'content contains the expected json (snippet)'
+ assert response.getContentAsString().contains(expectedContentSnippet)
+ where: 'following simulations are applied'
+ scenario | segmentName | segmentValue || expectedHttpStatus || expectedContentSnippet
+ 'no simulation' | 'anotherSegment' | 'some value' || HttpStatus.OK || '"id":"myId"'
+ 'delay 1' | 'dmiSimulation' | 'slowResponse_1' || HttpStatus.OK || '"id":"myId"'
+ 'http error' | 'dmiSimulation' | 'httpError_418' || HttpStatus.I_AM_A_TEAPOT || '"status":"418"'
+ }
+
+ def 'ProvMnS PATCH request with #scenario.'() {
+ given: 'url and some resource as body'
+ def url = "/ProvMnS/v1/someSegment=1/otherSegment=2/${segmentName}=${segmentValue}/finalSegment=3"
+ def requestBody = objectMapper.writeValueAsString(new ResourceOneOf('myId'))
+ when: 'patch request is executed'
+ def response = mockMvc.perform(patch(url)
+ .contentType("application/json-patch+json")
+ .content(requestBody))
+ .andReturn().response
+ then: 'response status is #expectedHttpStatus'
+ assert response.status == expectedHttpStatus.value()
+ and: 'content contains the expected json (snippet)'
+ assert response.getContentAsString().contains(expectedContentSnippet)
+ where: 'following simulations are applied'
+ scenario | segmentName | segmentValue || expectedHttpStatus || expectedContentSnippet
+ 'no simulation' | 'anotherSegment' | 'some value' || HttpStatus.OK || '"id":"myId"'
+ 'delay 1' | 'dmiSimulation' | 'slowResponse_1' || HttpStatus.OK || '"id":"myId"'
+ 'http error' | 'dmiSimulation' | 'httpError_418' || HttpStatus.I_AM_A_TEAPOT || '"status":"418"'
+ }
+
+ def 'ProvMnS DELETE request with #scenario.'() {
+ given: 'url'
+ def url = "/ProvMnS/v1/someSegment=1/otherSegment=2/${segmentName}=${segmentValue}/finalSegment=3"
+ when: 'delete request is executed'
+ def response = mockMvc.perform(delete(url)).andReturn().response
+ then: 'response status is #expectedHttpStatus'
+ assert response.status == expectedHttpStatus.value()
+ and: 'content only contains anything in case of an http error'
+ assert response.getContentAsString().contains(expectedContentSnippet)
+ where: 'following simulations are applied'
+ scenario | segmentName | segmentValue || expectedHttpStatus || expectedContentSnippet
+ 'no simulation' | 'anotherSegment' | 'some value' || HttpStatus.OK || ''
+ 'delay 1' | 'dmiSimulation' | 'slowResponse_1' || HttpStatus.OK || ''
+ 'http error' | 'dmiSimulation' | 'httpError_418' || HttpStatus.I_AM_A_TEAPOT || '"status":"418"'
+ }
+
+}
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<artifactId>groovy-json</artifactId>
<scope>test</scope>
</dependency>
-
</dependencies>
<build>
<plugins>
</plugin>
</plugins>
</build>
-</project>
\ No newline at end of file
+</project>
value = "v1/**",
produces = { "application/json" }
)
- ResponseEntity<Void> deleteMoi(HttpServletRequest httpServletRequest);
+ ResponseEntity<Object> deleteMoi(HttpServletRequest httpServletRequest);
/**
produces = { "application/json"}
)
- ResponseEntity<Resource> getMoi(
+ ResponseEntity<Object> getMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "scope", description = "This parameter extends the set of targeted resources beyond the "
+ "base resource identified with the path component of the URI. "
consumes = {"application/json-patch+json", "application/3gpp-json-patch+json" }
)
- ResponseEntity<Resource> patchMoi(
+ ResponseEntity<Object> patchMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "Resource", description = "The request body describes changes to be made to the target "
+ "resources. The following patch media types are available "
consumes = { "application/json" }
)
- ResponseEntity<Resource> putMoi(
+ ResponseEntity<Object> putMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "Resource", description = "", required = true) @Valid @RequestBody Resource resource
);
*/
@PostMapping("/v1/tagMapping")
public ResponseEntity<Map<String, String>> addTagForMapping(@RequestBody final Map<String, String> requestBody) {
+ log.info("tagMapping: add {}", requestBody);
moduleSetTagPerCmHandleId.putAll(requestBody);
return new ResponseEntity<>(requestBody, HttpStatus.CREATED);
}
* @return The map represents the module set tag mapping.
*/
@GetMapping("/v1/tagMapping")
- public ResponseEntity<Map<String, String>> getTagMapping() {
+ public ResponseEntity<Map<String, String>> getTagMapping() {
+ log.info("tagMapping: get");
return ResponseEntity.ok(moduleSetTagPerCmHandleId);
}
*/
@GetMapping("/v1/tagMapping/ch/{cmHandleId}")
public ResponseEntity<String> getTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
+ log.info("tagMapping: get cm handle id: {}", cmHandleId);
return ResponseEntity.ok(moduleSetTagPerCmHandleId.get(cmHandleId));
}
@PutMapping("/v1/tagMapping")
public ResponseEntity<Map<String, String>> updateTagMapping(@RequestBody final Map<String, String> requestBody) {
+ log.info("tagMapping: update: {}", requestBody);
moduleSetTagPerCmHandleId.putAll(requestBody);
return ResponseEntity.noContent().build();
}
*/
@DeleteMapping("/v1/tagMapping/ch/{cmHandleId}")
public ResponseEntity<String> deleteTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
+ log.info("tagMapping: remove cm handle id: {}", cmHandleId);
moduleSetTagPerCmHandleId.remove(cmHandleId);
return ResponseEntity.ok(String.format("Mapping of %s is deleted successfully", cmHandleId));
}
@ModuleInitialProcess
public ResponseEntity<String> getModuleReferences(@PathVariable("cmHandleId") final String cmHandleId,
@RequestBody final Object moduleReferencesRequest) {
+ log.info("get module references for cm handle id: {}", cmHandleId);
return processModuleRequest(moduleReferencesRequest, MODULE_REFERENCE_RESPONSE, moduleReferencesDelayMs);
}
public ResponseEntity<String> getModuleResources(
@PathVariable("cmHandleId") final String cmHandleId,
@RequestBody final Object moduleResourcesReadRequest) {
+ log.info("get module resources for cm handle id: {}", cmHandleId);
return processModuleRequest(moduleResourcesReadRequest, MODULE_RESOURCE_RESPONSE, moduleResourcesDelayMs);
}
@RequestParam(value = "topic", required = false) final String topic,
@RequestHeader(value = "Authorization", required = false) final String authorization,
@RequestBody final String requestBody) {
+ log.info("create resource data for cm handle id: {}", cmHandleId);
log.debug("DMI AUTH HEADER: {}", authorization);
final String passthroughOperationType = getPassthroughOperationType(requestBody);
if (passthroughOperationType.equals("read")) {
@RequestBody final DmiDataOperationRequest dmiDataOperationRequest) {
delay(writeDataForCmHandleDelayMs);
try {
- log.debug("Request received from the NCMP to DMI Plugin: {}",
+ log.info("Request received from the NCMP to DMI Plugin: {}",
objectMapper.writeValueAsString(dmiDataOperationRequest));
} catch (final JsonProcessingException jsonProcessingException) {
log.warn("Unable to process dmi data operation request to json string");
public ResponseEntity<SubjobWriteResponse> consumeWriteSubJobs(
@RequestBody final SubjobWriteRequest subJobWriteRequest,
@RequestParam("destination") final String destination) {
- log.debug("Destination: {}", destination);
- log.debug("Request body: {}", subJobWriteRequest);
+ log.info("cm write (datajob) request");
+ log.info("Destination: {}", destination);
+ log.info("Request body: {}", subJobWriteRequest);
return ResponseEntity.ok(new SubjobWriteResponse(String.valueOf(subJobWriteRequestCounter.incrementAndGet()),
"some-dmi-service-name", "my-data-producer-id"));
}
public ResponseEntity<Map<String, String>> retrieveDataJobStatus(
@PathVariable("dataProducerId") final String dataProducerId,
@PathVariable("dataProducerJobId") final String dataProducerJobId) {
- log.debug("Received request to retrieve data job status. Request ID: {}, Data Producer Job ID: {}",
+ log.info("Received request to retrieve data job status. Request ID: {}, Data Producer Job ID: {}",
dataProducerId, dataProducerJobId);
return ResponseEntity.ok(Map.of("status", "FINISHED"));
}
@PathVariable("dataProducerId") final String dataProducerId,
@PathVariable("dataProducerJobId") final String dataProducerJobId,
@RequestParam(name = "destination") final String destination) {
- log.debug("Received request to retrieve data job result. Data Producer ID: {}, "
+ log.info("Received request to retrieve data job result. Data Producer ID: {}, "
+ "Data Producer Job ID: {}, Destination: {}",
dataProducerId, dataProducerJobId, destination);
return ResponseEntity.ok(Map.of("result", "some status"));
package org.onap.cps.ncmp.dmi.rest.stub.controller;
-
import jakarta.servlet.http.HttpServletRequest;
+import java.util.Collections;
import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.dmi.provmns.api.ProvMnS;
import org.onap.cps.ncmp.dmi.provmns.model.ClassNameIdGetDataNodeSelectorParameter;
+import org.onap.cps.ncmp.dmi.provmns.model.ErrorResponseDefault;
import org.onap.cps.ncmp.dmi.provmns.model.Resource;
import org.onap.cps.ncmp.dmi.provmns.model.ResourceOneOf;
import org.onap.cps.ncmp.dmi.provmns.model.Scope;
+import org.onap.cps.ncmp.dmi.rest.stub.utils.Sleeper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@RequestMapping("${rest.api.provmns-base-path}")
@RequiredArgsConstructor
+@Slf4j
public class ProvMnsStubController implements ProvMnS {
+ static final ResourceOneOf dummyResource = new ResourceOneOf("some id");
+
+ static final Pattern PATTERN_SIMULATION = Pattern.compile("dmiSimulation=(\\w+_\\d{1,3})");
+ static final Pattern PATTERN_HTTP_ERROR = Pattern.compile("httpError_(\\d{3})");
+ static final Pattern PATTERN_SLOW_RESPONSE = Pattern.compile("slowResponse_(\\d{1,3})");
+
+ private final Sleeper sleeper;
+
+ static {
+ dummyResource.setObjectClass("dummyClass");
+ dummyResource.setObjectInstance("dummyInstance");
+ dummyResource.setAttributes(Collections.singletonMap("dummyAttribute", "dummy value"));
+ }
+
/**
* Replaces a complete single resource or creates it if it does not exist.
*
* @param httpServletRequest URI request including path
* @param resource Resource representation of the resource to be created or replaced
- * @return {@code ResponseEntity} The representation of the updated resource is returned in the response
+ * @return {@code Object} The representation of the updated resource is returned in the response
* message body.
*/
@Override
- public ResponseEntity<Resource> putMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
- return new ResponseEntity<>(resource, HttpStatus.OK);
+ public ResponseEntity<Object> putMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
+ log.info("putMoi: {}", resource);
+ final Optional<ResponseEntity<Object>> optionalResponseEntity = simulate(httpServletRequest);
+ return optionalResponseEntity.orElseGet(() -> new ResponseEntity<>(resource, HttpStatus.OK));
}
/**
* in the response message body.
*/
@Override
- public ResponseEntity<Resource> getMoi(final HttpServletRequest httpServletRequest, final Scope scope,
+ public ResponseEntity<Object> getMoi(final HttpServletRequest httpServletRequest, final Scope scope,
final String filter, final List<String> attributes,
final List<String> fields,
final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector) {
- return new ResponseEntity<>(new ResourceOneOf("exampleResourceId"), HttpStatus.OK);
+ log.info("getMoi: scope: {}, filter: {}, attributes: {}, fields: {}, dataNodeSelector: {}",
+ scope, filter, attributes, fields, dataNodeSelector);
+ final Optional<ResponseEntity<Object>> optionalResponseEntity = simulate(httpServletRequest);
+ return optionalResponseEntity.orElseGet(() -> new ResponseEntity<>(dummyResource, HttpStatus.OK));
}
/**
* @return {@code ResponseEntity} The updated resource representations are returned in the response message body.
*/
@Override
- public ResponseEntity<Resource> patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
- return new ResponseEntity<>(resource, HttpStatus.OK);
+ public ResponseEntity<Object> patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
+ log.info("patchMoi: {}", resource);
+ final Optional<ResponseEntity<Object>> optionalResponseEntity = simulate(httpServletRequest);
+ return optionalResponseEntity.orElseGet(() -> new ResponseEntity<>(resource, HttpStatus.OK));
}
/**
* @return {@code ResponseEntity} The response body is empty, HTTP status returned.
*/
@Override
- public ResponseEntity<Void> deleteMoi(final HttpServletRequest httpServletRequest) {
- return new ResponseEntity<>(HttpStatus.OK);
+ public ResponseEntity<Object> deleteMoi(final HttpServletRequest httpServletRequest) {
+ log.info("deleteMoi:");
+ final Optional<ResponseEntity<Object>> optionalResponseEntity = simulate(httpServletRequest);
+ return optionalResponseEntity.orElseGet(() -> new ResponseEntity<>(HttpStatus.OK));
}
+
+ private Optional<ResponseEntity<Object>> simulate(final HttpServletRequest httpServletRequest) {
+ Matcher matcher = PATTERN_SIMULATION.matcher(httpServletRequest.getRequestURI());
+ if (matcher.find()) {
+ final String simulation = matcher.group(1);
+ matcher = PATTERN_SLOW_RESPONSE.matcher(simulation);
+ if (matcher.matches()) {
+ haveALittleRest(Integer.parseInt(matcher.group(1)));
+ }
+ matcher = PATTERN_HTTP_ERROR.matcher(simulation);
+ if (matcher.matches()) {
+ return Optional.of(createErrorRsponseEntity(Integer.parseInt(matcher.group(1))));
+ }
+ }
+ return Optional.empty();
+ }
+
+ private void haveALittleRest(final int durationInSeconds) {
+ log.warn("Stub is mocking slow response; delay {} seconds", durationInSeconds);
+ try {
+ sleeper.haveALittleRest(durationInSeconds);
+ } catch (final InterruptedException e) {
+ log.trace("Sleep interrupted, re-interrupting the thread");
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private static ResponseEntity<Object> createErrorRsponseEntity(final int errorCode) {
+ log.warn("Stub is mocking an error response, code: {}", errorCode);
+ final ErrorResponseDefault errorResponseDefault = new ErrorResponseDefault("ERROR_FROM_STUB");
+ errorResponseDefault.setTitle("Title set by Stub");
+ errorResponseDefault.setStatus(String.valueOf(errorCode));
+ return new ResponseEntity<>(errorResponseDefault, HttpStatus.valueOf(errorCode));
+ }
+
}
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.
+ * 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.dmi.rest.stub.utils;
+
+import java.util.concurrent.TimeUnit;
+import org.springframework.stereotype.Component;
+
+/**
+ * This class is to extract out sleep functionality so the interrupted exception handling can
+ * be covered with a test (e.g. using spy on Sleeper) and help to get too 100% code coverage.
+ */
+@Component
+public class Sleeper {
+ public void haveALittleRest(final int durationInSeconds) throws InterruptedException {
+ TimeUnit.SECONDS.sleep(durationInSeconds);
+ }
+}