fa357e1fe4c27f5f076ec7f8bce1e0eecd5ea98d
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / test / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / RestClientServiceTest.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Copyright (C) 2019 Nordix Foundation
4  * Modifications Copyright © 2019 Huawei.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
20
21 import com.fasterxml.jackson.databind.JsonNode
22 import com.fasterxml.jackson.databind.ObjectMapper
23 import kotlinx.coroutines.CoroutineStart
24 import kotlinx.coroutines.async
25 import kotlinx.coroutines.runBlocking
26 import org.junit.After
27 import org.junit.Before
28 import org.junit.Test
29 import org.junit.runner.RunWith
30 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
31 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertyConfiguration
32 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BluePrintRestLibConfiguration
33 import org.springframework.beans.factory.annotation.Autowired
34 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
35 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
36 import org.springframework.boot.test.context.SpringBootTest
37 import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
38 import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory
39 import org.springframework.boot.web.server.WebServer
40 import org.springframework.context.annotation.Bean
41 import org.springframework.http.HttpMethod
42 import org.springframework.http.server.reactive.HttpHandler
43 import org.springframework.security.config.web.server.ServerHttpSecurity
44 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
45 import org.springframework.security.core.userdetails.User
46 import org.springframework.security.core.userdetails.UserDetails
47 import org.springframework.security.web.server.SecurityWebFilterChain
48 import org.springframework.test.context.ContextConfiguration
49 import org.springframework.test.context.TestPropertySource
50 import org.springframework.test.context.junit4.SpringRunner
51 import org.springframework.web.bind.annotation.DeleteMapping
52 import org.springframework.web.bind.annotation.GetMapping
53 import org.springframework.web.bind.annotation.PatchMapping
54 import org.springframework.web.bind.annotation.PostMapping
55 import org.springframework.web.bind.annotation.PutMapping
56 import org.springframework.web.bind.annotation.RequestHeader
57 import org.springframework.web.bind.annotation.RequestMapping
58 import org.springframework.web.bind.annotation.RestController
59 import kotlin.test.assertEquals
60 import kotlin.test.assertNotNull
61
62 @RunWith(SpringRunner::class)
63 @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class])
64 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
65 @ContextConfiguration(
66     classes = [BluePrintRestLibConfiguration::class, SampleController::class,
67         SecurityConfiguration::class,
68         BluePrintPropertyConfiguration::class, BluePrintPropertiesService::class]
69 )
70 @TestPropertySource(
71     properties =
72     [
73         "server.port=8443",
74         "server.ssl.enabled=true",
75         "server.ssl.key-store=classpath:keystore.p12",
76         "server.ssl.key-store-password=changeit",
77         "server.ssl.keyStoreType=PKCS12",
78         "server.ssl.keyAlias=tomcat",
79         "blueprintsprocessor.restclient.sample.type=basic-auth",
80         "blueprintsprocessor.restclient.sample.url=http://127.0.0.1:8081",
81         "blueprintsprocessor.restclient.sample.username=admin",
82         "blueprintsprocessor.restclient.sample.password=jans",
83         "blueprintsprocessor.restclient.test.type=ssl-basic-auth",
84         "blueprintsprocessor.restclient.test.url=https://localhost:8443",
85         "blueprintsprocessor.restclient.test.username=admin",
86         "blueprintsprocessor.restclient.test.password=jans",
87         "blueprintsprocessor.restclient.test.keyStoreInstance=PKCS12",
88         "blueprintsprocessor.restclient.test.sslTrust=src/test/resources/keystore.p12",
89         "blueprintsprocessor.restclient.test.sslTrustPassword=changeit"
90     ]
91 )
92 class RestClientServiceTest {
93
94     @Autowired
95     lateinit var bluePrintRestLibPropertyService: BluePrintRestLibPropertyService
96
97     @Autowired
98     lateinit var httpHandler: HttpHandler
99
100     lateinit var http: WebServer
101
102     fun localPort() = http.port
103
104     @Before
105     fun start() {
106         // Second Http server required for non-SSL requests to be processed along with the https server.
107         val factory: ReactiveWebServerFactory = NettyReactiveWebServerFactory(8081)
108         this.http = factory.getWebServer(this.httpHandler)
109         this.http.start()
110     }
111
112     @After
113     fun stop() {
114         this.http.stop()
115     }
116
117     @Test
118     fun testPatch() {
119         val restClientService = bluePrintRestLibPropertyService
120             .blueprintWebClientService("sample")
121         val response = restClientService.exchangeResource(
122             HttpMethod.PATCH.name, "/sample/name", ""
123         )
124         assertEquals(
125             "Patch request successful", response.body,
126             "failed to get patch response"
127         )
128     }
129
130     @Test
131     fun testBaseAuth() {
132         val restClientService = bluePrintRestLibPropertyService
133             .blueprintWebClientService("sample")
134         val headers = mutableMapOf<String, String>()
135         headers["X-Transaction-Id"] = "1234"
136         val response = restClientService.exchangeResource(
137             HttpMethod.GET.name,
138             "/sample/name", ""
139         )
140         assertNotNull(response.body, "failed to get response")
141     }
142
143     @Test
144     fun testSimpleBasicAuth() {
145         val json: String = "{\n" +
146                 "  \"type\" : \"basic-auth\",\n" +
147                 "  \"url\" : \"http://localhost:8081\",\n" +
148                 "  \"username\" : \"admin\",\n" +
149                 "  \"password\" : \"jans\"\n" +
150                 "}"
151         val mapper = ObjectMapper()
152         val actualObj: JsonNode = mapper.readTree(json)
153         val restClientService = bluePrintRestLibPropertyService
154             .blueprintWebClientService(actualObj)
155         lateinit var res: String
156         runBlocking {
157             val get = async(start = CoroutineStart.LAZY) {
158                 restClientService.exchangeNB(
159                     HttpMethod.GET.name,
160                     "/sample/basic", ""
161                 ).body
162             }
163             get.start()
164             res = get.await()
165         }
166         assertNotNull(res, "failed to get response")
167         assertEquals(res, "Basic request arrived successfully")
168     }
169
170     @Test
171     fun testSampleAaiReq() {
172         val restClientService = bluePrintRestLibPropertyService
173             .blueprintWebClientService("test")
174         val headers = mutableMapOf<String, String>()
175         headers["X-TransactionId"] = "9999"
176         headers["X-FromAppId"] = "AAI"
177         val post1 = "{\n" +
178                 "  \"customer\": {\n" +
179                 "    \"global-customer-id\": \"ONSDEMOBJHKCustomer\",\n" +
180                 "    \"subscriber-name\": \"ONSDEMOBJHKCustomer\",\n" +
181                 "    \"subscriber-type\": \"CUST\",\n" +
182                 "    \"resource-version\": \"1552985011163\"\n" +
183                 "  }\n" +
184                 "}"
185         lateinit var res1: Customer
186         lateinit var res2: Customer
187         lateinit var res3: String
188         lateinit var res4: String
189         lateinit var res5: String
190         lateinit var res6: String
191         runBlocking {
192             val get1 = async(start = CoroutineStart.LAZY) {
193                 restClientService.exchangeNB(
194                     HttpMethod.GET.name,
195                     "/sample/aai/v14/business/customers", "", headers,
196                     Customer::class.java
197                 ).body
198             }
199
200             val get2 = async(start = CoroutineStart.LAZY) {
201                 restClientService.exchangeNB(
202                     HttpMethod.GET.name,
203                     "/sample/aai/v14/business/customers", "", headers,
204                     Customer::class.java
205                 ).body
206             }
207
208             val post = async(start = CoroutineStart.LAZY) {
209                 restClientService.exchangeNB(
210                     HttpMethod.POST.name,
211                     "/sample/aai/v14/business/customers", post1, headers,
212                     String::class.java
213                 ).body
214             }
215
216             val put = async(start = CoroutineStart.LAZY) {
217                 restClientService.exchangeNB(
218                     HttpMethod.PUT.name,
219                     "/sample/aai/v14/business/customers", post1, headers,
220                     String::class.java
221                 ).body
222             }
223
224             val patch = async(start = CoroutineStart.LAZY) {
225                 restClientService.exchangeNB(
226                     HttpMethod.PATCH.name,
227                     "/sample/aai/v14/business/customers", post1, headers,
228                     String::class.java
229                 ).body
230             }
231
232             val delete = async(start = CoroutineStart.LAZY) {
233                 restClientService.exchangeNB(
234                     HttpMethod.DELETE.name,
235                     "/sample/aai/v14/business/customers", "", headers,
236                     String::class.java
237                 ).body
238             }
239
240             get1.start()
241             get2.start()
242             post.start()
243             put.start()
244             patch.start()
245             delete.start()
246             res1 = get1.await()
247             res2 = get2.await()
248             res3 = post.await()
249             res4 = put.await()
250             res5 = patch.await()
251             res6 = delete.await()
252         }
253         assertNotNull(res1, "failed to get response")
254         assertNotNull(res2, "failed to get response")
255         assertEquals(res1.id, "ONSDEMOBJHKCustomer")
256         assertEquals(res1.name, "ONSDEMOBJHKCustomer")
257         assertEquals(res1.type, "CUST")
258         assertEquals(res1.resource, "1552985011163")
259         assertEquals(res2.id, "ONSDEMOBJHKCustomer")
260         assertEquals(res2.name, "ONSDEMOBJHKCustomer")
261         assertEquals(res2.type, "CUST")
262         assertEquals(res2.resource, "1552985011163")
263         assertEquals(res3, "The message is successfully posted")
264         assertEquals(res4, "The put request is success")
265         assertEquals(res5, "The patch request is success")
266         assertEquals(res6, "The message is successfully deleted")
267     }
268 }
269
270 /**
271  * Sample controller code for testing both http and https requests.
272  */
273 @RestController
274 @RequestMapping("/sample")
275 open class SampleController {
276
277     @GetMapping("/name")
278     fun getName(): String = "Sample Controller"
279
280     @PatchMapping("/name")
281     fun patchName(): String = "Patch request successful"
282
283     @GetMapping("/basic")
284     fun getBasic(): String = "Basic request arrived successfully"
285
286     @GetMapping("/aai/v14/business/customers")
287     fun getAaiCustomers(
288         @RequestHeader(name = "X-TransactionId", required = true)
289         transId: String,
290         @RequestHeader(name = "X-FromAppId", required = true)
291         appId: String
292     ): String {
293         if (transId != "9999" || appId != "AAI") {
294             return ""
295         }
296         return "{\n" +
297                 "  \"id\": \"ONSDEMOBJHKCustomer\",\n" +
298                 "  \"name\": \"ONSDEMOBJHKCustomer\",\n" +
299                 "  \"type\": \"CUST\",\n" +
300                 "  \"resource\": \"1552985011163\"\n" +
301                 "}"
302     }
303
304     @PostMapping("/aai/v14/business/customers")
305     fun postAaiCustomers(
306         @RequestHeader(name = "X-TransactionId", required = true)
307         transId: String,
308         @RequestHeader(name = "X-FromAppId", required = true)
309         appId: String
310     ): String {
311         if (transId != "9999" || appId != "AAI") {
312             return ""
313         }
314         return "The message is successfully posted"
315     }
316
317     @PutMapping("/aai/v14/business/customers")
318     fun putAaiCustomers(
319         @RequestHeader(name = "X-TransactionId", required = true)
320         transId: String,
321         @RequestHeader(name = "X-FromAppId", required = true)
322         appId: String
323     ): String {
324         if (transId != "9999" || appId != "AAI") {
325             return ""
326         }
327         return "The put request is success"
328     }
329
330     @PatchMapping("/aai/v14/business/customers")
331     fun patchAaiCustomers(
332         @RequestHeader(name = "X-TransactionId", required = true)
333         transId: String,
334         @RequestHeader(name = "X-FromAppId", required = true)
335         appId: String
336     ): String {
337         if (transId != "9999" || appId != "AAI") {
338             return ""
339         }
340         return "The patch request is success"
341     }
342
343     @DeleteMapping("/aai/v14/business/customers")
344     fun deleteAaiCustomers(
345         @RequestHeader(name = "X-TransactionId", required = true)
346         transId: String,
347         @RequestHeader(name = "X-FromAppId", required = true)
348         appId: String
349     ): String {
350         if (transId != "9999" || appId != "AAI") {
351             return ""
352         }
353         return "The message is successfully deleted"
354     }
355 }
356
357 /**
358  * Security configuration required for basic authentication with username and
359  * password for any request in the server.
360  */
361 open class SecurityConfiguration {
362
363     @Bean
364     open fun userDetailsService(): MapReactiveUserDetailsService {
365         val user: UserDetails = User.withDefaultPasswordEncoder()
366             .username("admin")
367             .password("jans")
368             .roles("USER")
369             .build()
370         return MapReactiveUserDetailsService(user)
371     }
372
373     @Bean
374     open fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
375         return http
376             .csrf().disable()
377             .authorizeExchange().anyExchange().authenticated()
378             .and().httpBasic()
379             .and().build()
380     }
381 }
382
383 /**
384  * Data class required for response
385  */
386 data class Customer(
387     val id: String,
388     val name: String,
389     val type: String,
390     val resource: String
391 )