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