/**
* Copyright 2013 the original author or authors.
*
* 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 io.neba.core.resourcemodels.caching;
import io.neba.core.util.Key;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.FilterChain;
import javax.servlet.ServletResponse;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
* @author Olaf Otto
*/
@RunWith(MockitoJUnitRunner.class)
public class RequestScopedResourceModelCacheTest {
@Mock
private Resource resource;
@Mock
private SlingHttpServletRequest request;
@Mock
private RequestPathInfo requestPathInfo;
@Mock
private ServletResponse response;
@Mock
private FilterChain chain;
@Mock
private Logger logger;
private Object model = new Object();
private Class<?> modelType = Object.class;
private Object cachedModel;
@InjectMocks
private RequestScopedResourceModelCache testee;
@Before
public void setUp() throws Exception {
doReturn("GET").when(this.request).getMethod();
doReturn(this.requestPathInfo).when(this.request).getRequestPathInfo();
}
@Test
public void testLookupOfModel() throws Exception {
request(() -> {
withResourcePath("/junit/test/1");
lookupModelFromCache();
assertModelIsNotInCache();
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testLookupOfDifferentResourcePaths() throws Exception {
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
withResourcePath("/junit/test/2");
lookupModelFromCache();
assertModelIsNotInCache();
withResourcePath("/junit/test/1");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsNotSelectorSensitiveWithoutSafeMode() throws Exception {
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withSelector("new.selector");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsNotSuffixSensitiveWithoutSafeMode() throws Exception {
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withSuffix("/newSuffix");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsNotPagePathSensitiveWithoutSafeMode() throws Exception {
request(() -> {
withPath("/old/path");
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withPath("/new/path");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsNotSensitiveToQueryStringWithoutSafeMode() throws Exception {
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withQueryString("new=parameter");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsSelectorSensitiveInSafeMode() throws Exception {
request(() -> {
withSafeMode();
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withSelector("new.selector");
lookupModelFromCache();
assertModelIsNotInCache();
return null;
});
}
@Test
public void testCacheIsSuffixSensitiveInSafeMode() throws Exception {
request(() -> {
withSafeMode();
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withSuffix("/newSuffix");
lookupModelFromCache();
assertModelIsNotInCache();
return null;
});
}
@Test
public void testCacheIsPagePathSensitiveInSafeMode() throws Exception {
request(() -> {
withSafeMode();
withPath("/old/path");
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withPath("/new/path");
lookupModelFromCache();
assertModelIsNotInCache();
return null;
});
}
@Test
public void testCacheIsNotSensitiveToPathChangesBelowCurrentPageInSafeMode() throws Exception {
request(() -> {
withSafeMode();
withPath("/a/page");
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withPath("/a/page/jcr:content/parsys");
lookupModelFromCache();
assertModelWasFoundInCache();
return null;
});
}
@Test
public void testCacheIsSensitiveToQueryStringInSafeMode() throws Exception {
request(() -> {
withSafeMode();
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
assertModelWasFoundInCache();
withQueryString("new=parameter");
lookupModelFromCache();
assertModelIsNotInCache();
return null;
});
}
@Test
public void testCacheGracefullyHandlesMissingRequestContextDuringCacheWrite() throws Exception {
withoutRequestAttributes();
putModelInCache();
}
@Test
public void testCacheGracefullyHandlesMissingRequestContextDuringCacheRead() throws Exception {
withoutRequestAttributes();
lookupModelFromCache();
assertModelIsNotInCache();
}
@Test
public void testStatisticsEnabledForAnyRequestUri() throws Exception {
enableStatistics();
withRequestUri("/arbitrary/request/uri.html");
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
lookupModelFromCache();
lookupModelFromCache();
return null;
});
verifyStatisticsReportLogs("Request scoped cache report for GET /arbitrary/request/uri.html:\n" +
"Hits: 2, misses: 0, writes: 1, total number of items: 1\n" +
"Key {/junit/test/1, class java.lang.Object}: misses=0, hits=2, writes=1\n");
verifyNothingElseIsReported();
}
@Test
public void testStatisticsEnabledForSpecificRequestUri() throws Exception {
withStatisticsRestrictedTo("/junit/test/2");
withRequestUri("/junit/test/1.html");
enableStatistics();
request(() -> {
withResourcePath("/junit/test/1");
putModelInCache();
return null;
});
verifyNothingIsReported();
withRequestUri("/junit/test/2.html");
request(() -> {
withResourcePath("/junit/test/2");
putModelInCache();
return null;
});
verifyStatisticsReportLogs("Request scoped cache report for GET /junit/test/2.html:\n" +
"Hits: 0, misses: 0, writes: 1, total number of items: 1\n" +
"Key {/junit/test/2, class java.lang.Object}: misses=0, hits=0, writes=1\n");
verifyNothingElseIsReported();
}
private void verifyNothingIsReported() {
verify(this.logger, never()).info(anyString());
}
private void withStatisticsRestrictedTo(String urlFragment) {
this.testee.setRestrictStatisticsTo(urlFragment);
}
private void verifyNothingElseIsReported() {
verifyNoMoreInteractions(logger);
}
private void verifyStatisticsReportLogs(String msg) {
verify(logger).info(msg);
}
private void enableStatistics() {
this.testee.setEnableStatistics(true);
}
private void withSafeMode() {
this.testee.setSafeMode(true);
}
private void withQueryString(String queryString) {
when(this.request.getQueryString()).thenReturn(queryString);
}
private void withPath(String path) {
when(this.requestPathInfo.getResourcePath()).thenReturn(path);
}
private void withSuffix(String suffix) {
when(this.requestPathInfo.getSuffix()).thenReturn(suffix);
}
private void withSelector(String selectorString) {
when(this.requestPathInfo.getSelectorString()).thenReturn(selectorString);
}
private void assertModelWasFoundInCache() {
assertThat(this.cachedModel).isEqualTo(this.model);
}
private void putModelInCache() {
testee.put(this.resource, this.model, new Key(this.resource.getPath(), this.modelType));
}
private void assertModelIsNotInCache() {
assertThat(this.cachedModel).isNull();
}
private void lookupModelFromCache() throws Exception {
cachedModel = testee.get(new Key(this.resource.getPath(), this.modelType));
}
private void withResourcePath(String path) {
doReturn(path).when(this.resource).getPath();
}
private void withRequestUri(String uri) {
doReturn(uri).when(this.request).getRequestURI();
}
private void request(final Callable<Object> callable) throws Exception {
doAnswer(invocationOnMock -> callable.call()).when(this.chain).doFilter(eq(this.request), eq(this.response));
this.testee.doFilter(this.request, this.response, this.chain);
}
private void withoutRequestAttributes() {
RequestContextHolder.setRequestAttributes(null);
}
}