2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2021-2022 Bell Canada.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.temporal.controller.rest
23 import org.onap.cps.temporal.controller.utils.DateTimeUtility
24 import com.fasterxml.jackson.databind.ObjectMapper
25 import org.onap.cps.temporal.domain.Operation
27 import java.time.OffsetDateTime
28 import org.onap.cps.temporal.controller.rest.model.AnchorDetails
29 import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapperImpl
30 import org.onap.cps.temporal.controller.rest.model.AnchorHistory
31 import org.onap.cps.temporal.controller.rest.model.ErrorMessage
32 import org.onap.cps.temporal.controller.rest.model.SortMapper
33 import org.onap.cps.temporal.domain.NetworkData
34 import org.onap.cps.temporal.domain.SearchCriteria
35 import org.onap.cps.temporal.service.NetworkDataService
36 import org.spockframework.spring.SpringBean
37 import org.springframework.beans.factory.annotation.Autowired
38 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
39 import org.springframework.context.annotation.Import
40 import org.springframework.data.domain.PageRequest
41 import org.springframework.data.domain.SliceImpl
42 import org.springframework.data.domain.Sort
43 import org.springframework.http.HttpStatus
44 import org.springframework.http.MediaType
45 import org.springframework.security.test.context.support.WithMockUser
46 import org.springframework.test.web.servlet.MockMvc
47 import spock.lang.Specification
48 import spock.lang.Shared
50 @WebMvcTest(QueryController)
51 @Import([SortMapper, AnchorDetailsMapperImpl])
53 class QueryControllerSpec extends Specification {
56 NetworkDataService mockNetworkDataService = Mock()
61 def myDataspace = 'my-dataspace'
63 def myAnchor = 'my-anchor'
65 def mySchemaset = 'my-schemaset'
67 def objectMapper = new ObjectMapper()
70 def observedDescSortOrder = new Sort.Order(Sort.Direction.DESC, 'observed_timestamp')
72 def anchorAscSortOrder = new Sort.Order(Sort.Direction.ASC, 'anchor')
74 def 'Get #endpointName: default values if missing'() {
76 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName,
77 [dataspace: myDataspace] << urlSpecifParams)
78 given: 'network data to be returned'
79 def networkData = createNetworkData()
80 when: 'endpoint is called without pageNumber, pageLimit, sort and pointInTime'
81 def requestBuilder = controllerDataBuilder.
82 createMockHttpRequestBuilder();
83 def response = mvc.perform(requestBuilder).andReturn().response
84 then: 'pageNumber, pageSize and sort has default values'
86 def expectedPageable = PageRequest.of(0, 1000,
87 Sort.by(Sort.Order.desc('observed_timestamp')))
88 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> {
89 SearchCriteria searchCriteria ->
90 assert searchCriteria.getPageable() == expectedPageable
91 assert searchCriteria.getObservedAfter() == null
92 assert searchCriteria.getCreatedBefore().isAfter(OffsetDateTime.now().minusMinutes(2))
93 return new SliceImpl([networkData], searchCriteria.getPageable(), false)
97 response.getStatus() == HttpStatus.OK.value()
98 def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
99 and: 'content has expected values'
100 anchorHistory.getPreviousRecordsLink() == null
101 anchorHistory.getNextRecordsLink() == null
102 anchorHistory.getRecords() == List.of(toAnchorDetails(networkData))
104 endpointName | urlSpecifParams
105 'anchor by name' | [anchor: myAnchor]
106 'anchors by schemaset' | [schemaSet: mySchemaset]
109 def 'Get #endpointName: query data #scenario'() {
110 def inputParameters = [
111 dataspace : myDataspace,
112 pointInTime : '2021-07-24T01:00:01.000-0400',
113 pageNumber : 2, pageLimit: 10,
114 sortAsString: 'observed_timestamp:desc']
115 inputParameters << urlSpecifParams
116 inputParameters << parameters
117 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, inputParameters)
119 def searchCriteria = controllerDataBuilder.createSearchCriteriaBuilder().build()
120 def networkData = createNetworkData()
121 mockNetworkDataService.searchNetworkData(searchCriteria) >> new SliceImpl<NetworkData>(
122 List.of(networkData), searchCriteria.getPageable(), true)
123 when: 'endpoint is called with all parameters'
124 def requestBuilder = controllerDataBuilder.createMockHttpRequestBuilder()
125 def response = mvc.perform(requestBuilder
126 .contentType(MediaType.APPLICATION_JSON)).andReturn().response
127 def responseBody = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
129 response.getStatus() == HttpStatus.OK.value()
130 and: 'next and previous record links have expected value'
131 controllerDataBuilder.isExpectedNextRecordsLink(responseBody.getNextRecordsLink())
132 controllerDataBuilder.isExpectedPreviousRecordsLink(responseBody.getPreviousRecordsLink())
133 and: 'has expected network data records'
134 responseBody.getRecords().size() == 1
135 responseBody.getRecords() == [toAnchorDetails(networkData)]
137 scenario | endpointName | urlSpecifParams | parameters
138 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: null]
139 'with observedTimestampAfter and without payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null]
140 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello+world"}']
141 'with observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}']
142 'without observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: null]
143 'with observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null]
144 'without observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello world"}']
145 'with observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}']
148 def 'Get #endpointName: Sort by #sortAsString'() {
149 given: 'sort parameters'
150 def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams
151 when: 'endpoint is called'
152 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
153 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
154 .andReturn().response
155 then: 'network data service is called with expected sort'
156 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> {
157 SearchCriteria searchCriteria ->
158 assert searchCriteria.getPageable().getSort() == expectedSort
159 return new SliceImpl([], searchCriteria.getPageable(), true)
161 and: 'response is ok'
162 response.getStatus() == HttpStatus.OK.value()
163 def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
164 and: 'content has expected values'
165 controllerDataBuilder.isExpectedNextRecordsLink(anchorHistory.getNextRecordsLink())
166 anchorHistory.getPreviousRecordsLink() == null
168 endpointName | uriSpecificParams | sortAsString || expectedSort
169 'anchor by name' | [anchor: myAnchor] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder)
170 'anchor by name' | [anchor: myAnchor] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder)
171 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder)
172 'anchors by schemaset' | [schemaSet: mySchemaset] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder)
175 def 'Get #endpointName Error handling: invalid date format in #queryParamName '() {
176 given: 'sort parameters'
177 def parameters = [dataspace: myDataspace] << uriSpecificParams
178 parameters[queryParamName] = 'invalid-date-string'
179 when: 'endpoint is called'
180 QueryControllerDataBuilder dataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
181 def response = mvc.perform(dataBuilder.createMockHttpRequestBuilder())
182 .andReturn().response
183 then: 'received bad request status'
184 response.getStatus() == HttpStatus.BAD_REQUEST.value()
186 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
187 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
188 errorMessage.getMessage().contains(queryParamName)
189 errorMessage.getMessage().contains("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
191 endpointName | uriSpecificParams | queryParamName
192 'anchor by name' | [anchor: myAnchor] | 'pointInTime'
193 'anchor by name' | [anchor: myAnchor] | 'observedTimestampAfter'
194 'anchors by schemaset' | [schemaSet: mySchemaset] | 'pointInTime'
195 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observedTimestampAfter'
198 def 'Get #endpointName Error handling: invalid sort format #scenario'() {
199 given: 'sort parameters'
200 def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams
201 when: 'endpoint is called'
202 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
203 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
204 .andReturn().response
205 then: 'received bad request status'
206 response.getStatus() == HttpStatus.BAD_REQUEST.value()
208 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
209 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
210 errorMessage.getMessage().contains("sort")
211 errorMessage.getMessage().contains("'$sortAsString'")
212 errorMessage.getMessage().contains('<fieldname>:<direction>,...,<fieldname>:<direction>')
214 scenario | sortAsString | endpointName | uriSpecificParams
215 'missing direction' | 'observed_timestamp' | 'anchor by name' | [anchor: myAnchor]
216 'missing separator' | 'observed_timestampdesc' | 'anchor by name' | [anchor: myAnchor]
217 'missing direction' | 'observed_timestamp' | 'anchors by schemaset' | [schemaSet: mySchemaset]
218 'missing separator' | 'observed_timestampdesc' | 'anchors by schemaset' | [schemaSet: mySchemaset]
221 def 'Get #endpointName Error handling: invalid simple payload filter '() {
222 given: 'payload filter parameters'
223 def parameters = [dataspace: myDataspace, payloadFilter: 'invalid-json'] << uriSpecificParams
224 when: 'endpoint is called'
225 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
226 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
227 .andReturn().response
228 then: 'received bad request status'
229 response.getStatus() == HttpStatus.BAD_REQUEST.value()
231 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
232 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
233 errorMessage.getMessage().contains('simplePayloadFilter')
234 where: 'endpoints are provided'
235 endpointName | uriSpecificParams
236 'anchor by name' | [anchor: myAnchor]
237 'anchors by schemaset' | [schemaSet: mySchemaset]
240 NetworkData createNetworkData() {
241 return NetworkData.builder().dataspace(myDataspace)
242 .schemaSet(mySchemaset).anchor(myAnchor).payload('{"message" : "Hello World"}')
243 .observedTimestamp(OffsetDateTime.now())
244 .operation(Operation.CREATE)
245 .createdTimestamp(OffsetDateTime.now()).build()
248 AnchorDetails toAnchorDetails(NetworkData networkData) {
249 AnchorDetails anchorDetails = new AnchorDetails()
250 anchorDetails.setDataspace(networkData.getDataspace())
251 anchorDetails.setAnchor(networkData.getAnchor())
252 anchorDetails.setSchemaSet(networkData.getSchemaSet())
253 anchorDetails.setObservedTimestamp(DateTimeUtility.toString(networkData.getObservedTimestamp()))
254 anchorDetails.setOperation(AnchorDetails.OperationEnum.valueOf(networkData.getOperation().toString()))
255 anchorDetails.setData(networkData.getPayload())