blob: 49322485c4755adfac675740957781a92f091560 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
import com.google.inject.Key;
import com.google.inject.util.Providers;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
public class AllRequestFilterFilterProxyTest {
/**
* Set of filters for FilterProxy
*
* <p>This set is used to as set of filters when fetching an {@link AllRequestFilter.FilterProxy}
* instance through {@link #getFilterProxy()}.
*/
private DynamicSet<AllRequestFilter> filters;
@Before
public void setUp() throws Exception {
// Force starting each test with an initially empty set of filters.
// Filters get added by the tests themselves.
filters = new DynamicSet<>();
}
// The wrapping of {@link #getFilterProxy()} and
// {@link #addFilter(AllRequestFilter)} into separate methods may seem
// overengineered at this point. However, if in the future we decide to not
// test the inner class directly, but rather test from the outside using
// Guice Injectors, it is now sufficient to change only those two methods,
// and we need not mess with the individual tests.
/**
* Obtain a FilterProxy with a known DynamicSet of filters
*
* <p>The returned {@link AllRequestFilter.FilterProxy} can have new filters added dynamically by
* calling {@link #addFilter(AllRequestFilter)}.
*/
private AllRequestFilter.FilterProxy getFilterProxy() {
return new AllRequestFilter.FilterProxy(filters);
}
/**
* Add a filter to created FilterProxy instances
*
* <p>This method adds the given filter to all {@link AllRequestFilter.FilterProxy} instances
* created by {@link #getFilterProxy()}.
*/
private ReloadableRegistrationHandle<AllRequestFilter> addFilter(AllRequestFilter filter) {
Key<AllRequestFilter> key = Key.get(AllRequestFilter.class);
return filters.add("gerrit", key, Providers.of(filter));
}
@Test
public void noFilters() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
verify(chain).doFilter(req, res);
}
@Test
public void singleFilterNoBubbling() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
AllRequestFilter filter = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filter);
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
InOrder inorder = inOrder(filter);
inorder.verify(filter).init(config);
inorder.verify(filter).doFilter(eq(req), eq(res), any(FilterChain.class));
inorder.verify(filter).destroy();
}
@Test
public void singleFilterBubbling() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChain = ArgumentCaptor.forClass(FilterChain.class);
AllRequestFilter filter = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filter);
InOrder inorder = inOrder(filter, chain);
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
inorder.verify(filter).init(config);
inorder.verify(filter).doFilter(eq(req), eq(res), capturedChain.capture());
capturedChain.getValue().doFilter(req, res);
inorder.verify(chain).doFilter(req, res);
filterProxy.destroy();
inorder.verify(filter).destroy();
}
@Test
public void twoFiltersNoBubbling() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
AllRequestFilter filterA = mock(AllRequestFilter.class);
AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
addFilter(filterB);
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
InOrder inorder = inOrder(filterA, filterB);
inorder.verify(filterA).init(config);
inorder.verify(filterB).init(config);
inorder.verify(filterA).doFilter(eq(req), eq(res), any(FilterChain.class));
inorder.verify(filterA).destroy();
inorder.verify(filterB).destroy();
}
@Test
public void twoFiltersBubbling() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainA = ArgumentCaptor.forClass(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainB = ArgumentCaptor.forClass(FilterChain.class);
AllRequestFilter filterA = mock(AllRequestFilter.class);
AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
addFilter(filterB);
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
InOrder inorder = inOrder(filterA, filterB, chain);
inorder.verify(filterA).init(config);
inorder.verify(filterB).init(config);
inorder.verify(filterA).doFilter(eq(req), eq(res), capturedChainA.capture());
capturedChainA.getValue().doFilter(req, res);
inorder.verify(filterB).doFilter(eq(req), eq(res), capturedChainB.capture());
capturedChainB.getValue().doFilter(req, res);
inorder.verify(chain).doFilter(req, res);
filterProxy.destroy();
inorder.verify(filterA).destroy();
inorder.verify(filterB).destroy();
}
@Test
public void postponedLoading() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req1 = new FakeHttpServletRequest();
HttpServletRequest req2 = new FakeHttpServletRequest();
HttpServletResponse res1 = new FakeHttpServletResponse();
HttpServletResponse res2 = new FakeHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainA1 = ArgumentCaptor.forClass(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainA2 = ArgumentCaptor.forClass(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainB = ArgumentCaptor.forClass(FilterChain.class);
AllRequestFilter filterA = mock(AllRequestFilter.class);
AllRequestFilter filterB = mock(AllRequestFilter.class);
InOrder inorder = inOrder(filterA, filterB, chain);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
filterProxy.init(config);
filterProxy.doFilter(req1, res1, chain);
inorder.verify(filterA).init(config);
inorder.verify(filterA).doFilter(eq(req1), eq(res1), capturedChainA1.capture());
capturedChainA1.getValue().doFilter(req1, res1);
inorder.verify(chain).doFilter(req1, res1);
addFilter(filterB); // <-- Adds filter after filterProxy's init got called.
filterProxy.doFilter(req2, res2, chain);
// after filterProxy's init finished. Nonetheless filterB gets initialized.
inorder.verify(filterA).doFilter(eq(req2), eq(res2), capturedChainA2.capture());
capturedChainA2.getValue().doFilter(req2, res2);
inorder.verify(filterB).init(config); // <-- This is crucial part. filterB got loaded
inorder.verify(filterB).doFilter(eq(req2), eq(res2), capturedChainB.capture());
capturedChainB.getValue().doFilter(req2, res2);
inorder.verify(chain).doFilter(req2, res2);
filterProxy.destroy();
inorder.verify(filterA).destroy();
inorder.verify(filterB).destroy();
}
@Test
public void dynamicUnloading() throws Exception {
FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req1 = new FakeHttpServletRequest();
HttpServletRequest req2 = new FakeHttpServletRequest();
HttpServletRequest req3 = new FakeHttpServletRequest();
HttpServletResponse res1 = new FakeHttpServletResponse();
HttpServletResponse res2 = new FakeHttpServletResponse();
HttpServletResponse res3 = new FakeHttpServletResponse();
Plugin plugin = mock(Plugin.class);
FilterChain chain = mock(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainA1 = ArgumentCaptor.forClass(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainB1 = ArgumentCaptor.forClass(FilterChain.class);
ArgumentCaptor<FilterChain> capturedChainB2 = ArgumentCaptor.forClass(FilterChain.class);
AllRequestFilter filterA = mock(AllRequestFilter.class);
AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
ReloadableRegistrationHandle<AllRequestFilter> handleFilterA = addFilter(filterA);
ReloadableRegistrationHandle<AllRequestFilter> handleFilterB = addFilter(filterB);
InOrder inorder = inOrder(filterA, filterB, chain);
filterProxy.init(config);
inorder.verify(filterA).init(config);
inorder.verify(filterB).init(config);
// Request #1 with filterA and filterB
filterProxy.doFilter(req1, res1, chain);
inorder.verify(filterA).doFilter(eq(req1), eq(res1), capturedChainA1.capture());
capturedChainA1.getValue().doFilter(req1, res1);
inorder.verify(filterB).doFilter(eq(req1), eq(res1), capturedChainB1.capture());
capturedChainB1.getValue().doFilter(req1, res1);
inorder.verify(chain).doFilter(req1, res1);
// Unloading filterA
handleFilterA.remove();
filterProxy.onStopPlugin(plugin);
inorder.verify(filterA).destroy(); // Cleaning up of filterA after it got unloaded
// Request #2 only with filterB
filterProxy.doFilter(req2, res2, chain);
inorder.verify(filterB).doFilter(eq(req2), eq(res2), capturedChainB2.capture());
inorder.verify(filterA, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
capturedChainB2.getValue().doFilter(req2, res2);
inorder.verify(chain).doFilter(req2, res2);
// Unloading filterB
handleFilterB.remove();
filterProxy.onStopPlugin(plugin);
inorder.verify(filterB).destroy(); // Cleaning up of filterA after it got unloaded
// Request #3 with no additional filters
filterProxy.doFilter(req3, res3, chain);
inorder.verify(chain).doFilter(req3, res3);
inorder.verify(filterA, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
inorder.verify(filterB, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
filterProxy.destroy();
}
}