/*
* 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.protocol;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.classextension.EasyMock.reset;
import org.apache.shindig.common.JsonAssert;
import org.apache.shindig.common.testing.FakeGadgetToken;
import org.apache.shindig.protocol.conversion.BeanJsonConverter;
import org.apache.shindig.protocol.multipart.FormDataItem;
import org.apache.shindig.protocol.multipart.MultipartFormParser;
import org.easymock.IMocksControl;
import org.easymock.classextension.EasyMock;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
*
*/
public class JsonRpcServletTest extends Assert {
private static final FakeGadgetToken FAKE_GADGET_TOKEN = new FakeGadgetToken()
.setOwnerId("john.doe").setViewerId("john.doe");
private static final String IMAGE_FIELDNAME = "profile-photo";
private static final String IMAGE_DATA = "image data";
private static final byte[] IMAGE_DATA_BYTES = IMAGE_DATA.getBytes();
private static final String IMAGE_TYPE = "image/jpeg";
private HttpServletRequest req;
private HttpServletResponse res;
private JsonRpcServlet servlet;
private MultipartFormParser multipartFormParser;
private final IMocksControl mockControl = EasyMock.createNiceControl();
private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
private final PrintWriter writer = new PrintWriter(stream);
private final TestHandler handler = new TestHandler();
@Before
public void setUp() throws Exception {
servlet = new JsonRpcServlet();
req = mockControl.createMock(HttpServletRequest.class);
res = mockControl.createMock(HttpServletResponse.class);
multipartFormParser = mockControl.createMock(MultipartFormParser.class);
EasyMock.expect(multipartFormParser.isMultipartContent(req)).andStubReturn(false);
servlet.setMultipartFormParser(multipartFormParser);
BeanJsonConverter converter = new BeanJsonConverter(Guice.createInjector());
HandlerRegistry registry = new DefaultHandlerRegistry(null, null,
new HandlerExecutionListener.NoOpHandler());
registry.addHandlers(Collections.<Object>singleton(handler));
servlet.setHandlerRegistry(registry);
servlet.setBeanConverters(converter, null, null);
handler.setMock(new TestHandler() {
@Override
public Object get(RequestItem req) {
return ImmutableMap.of("foo", "bar");
}
});
}
private String getOutput() throws IOException {
writer.close();
return stream.toString("UTF-8");
}
@Test
public void testMethodRecognition() throws Exception {
setupRequest("{method:test.get,id:id,params:{userId:5,groupId:@self}}");
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("{id: 'id', data: {foo:'bar'}}", getOutput());
}
@Test
public void testPostMultipartFormData() throws Exception {
reset(multipartFormParser);
handler.setMock(new TestHandler() {
@Override
public Object get(RequestItem req) {
FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
return ImmutableMap.of("image-data", new String(item.get()),
"image-type", item.getContentType(),
"image-ref", req.getParameter("image-ref"));
}
});
expect(req.getMethod()).andStubReturn("POST");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
expect(req.getContentType()).andStubReturn(ContentTypes.MULTIPART_FORM_CONTENT_TYPE);
res.setCharacterEncoding("UTF-8");
res.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
List<FormDataItem> formItems = Lists.newArrayList();
String request = "{method:'test.get',id:'id',params:" +
"{userId:5,groupId:'@self',image-ref:'@" + IMAGE_FIELDNAME + "'}}";
formItems.add(mockFormDataItem(JsonRpcServlet.REQUEST_PARAM,
ContentTypes.OUTPUT_JSON_CONTENT_TYPE, request.getBytes(), true));
formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA_BYTES, false));
expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
expect(multipartFormParser.parse(req)).andReturn(formItems);
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("{id: 'id', data: {image-data:'" + IMAGE_DATA +
"', image-type:'" + IMAGE_TYPE + "', image-ref:'@" + IMAGE_FIELDNAME + "'}}", getOutput());
}
/**
* Test that it passes even when content-type is not set for "request" parameter. This would
* be the case where the request is published via webform.
*/
@Test
public void testPostMultipartFormDataWithRequestFieldHavingNoContentType() throws Exception {
reset(multipartFormParser);
handler.setMock(new TestHandler() {
@Override
public Object get(RequestItem req) {
FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
return ImmutableMap.of("image-data", new String(item.get()),
"image-type", item.getContentType(),
"image-ref", req.getParameter("image-ref"));
}
});
expect(req.getMethod()).andStubReturn("POST");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
expect(req.getContentType()).andStubReturn(ContentTypes.MULTIPART_FORM_CONTENT_TYPE);
res.setCharacterEncoding("UTF-8");
res.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
List<FormDataItem> formItems = Lists.newArrayList();
String request = "{method:'test.get',id:'id',params:" +
"{userId:5,groupId:'@self',image-ref:'@" + IMAGE_FIELDNAME + "'}}";
formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA_BYTES, false));
formItems.add(mockFormDataItem("request", null, request.getBytes(), true));
expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
expect(multipartFormParser.parse(req)).andReturn(formItems);
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("{id: 'id', data: {image-data:'" + IMAGE_DATA +
"', image-type:'" + IMAGE_TYPE + "', image-ref:'@" + IMAGE_FIELDNAME + "'}}", getOutput());
}
/**
* Test that any form-data other than "request" does not undergo any content type check.
*/
@Test
public void testPostMultipartFormDataOnlyRequestFieldHasContentTypeChecked()
throws Exception {
reset(multipartFormParser);
handler.setMock(new TestHandler() {
@Override
public Object get(RequestItem req) {
FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
return ImmutableMap.of("image-data", new String(item.get()),
"image-type", item.getContentType(),
"image-ref", req.getParameter("image-ref"));
}
});
expect(req.getMethod()).andStubReturn("POST");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
expect(req.getContentType()).andStubReturn(ContentTypes.MULTIPART_FORM_CONTENT_TYPE);
res.setCharacterEncoding("UTF-8");
res.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
List<FormDataItem> formItems = Lists.newArrayList();
String request = "{method:'test.get',id:'id',params:" +
"{userId:5,groupId:'@self',image-ref:'@" + IMAGE_FIELDNAME + "'}}";
formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA_BYTES, false));
formItems.add(mockFormDataItem("oauth_hash", "application/octet-stream",
"oauth-hash".getBytes(), true));
formItems.add(mockFormDataItem("request", null, request.getBytes(), true));
formItems.add(mockFormDataItem("oauth_signature", "application/octet-stream",
"oauth_signature".getBytes(), true));
expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
expect(multipartFormParser.parse(req)).andReturn(formItems);
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("{id: 'id', data: {image-data:'" + IMAGE_DATA +
"', image-type:'" + IMAGE_TYPE + "', image-ref:'@" + IMAGE_FIELDNAME + "'}}", getOutput());
}
/**
* Test that "request" field undergoes contentType check, and error is thrown if wrong content
* type is present.
*/
@Test
public void testPostMultipartFormDataRequestFieldIsSubjectedToContentTypeCheck()
throws Exception {
reset(multipartFormParser);
handler.setMock(new TestHandler() {
@Override
public Object get(RequestItem req) {
FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
return ImmutableMap.of("image-data", item.get(),
"image-type", item.getContentType(),
"image-ref", req.getParameter("image-ref"));
}
});
expect(req.getMethod()).andStubReturn("POST");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
expect(req.getContentType()).andStubReturn(ContentTypes.MULTIPART_FORM_CONTENT_TYPE);
res.setCharacterEncoding("UTF-8");
res.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
List<FormDataItem> formItems = Lists.newArrayList();
String request = "{method:'test.get',id:'id',params:" +
"{userId:5,groupId:'@self',image-ref:'@" + IMAGE_FIELDNAME + "'}}";
formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA_BYTES, false));
formItems.add(mockFormDataItem("request", "application/octet-stream", request.getBytes(),
true));
expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
expect(multipartFormParser.parse(req)).andReturn(formItems);
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
String output = getOutput();
assertTrue(-1 != output.indexOf("Unsupported Content-Type application/octet-stream"));
}
@Test
public void testInvalidService() throws Exception {
setupRequest("{method:junk.get,id:id,params:{userId:5,groupId:@self}}");
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals(
"{id:id,error:{message:'notImplemented: The method junk.get is not implemented',code:501}}",
getOutput());
}
/**
* Tests a data handler that returns a failed Future.
* @throws Exception on failure
*/
@Test
public void testFailedRequest() throws Exception {
setupRequest("{id:id,method:test.futureException}");
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals(
"{id:id,error:{message:'badRequest: FAILURE_MESSAGE',code:400}}", getOutput());
}
@Test
public void testBasicBatch() throws Exception {
setupRequest("[{method:test.get,id:'1'},{method:test.get,id:'2'}]");
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("[{id:'1',data:{foo:'bar'}},{id:'2',data:{foo:'bar'}}]",
getOutput());
}
@Test
public void testGetExecution() throws Exception {
expect(req.getParameterMap()).andStubReturn(
ImmutableMap.of("method", new String[]{"test.get"}, "id", new String[]{"1"}));
expect(req.getMethod()).andStubReturn("GET");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
res.setCharacterEncoding("UTF-8");
expect(res.getWriter()).andReturn(writer);
expectLastCall();
mockControl.replay();
servlet.service(req, res);
mockControl.verify();
JsonAssert.assertJsonEquals("{id:'1',data:{foo:'bar'}}", getOutput());
}
@Test
public void testGetJsonResponseWithKey() throws Exception {
ResponseItem responseItem = new ResponseItem("Name");
Object result = servlet.getJSONResponse("my-key", responseItem);
JsonAssert.assertObjectEquals("{id: 'my-key', data: 'Name'}", result);
}
@Test
public void testGetJsonResponseWithoutKey() throws Exception {
ResponseItem responseItem = new ResponseItem("Name");
Object result = servlet.getJSONResponse(null, responseItem);
JsonAssert.assertObjectEquals("{data: 'Name'}", result);
}
@Test
public void testGetJsonResponseErrorWithData() throws Exception {
ResponseItem responseItem = new ResponseItem(401, "Error Message", "Optional Data");
Object result = servlet.getJSONResponse(null, responseItem);
JsonAssert.assertObjectEquals(
"{error: {message: 'unauthorized: Error Message', data: 'Optional Data', code: 401}}",
result);
}
@Test
public void testGetJsonResponseErrorWithoutData() throws Exception {
ResponseItem responseItem = new ResponseItem(401, "Error Message");
Object result = servlet.getJSONResponse(null, responseItem);
JsonAssert.assertObjectEquals(
"{error: {message:'unauthorized: Error Message', code:401}}",
result);
}
private void setupRequest(String json) throws IOException {
final InputStream in = new ByteArrayInputStream(json.getBytes());
ServletInputStream stream = new ServletInputStream() {
@Override
public int read() throws IOException {
return in.read();
}
};
expect(req.getInputStream()).andStubReturn(stream);
expect(req.getMethod()).andStubReturn("POST");
expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
expect(req.getContentType()).andStubReturn(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
res.setCharacterEncoding("UTF-8");
res.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
}
private FormDataItem mockFormDataItem(String fieldName, String contentType, byte content[],
boolean isFormField) throws IOException {
InputStream in = new ByteArrayInputStream(content);
FormDataItem formDataItem = mockControl.createMock(FormDataItem.class);
expect(formDataItem.getContentType()).andStubReturn(contentType);
expect(formDataItem.getSize()).andStubReturn((long) content.length);
expect(formDataItem.get()).andStubReturn(content);
expect(formDataItem.getAsString()).andStubReturn(new String(content));
expect(formDataItem.getFieldName()).andStubReturn(fieldName);
expect(formDataItem.isFormField()).andStubReturn(isFormField);
expect(formDataItem.getInputStream()).andStubReturn(in);
return formDataItem;
}
}