xref: /OpenGrok/opengrok-web/src/test/java/org/opengrok/web/api/v1/controller/MessagesControllerTest.java (revision 712a1733f06b0cf36128c401023d28622cd162d7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
23  */
24 package org.opengrok.web.api.v1.controller;
25 
26 import com.fasterxml.jackson.core.JsonProcessingException;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import jakarta.ws.rs.client.Entity;
29 import jakarta.ws.rs.core.Application;
30 import jakarta.ws.rs.core.GenericType;
31 import jakarta.ws.rs.core.MediaType;
32 import jakarta.ws.rs.core.Response;
33 import jakarta.ws.rs.core.UriBuilder;
34 import org.glassfish.grizzly.http.server.HttpServer;
35 import org.glassfish.jersey.client.ClientConfig;
36 import org.glassfish.jersey.client.ClientProperties;
37 import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
38 import org.glassfish.jersey.server.ResourceConfig;
39 import org.glassfish.jersey.test.DeploymentContext;
40 import org.glassfish.jersey.test.spi.TestContainer;
41 import org.glassfish.jersey.test.spi.TestContainerException;
42 import org.glassfish.jersey.test.spi.TestContainerFactory;
43 import org.junit.jupiter.api.AfterEach;
44 import org.junit.jupiter.api.BeforeEach;
45 import org.junit.jupiter.api.Test;
46 import org.opengrok.indexer.configuration.RuntimeEnvironment;
47 import org.opengrok.indexer.web.messages.Message;
48 import org.opengrok.indexer.web.messages.MessagesContainer;
49 import org.opengrok.indexer.web.messages.MessagesContainer.AcceptedMessage;
50 
51 import java.io.IOException;
52 import java.lang.reflect.Field;
53 import java.net.URI;
54 import java.time.Duration;
55 import java.util.Arrays;
56 import java.util.Collections;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Set;
60 
61 import static org.hamcrest.MatcherAssert.assertThat;
62 import static org.hamcrest.Matchers.contains;
63 import static org.junit.jupiter.api.Assertions.assertEquals;
64 import static org.junit.jupiter.api.Assertions.assertFalse;
65 import static org.junit.jupiter.api.Assertions.assertTrue;
66 
67 public class MessagesControllerTest extends OGKJerseyTest {
68 
69     private static final GenericType<List<AcceptedMessageModel>> messagesType = new GenericType<>() {
70     };
71 
72     private final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
73 
74     private static class AcceptedMessageModel {
75         public String created;
76         public String expiration;
77         public boolean expired;
78         public String text;
79         public String messageLevel;
80         public Set<String> tags;
81     }
82 
83     @Override
configure()84     protected Application configure() {
85         return new ResourceConfig(MessagesController.class);
86     }
87 
88     // Allow entity body for DELETE method on the client side.
89     @Override
configureClient(ClientConfig config)90     protected void configureClient(ClientConfig config) {
91         config.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
92     }
93 
94     // Allow entity body for DELETE method on the server side.
95     public static class CustomGrizzlyTestContainerFactory implements TestContainerFactory {
CustomGrizzlyTestContainerFactory()96         public CustomGrizzlyTestContainerFactory() {
97         }
98 
create(URI baseUri, DeploymentContext context)99         public TestContainer create(URI baseUri, DeploymentContext context) {
100             return new GrizzlyTestContainer(baseUri, context);
101         }
102 
103         private static class GrizzlyTestContainer implements TestContainer {
104             private URI baseUri;
105             private final HttpServer server;
106 
GrizzlyTestContainer(URI baseUri, DeploymentContext context)107             private GrizzlyTestContainer(URI baseUri, DeploymentContext context) {
108                 this.baseUri = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
109                 this.server = GrizzlyHttpServerFactory.createHttpServer(this.baseUri, context.getResourceConfig(), false);
110                 this.server.getServerConfiguration().setAllowPayloadForUndefinedHttpMethods(true);
111             }
112 
getClientConfig()113             public ClientConfig getClientConfig() {
114                 return null;
115             }
116 
getBaseUri()117             public URI getBaseUri() {
118                 return this.baseUri;
119             }
120 
start()121             public void start() {
122                 if (this.server.isStarted()) {
123                     return;
124                 }
125 
126                 try {
127                     this.server.start();
128                     if (this.baseUri.getPort() == 0) {
129                         this.baseUri = UriBuilder.fromUri(this.baseUri)
130                                 .port(this.server.getListener("grizzly").getPort())
131                                 .build();
132                     }
133                 } catch (IOException e) {
134                     throw new TestContainerException(e);
135                 }
136             }
137 
stop()138             public void stop() {
139                 if (this.server.isStarted()) {
140                     this.server.shutdownNow();
141                 }
142             }
143         }
144     }
145 
146     @Override
getTestContainerFactory()147     protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
148         return new CustomGrizzlyTestContainerFactory();
149     }
150 
151     @BeforeEach
setupMessageListener()152     public void setupMessageListener() throws Exception {
153         setMessageContainer(env, new MessagesContainer());
154         env.startExpirationTimer();
155     }
156 
157     @AfterEach
tearDownMessageListener()158     public void tearDownMessageListener() {
159         env.stopExpirationTimer();
160     }
161 
setMessageContainer(RuntimeEnvironment env, MessagesContainer container)162     private void setMessageContainer(RuntimeEnvironment env, MessagesContainer container) throws Exception {
163         Field f = RuntimeEnvironment.class.getDeclaredField("messagesContainer");
164         f.setAccessible(true);
165         f.set(env, container);
166     }
167 
168     @Test
addMessageTest()169     public void addMessageTest() {
170         addMessage("test message");
171 
172         assertFalse(env.getMessages().isEmpty());
173 
174         AcceptedMessage msg = env.getMessages().first();
175 
176         assertEquals("test&nbsp;message", msg.getMessage().getText());
177     }
178 
179     @Test
addMessageWithInvalidLevel()180     public void addMessageWithInvalidLevel() throws JsonProcessingException {
181         // Construct correct Message object first.
182         Message msg = new Message(
183                 "message with broken message level",
184                 Collections.singleton(MessagesContainer.MESSAGES_MAIN_PAGE_TAG),
185                 Message.MessageLevel.INFO,
186                 Duration.ofMinutes(10));
187 
188         // Convert it to JSON string and replace the messageLevel value.
189         ObjectMapper objectMapper = new ObjectMapper();
190         final String invalidMessageLevel = "invalid";
191         String msgAsString = objectMapper.writeValueAsString(msg);
192         msgAsString = msgAsString.replaceAll(Message.MessageLevel.INFO.toString(), invalidMessageLevel);
193         assertTrue(msgAsString.contains(invalidMessageLevel));
194 
195         // Finally, send the request as JSON string.
196         Response r = target("messages")
197                 .request(MediaType.APPLICATION_JSON_TYPE)
198                 .post(Entity.json(msgAsString));
199 
200         assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus());
201     }
202 
addMessage(String text, String... tags)203     private void addMessage(String text, String... tags) {
204         if (tags == null || tags.length == 0) {
205             tags = new String[] {MessagesContainer.MESSAGES_MAIN_PAGE_TAG};
206         }
207 
208         Message m = new Message(
209                 text,
210                 new HashSet<>(Arrays.asList(tags)),
211                 Message.MessageLevel.INFO,
212                 Duration.ofMinutes(10));
213 
214         target("messages")
215                 .request()
216                 .post(Entity.json(m));
217     }
218 
219     @Test
removeMessageTest()220     public void removeMessageTest() {
221         env.addMessage(new Message(
222                 "test",
223                 Collections.singleton(MessagesContainer.MESSAGES_MAIN_PAGE_TAG),
224                 Message.MessageLevel.INFO,
225                 Duration.ofMinutes(10)
226         ));
227 
228         assertFalse(env.getMessages().isEmpty());
229 
230         removeMessages(MessagesContainer.MESSAGES_MAIN_PAGE_TAG);
231 
232         assertTrue(env.getMessages().isEmpty());
233     }
234 
removeMessages(final String tag)235     private void removeMessages(final String tag) {
236         target("messages")
237                 .queryParam("tag", tag)
238                 .request()
239                 .delete();
240     }
241 
removeMessages(final String tag, final String text)242     private void removeMessages(final String tag, final String text) {
243         Entity<String> requestEntity = Entity.entity(text, MediaType.TEXT_PLAIN);
244         target("messages")
245                 .queryParam("tag", tag)
246                 .request()
247                 .build("DELETE", requestEntity).
248                 invoke();
249     }
250 
251     @Test
addAndRemoveTest()252     public void addAndRemoveTest() {
253         addMessage("test", "test");
254         addMessage("test", "test");
255 
256         assertEquals(2, env.getMessages("test").size());
257 
258         removeMessages("test");
259 
260         assertTrue(env.getMessages("test").isEmpty());
261     }
262 
263     @Test
addAndRemoveWithTextTest()264     public void addAndRemoveWithTextTest() {
265         final String tag = "foo";
266         final String text = "text";
267 
268         addMessage(text, tag);
269         assertEquals(1, env.getMessages(tag).size());
270 
271         removeMessages(tag + "bar", text);
272         assertEquals(1, env.getMessages(tag).size());
273 
274         removeMessages(tag, text + "bar");
275         assertEquals(1, env.getMessages(tag).size());
276 
277         removeMessages(tag, text);
278         assertTrue(env.getMessages(tag).isEmpty());
279     }
280 
281     @Test
addAndRemoveDifferentTagsTest()282     public void addAndRemoveDifferentTagsTest() {
283         addMessage("test", "tag1");
284         addMessage("test", "tag2");
285 
286         assertEquals(1, env.getMessages("tag1").size());
287         assertEquals(1, env.getMessages("tag2").size());
288 
289         removeMessages("tag1");
290 
291         assertEquals(0, env.getMessages("tag1").size());
292         assertEquals(1, env.getMessages("tag2").size());
293 
294         removeMessages("tag2");
295 
296         assertTrue(env.getMessages("tag2").isEmpty());
297     }
298 
299     @Test
addMessageNegativeDurationTest()300     public void addMessageNegativeDurationTest() throws Exception {
301         Message m = new Message("text",
302                 Collections.singleton("test"),
303                 Message.MessageLevel.INFO,
304                 Duration.ofMinutes(1));
305         setDuration(m, Duration.ofMinutes(-10));
306 
307         Response r = target("messages")
308                 .request()
309                 .post(Entity.json(m));
310 
311         assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus());
312     }
313 
setDuration(final Message m, final Duration duration)314     private void setDuration(final Message m, final Duration duration) throws Exception {
315         Field f = Message.class.getDeclaredField("duration");
316         f.setAccessible(true);
317         f.set(m, duration);
318     }
319 
320     @Test
addEmptyMessageTest()321     public void addEmptyMessageTest() throws Exception {
322         Message m = new Message("text",
323                 Collections.singleton("test"),
324                 Message.MessageLevel.INFO,
325                 Duration.ofMinutes(1));
326         setText(m, "");
327 
328         Response r = target("messages")
329                 .request()
330                 .post(Entity.json(m));
331 
332         assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus());
333     }
334 
setText(final Message m, final String text)335     private void setText(final Message m, final String text) throws Exception {
336         Field f = Message.class.getDeclaredField("text");
337         f.setAccessible(true);
338         f.set(m, text);
339     }
340 
341     @Test
getAllMessagesTest()342     public void getAllMessagesTest() {
343         addMessage("text1", "info");
344         addMessage("text2", "main");
345 
346         List<AcceptedMessageModel> allMessages = target("messages")
347                 .request()
348                 .get(messagesType);
349 
350         assertEquals(2, allMessages.size());
351     }
352 
353     @Test
getSpecificMessageTest()354     public void getSpecificMessageTest() {
355         addMessage("text", "info");
356 
357         List<AcceptedMessageModel> messages = target("messages")
358                 .queryParam("tag", "info")
359                 .request()
360                 .get(messagesType);
361 
362         assertEquals(1, messages.size());
363         assertEquals("text", messages.get(0).text);
364 
365         assertThat(messages.get(0).tags, contains("info"));
366     }
367 
368     @Test
multipleTagsTest()369     public void multipleTagsTest() {
370         addMessage("test", "info", "main");
371 
372         List<AcceptedMessageModel> allMessages = target("messages")
373                 .request()
374                 .get(messagesType);
375 
376         assertEquals(1, allMessages.size());
377     }
378 
379     @Test
multipleMessageAndTagsTest()380     public void multipleMessageAndTagsTest() {
381         addMessage("test1", "tag1", "tag2");
382         addMessage("test2", "tag3", "tag4");
383 
384         List<AcceptedMessageModel> allMessages = target("messages")
385                 .queryParam("tag", "tag3")
386                 .request()
387                 .get(messagesType);
388 
389         assertEquals(1, allMessages.size());
390     }
391 }
392