/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.shindig.gadgets.rewrite.image;
import static
org.apache.shindig.gadgets.rewrite.image.BasicImageRewriter.CONTENT_TYPE_AND_EXTENSION_MISMATCH;
import static
org.apache.shindig.gadgets.rewrite.image.BasicImageRewriter.CONTENT_TYPE_AND_MIME_MISMATCH;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.createControl;
import org.apache.commons.io.IOUtils;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.uri.UriCommon.Param;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.rewrite.ResponseRewriter;
import org.easymock.IMocksControl;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
/**
* Tests for the {@link ImageRewriter} class.
*/
public class ImageRewriterTest extends Assert {
/** The image used for the scaling tests, a head-shot of a white dog, 500pix x 500pix */
private static final String SCALE_IMAGE = "org/apache/shindig/gadgets/rewrite/image/dog.gif";
/** A 60 x 30 image whose size in bytes expands when resized to 120 x 60 */
private static final String EXPAND_IMAGE = "org/apache/shindig/gadgets/rewrite/image/expand.gif";
/** A 600 x 400 image whose size used to cause trouble with rounding when resizing to 171 x 171 */
private static final String RATIO_IMAGE = "org/apache/shindig/gadgets/rewrite/image/ratio.gif";
/**
* This image has a huge memory footprint that the rewriter should refuse to resize, but not
* refuse to render. The response containing this image should not be rewritten.
*/
private static final String HUGE_IMAGE = "org/apache/shindig/gadgets/rewrite/image/huge.gif";
private static final String CONTENT_TYPE_BOGUS = "notimage/anything";
private static final String CONTENT_TYPE_JPG = "image/jpeg";
private static final String CONTENT_TYPE_GIF = "image/gif";
private static final String CONTENT_TYPE_PNG = "image/png";
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final Uri IMAGE_URL = Uri.parse("http://www.example.com/image.gif");
private ResponseRewriter rewriter;
private IMocksControl mockControl;
@Before
public void setUp() throws Exception {
rewriter = new BasicImageRewriter(new OptimizerConfig());
mockControl = createControl();
}
/** Makes a new {@link HttpResponse} with an image content. */
private HttpResponseBuilder getImageResponse(String contentType, byte[] imageBytes) {
return new HttpResponseBuilder()
.setHeader(CONTENT_TYPE_HEADER, contentType)
.setHttpStatusCode(HttpResponse.SC_OK)
.setResponse(imageBytes);
}
/** Extracts an image by its resource name and converts it into a byte array. */
private byte[] getImageBytes(String imageResourceName) throws IOException {
ClassLoader classLoader = getClass().getClassLoader();
byte[] imageBytes = IOUtils.toByteArray(classLoader.getResourceAsStream(imageResourceName));
assertNotNull(imageBytes);
return imageBytes;
}
private BufferedImage getResizedHttpResponseContent(String sourceContentType,
String targetContentType, String imageName, Integer width, Integer height, Integer quality)
throws Exception {
return getResizedHttpResponseContent(
sourceContentType, targetContentType, imageName, width, height, quality, false);
}
private BufferedImage getResizedHttpResponseContent(String sourceContentType,
String targetContentType, String imageName, Integer width, Integer height, Integer quality,
boolean noExpand)
throws Exception {
HttpResponseBuilder response = getImageResponse(sourceContentType, getImageBytes(imageName));
HttpRequest request = getMockRequest(width, height, quality, noExpand);
mockControl.replay();
rewriter.rewrite(request, response);
mockControl.verify();
assertEquals(targetContentType, response.getHeader(CONTENT_TYPE_HEADER));
return ImageIO.read(response.getContentBytes());
}
private HttpRequest getMockRequest(Integer width, Integer height, Integer quality, boolean noExpand) {
HttpRequest request = mockControl.createMock(HttpRequest.class);
expect(request.getUri()).andReturn(IMAGE_URL);
expect(request.getParamAsInteger(Param.RESIZE_QUALITY.getKey())).andReturn(quality);
expect(request.getParamAsInteger(Param.RESIZE_WIDTH.getKey())).andReturn(width);
expect(request.getParamAsInteger(Param.RESIZE_HEIGHT.getKey())).andReturn(height);
expect(request.getParam(Param.NO_EXPAND.getKey())).andReturn(noExpand ? "1" : null).anyTimes();
return request;
}
@Test
public void testRewriteValidImageWithValidMimeAndExtn() throws Exception {
byte[] bytes = getImageBytes("org/apache/shindig/gadgets/rewrite/image/inefficient.png");
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_PNG, bytes);
int originalContentLength = response.getContentLength();
rewriter.rewrite(new HttpRequest(Uri.parse("some.png")), response);
assertEquals(HttpResponse.SC_OK, response.getHttpStatusCode());
assertTrue(response.getContentLength() < originalContentLength);
}
@Test
public void testRewriteValidImageWithInvalidMimeAndFileExtn() throws Exception {
byte[] bytes = getImageBytes("org/apache/shindig/gadgets/rewrite/image/inefficient.png");
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_BOGUS, bytes);
int originalContentLength = response.getContentLength();
rewriter.rewrite(new HttpRequest(Uri.parse("some.junk")), response);
assertEquals(HttpResponse.SC_OK, response.getHttpStatusCode());
assertEquals(response.getContentLength(), originalContentLength);
}
@Test
public void testRewriteInvalidImageContentWithValidMime() throws Exception {
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_PNG, "This is not a PNG".getBytes());
rewriter.rewrite(new HttpRequest(Uri.parse("some.junk")), response);
assertEquals(HttpResponse.SC_UNSUPPORTED_MEDIA_TYPE, response.getHttpStatusCode());
assertEquals(CONTENT_TYPE_AND_MIME_MISMATCH, response.create().getResponseAsString());
}
@Test
public void testRewriteInvalidImageContentWithValidFileExtn() throws Exception {
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_BOGUS, "This is not an image".getBytes());
rewriter.rewrite(new HttpRequest(Uri.parse("some.png")), response);
assertEquals(HttpResponse.SC_UNSUPPORTED_MEDIA_TYPE, response.getHttpStatusCode());
assertEquals(CONTENT_TYPE_AND_EXTENSION_MISMATCH,
response.create().getResponseAsString());
}
@Test
public void testNoRewriteAnimatedGIF() throws Exception {
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_GIF,
getImageBytes("org/apache/shindig/gadgets/rewrite/image/animated.gif"));
int changesBefore = response.getNumChanges();
rewriter.rewrite(new HttpRequest(Uri.parse("animated.gif")), response);
assertEquals(changesBefore, response.getNumChanges());
}
@Test
public void testRewriteUnAnimatedGIF() throws Exception {
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_GIF,
getImageBytes("org/apache/shindig/gadgets/rewrite/image/large.gif"));
rewriter.rewrite(new HttpRequest(Uri.parse("large.gif")), response);
assertEquals(CONTENT_TYPE_PNG, response.getHeader(CONTENT_TYPE_HEADER));
}
// Resizing image tests
//
// Checks at least the basic image parameters. It is rather nontrivial to check for the actual
// image content, as the ImageIO implementations vary across JVMs, so we have to skip it.
@Test
public void testResize_width() throws Exception {
BufferedImage image = getResizedHttpResponseContent(CONTENT_TYPE_GIF, CONTENT_TYPE_JPG,
SCALE_IMAGE, 100 /* width */, null /* height */, null /* quality */);
assertEquals(100, image.getWidth());
assertEquals(100, image.getHeight());
}
@Test
public void testResize_height() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, null, 100, null);
assertEquals(100, image.getWidth());
assertEquals(100, image.getHeight());
}
@Test
public void testResize_both() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, 100, 100, null);
assertEquals(100, image.getWidth());
assertEquals(100, image.getHeight());
}
@Test
public void testResize_all() throws Exception {
// The quality hint apparently has no effect on the result here
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, 100, 100, 10);
assertEquals(100, image.getWidth());
assertEquals(100, image.getHeight());
}
@Test
public void testResize_wideImage() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, 100, 50, null);
assertEquals(100, image.getWidth());
assertEquals(50, image.getHeight());
}
@Test
public void testResize_tallImage() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, 50, 100, null);
assertEquals(50, image.getWidth());
assertEquals(100, image.getHeight());
}
@Test
public void testResize_skipResizeHugeOutputImage() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, SCALE_IMAGE, 10000, 10000, null);
assertEquals(500, image.getWidth());
assertEquals(500, image.getHeight());
}
@Test
public void testResize_brokenParameter() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_GIF, SCALE_IMAGE, -1, null, null);
assertEquals(500, image.getWidth());
assertEquals(500, image.getHeight());
}
@Test
public void testResize_expandImage() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, EXPAND_IMAGE, 120, 60, null);
assertEquals(120, image.getWidth());
assertEquals(60, image.getHeight());
}
@Test
public void testResize_noExpandImage() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_PNG /* still optimized */,
EXPAND_IMAGE, 120, 60, null, true /* no expand */);
assertEquals(60, image.getWidth());
assertEquals(30, image.getHeight());
}
@Test
public void testResize_refuseHugeInputImages() throws Exception {
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_GIF, getImageBytes(HUGE_IMAGE));
HttpRequest request = getMockRequest(120, 60, null, false);
mockControl.replay();
rewriter.rewrite(request, response);
mockControl.verify();
assertEquals(HttpResponse.SC_FORBIDDEN, response.getHttpStatusCode());
}
@Test
public void testResize_acceptServeHugeImages() throws Exception {
byte[] imageBytes = getImageBytes(HUGE_IMAGE);
HttpResponseBuilder response = getImageResponse(CONTENT_TYPE_GIF, imageBytes);
HttpRequest request = getMockRequest(null, null, null, false);
mockControl.replay();
rewriter.rewrite(request, response);
mockControl.verify();
assertEquals(HttpResponse.SC_OK, response.getHttpStatusCode());
assertTrue(Arrays.equals(imageBytes, IOUtils.toByteArray(response.getContentBytes())));
}
@Test
public void testResize_avoidFloatingPointRoundingProblems() throws Exception {
BufferedImage image = getResizedHttpResponseContent(
CONTENT_TYPE_GIF, CONTENT_TYPE_PNG, RATIO_IMAGE, 171, 171, null, true);
assertEquals(171, image.getWidth());
assertEquals(114, image.getHeight());
}
}