2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2021 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 java.time.OffsetDateTime
26 import org.onap.cps.temporal.controller.rest.model.AnchorDetails
27 import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapperImpl
28 import org.onap.cps.temporal.controller.rest.model.AnchorHistory
29 import org.onap.cps.temporal.controller.rest.model.ErrorMessage
30 import org.onap.cps.temporal.controller.rest.model.SortMapper
31 import org.onap.cps.temporal.domain.NetworkData
32 import org.onap.cps.temporal.domain.SearchCriteria
33 import org.onap.cps.temporal.service.NetworkDataService
34 import org.spockframework.spring.SpringBean
35 import org.springframework.beans.factory.annotation.Autowired
36 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
37 import org.springframework.context.annotation.Import
38 import org.springframework.data.domain.PageRequest
39 import org.springframework.data.domain.SliceImpl
40 import org.springframework.data.domain.Sort
41 import org.springframework.http.HttpStatus
42 import org.springframework.http.MediaType
43 import org.springframework.security.test.context.support.WithMockUser
44 import org.springframework.test.web.servlet.MockMvc
45 import spock.lang.Specification
46 import spock.lang.Shared
48 @WebMvcTest(QueryController)
49 @Import([SortMapper, AnchorDetailsMapperImpl])
51 class QueryControllerSpec extends Specification {
54 NetworkDataService mockNetworkDataService = Mock()
59 def myDataspace = 'my-dataspace'
61 def myAnchor = 'my-anchor'
63 def mySchemaset = 'my-schemaset'
65 def objectMapper = new ObjectMapper()
68 def observedDescSortOrder = new Sort.Order(Sort.Direction.DESC, 'observed_timestamp')
70 def anchorAscSortOrder = new Sort.Order(Sort.Direction.ASC, 'anchor')
72 def 'Get #endpointName: default values if missing'() {
74 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName,
75 [dataspace: myDataspace] << urlSpecifParams)
76 given: 'network data to be returned'
77 def networkData = createNetworkData()
78 when: 'endpoint is called without pageNumber, pageLimit, sort and pointInTime'
79 def requestBuilder = controllerDataBuilder.
80 createMockHttpRequestBuilder();
81 def response = mvc.perform(requestBuilder).andReturn().response
82 then: 'pageNumber, pageSize and sort has default values'
84 def expectedPageable = PageRequest.of(0, 1000,
85 Sort.by(Sort.Order.desc('observed_timestamp')))
86 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> {
87 SearchCriteria searchCriteria ->
88 assert searchCriteria.getPageable() == expectedPageable
89 assert searchCriteria.getObservedAfter() == null
90 assert searchCriteria.getCreatedBefore().isAfter(OffsetDateTime.now().minusMinutes(2))
91 return new SliceImpl([networkData], searchCriteria.getPageable(), false)
95 response.getStatus() == HttpStatus.OK.value()
96 def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
97 and: 'content has expected values'
98 anchorHistory.getPreviousRecordsLink() == null
99 anchorHistory.getNextRecordsLink() == null
100 anchorHistory.getRecords() == List.of(toAnchorDetails(networkData))
102 endpointName | urlSpecifParams
103 'anchor by name' | [anchor: myAnchor]
104 'anchors by schemaset' | [schemaSet: mySchemaset]
107 def 'Get #endpointName: query data #scenario'() {
108 def inputParameters = [
109 dataspace : myDataspace,
110 pointInTime : '2021-07-24T01:00:01.000-0400',
111 pageNumber : 2, pageLimit: 10,
112 sortAsString: 'observed_timestamp:desc']
113 inputParameters << urlSpecifParams
114 inputParameters << parameters
115 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, inputParameters)
117 def searchCriteria = controllerDataBuilder.createSearchCriteriaBuilder().build()
118 def networkData = createNetworkData()
119 mockNetworkDataService.searchNetworkData(searchCriteria) >> new SliceImpl<NetworkData>(
120 List.of(networkData), searchCriteria.getPageable(), true)
121 when: 'endpoint is called with all parameters'
122 def requestBuilder = controllerDataBuilder.createMockHttpRequestBuilder()
123 def response = mvc.perform(requestBuilder
124 .contentType(MediaType.APPLICATION_JSON)).andReturn().response
125 def responseBody = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
127 response.getStatus() == HttpStatus.OK.value()
128 and: 'next and previous record links have expected value'
129 controllerDataBuilder.isExpectedNextRecordsLink(responseBody.getNextRecordsLink())
130 controllerDataBuilder.isExpectedPreviousRecordsLink(responseBody.getPreviousRecordsLink())
131 and: 'has expected network data records'
132 responseBody.getRecords().size() == 1
133 responseBody.getRecords() == [toAnchorDetails(networkData)]
135 scenario | endpointName | urlSpecifParams | parameters
136 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: null]
137 'with observedTimestampAfter and without payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null]
138 'without observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello+world"}']
139 'with observedTimestampAfter and with payloadFilter' | 'anchor by name' | [anchor: myAnchor] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}']
140 'without observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: null]
141 'with observedTimestampAfter and without payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000-0400', payloadFilter: null]
142 'without observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: null, payloadFilter: '{"message" : "hello world"}']
143 'with observedTimestampAfter and with payloadFilter' | 'anchors by schemaset' | [schemaSet: mySchemaset] | [observedTimestampAfter: '2021-07-24T03:00:01.000+0400', payloadFilter: '{"message" : "hello world"}']
146 def 'Get #endpointName: Sort by #sortAsString'() {
147 given: 'sort parameters'
148 def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams
149 when: 'endpoint is called'
150 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
151 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
152 .andReturn().response
153 then: 'network data service is called with expected sort'
154 1 * mockNetworkDataService.searchNetworkData(_ as SearchCriteria) >> {
155 SearchCriteria searchCriteria ->
156 assert searchCriteria.getPageable().getSort() == expectedSort
157 return new SliceImpl([], searchCriteria.getPageable(), true)
159 and: 'response is ok'
160 response.getStatus() == HttpStatus.OK.value()
161 def anchorHistory = objectMapper.readValue(response.getContentAsString(), AnchorHistory)
162 and: 'content has expected values'
163 controllerDataBuilder.isExpectedNextRecordsLink(anchorHistory.getNextRecordsLink())
164 anchorHistory.getPreviousRecordsLink() == null
166 endpointName | uriSpecificParams | sortAsString || expectedSort
167 'anchor by name' | [anchor: myAnchor] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder)
168 'anchor by name' | [anchor: myAnchor] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder)
169 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observed_timestamp:desc' || Sort.by(observedDescSortOrder)
170 'anchors by schemaset' | [schemaSet: mySchemaset] | 'anchor:asc,observed_timestamp:desc' || Sort.by(anchorAscSortOrder, observedDescSortOrder)
173 def 'Get #endpointName Error handling: invalid date format in #queryParamName '() {
174 given: 'sort parameters'
175 def parameters = [dataspace: myDataspace] << uriSpecificParams
176 parameters[queryParamName] = 'invalid-date-string'
177 when: 'endpoint is called'
178 QueryControllerDataBuilder dataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
179 def response = mvc.perform(dataBuilder.createMockHttpRequestBuilder())
180 .andReturn().response
181 then: 'received bad request status'
182 response.getStatus() == HttpStatus.BAD_REQUEST.value()
184 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
185 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
186 errorMessage.getMessage().contains(queryParamName)
187 errorMessage.getMessage().contains("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
189 endpointName | uriSpecificParams | queryParamName
190 'anchor by name' | [anchor: myAnchor] | 'pointInTime'
191 'anchor by name' | [anchor: myAnchor] | 'observedTimestampAfter'
192 'anchors by schemaset' | [schemaSet: mySchemaset] | 'pointInTime'
193 'anchors by schemaset' | [schemaSet: mySchemaset] | 'observedTimestampAfter'
196 def 'Get #endpointName Error handling: invalid sort format #scenario'() {
197 given: 'sort parameters'
198 def parameters = [dataspace: myDataspace, sortAsString: sortAsString] << uriSpecificParams
199 when: 'endpoint is called'
200 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
201 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
202 .andReturn().response
203 then: 'received bad request status'
204 response.getStatus() == HttpStatus.BAD_REQUEST.value()
206 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
207 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
208 errorMessage.getMessage().contains("sort")
209 errorMessage.getMessage().contains("'$sortAsString'")
210 errorMessage.getMessage().contains('<fieldname>:<direction>,...,<fieldname>:<direction>')
212 scenario | sortAsString | endpointName | uriSpecificParams
213 'missing direction' | 'observed_timestamp' | 'anchor by name' | [anchor: myAnchor]
214 'missing separator' | 'observed_timestampdesc' | 'anchor by name' | [anchor: myAnchor]
215 'missing direction' | 'observed_timestamp' | 'anchors by schemaset' | [schemaSet: mySchemaset]
216 'missing separator' | 'observed_timestampdesc' | 'anchors by schemaset' | [schemaSet: mySchemaset]
219 def 'Get #endpointName Error handling: invalid simple payload filter '() {
220 given: 'payload filter parameters'
221 def parameters = [dataspace: myDataspace, payloadFilter: 'invalid-json'] << uriSpecificParams
222 when: 'endpoint is called'
223 def controllerDataBuilder = new QueryControllerDataBuilder(endpointName, parameters)
224 def response = mvc.perform(controllerDataBuilder.createMockHttpRequestBuilder())
225 .andReturn().response
226 then: 'received bad request status'
227 response.getStatus() == HttpStatus.BAD_REQUEST.value()
229 def errorMessage = objectMapper.readValue(response.getContentAsString(), ErrorMessage)
230 errorMessage.getStatus() == HttpStatus.BAD_REQUEST.value().toString()
231 errorMessage.getMessage().contains('simplePayloadFilter')
232 where: 'endpoints are provided'
233 endpointName | uriSpecificParams
234 'anchor by name' | [anchor: myAnchor]
235 'anchors by schemaset' | [schemaSet: mySchemaset]
238 NetworkData createNetworkData() {
239 return NetworkData.builder().dataspace(myDataspace)
240 .schemaSet(mySchemaset).anchor(myAnchor).payload('{"message" : "Hello World"}')
241 .observedTimestamp(OffsetDateTime.now())
242 .createdTimestamp(OffsetDateTime.now()).build()
245 AnchorDetails toAnchorDetails(NetworkData networkData) {
246 AnchorDetails anchorDetails = new AnchorDetails()
247 anchorDetails.setDataspace(networkData.getDataspace())
248 anchorDetails.setAnchor(networkData.getAnchor())
249 anchorDetails.setSchemaSet(networkData.getSchemaSet())
250 anchorDetails.setObservedTimestamp(DateTimeUtility.toString(networkData.getObservedTimestamp()))
251 anchorDetails.setData(networkData.getPayload())