From ea04c07ad990b5543766e95e234cae746bd1fbc1 Mon Sep 17 00:00:00 2001 From: Renu Kumari Date: Tue, 10 Aug 2021 16:43:04 -0400 Subject: Validate controller input and provide expected response - validate endpoint input - set default values if it is missing in the optional fields - used hateoas for previous and next record link generation - use service layer in controller layer Issue-ID: CPS-449 Signed-off-by: Renu Kumari Change-Id: I7936cd9e8e7dead3b5650b421bb12f10d14ffa9b --- .../model/CpsDataUpdatedEventMapperSpec.groovy | 2 +- .../rest/QueryControllerDataBuilder.groovy | 159 ++++++++++++++ .../controller/rest/QueryControllerSpec.groovy | 234 ++++++++++++++++++--- .../cps/temporal/domain/SearchCriteriaSpec.groovy | 33 ++- .../NetworkDataRepositoryImplSpec.groovy | 3 +- .../service/NetworkDataServiceImplSpec.groovy | 33 ++- 6 files changed, 427 insertions(+), 37 deletions(-) create mode 100644 src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerDataBuilder.groovy (limited to 'src/test/groovy/org/onap') diff --git a/src/test/groovy/org/onap/cps/temporal/controller/event/model/CpsDataUpdatedEventMapperSpec.groovy b/src/test/groovy/org/onap/cps/temporal/controller/event/model/CpsDataUpdatedEventMapperSpec.groovy index 132ff6d..a51c4fe 100644 --- a/src/test/groovy/org/onap/cps/temporal/controller/event/model/CpsDataUpdatedEventMapperSpec.groovy +++ b/src/test/groovy/org/onap/cps/temporal/controller/event/model/CpsDataUpdatedEventMapperSpec.groovy @@ -110,7 +110,7 @@ class CpsDataUpdatedEventMapperSpec extends Specification { result != null and: 'all result entity properties are the ones from the event' result.getObservedTimestamp() == - OffsetDateTime.parse(event.getContent().getObservedTimestamp(), isoTimestampFormatter) + OffsetDateTime.parse(event.getContent().getObservedTimestamp(), isoTimestampFormatter) result.getDataspace() == event.getContent().getDataspaceName() result.getSchemaSet() == event.getContent().getSchemaSetName() result.getAnchor() == event.getContent().getAnchorName() diff --git a/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerDataBuilder.groovy b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerDataBuilder.groovy new file mode 100644 index 0000000..dee1e06 --- /dev/null +++ b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerDataBuilder.groovy @@ -0,0 +1,159 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2021 Bell Canada. + * ================================================================================ + * 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.temporal.controller.rest + +import org.onap.cps.temporal.controller.utils.DateTimeUtility +import org.onap.cps.temporal.domain.SearchCriteria +import org.springframework.data.domain.Sort +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.util.CollectionUtils +import org.springframework.util.MultiValueMap +import org.springframework.web.util.DefaultUriBuilderFactory +import org.springframework.web.util.UriComponentsBuilder +import org.springframework.web.util.UriUtils + +import java.nio.charset.Charset +import java.time.OffsetDateTime + +/* +To create objects required for the test based on same input + */ + +class QueryControllerDataBuilder { + + private static String POINT_IN_TIME_QUERY_PARAM = 'pointInTime' + private static String OBSERVED_TIMESTAMP_AFTER_QUERY_PARAM = 'observedTimestampAfter' + private static String PAGE_NUMBER_QUERY_PARAM = 'pageNumber' + private static String PAGE_LIMIT_QUERY_PARAM = 'pageLimit' + private static String SORT_QUERY_PARAM = 'sort' + private static String SIMPLE_PAYLOAD_FILTER_QUERY_PARAM = 'simplePayloadFilter' + + private static int DEFAULT_PAGE_NUMBER = 0 + private static int DEFAULT_PAGE_SIZE = 1000 + private static String DEFAULT_SORT = 'observed_timestamp:desc' + + private static Map SORT_MAP = ['anchor:asc' : Sort.by(Sort.Direction.ASC, 'anchor'), + 'observed_timestamp:desc': Sort.by(Sort.Direction.DESC, 'observed_timestamp')] + private static Map URI_MAP = + ['anchor by name' : '/cps-temporal/api/v1/dataspaces/{dataspace}/anchors/{anchor}/history', + 'anchors by schemaset': '/cps-temporal/api/v1/dataspaces/{dataspace}/anchors/history?schema-set-name={schemaSet}'] + + Map parameters + String endpoint + + QueryControllerDataBuilder(final String endPointName, final Map parameters) { + this.parameters = parameters + def replacements = ['{dataspace}': parameters.dataspace, + '{schemaSet}': parameters.schemaSet, + '{anchor}' : parameters.anchor] + endpoint = URI_MAP.get(endPointName).replace(replacements) + } + + MockHttpServletRequestBuilder createMockHttpRequestBuilder() { + def requestBuilder = MockMvcRequestBuilders.get(endpoint) + if (parameters.pointInTime != null) + requestBuilder.queryParam(POINT_IN_TIME_QUERY_PARAM, parameters.pointInTime) + if (parameters.observedTimestampAfter != null) + requestBuilder.queryParam(OBSERVED_TIMESTAMP_AFTER_QUERY_PARAM, parameters.observedTimestampAfter) + if (parameters.pageNumber != null) + requestBuilder.queryParam(PAGE_NUMBER_QUERY_PARAM, parameters.pageNumber.toString()) + if (parameters.pageLimit != null) + requestBuilder.queryParam(PAGE_LIMIT_QUERY_PARAM, parameters.pageLimit.toString()) + if (parameters.sortAsString != null) + requestBuilder.queryParam(SORT_QUERY_PARAM, parameters.sortAsString) + if (parameters.payloadFilter != null) + requestBuilder.queryParam(SIMPLE_PAYLOAD_FILTER_QUERY_PARAM, parameters.payloadFilter) + return requestBuilder.contentType(MediaType.APPLICATION_JSON) + } + + SearchCriteria.Builder createSearchCriteriaBuilder() { + def searchCriteriaBuilder = SearchCriteria.builder() + searchCriteriaBuilder.dataspaceName(parameters.dataspace) + .anchorName(parameters.anchor) + .schemaSetName(parameters.schemaSet) + if (parameters.pointInTime != null) + searchCriteriaBuilder.createdBefore(DateTimeUtility.toOffsetDateTime(parameters.pointInTime)) + if (parameters.observedTimestampAfter != null) + searchCriteriaBuilder.observedAfter(DateTimeUtility.toOffsetDateTime(parameters.observedTimestampAfter)) + if (parameters.pageNumber != null) + searchCriteriaBuilder.pagination(parameters.pageNumber, parameters.pageLimit) + if (parameters.payloadFilter != null) + searchCriteriaBuilder.simplePayloadFilter(parameters.payloadFilter) + if (parameters.sortAsString != null) + searchCriteriaBuilder.sort(SORT_MAP.get(((String) parameters.sortAsString).toLowerCase())) + return searchCriteriaBuilder + } + + private int getPageNumber() { + return parameters.pageNumber == null ? + DEFAULT_PAGE_NUMBER : + parameters.pageNumber + } + + void isExpectedNextRecordsLink(String actualNextLink) { + isExpectedLink(getPageNumber() + 1, actualNextLink) + } + + void isExpectedPreviousRecordsLink(String actualNextLink) { + isExpectedLink(getPageNumber() - 1, actualNextLink) + } + + void isExpectedLink(int pageNumber, String actualLink) { + def actualUriComponents = UriComponentsBuilder.fromUriString(actualLink).build() + def actualQueryParams = actualUriComponents.getQueryParams() + + if (parameters.observedTimestampAfter != null) { + validateQueryParam(OBSERVED_TIMESTAMP_AFTER_QUERY_PARAM, parameters.observedTimestampAfter, actualQueryParams) + } + if (parameters.payloadFilter != null) { + validateQueryParam(SIMPLE_PAYLOAD_FILTER_QUERY_PARAM, parameters.payloadFilter, actualQueryParams) + } + validatePointInTime(actualQueryParams) + validateQueryParam(PAGE_NUMBER_QUERY_PARAM, Integer.toString(pageNumber), actualQueryParams) + validateQueryParam(PAGE_LIMIT_QUERY_PARAM, + Integer.toString(parameters.pageLimit == null ? DEFAULT_PAGE_SIZE : parameters.pageLimit), actualQueryParams) + validateQueryParam(SORT_QUERY_PARAM, + parameters.sortAsString == null ? DEFAULT_SORT : parameters.sortAsString, actualQueryParams) + + } + + private void validateQueryParam(String paramName, Object expectedValue, MultiValueMap queryParams) { + def values = queryParams.get(paramName) + assert (!CollectionUtils.isEmpty(values)) + assert (expectedValue == URLDecoder.decode(values.get(0), Charset.defaultCharset())) + } + + boolean validatePointInTime(MultiValueMap queryParams) { + + def values = queryParams.get(POINT_IN_TIME_QUERY_PARAM) + assert (!CollectionUtils.isEmpty(values)) + def actualValue = URLDecoder.decode(values.get(0), Charset.defaultCharset()) + + if (parameters.pointInTime == null) { + assert DateTimeUtility.toOffsetDateTime(actualValue).isAfter(OffsetDateTime.now().minusMinutes(2)) + } else { + assert parameters.pointInTime == actualValue + } + } + +} diff --git a/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy index 771a3fc..a18a134 100644 --- a/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy +++ b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy @@ -20,59 +20,237 @@ package org.onap.cps.temporal.controller.rest +import org.onap.cps.temporal.controller.utils.DateTimeUtility +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders + +import java.time.OffsetDateTime +import org.onap.cps.temporal.controller.rest.model.AnchorDetails +import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapperImpl +import org.onap.cps.temporal.controller.rest.model.AnchorHistory +import org.onap.cps.temporal.controller.rest.model.ErrorMessage +import org.onap.cps.temporal.controller.rest.model.SortMapper +import org.onap.cps.temporal.domain.NetworkData +import org.onap.cps.temporal.domain.SearchCriteria +import org.onap.cps.temporal.service.NetworkDataService +import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.context.annotation.Import +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.SliceImpl +import org.springframework.data.domain.Sort import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get - +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper +import spock.lang.Shared import spock.lang.Specification -/** - * Specification for Query Controller. - */ @WebMvcTest(QueryController) +@Import([SortMapper, QueryResponseFactory, AnchorDetailsMapperImpl]) class QueryControllerSpec extends Specification { + @SpringBean + NetworkDataService mockNetworkDataService = Mock() + @Autowired MockMvc mvc - @Value('${rest.api.base-path}') - def basePath - def myDataspace = 'my-dataspace' + @Shared def myAnchor = 'my-anchor' + @Shared def mySchemaset = 'my-schemaset' + @Shared + def objectMapper = new ObjectMapper() - def 'Get anchors by name is not implemented.'(){ - given: 'an endpoint' - def getAnchorsByNameEndpoint = "${basePath}/v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/history" + @Shared + def observedDescSortOrder = new Sort.Order(Sort.Direction.DESC, 'observed_timestamp') + @Shared + def anchorAscSortOrder = new Sort.Order(Sort.Direction.ASC, 'anchor') - when: 'get anchors by name endpoint is called' - def response = mvc.perform( get(getAnchorsByNameEndpoint, myDataspace, myAnchor) - .contentType(MediaType.APPLICATION_JSON)) - .andReturn().response + def 'Get #endpointName: default values if missing'() { - then: 'received unsupported operation response' - response.getStatus() == HttpStatus.NOT_IMPLEMENTED.value() + def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, + [dataspace: myDataspace] << urlSpecifParams) + given: 'network data to be returned' + def networkData = createNetworkData() + when: 'endpoint is called without pageNumber, pageLimit, sort and pointInTime' + def requestBuilder = controllerDataBuilder. + createMockHttpRequestBuilder(); + def response = mvc.perform(requestBuilder).andReturn().response + then: 'pageNumber, pageSize and sort has default values' + interaction { + def expectedPageable = PageRequest.of(0, 1000, + Sort.by(Sort.Order.desc('observed_timestamp'))) + 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> { + SearchCriteria searchCriteria -> + assert searchCriteria.getPageable() == expectedPageable + assert searchCriteria.getObservedAfter() == null + assert searchCriteria.getCreatedBefore().isAfter(OffsetDateTime.now().minusMinutes(2)) + return new SliceImpl([networkData], searchCriteria.getPageable(), false) + } + } + and: 'response is ok' + response.getStatus() == HttpStatus.OK.value() + def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory) + and: 'content has expected values' + anchorHistory.getPreviousRecordsLink() == null + anchorHistory.getNextRecordsLink() == null + anchorHistory.getRecords() == List.of(toAnchorDetails(networkData)) + where: + endpointName | urlSpecifParams + 'anchor by name' | [anchor: myAnchor] + 'anchors by schemaset' | [schemaSet: mySchemaset] + } + def 'Get #endpointName: query data #scenario'() { + def inputParameters = [ + dataspace : myDataspace, + pointInTime : '2021-07-24T01:00:01.000-0400', + pageNumber : 2, pageLimit: 10, + sortAsString: 'observed_timestamp:desc'] + inputParameters << urlSpecifParams + inputParameters << parameters + def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, inputParameters) + given: + def searchCriteria = controllerDataBuilder.createSearchCriteriaBuilder().build() + def networkData = createNetworkData() + mockNetworkDataService.searchNetworkData(searchCriteria) >> new SliceImpl( + List.of(networkData), searchCriteria.getPageable(), true) + when: 'endpoint is called with all parameters' + def requestBuilder = controllerDataBuilder.createMockHttpRequestBuilder() + def response = mvc.perform(requestBuilder + .contentType(MediaType.APPLICATION_JSON)).andReturn().response + def responseBody = objectMapper.readValue(response.getContentAsString(), AnchorHistory) + then: 'status is ok' + response.getStatus() == HttpStatus.OK.value() + and: 'next and previous record links have expected value' + controllerDataBuilder.isExpectedNextRecordsLink(responseBody.getNextRecordsLink()) + controllerDataBuilder.isExpectedPreviousRecordsLink(responseBody.getPreviousRecordsLink()) + and: 'has expected network data records' + responseBody.getRecords().size() == 1 + responseBody.getRecords() == [toAnchorDetails(networkData)] + where: + scenario | endpointName | urlSpecifParams | parameters + 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: null] + 'with observedTimestampAfter and without payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null] + 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello+world"}'] + 'with observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}'] + 'without observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: null] + 'with observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null] + 'without observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello world"}'] + 'with observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}'] } - def 'Get anchors by dataspace name is not implemented.'(){ - given: 'an endpoint' - def getAnchorsByDataspaceEndpoint = "${basePath}/v1/dataspaces/{dataspace-name}/anchors/history" + def 'Get #endpointName: Sort by #sortAsString'() { + given: 'sort parameters' + def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams + when: 'endpoint is called' + def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters) + def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder()) + .andReturn().response + then: 'network data service is called with expected sort' + 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> { + SearchCriteria searchCriteria -> + assert searchCriteria.getPageable().getSort() == expectedSort + return new SliceImpl([], searchCriteria.getPageable(), true) + } + and: 'response is ok' + response.getStatus() == HttpStatus.OK.value() + def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory) + and: 'content has expected values' + controllerDataBuilder.isExpectedNextRecordsLink(anchorHistory.getNextRecordsLink()) + anchorHistory.getPreviousRecordsLink() == null + where: + endpointName | uriSpecificParams | sortAsString || expectedSort + 'anchor by name' | [anchor: myAnchor] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder) + 'anchor by name' | [anchor: myAnchor] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder) + 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder) + 'anchors by schemaset' | [schemaSet: mySchemaset] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder) + } - when: 'get anchors by dataspace name endpoint is called' - def response = mvc.perform( get(getAnchorsByDataspaceEndpoint, myDataspace).queryParam('schema-set-name', mySchemaset) - .contentType(MediaType.APPLICATION_JSON)) - .andReturn().response + def 'Get #endpointName Error handling: invalid date format in #queryParamName '() { + given: 'sort parameters' + def parameters = [dataspace: myDataspace] << uriSpecificParams + parameters[queryParamName] = 'invalid-date-string' + when: 'endpoint is called' + QueryControllerDataBuilder dataBuilder = new QueryControllerDataBuilder(endpointName, parameters) + def response = mvc.perform(dataBuilder.createMockHttpRequestBuilder()) + .andReturn().response + then: 'received bad request status' + response.getStatus() == HttpStatus.BAD_REQUEST.value() + and: 'error details' + def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage) + errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString() + errorMessage.getMessage().contains(queryParamName) + errorMessage.getMessage().contains("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + where: + endpointName | uriSpecificParams | queryParamName + 'anchor by name' | [anchor: myAnchor] | 'pointInTime' + 'anchor by name' | [anchor: myAnchor] | 'observedTimestampAfter' + 'anchors by schemaset' | [schemaSet: mySchemaset] | 'pointInTime' + 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observedTimestampAfter' + } + + def 'Get #endpointName Error handling: invalid sort format #scenario'() { + given: 'sort parameters' + def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams + when: 'endpoint is called' + def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters) + def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder()) + .andReturn().response + then: 'received bad request status' + response.getStatus() == HttpStatus.BAD_REQUEST.value() + and: 'error details' + def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage) + errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString() + errorMessage.getMessage().contains("sort") + errorMessage.getMessage().contains("'$sortAsString'") + errorMessage.getMessage().contains(':,...,:') + where: + scenario | sortAsString | endpointName | uriSpecificParams + 'missing direction' | 'observed_timestamp' | 'anchor by name' | [anchor: myAnchor] + 'missing separator' | 'observed_timestampdesc' | 'anchor by name' | [anchor: myAnchor] + 'missing direction' | 'observed_timestamp' | 'anchors by schemaset' | [schemaSet: mySchemaset] + 'missing separator' | 'observed_timestampdesc' | 'anchors by schemaset' | [schemaSet: mySchemaset] + } - then: 'received unsupported operation response' - response.getStatus() == HttpStatus.NOT_IMPLEMENTED.value() + def 'Get #endpointName Error handling: invalid simple payload filter '() { + given: 'payload filter parameters' + def parameters = [dataspace: myDataspace, payloadFilter: 'invalid-json'] << uriSpecificParams + when: 'endpoint is called' + def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters) + def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder()) + .andReturn().response + then: 'received bad request status' + response.getStatus() == HttpStatus.BAD_REQUEST.value() + and: 'error details' + def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage) + errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString() + errorMessage.getMessage().contains('simplePayloadFilter') + where: 'endpoints are provided' + endpointName | uriSpecificParams + 'anchor by name' | [anchor: myAnchor] + 'anchors by schemaset' | [schemaSet: mySchemaset] + } + NetworkData createNetworkData() { + return NetworkData.builder().dataspace(myDataspace) + .schemaSet(mySchemaset).anchor(myAnchor).payload('{"message" : "Hello World"}') + .observedTimestamp(OffsetDateTime.now()) + .createdTimestamp(OffsetDateTime.now()).build() } + AnchorDetails toAnchorDetails(NetworkData networkData) { + AnchorDetails anchorDetails = new AnchorDetails() + anchorDetails.setDataspace(networkData.getDataspace()) + anchorDetails.setAnchor(networkData.getAnchor()) + anchorDetails.setSchemaSet(networkData.getSchemaSet()) + anchorDetails.setObservedTimestamp(DateTimeUtility.toString(networkData.getObservedTimestamp())) + anchorDetails.setData(networkData.getPayload()) + return anchorDetails + } + + } diff --git a/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy b/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy index d7b6d1f..3d6a354 100644 --- a/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy +++ b/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy @@ -21,7 +21,6 @@ package org.onap.cps.temporal.domain import org.springframework.data.domain.Sort import spock.lang.Specification - import java.time.OffsetDateTime class SearchCriteriaSpec extends Specification { @@ -57,11 +56,13 @@ class SearchCriteriaSpec extends Specification { def 'Search Criteria with the provided values.'() { given: 'sort by parameter' - def sortBy = Sort.by(Sort.Direction.ASC, 'observed_timestamp') + def sortBy = Sort.by(Sort.Direction.DESC, 'observed_timestamp') and: 'data created one day ago' def lastDayAsCreatedBefore = OffsetDateTime.now().minusDays(1) and: 'observed timestamp' def nowAsObservedAfter = OffsetDateTime.now() + and: 'simple payload filter' + def simplePayloadFilter = '{"message":"hello world"}' when: 'search criteria is created' def searchCriteria = SearchCriteria.builder() @@ -69,6 +70,7 @@ class SearchCriteriaSpec extends Specification { .schemaSetName(myschemaSetName) .anchorName(myAnchorName) .pagination(0, 10) + .simplePayloadFilter(simplePayloadFilter) .sort(sortBy) .observedAfter(nowAsObservedAfter) .createdBefore(lastDayAsCreatedBefore) @@ -81,6 +83,7 @@ class SearchCriteriaSpec extends Specification { anchorName == myAnchorName observedAfter == nowAsObservedAfter createdBefore == lastDayAsCreatedBefore + it.simplePayloadFilter == simplePayloadFilter pageable.getPageNumber() == 0 pageable.getPageSize() == 10 pageable.getSort() == sortBy @@ -117,13 +120,35 @@ class SearchCriteriaSpec extends Specification { thrown(IllegalStateException) } - def 'Error Handling: sort must be not null.'() { + def 'Error Handling: sort based on #scenario .'() { when: 'search criteria is created without sorting information' SearchCriteria.builder() .dataspaceName(myDataspace) .anchorName(myAnchorName) .pagination(0, 1) - .sort(null) + .sort(sort) + .build() + then: 'exception is thrown' + def illegalArgumentException = thrown(IllegalArgumentException) + def message = illegalArgumentException.getMessage(); + assert message.contains("sort") + assert message.contains(expectedExceptionMessage) + where: + scenario | sort | expectedExceptionMessage + 'null' | null | "null" + 'unsupported properties' | Sort.by(Sort.Direction.ASC, 'unsupported') | "Invalid sorting" + 'missing required sort' | Sort.by(Sort.Direction.ASC, 'anchor') | 'Missing mandatory sort' + } + + def 'Error Handling: Invalid simple payload filter.'() { + given: 'invalid simple payload filter' + def inavlidSimplePayloadFilter = 'invalid-json' + when: 'search criteria is created without invalid simple payload filter' + SearchCriteria.builder() + .dataspaceName(myDataspace) + .anchorName(myAnchorName) + .pagination(0, 1) + .simplePayloadFilter(inavlidSimplePayloadFilter) .build() then: 'exception is thrown' thrown(IllegalArgumentException) diff --git a/src/test/groovy/org/onap/cps/temporal/repository/NetworkDataRepositoryImplSpec.groovy b/src/test/groovy/org/onap/cps/temporal/repository/NetworkDataRepositoryImplSpec.groovy index a5cc721..d33df75 100644 --- a/src/test/groovy/org/onap/cps/temporal/repository/NetworkDataRepositoryImplSpec.groovy +++ b/src/test/groovy/org/onap/cps/temporal/repository/NetworkDataRepositoryImplSpec.groovy @@ -165,8 +165,7 @@ class NetworkDataRepositoryImplSpec extends Specification { } where: scenario | sortOrder || expectedObservedTimestamp | expectedAnchorName - 'observed timestamp asc' | Sort.by(observedAscSortOrder) || '2021-07-22 00:00:01.000' | 'ANCHOR-01' - 'observed timestamp asc' | Sort.by(observedDescSortOrder) || '2021-07-24 00:00:01.000' | 'ANCHOR-02' + 'observed timestamp desc' | Sort.by(observedDescSortOrder) || '2021-07-24 00:00:01.000' | 'ANCHOR-02' 'anchor asc, ' + 'observed timestamp desc' | Sort.by(anchorAscSortOrder, observedDescSortOrder) || '2021-07-23 00:00:01.000' | 'ANCHOR-01' diff --git a/src/test/groovy/org/onap/cps/temporal/service/NetworkDataServiceImplSpec.groovy b/src/test/groovy/org/onap/cps/temporal/service/NetworkDataServiceImplSpec.groovy index c55c3c7..2e04ca8 100644 --- a/src/test/groovy/org/onap/cps/temporal/service/NetworkDataServiceImplSpec.groovy +++ b/src/test/groovy/org/onap/cps/temporal/service/NetworkDataServiceImplSpec.groovy @@ -22,7 +22,14 @@ package org.onap.cps.temporal.service import org.onap.cps.temporal.domain.NetworkDataId import org.onap.cps.temporal.domain.SearchCriteria +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.domain.PageImpl +import org.springframework.test.context.ContextConfiguration + +import javax.validation.ValidationException import java.time.OffsetDateTime import org.onap.cps.temporal.domain.NetworkData import org.onap.cps.temporal.repository.NetworkDataRepository @@ -31,11 +38,18 @@ import spock.lang.Specification /** * Test specification for network data service. */ +@SpringBootTest +@ContextConfiguration(classes = NetworkDataServiceImpl) class NetworkDataServiceImplSpec extends Specification { - def mockNetworkDataRepository = Mock(NetworkDataRepository) + @SpringBean + NetworkDataRepository mockNetworkDataRepository = Mock() + + @Autowired + NetworkDataService objectUnderTest - def objectUnderTest = new NetworkDataServiceImpl(mockNetworkDataRepository) + @Value('${app.query.response.max-page-size}') + int maxPageSize def networkData = new NetworkData() @@ -88,4 +102,19 @@ class NetworkDataServiceImplSpec extends Specification { } + def 'Query network data with more than max page-size'() { + given: 'search criteria with more than max page size' + def searchCriteria = SearchCriteria.builder() + .dataspaceName('my-dataspaceName') + .schemaSetName('my-schemaset') + .pagination(0, maxPageSize + 1) + .build() + when: 'search is executed' + objectUnderTest.searchNetworkData(searchCriteria) + + then: 'throws error' + thrown(ValidationException) + + } + } -- cgit 1.2.3-korg