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