/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* 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.
* #L%
*/
package io.wcm.wcm.commons.caching;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_CACHE_CONTROL;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_DISPATCHER;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_EXPIRES;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_IF_MODIFIED_SINCE;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_LAST_MODIFIED;
import static io.wcm.wcm.commons.caching.CacheHeader.HEADER_PRAGMA;
import static io.wcm.wcm.commons.caching.CacheHeader.formatDate;
import static io.wcm.wcm.commons.caching.ModificationDateTest.SAMPLE_CALENDAR_1;
import static io.wcm.wcm.commons.caching.ModificationDateTest.SAMPLE_CALENDAR_2;
import static io.wcm.wcm.commons.caching.ModificationDateTest.applyLastModified;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import com.day.cq.wcm.api.WCMMode;
@RunWith(MockitoJUnitRunner.class)
public class CacheHeaderTest {
@Mock
private Resource resource;
@Mock
private SlingHttpServletRequest request;
@Mock
private SlingHttpServletResponse response;
@Before
public void setUp() {
applyLastModified(resource, SAMPLE_CALENDAR_1);
}
@Test
public void testIsNotModified_WithoutIfModifiedSinceHeader_Publish() throws Exception {
when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.DISABLED);
assertFalse(CacheHeader.isNotModified(resource, request, response));
verify(response).setHeader(HEADER_LAST_MODIFIED, formatDate(SAMPLE_CALENDAR_1.getTime()));
verifyNoMoreInteractions(response);
}
@Test
public void testIsNotModified_WithoutIfModifiedSinceHeader_Author() throws Exception {
when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.EDIT);
assertFalse(CacheHeader.isNotModified(resource, request, response));
verify(response).setHeader(HEADER_LAST_MODIFIED, formatDate(SAMPLE_CALENDAR_1.getTime()));
verify(response).setHeader(HEADER_EXPIRES, "-1");
verifyNoMoreInteractions(response);
}
@Test
public void testIsNotModified_WithIfModifiedSinceHeader_Publish() throws Exception {
when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.DISABLED);
when(request.getHeader(HEADER_IF_MODIFIED_SINCE)).thenReturn(formatDate(SAMPLE_CALENDAR_2.getTime()));
assertTrue(CacheHeader.isNotModified(resource, request, response));
verify(response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
verifyNoMoreInteractions(response);
}
@Test
public void testIsNotModified_WithIfModifiedSinceHeader_Author() throws Exception {
when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.EDIT);
when(request.getHeader(HEADER_IF_MODIFIED_SINCE)).thenReturn(formatDate(SAMPLE_CALENDAR_2.getTime()));
assertTrue(CacheHeader.isNotModified(resource, request, response));
verify(response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
verifyNoMoreInteractions(response);
}
@Test
public void testSetNonCachingHeaders() throws Exception {
CacheHeader.setNonCachingHeaders(response);
verify(response).setHeader(HEADER_PRAGMA, "no-cache");
verify(response).setHeader(HEADER_CACHE_CONTROL, "no-cache");
verify(response).setHeader(HEADER_EXPIRES, "0");
verify(response).setHeader(HEADER_DISPATCHER, "no-cache");
verifyNoMoreInteractions(response);
}
@Test
public void testSetExpires() {
CacheHeader.setExpires(response, null);
verify(response).setHeader(CacheHeader.HEADER_EXPIRES, "-1");
Date date = new Date();
CacheHeader.setExpires(response, date);
verify(response).setHeader(CacheHeader.HEADER_EXPIRES, formatDate(date));
}
@Test
public void testSetExpiresSeconds() {
doAnswer(new ValidateDateHeaderAnswer(200 * DateUtils.MILLIS_PER_SECOND))
.when(response).setHeader(eq(CacheHeader.HEADER_EXPIRES), anyString());
CacheHeader.setExpiresSeconds(response, 200);
}
@Test
public void testSetExpiresHours() {
doAnswer(new ValidateDateHeaderAnswer(15 * DateUtils.MILLIS_PER_HOUR))
.when(response).setHeader(eq(CacheHeader.HEADER_EXPIRES), anyString());
CacheHeader.setExpiresHours(response, 15);
}
@Test
public void testSetExpiresDays() {
doAnswer(new ValidateDateHeaderAnswer(20 * DateUtils.MILLIS_PER_DAY))
.when(response).setHeader(eq(CacheHeader.HEADER_EXPIRES), anyString());
CacheHeader.setExpiresDays(response, 20);
}
/**
* Parses a expires date header value and checks against a diff to the current time with a tolerance +/- 1h, 5secs.
* (the 1h to avoid failing tests if the daylight saving time switch is during this period)
*/
private final class ValidateDateHeaderAnswer implements Answer {
private static final long TIMESPAN_DIFF_TOLERANCE_MILLISECONDS = DateUtils.MILLIS_PER_SECOND * 5
+ DateUtils.MILLIS_PER_HOUR; // add
private final long mTimespanMillisecondsFrom;
private final long mTimespanMillisecondsTo;
private final long mNow;
ValidateDateHeaderAnswer(long pTimespanMilliseconds) {
mTimespanMillisecondsFrom = pTimespanMilliseconds - TIMESPAN_DIFF_TOLERANCE_MILLISECONDS;
mTimespanMillisecondsTo = pTimespanMilliseconds + TIMESPAN_DIFF_TOLERANCE_MILLISECONDS;
mNow = Calendar.getInstance().getTimeInMillis();
}
@Override
public Object answer(InvocationOnMock pInvocation) throws ParseException {
String headerValue = pInvocation.getArguments()[1].toString();
Date dateValue = CacheHeader.parseDate(headerValue);
long diffMilliseconds = dateValue.getTime() - mNow;
if (diffMilliseconds < mTimespanMillisecondsFrom || diffMilliseconds > mTimespanMillisecondsTo) {
fail("time diff " + diffMilliseconds + "ms is not between "
+ mTimespanMillisecondsFrom + "ms and " + mTimespanMillisecondsTo + "ms, "
+ "header value is: " + headerValue + ", "
+ "date is: " + dateValue + ", "
+ "diff is: " + DurationFormatUtils.formatDurationWords(diffMilliseconds, true, true) + ", "
+ "now is: " + new Date(mNow));
}
return null;
}
}
}