216c4fd96579693c3bf8bf3386d357ff01bb939f
[policy/common.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
7  * Modifications Copyright (C) 2023 Nordix Foundation.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.common.endpoints.http.server.test;
24
25 import static org.assertj.core.api.Assertions.assertThat;
26 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.assertTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35
36 import io.prometheus.client.servlet.jakarta.exporter.MetricsServlet;
37 import jakarta.servlet.FilterChain;
38 import jakarta.servlet.ServletException;
39 import jakarta.servlet.ServletRequest;
40 import jakarta.servlet.ServletResponse;
41 import jakarta.servlet.http.HttpServletRequest;
42 import jakarta.ws.rs.Consumes;
43 import jakarta.ws.rs.POST;
44 import jakarta.ws.rs.Path;
45 import jakarta.ws.rs.Produces;
46 import jakarta.ws.rs.core.MediaType;
47 import jakarta.ws.rs.core.Response;
48 import java.io.IOException;
49 import java.io.PrintWriter;
50 import java.net.HttpURLConnection;
51 import java.net.URL;
52 import java.nio.charset.StandardCharsets;
53 import java.util.Arrays;
54 import java.util.Base64;
55 import java.util.List;
56 import java.util.Properties;
57 import lombok.Getter;
58 import org.apache.commons.io.IOUtils;
59 import org.junit.AfterClass;
60 import org.junit.Before;
61 import org.junit.BeforeClass;
62 import org.junit.Test;
63 import org.mockito.ArgumentCaptor;
64 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
65 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactory;
66 import org.onap.policy.common.endpoints.http.server.JsonExceptionMapper;
67 import org.onap.policy.common.endpoints.http.server.RestServer;
68 import org.onap.policy.common.endpoints.http.server.RestServer.Factory;
69 import org.onap.policy.common.endpoints.http.server.YamlExceptionMapper;
70 import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
71 import org.onap.policy.common.endpoints.http.server.aaf.AafAuthFilter;
72 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
73 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
74 import org.onap.policy.common.gson.GsonMessageBodyHandler;
75 import org.onap.policy.common.utils.coder.StandardCoder;
76 import org.onap.policy.common.utils.network.NetworkUtil;
77 import org.springframework.test.util.ReflectionTestUtils;
78
79 public class RestServerTest {
80     private static final String METRICS_URI = "/metrics";
81     private static final String SERVER1 = "my-server-A";
82     private static final String SERVER2 = "my-server-B";
83     private static final String FACTORY_FIELD = "factory";
84     private static final String HOST = "my-host";
85     private static final String PARAM_NAME = "my-param";
86     private static final String PASS = "my-pass";
87     private static final Integer PORT = 9876;
88     private static final String USER = "my-user";
89
90     private static Factory saveFactory;
91     private static RestServer realRest;
92     private static int realPort;
93     private static RestServerParameters params;
94
95     private RestServer rest;
96     private HttpServletServer server1;
97     private HttpServletServer server2;
98     private Factory factory;
99     private HttpServletServerFactory serverFactory;
100     private String errorMsg;
101
102     /**
103      * Starts the REST server.
104      * @throws Exception if an error occurs
105      */
106     @BeforeClass
107     public static void setUpBeforeClass() throws Exception {
108         saveFactory = (Factory) ReflectionTestUtils.getField(RestServer.class, FACTORY_FIELD);
109
110         realPort = NetworkUtil.allocPort();
111
112         initRealParams();
113
114         realRest = new RestServer(params, (Class<AafAuthFilter>) null, RealProvider.class) {
115             @Override
116             protected Properties getServerProperties(RestServerParameters restServerParameters, String names) {
117                 Properties props = super.getServerProperties(restServerParameters, names);
118
119                 String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "."
120                                 + restServerParameters.getName();
121                 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
122
123                 return props;
124             }
125         };
126
127         realRest.start();
128         assertTrue(NetworkUtil.isTcpPortOpen(params.getHost(), params.getPort(), 100, 100));
129     }
130
131     /**
132      * Restores the factory and stops the REST server.
133      */
134     @AfterClass
135     public static void tearDownAfterClass() {
136         ReflectionTestUtils.setField(RestServer.class, FACTORY_FIELD, saveFactory);
137
138         realRest.stop();
139     }
140
141     /**
142      * Initializes mocks.
143      */
144     @Before
145     public void setUp() {
146         server1 = mock(HttpServletServer.class);
147         server2 = mock(HttpServletServer.class);
148         factory = mock(Factory.class);
149         serverFactory = mock(HttpServletServerFactory.class);
150
151         initParams();
152
153         when(factory.getServerFactory()).thenReturn(serverFactory);
154         when(serverFactory.build(any())).thenReturn(Arrays.asList(server1, server2));
155
156         when(server1.getName()).thenReturn(SERVER1);
157         when(server2.getName()).thenReturn(SERVER2);
158
159         ReflectionTestUtils.setField(RestServer.class, FACTORY_FIELD, factory);
160     }
161
162     @Test
163     public void testRestServer() {
164         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
165
166         rest.start();
167         verify(server1).start();
168         verify(server2).start();
169
170         rest.stop();
171         verify(server1).stop();
172         verify(server2).stop();
173     }
174
175     @Test
176     public void testRestServerListList() {
177         rest = new RestServer(params, List.of(Filter.class, Filter2.class), List.of(Provider1.class, Provider2.class));
178
179         rest.start();
180         verify(server1).start();
181         verify(server2).start();
182
183         rest.stop();
184         verify(server1).stop();
185         verify(server2).stop();
186     }
187
188     @Test
189     public void testRestServer_NoAaf() {
190         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
191         verify(server1, never()).addFilterClass(any(), any());
192         verify(server2, never()).addFilterClass(any(), any());
193     }
194
195     @Test
196     public void testRestServer_OnlyOneAaf() {
197         when(server2.isAaf()).thenReturn(true);
198
199         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
200
201         verify(server1, never()).addFilterClass(any(), any());
202         verify(server2).addFilterClass(null, Filter.class.getName());
203     }
204
205     @Test
206     public void testRestServer_BothAaf() {
207         when(server1.isAaf()).thenReturn(true);
208         when(server2.isAaf()).thenReturn(true);
209
210         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
211
212         verify(server1).addFilterClass(null, Filter.class.getName());
213         verify(server2).addFilterClass(null, Filter.class.getName());
214     }
215
216     @Test
217     public void testRestServer_BothAaf_NoFilter() {
218         when(server1.isAaf()).thenReturn(true);
219         when(server2.isAaf()).thenReturn(true);
220
221         rest = new RestServer(params, (Class<AafAuthFilter>) null, Provider1.class, Provider2.class);
222
223         verify(server1, never()).addFilterClass(any(), any());
224         verify(server2, never()).addFilterClass(any(), any());
225     }
226
227     @Test
228     public void testRestServer_MissingProviders() {
229         assertThatIllegalArgumentException().isThrownBy(() -> new RestServer(params, Filter.class));
230     }
231
232     @Test
233     public void testGetServerProperties_testGetProviderNames() {
234         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
235
236         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
237         verify(serverFactory).build(cap.capture());
238
239         Properties props = cap.getValue();
240         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
241
242         assertEquals(HOST, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX));
243         assertEquals(String.valueOf(PORT),
244                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX));
245         assertEquals(Provider1.class.getName() + "," + Provider2.class.getName(),
246                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX));
247         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX));
248         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX));
249         assertEquals(USER, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_USERNAME_SUFFIX));
250         assertEquals(PASS, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_PASSWORD_SUFFIX));
251         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HTTPS_SUFFIX));
252         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_AAF_SUFFIX));
253         assertEquals(String.join(",", GsonMessageBodyHandler.class.getName(), YamlMessageBodyHandler.class.getName(),
254                         JsonExceptionMapper.class.getName(), YamlExceptionMapper.class.getName()),
255                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER));
256         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
257     }
258
259     @Test
260     public void testExplicitPrometheusAddedToProperty() {
261         when(params.isPrometheus()).thenReturn(true);
262         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
263         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
264         verify(serverFactory).build(cap.capture());
265
266         Properties props = cap.getValue();
267         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
268
269         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
270         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX)).isBlank();
271         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX)).isBlank();
272     }
273
274     @Test
275     public void testStandardSevletAddedToProperty() {
276         when(params.getServletUriPath()).thenReturn("/metrics");
277         when(params.getServletClass()).thenReturn(MetricsServlet.class.getName());
278         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
279         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
280         verify(serverFactory).build(cap.capture());
281
282         Properties props = cap.getValue();
283         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
284
285         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
286         assertEquals(METRICS_URI,
287             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX));
288         assertEquals(MetricsServlet.class.getName(),
289             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX));
290     }
291
292     @Test
293     public void testInvalidJson() throws Exception {
294         initRealParams();
295
296         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest())));
297         assertEquals(400, roundTrip("{'bogus-json'"));
298         assertThat(errorMsg).contains("Invalid request");
299     }
300
301     @Test
302     public void testInvalidYaml() throws Exception {
303         initRealParams();
304
305         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest()),
306                         YamlMessageBodyHandler.APPLICATION_YAML));
307         assertEquals(400, roundTrip("<bogus yaml", YamlMessageBodyHandler.APPLICATION_YAML));
308         assertThat(errorMsg).contains("Invalid request");
309     }
310
311     private int roundTrip(String request) throws IOException {
312         return roundTrip(request, MediaType.APPLICATION_JSON);
313     }
314
315     private int roundTrip(String request, String mediaType) throws IOException {
316         URL url = new URL("http://" + params.getHost() + ":" + params.getPort() + "/request");
317         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
318         conn.setDoInput(true);
319         conn.setDoOutput(true);
320         conn.setRequestMethod("POST");
321         String auth = params.getUserName() + ":" + params.getPassword();
322         conn.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()));
323         conn.setRequestProperty("Content-type", mediaType);
324         conn.setRequestProperty("Accept", mediaType);
325         conn.connect();
326
327         try (PrintWriter wtr = new PrintWriter(conn.getOutputStream())) {
328             wtr.write(request);
329         }
330
331         int code = conn.getResponseCode();
332
333         if (code == 200) {
334             errorMsg = "";
335         } else {
336             errorMsg = IOUtils.toString(conn.getErrorStream(), StandardCharsets.UTF_8);
337         }
338
339         return code;
340     }
341
342     @Test
343     public void testToString() {
344         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
345         assertNotNull(rest.toString());
346     }
347
348     @Test
349     public void testFactory() {
350         assertNotNull(saveFactory);
351         assertNotNull(saveFactory.getServerFactory());
352     }
353
354     private static void initRealParams() {
355         initParams();
356
357         when(params.getHost()).thenReturn("localhost");
358         when(params.getPort()).thenReturn(realPort);
359         when(params.isHttps()).thenReturn(false);
360         when(params.isAaf()).thenReturn(false);
361     }
362
363     private static void initParams() {
364         params = mock(RestServerParameters.class);
365
366         when(params.getHost()).thenReturn(HOST);
367         when(params.getName()).thenReturn(PARAM_NAME);
368         when(params.getPassword()).thenReturn(PASS);
369         when(params.getPort()).thenReturn(PORT);
370         when(params.getUserName()).thenReturn(USER);
371         when(params.isAaf()).thenReturn(true);
372         when(params.isHttps()).thenReturn(true);
373     }
374
375     private static class Filter extends AafAuthFilter {
376         @Override
377         protected String getPermissionType(HttpServletRequest request) {
378             return "";
379         }
380
381         @Override
382         protected String getPermissionInstance(HttpServletRequest request) {
383             return "";
384         }
385     }
386
387     private static class Filter2 implements jakarta.servlet.Filter {
388         @Override
389         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
390                         throws IOException, ServletException {
391             // do nothing
392         }
393     }
394
395     private static class Provider1 {
396         private Provider1() {
397             // do nothing
398         }
399     }
400
401     private static class Provider2 {
402         private Provider2() {
403             // do nothing
404         }
405     }
406
407     @Path("/")
408     @Produces({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
409     @Consumes({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
410     public static class RealProvider {
411         @POST
412         @Path("/request")
413         public Response decision(MyRequest body) {
414             return Response.status(Response.Status.OK).entity(new MyResponse()).build();
415         }
416     }
417
418     @Getter
419     public static class MyRequest {
420         private String data;
421     }
422
423     @Getter
424     public static class MyResponse {
425         private String text = "hello";
426     }
427 }