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 static org.junit.jupiter.api.Assertions.assertEquals; 27 import static org.junit.jupiter.api.Assertions.assertFalse; 28 import static org.junit.jupiter.api.Assertions.assertTrue; 29 import static org.mockito.Mockito.reset; 30 import static org.mockito.Mockito.verify; 31 import static org.opengrok.web.api.v1.controller.ApiUtils.waitForTask; 32 33 import java.util.concurrent.CountDownLatch; 34 35 import jakarta.servlet.http.HttpServletRequest; 36 import jakarta.ws.rs.client.Entity; 37 import jakarta.ws.rs.core.Response; 38 import org.glassfish.jersey.internal.inject.AbstractBinder; 39 import org.glassfish.jersey.server.ResourceConfig; 40 import org.glassfish.jersey.servlet.ServletContainer; 41 import org.glassfish.jersey.test.DeploymentContext; 42 import org.glassfish.jersey.test.ServletDeploymentContext; 43 import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; 44 import org.glassfish.jersey.test.spi.TestContainerException; 45 import org.glassfish.jersey.test.spi.TestContainerFactory; 46 import org.junit.jupiter.api.BeforeAll; 47 import org.junit.jupiter.api.Test; 48 import org.junit.jupiter.api.extension.ExtendWith; 49 import org.mockito.Mock; 50 import org.mockito.junit.jupiter.MockitoExtension; 51 import org.opengrok.indexer.configuration.Configuration; 52 import org.opengrok.indexer.configuration.RuntimeEnvironment; 53 import org.opengrok.indexer.web.DummyHttpServletRequest; 54 import org.opengrok.web.PageConfig; 55 import org.opengrok.web.api.ApiTaskManager; 56 import org.opengrok.web.api.v1.suggester.provider.service.SuggesterService; 57 58 @ExtendWith(MockitoExtension.class) 59 class ConfigurationControllerTest extends OGKJerseyTest { 60 61 private final RuntimeEnvironment env = RuntimeEnvironment.getInstance(); 62 63 @Mock 64 private SuggesterService suggesterService; 65 66 @BeforeAll setup()67 static void setup() { 68 ApiTaskManager.getInstance().addPool("configuration", 1); 69 } 70 71 @Override getTestContainerFactory()72 protected TestContainerFactory getTestContainerFactory() throws TestContainerException { 73 return new GrizzlyWebTestContainerFactory(); 74 } 75 76 @Override configureDeployment()77 protected DeploymentContext configureDeployment() { 78 return ServletDeploymentContext. 79 forServlet(new ServletContainer(new ResourceConfig(ConfigurationController.class) 80 .register(new AbstractBinder() { 81 @Override 82 protected void configure() { 83 bind(suggesterService).to(SuggesterService.class); 84 } 85 }))).build(); 86 } 87 88 @Test 89 void testApplySetAndGetBasicConfig() { 90 Configuration config = new Configuration(); 91 String srcRoot = "/foo"; 92 config.setSourceRoot(srcRoot); 93 94 String configStr = config.getXMLRepresentationAsString(); 95 96 Response response = target("configuration") 97 .request() 98 .put(Entity.xml(configStr)); 99 waitForTask(response); 100 101 assertEquals(srcRoot, env.getSourceRootPath()); 102 103 String returnedConfig = target("configuration") 104 .request() 105 .get(String.class); 106 107 assertEquals(configStr, returnedConfig); 108 } 109 110 @Test 111 void testApplySetInvalidMethod() { 112 Response r = setValue("noMethodExists", "1000"); 113 114 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 115 } 116 117 private Response setValue(final String field, final String value) { 118 Response response = target("configuration") 119 .path(field) 120 .request() 121 .put(Entity.text(value)); 122 123 return waitForTask(response); 124 } 125 126 @Test 127 void testApplyGetInvalidMethod() { 128 Response r = target("configuration") 129 .path("FooBar") 130 .request() 131 .get(); 132 133 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 134 } 135 136 @Test 137 void testApplySetInvalidMethodParameter() { 138 Response r = setValue("setDefaultProjects", "1000"); // expecting Set 139 140 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 141 } 142 143 @Test 144 void testApplySetOptionInteger() { 145 assertEquals(25, env.getHitsPerPage()); 146 147 setValue("hitsPerPage", "1000"); 148 149 assertEquals(1000, env.getHitsPerPage()); 150 151 env.setHitsPerPage(25); 152 } 153 154 @Test 155 void testApplySetOptionInvalidInteger() { 156 Response r = setValue("hitsPerPage", "abcd"); 157 158 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 159 } 160 161 @Test 162 void testApplySetOptionBooleanTrue() { 163 testSetChattyStatusPageTrue("true"); 164 testSetChattyStatusPageTrue("on"); 165 testSetChattyStatusPageTrue("1"); 166 } 167 168 private void testSetChattyStatusPageTrue(final String value) { 169 env.setChattyStatusPage(false); 170 171 setValue("chattyStatusPage", value); 172 173 assertTrue(env.isChattyStatusPage()); 174 } 175 176 @Test 177 void testApplySetOptionBooleanFalse() { 178 testSetChattyStatusPageFalse("false"); 179 testSetChattyStatusPageFalse("off"); 180 testSetChattyStatusPageFalse("0"); 181 } 182 183 private void testSetChattyStatusPageFalse(final String value) { 184 env.setChattyStatusPage(true); 185 186 setValue("chattyStatusPage", value); 187 188 assertFalse(env.isChattyStatusPage()); 189 } 190 191 @Test 192 void testApplySetOptionInvalidBoolean1() { 193 Response r = setValue("chattyStatusPage", "1000"); // only 1 is accepted as true 194 195 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 196 } 197 198 @Test 199 void testApplySetOptionInvalidBoolean2() { 200 Response r = setValue("chattyStatusPage", "anything"); 201 202 assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), r.getStatus()); 203 } 204 205 @Test 206 void testApplySetOptionString() { 207 String old = env.getUserPage(); 208 209 setValue("userPage", "http://users.portal.com?user="); 210 211 assertEquals("http://users.portal.com?user=", env.getUserPage()); 212 213 setValue("userPage", "some complicated \"string\" with &#~Đ`[đ\\ characters"); 214 215 assertEquals("some complicated \"string\" with &#~Đ`[đ\\ characters", env.getUserPage()); 216 217 env.setUserPage(old); 218 } 219 220 @Test 221 void testApplyGetOptionString() { 222 env.setSourceRoot("/foo/bar"); 223 String response = target("configuration") 224 .path("sourceRoot") 225 .request() 226 .get(String.class); 227 228 assertEquals(response, env.getSourceRootPath()); 229 } 230 231 @Test 232 void testApplyGetOptionInteger() { 233 int hitsPerPage = target("configuration") 234 .path("hitsPerPage") 235 .request() 236 .get(int.class); 237 238 assertEquals(env.getHitsPerPage(), hitsPerPage); 239 } 240 241 @Test 242 void testApplyGetOptionBoolean() { 243 boolean response = target("configuration") 244 .path("historyCache") 245 .request() 246 .get(boolean.class); 247 248 assertEquals(env.isHistoryCache(), response); 249 } 250 251 @Test 252 void testSuggesterServiceNotifiedOnConfigurationFieldChange() { 253 reset(suggesterService); 254 setValue("sourceRoot", "test"); 255 verify(suggesterService).refresh(); 256 } 257 258 @Test 259 void testSuggesterServiceNotifiedOnConfigurationChange() throws InterruptedException { 260 reset(suggesterService); 261 Response response = target("configuration") 262 .request() 263 .put(Entity.xml(new Configuration().getXMLRepresentationAsString())); 264 waitForTask(response); 265 verify(suggesterService).refresh(); 266 } 267 268 @Test 269 void testConfigValueSetVsThread() throws InterruptedException { 270 int origValue = env.getHitsPerPage(); 271 final int[] threadValue = new int[1]; 272 final CountDownLatch startLatch = new CountDownLatch(1); 273 final CountDownLatch endLatch = new CountDownLatch(1); 274 275 Thread thread = new Thread(() -> { 276 HttpServletRequest req = new DummyHttpServletRequest(); 277 PageConfig pageConfig = PageConfig.get(req); 278 RuntimeEnvironment e = pageConfig.getEnv(); 279 startLatch.countDown(); 280 // Wait for hint of termination, save the value and exit. 281 try { 282 endLatch.await(); 283 } catch (InterruptedException ex) { 284 ex.printStackTrace(); 285 } 286 threadValue[0] = e.getHitsPerPage(); 287 }); 288 289 thread.start(); 290 startLatch.await(); 291 292 // Set brand-new configuration. 293 int newValue = origValue + 42; 294 Configuration config = new Configuration(); 295 config.setHitsPerPage(newValue); 296 String configStr = config.getXMLRepresentationAsString(); 297 Response response = target("configuration") 298 .request() 299 .put(Entity.xml(configStr)); 300 waitForTask(response); 301 302 // Unblock the thread. 303 endLatch.countDown(); 304 thread.join(); 305 306 // Check thread's view of the variable. 307 assertEquals(newValue, threadValue[0]); 308 309 // Revert the value back to the default. 310 env.setHitsPerPage(origValue); 311 } 312 } 313