View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe;
12  
13  import static org.junit.jupiter.api.Assertions.assertEquals;
14  import static org.junit.jupiter.api.Assertions.assertNotNull;
15  import static org.junit.jupiter.api.Assertions.assertNull;
16  import static org.junit.jupiter.api.Assertions.assertTrue;
17  import static org.mockito.ArgumentMatchers.any;
18  import static org.mockito.ArgumentMatchers.eq;
19  import static org.mockito.Mockito.mock;
20  import static org.mockito.Mockito.times;
21  import static org.mockito.Mockito.verify;
22  import static org.mockito.Mockito.when;
23  
24  import jakarta.servlet.ServletContext;
25  
26  import java.io.InputStream;
27  import java.net.URI;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  import javax.management.MBeanServer;
34  import javax.management.ObjectName;
35  
36  import org.apache.catalina.Container;
37  import org.apache.catalina.Context;
38  import org.apache.catalina.Engine;
39  import org.apache.catalina.Host;
40  import org.apache.catalina.LifecycleState;
41  import org.apache.catalina.Pipeline;
42  import org.apache.catalina.Service;
43  import org.apache.catalina.Valve;
44  import org.apache.catalina.Wrapper;
45  import org.apache.catalina.connector.Connector;
46  import org.apache.jasper.JspCompilationContext;
47  import org.apache.jasper.Options;
48  import org.apache.jasper.compiler.JspRuntimeContext;
49  import org.junit.jupiter.api.Test;
50  
51  import psiprobe.model.ApplicationParam;
52  import psiprobe.model.ApplicationResource;
53  import psiprobe.model.FilterInfo;
54  import psiprobe.model.FilterMapping;
55  import psiprobe.model.jsp.Summary;
56  
57  /**
58   * Tests for {@link AbstractTomcatContainer}.
59   */
60  class AbstractTomcatContainerTest {
61  
62    private static class TestTomcatContainer extends AbstractTomcatContainer {
63  
64      private final Valve valve = mock(Valve.class);
65  
66      @Override
67      protected Object getNamingToken(Context context) {
68        return "token";
69      }
70  
71      @Override
72      protected JspCompilationContext createJspCompilationContext(String name, Options opt,
73          ServletContext sctx, JspRuntimeContext jrctx, ClassLoader classLoader) {
74        return null;
75      }
76  
77      @Override
78      protected Valve createValve() {
79        return valve;
80      }
81  
82      @Override
83      public boolean canBoundTo(String binding) {
84        return true;
85      }
86  
87      @Override
88      public List<FilterMapping> getApplicationFilterMaps(Context context) {
89        return List.of();
90      }
91  
92      @Override
93      public void addContextResource(Context context, List<ApplicationResource> resourceList) {
94        // no-op for tests
95      }
96  
97      @Override
98      public void addContextResourceLink(Context context, List<ApplicationResource> resourceList) {
99        // no-op for tests
100     }
101 
102     @Override
103     public List<FilterInfo> getApplicationFilters(Context context) {
104       return List.of();
105     }
106 
107     @Override
108     public List<ApplicationParam> getApplicationInitParams(Context context) {
109       return List.of();
110     }
111 
112     @Override
113     public boolean resourceExists(String name, Context context) {
114       return false;
115     }
116 
117     @Override
118     public InputStream getResourceStream(String name, Context context) {
119       return null;
120     }
121 
122     @Override
123     public Long[] getResourceAttributes(String name, Context context) {
124       return new Long[] {0L, 0L};
125     }
126   }
127 
128   @Test
129   void setWrapperInitializesContainerStateAndRemovesValveOnUnset() {
130     TestTomcatContainer container = new TestTomcatContainer();
131     Wrapper wrapper = mock(Wrapper.class);
132     Container parent = mock(Container.class);
133     Host host = mock(Host.class);
134     Engine engine = mock(Engine.class);
135     Service service = mock(Service.class);
136     Pipeline pipeline = mock(Pipeline.class);
137 
138     when(wrapper.getParent()).thenReturn(parent);
139     when(parent.getParent()).thenReturn(host);
140     when(host.getParent()).thenReturn(engine);
141     when(host.getPipeline()).thenReturn(pipeline);
142     when(host.getName()).thenReturn("localhost");
143     when(host.getAppBase()).thenReturn("webapps");
144     when(engine.getService()).thenReturn(service);
145     when(engine.getName()).thenReturn("Catalina");
146     when(service.findConnectors()).thenReturn(new Connector[] {mock(Connector.class)});
147 
148     container.setWrapper(wrapper);
149     assertEquals(1, container.findConnectors().size());
150 
151     container.setWrapper(null);
152     verify(pipeline, times(1)).addValve(any());
153     verify(pipeline, times(1)).removeValve(any());
154   }
155 
156   @Test
157   void formatContextNameAndFilenameHandleRootAndNestedPaths() {
158     TestTomcatContainer container = new TestTomcatContainer();
159 
160     assertNull(container.formatContextName(null));
161     assertEquals("", container.formatContextName("ROOT"));
162     assertEquals("", container.formatContextName("/ROOT"));
163     assertEquals("/app", container.formatContextName("app"));
164     assertEquals("##v1", container.formatContextName("/ROOT##v1"));
165 
166     assertNull(container.formatContextFilename(null));
167     assertEquals("ROOT", container.formatContextFilename("/"));
168     assertEquals("ROOT##v2", container.formatContextFilename("/##v2"));
169     assertEquals("a#b", container.formatContextFilename("/a/b"));
170   }
171 
172   @Test
173   void findContextFallsBackToSlashForRoot() {
174     TestTomcatContainer container = new TestTomcatContainer();
175     Host host = mock(Host.class);
176     Context rootContext = mock(Context.class);
177 
178     container.host = host;
179     when(host.findChild("")).thenReturn(null);
180     when(host.findChild("/")).thenReturn(rootContext);
181 
182     assertEquals(rootContext, container.findContext("/"));
183   }
184 
185   @Test
186   void getAppBaseSupportsRelativeAndAbsolutePaths() {
187     TestTomcatContainer container = new TestTomcatContainer();
188     Host host = mock(Host.class);
189 
190     container.host = host;
191 
192     String oldBase = System.getProperty("catalina.base");
193     try {
194       System.setProperty("catalina.base", "/tmp/catalina-base");
195 
196       when(host.getAppBase()).thenReturn("webapps");
197       assertEquals(Path.of("/tmp/catalina-base", "webapps").toString(),
198           container.getAppBase().toPath().toString());
199 
200       String absoluteAppBase =
201           Path.of(System.getProperty("java.io.tmpdir"), "opt-webapps").toAbsolutePath().toString();
202       when(host.getAppBase()).thenReturn(absoluteAppBase);
203       assertEquals(Path.of(absoluteAppBase).toString(), container.getAppBase().toPath().toString());
204     } finally {
205       if (oldBase != null) {
206         System.setProperty("catalina.base", oldBase);
207       } else {
208         System.clearProperty("catalina.base");
209       }
210     }
211   }
212 
213   @Test
214   void findContextsReturnsOnlyContextChildren() {
215     TestTomcatContainer container = new TestTomcatContainer();
216     Host host = mock(Host.class);
217     Context context = mock(Context.class);
218     Container other = mock(Container.class);
219 
220     container.host = host;
221     when(host.findChildren()).thenReturn(new Container[] {context, other});
222 
223     List<Context> contexts = container.findContexts();
224     assertEquals(1, contexts.size());
225     assertEquals(context, contexts.get(0));
226   }
227 
228   @Test
229   void stopAndStartOperateWhenContextExists() throws Exception {
230     TestTomcatContainer container = new TestTomcatContainer();
231     Host host = mock(Host.class);
232     Context context = mock(Context.class);
233 
234     container.host = host;
235     when(host.findChild("/app")).thenReturn(context);
236 
237     container.stop("/app");
238     container.start("/app");
239 
240     verify(context, times(1)).stop();
241     verify(context, times(1)).start();
242   }
243 
244   @Test
245   void installWarInvokesMbeanChangesFlow() throws Exception {
246     TestTomcatContainer container = new TestTomcatContainer();
247     MBeanServer mBeanServer = mock(MBeanServer.class);
248 
249     container.mbeanServer = mBeanServer;
250     container.objectNameDeployer = new ObjectName("Catalina:type=Deployer,host=localhost");
251 
252     when(mBeanServer.invoke(any(), eq("tryAddServiced"), any(), any())).thenReturn(Boolean.TRUE);
253     when(mBeanServer.invoke(any(), eq("check"), any(), any())).thenReturn(null);
254     when(mBeanServer.invoke(any(), eq("removeServiced"), any(), any())).thenReturn(null);
255 
256     container.installWar("/app");
257 
258     verify(mBeanServer, times(1)).invoke(any(), eq("check"), any(), any());
259     verify(mBeanServer, times(1)).invoke(any(), eq("removeServiced"), any(), any());
260   }
261 
262   @Test
263   void getConfigFileReturnsOnlyFileScheme() throws Exception {
264     TestTomcatContainer container = new TestTomcatContainer();
265     Context context = mock(Context.class);
266 
267     Path temp = Files.createTempFile("ctx", ".xml");
268     temp.toFile().deleteOnExit();
269     when(context.getConfigFile()).thenReturn(temp.toUri().toURL());
270     assertNotNull(container.getConfigFile(context));
271 
272     when(context.getConfigFile()).thenReturn(URI.create("https://example.com/context.xml").toURL());
273     assertNull(container.getConfigFile(context));
274   }
275 
276   @Test
277   void addFilterMappingSupportsUrlAndServletMappings() {
278     TestTomcatContainer container = new TestTomcatContainer();
279     List<FilterMapping> mappings = new ArrayList<>();
280 
281     container.addFilterMapping("f1", "REQUEST", "FilterClass", new String[] {"/a", "/b"}, mappings,
282         AbstractTomcatContainer.FilterMapType.URL);
283     container.addFilterMapping("f2", "REQUEST", "FilterClass2", new String[] {"s1"}, mappings,
284         AbstractTomcatContainer.FilterMapType.SERVLET_NAME);
285 
286     assertEquals(3, mappings.size());
287     assertEquals("/a", mappings.get(0).getUrl());
288     assertEquals("s1", mappings.get(2).getServletName());
289   }
290 
291   @Test
292   void getAvailableReflectsLifecycleState() {
293     TestTomcatContainer container = new TestTomcatContainer();
294     Context context = mock(Context.class);
295 
296     when(context.getState()).thenReturn(LifecycleState.STARTED);
297     assertTrue(container.getAvailable(context));
298   }
299 
300   @Test
301   void getNameAndHostNameUseHostHierarchy() {
302     TestTomcatContainer container = new TestTomcatContainer();
303     Host host = mock(Host.class);
304     Engine engine = mock(Engine.class);
305 
306     container.host = host;
307     when(host.getName()).thenReturn("localhost");
308     when(host.getParent()).thenReturn(engine);
309     when(engine.getName()).thenReturn("Catalina");
310 
311     assertEquals("localhost", container.getHostName());
312     assertEquals("Catalina", container.getName());
313   }
314 
315   @Test
316   void listContextJspsHandlesMissingJspServletWithoutThrowing() {
317     TestTomcatContainer container = new TestTomcatContainer();
318     Context context = mock(Context.class);
319     Summary summary = new Summary();
320 
321     when(context.findChild("jsp")).thenReturn(null);
322     when(context.getName()).thenReturn("/app");
323 
324     container.listContextJsps(context, summary, false);
325     assertNotNull(summary.getItems());
326     assertTrue(summary.getItems().isEmpty());
327   }
328 }