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 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