a789a66f6e3365b14396d783e611db2b064f1dd3
[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-2024 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.ServletRequest;
39 import jakarta.servlet.ServletResponse;
40 import jakarta.ws.rs.Consumes;
41 import jakarta.ws.rs.POST;
42 import jakarta.ws.rs.Path;
43 import jakarta.ws.rs.Produces;
44 import jakarta.ws.rs.core.MediaType;
45 import jakarta.ws.rs.core.Response;
46 import java.io.IOException;
47 import java.io.PrintWriter;
48 import java.net.HttpURLConnection;
49 import java.net.URL;
50 import java.nio.charset.StandardCharsets;
51 import java.util.Arrays;
52 import java.util.Base64;
53 import java.util.List;
54 import java.util.Properties;
55 import lombok.Getter;
56 import org.apache.commons.io.IOUtils;
57 import org.junit.AfterClass;
58 import org.junit.Before;
59 import org.junit.BeforeClass;
60 import org.junit.Test;
61 import org.mockito.ArgumentCaptor;
62 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
63 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactory;
64 import org.onap.policy.common.endpoints.http.server.JsonExceptionMapper;
65 import org.onap.policy.common.endpoints.http.server.RestServer;
66 import org.onap.policy.common.endpoints.http.server.RestServer.Factory;
67 import org.onap.policy.common.endpoints.http.server.YamlExceptionMapper;
68 import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
69 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
70 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
71 import org.onap.policy.common.gson.GsonMessageBodyHandler;
72 import org.onap.policy.common.utils.coder.StandardCoder;
73 import org.onap.policy.common.utils.network.NetworkUtil;
74 import org.springframework.test.util.ReflectionTestUtils;
75
76 public class RestServerTest {
77     private static final String METRICS_URI = "/metrics";
78     private static final String SERVER1 = "my-server-A";
79     private static final String SERVER2 = "my-server-B";
80     private static final String FACTORY_FIELD = "factory";
81     private static final String HOST = "my-host";
82     private static final String PARAM_NAME = "my-param";
83     private static final String PASS = "my-pass";
84     private static final Integer PORT = 9876;
85     private static final String USER = "my-user";
86
87     private static Factory saveFactory;
88     private static RestServer realRest;
89     private static int realPort;
90     private static RestServerParameters params;
91
92     private RestServer rest;
93     private HttpServletServer server1;
94     private HttpServletServer server2;
95     private Factory factory;
96     private HttpServletServerFactory serverFactory;
97     private String errorMsg;
98
99     /**
100      * Starts the REST server.
101      * @throws Exception if an error occurs
102      */
103     @BeforeClass
104     public static void setUpBeforeClass() throws Exception {
105         saveFactory = (Factory) ReflectionTestUtils.getField(RestServer.class, FACTORY_FIELD);
106
107         realPort = NetworkUtil.allocPort();
108
109         initRealParams();
110
111         realRest = new RestServer(params, RealProvider.class) {
112             @Override
113             protected Properties getServerProperties(RestServerParameters restServerParameters, String names) {
114                 Properties props = super.getServerProperties(restServerParameters, names);
115
116                 String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "."
117                                 + restServerParameters.getName();
118                 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
119
120                 return props;
121             }
122         };
123
124         realRest.start();
125         assertTrue(NetworkUtil.isTcpPortOpen(params.getHost(), params.getPort(), 100, 100));
126     }
127
128     /**
129      * Restores the factory and stops the REST server.
130      */
131     @AfterClass
132     public static void tearDownAfterClass() {
133         ReflectionTestUtils.setField(RestServer.class, FACTORY_FIELD, saveFactory);
134
135         realRest.stop();
136     }
137
138     /**
139      * Initializes mocks.
140      */
141     @Before
142     public void setUp() {
143         server1 = mock(HttpServletServer.class);
144         server2 = mock(HttpServletServer.class);
145         factory = mock(Factory.class);
146         serverFactory = mock(HttpServletServerFactory.class);
147
148         initParams();
149
150         when(factory.getServerFactory()).thenReturn(serverFactory);
151         when(serverFactory.build(any())).thenReturn(Arrays.asList(server1, server2));
152
153         when(server1.getName()).thenReturn(SERVER1);
154         when(server2.getName()).thenReturn(SERVER2);
155
156         ReflectionTestUtils.setField(RestServer.class, FACTORY_FIELD, factory);
157     }
158
159     @Test
160     public void testRestServer() {
161         rest = new RestServer(params, Filter2.class, Provider1.class, Provider2.class);
162
163         rest.start();
164         verify(server1).start();
165         verify(server2).start();
166
167         rest.stop();
168         verify(server1).stop();
169         verify(server2).stop();
170     }
171
172     @Test
173     public void testRestServerListList() {
174         rest = new RestServer(params, List.of(Filter2.class), List.of(Provider1.class, Provider2.class));
175
176         rest.start();
177         verify(server1).start();
178         verify(server2).start();
179
180         rest.stop();
181         verify(server1).stop();
182         verify(server2).stop();
183     }
184
185     @Test
186     public void testRestServer_MissingProviders() {
187         assertThatIllegalArgumentException().isThrownBy(() -> new RestServer(params, List.of(Filter2.class), null));
188     }
189
190     @Test
191     public void testGetServerProperties_testGetProviderNames() {
192         rest = new RestServer(params, Provider1.class, Provider2.class);
193
194         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
195         verify(serverFactory).build(cap.capture());
196
197         Properties props = cap.getValue();
198         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
199
200         assertEquals(HOST, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX));
201         assertEquals(String.valueOf(PORT),
202                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX));
203         assertEquals(Provider1.class.getName() + "," + Provider2.class.getName(),
204                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX));
205         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX));
206         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX));
207         assertEquals(USER, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_USERNAME_SUFFIX));
208         assertEquals(PASS, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_PASSWORD_SUFFIX));
209         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HTTPS_SUFFIX));
210         assertEquals(String.join(",", GsonMessageBodyHandler.class.getName(), YamlMessageBodyHandler.class.getName(),
211                         JsonExceptionMapper.class.getName(), YamlExceptionMapper.class.getName()),
212                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER));
213         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
214     }
215
216     @Test
217     public void testExplicitPrometheusAddedToProperty() {
218         when(params.isPrometheus()).thenReturn(true);
219         rest = new RestServer(params, Filter2.class, Provider1.class, Provider2.class);
220         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
221         verify(serverFactory).build(cap.capture());
222
223         Properties props = cap.getValue();
224         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
225
226         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
227         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX)).isBlank();
228         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX)).isBlank();
229     }
230
231     @Test
232     public void testStandardServletAddedToProperty() {
233         when(params.getServletUriPath()).thenReturn("/metrics");
234         when(params.getServletClass()).thenReturn(MetricsServlet.class.getName());
235         rest = new RestServer(params, Filter2.class, Provider1.class, Provider2.class);
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("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
243         assertEquals(METRICS_URI,
244             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX));
245         assertEquals(MetricsServlet.class.getName(),
246             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX));
247     }
248
249     @Test
250     public void testInvalidJson() throws Exception {
251         initRealParams();
252
253         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest())));
254         assertEquals(400, roundTrip("{'bogus-json'"));
255         assertThat(errorMsg).contains("Invalid request");
256     }
257
258     @Test
259     public void testInvalidYaml() throws Exception {
260         initRealParams();
261
262         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest()),
263                         YamlMessageBodyHandler.APPLICATION_YAML));
264         assertEquals(400, roundTrip("<bogus yaml", YamlMessageBodyHandler.APPLICATION_YAML));
265         assertThat(errorMsg).contains("Invalid request");
266     }
267
268     private int roundTrip(String request) throws IOException {
269         return roundTrip(request, MediaType.APPLICATION_JSON);
270     }
271
272     private int roundTrip(String request, String mediaType) throws IOException {
273         URL url = new URL("http://" + params.getHost() + ":" + params.getPort() + "/request");
274         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
275         conn.setDoInput(true);
276         conn.setDoOutput(true);
277         conn.setRequestMethod("POST");
278         String auth = params.getUserName() + ":" + params.getPassword();
279         conn.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()));
280         conn.setRequestProperty("Content-type", mediaType);
281         conn.setRequestProperty("Accept", mediaType);
282         conn.connect();
283
284         try (PrintWriter wtr = new PrintWriter(conn.getOutputStream())) {
285             wtr.write(request);
286         }
287
288         int code = conn.getResponseCode();
289
290         if (code == 200) {
291             errorMsg = "";
292         } else {
293             errorMsg = IOUtils.toString(conn.getErrorStream(), StandardCharsets.UTF_8);
294         }
295
296         return code;
297     }
298
299     @Test
300     public void testToString() {
301         rest = new RestServer(params, Filter2.class, Provider1.class, Provider2.class);
302         assertNotNull(rest.toString());
303     }
304
305     @Test
306     public void testFactory() {
307         assertNotNull(saveFactory);
308         assertNotNull(saveFactory.getServerFactory());
309     }
310
311     private static void initRealParams() {
312         initParams();
313
314         when(params.getHost()).thenReturn("localhost");
315         when(params.getPort()).thenReturn(realPort);
316         when(params.isHttps()).thenReturn(false);
317         when(params.isAaf()).thenReturn(false);
318     }
319
320     private static void initParams() {
321         params = mock(RestServerParameters.class);
322
323         when(params.getHost()).thenReturn(HOST);
324         when(params.getName()).thenReturn(PARAM_NAME);
325         when(params.getPassword()).thenReturn(PASS);
326         when(params.getPort()).thenReturn(PORT);
327         when(params.getUserName()).thenReturn(USER);
328         when(params.isAaf()).thenReturn(true);
329         when(params.isHttps()).thenReturn(true);
330     }
331
332     private static class Filter2 implements jakarta.servlet.Filter {
333         @Override
334         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
335             // do nothing
336         }
337     }
338
339     private static class Provider1 {
340         private Provider1() {
341             // do nothing
342         }
343     }
344
345     private static class Provider2 {
346         private Provider2() {
347             // do nothing
348         }
349     }
350
351     @Path("/")
352     @Produces({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
353     @Consumes({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
354     public static class RealProvider {
355         @POST
356         @Path("/request")
357         public Response decision(MyRequest body) {
358             return Response.status(Response.Status.OK).entity(new MyResponse()).build();
359         }
360     }
361
362     @Getter
363     public static class MyRequest {
364         private String data;
365     }
366
367     @Getter
368     public static class MyResponse {
369         private String text = "hello";
370     }
371 }