/* * 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.Blob */ package org.apache.shindig.gadgets.preload; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.apache.shindig.common.JsonAssert; import org.apache.shindig.common.JsonSerializer; import org.apache.shindig.common.JsonUtil; import org.apache.shindig.config.ContainerConfig; import org.apache.shindig.expressions.Expressions; import org.apache.shindig.gadgets.Gadget; import org.apache.shindig.gadgets.GadgetELResolver; 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.http.RequestPipeline; import org.apache.shindig.gadgets.spec.GadgetSpec; import org.apache.shindig.gadgets.spec.PipelinedData; import org.apache.shindig.gadgets.spec.PipelinedData.Batch; import org.easymock.EasyMock; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * Test for PipelinedDataPreloader. */ public class PipelinedDataPreloaderTest extends PreloaderTestFixture { private ContainerConfig containerConfig; private final Expressions expressions = Expressions.forTesting(); private static final String XML = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <os:PeopleRequest key=\"p\" userIds=\"you\"/>" + " <os:PersonAppDataRequest key=\"a\" userId=\"she\"/>" + "</Content></Module>"; private static final String HTTP_REQUEST_URL = "http://example.org/preload.html"; private static final String PARAMS = "a=b&c=d"; private static final String XML_PARAMS = "a=b&c=d"; private static final String XML_WITH_HTTP_REQUEST = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <os:HttpRequest key=\"p\" href=\"" + HTTP_REQUEST_URL + "\" " + "refreshInterval=\"60\" method=\"POST\"/>" + "</Content></Module>"; private static final String XML_WITH_VARIABLE = "<Module " + "xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\" " + "xmlns:osx=\"" + PipelinedData.EXTENSION_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <osx:Variable key=\"p\" value=\"${1+1}\"/>" + "</Content></Module>"; private static final String XML_WITH_HTTP_REQUEST_FOR_TEXT = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <os:HttpRequest key=\"p\" format=\"text\" href=\"" + HTTP_REQUEST_URL + "\" " + "refreshInterval=\"60\" method=\"POST\"/>" + "</Content></Module>"; private static final String XML_WITH_HTTP_REQUEST_AND_PARAMS = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <os:HttpRequest key=\"p\" href=\"" + HTTP_REQUEST_URL + "\" " + " method=\"POST\" params=\"" + XML_PARAMS + "\"/>" + "</Content></Module>"; private static final String XML_WITH_HTTP_REQUEST_AND_GET_PARAMS = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\" view=\"profile\">" + " <os:HttpRequest key=\"p\" href=\"" + HTTP_REQUEST_URL + "\" " + " method=\"GET\" params=\"" + XML_PARAMS + "\"/>" + "</Content></Module>"; private static final String XML_IN_DEFAULT_CONTAINER = "<Module xmlns:os=\"" + PipelinedData.OPENSOCIAL_NAMESPACE + "\">" + "<ModulePrefs title=\"Title\"/>" + "<Content href=\"http://example.org/proxied.php\">" + " <os:PeopleRequest key=\"p\" userIds=\"you\"/>" + " <os:PersonAppDataRequest key=\"a\" userId=\"she\"/>" + "</Content></Module>"; @Before public void createContainerConfig() { containerConfig = EasyMock.createMock(ContainerConfig.class); EasyMock.expect(containerConfig.getString(CONTAINER, "gadgets.osDataUri")).andStubReturn( "http://%host%/social/rpc"); EasyMock.replay(containerConfig); } @Test public void testSocialPreload() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML); String socialResult = "[{id:'p', result:1}, {id:'a', result:2}]"; RecordingRequestPipeline pipeline = new RecordingRequestPipeline(socialResult); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; contextParams.put("st", "token"); Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); assertEquals(1, tasks.size()); // Nothing fetched yet assertEquals(0, pipeline.requests.size()); Collection<Object> result = tasks.iterator().next().call().toJson(); assertEquals(2, result.size()); JSONObject resultWithKeyP = new JSONObject("{id: 'p', result: 1}"); JSONObject resultWithKeyA = new JSONObject("{id: 'a', result: 2}"); Map<String, String> resultsById = getResultsById(result); JsonAssert.assertJsonEquals(resultWithKeyA.toString(), resultsById.get("a")); JsonAssert.assertJsonEquals(resultWithKeyP.toString(), resultsById.get("p")); // Should have only fetched one request assertEquals(1, pipeline.requests.size()); HttpRequest request = pipeline.requests.get(0); assertEquals("http://" + context.getHost() + "/social/rpc?st=token", request.getUri() .toString()); assertEquals("POST", request.getMethod()); assertTrue(request.getContentType().startsWith("application/json")); } @Test public void testSocialPreloadWithBatchError() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML); String socialResult = "{code: 401, message: 'unauthorized'}"; RecordingRequestPipeline pipeline = new RecordingRequestPipeline(socialResult); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; contextParams.put("st", "token"); Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); assertEquals(1, tasks.size()); // Nothing fetched yet assertEquals(0, pipeline.requests.size()); Collection<Object> result = tasks.iterator().next().call().toJson(); assertEquals(2, result.size()); JSONObject resultWithKeyP = new JSONObject("{id: 'p', error: {code: 401, message: 'unauthorized'}}"); JSONObject resultWithKeyA = new JSONObject("{id: 'a', error: {code: 401, message: 'unauthorized'}}"); Map<String, String> resultsById = getResultsById(result); JsonAssert.assertJsonEquals(resultWithKeyA.toString(), resultsById.get("a")); JsonAssert.assertJsonEquals(resultWithKeyP.toString(), resultsById.get("p")); } @Test public void testSocialPreloadWithHttpError() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML); HttpResponse httpError = new HttpResponseBuilder() .setHttpStatusCode(HttpResponse.SC_INTERNAL_SERVER_ERROR) .create(); RecordingRequestPipeline pipeline = new RecordingRequestPipeline(httpError); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; contextParams.put("st", "token"); Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); Collection<Object> result = tasks.iterator().next().call().toJson(); assertEquals(2, result.size()); JSONObject resultWithKeyP = new JSONObject("{id: 'p', error: {code: 500}}"); JSONObject resultWithKeyA = new JSONObject("{id: 'a', error: {code: 500}}"); Map<String, String> resultsById = getResultsById(result); JsonAssert.assertJsonEquals(resultWithKeyA.toString(), resultsById.get("a")); JsonAssert.assertJsonEquals(resultWithKeyP.toString(), resultsById.get("p")); } @Test /** * Verify that social preloads where the request doesn't contain a token * serve up 403s for the preloaded data, instead of failing the whole request. */ public void testSocialPreloadWithoutToken() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML); RecordingRequestPipeline pipeline = new RecordingRequestPipeline(""); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; // But don't set the security token Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); PreloadedData data = tasks.iterator().next().call(); JSONObject resultWithKeyA = new JSONObject( "{error:{code:403,data:{content:\"Security token missing\"}},id:\"a\"}"); JSONObject resultWithKeyP = new JSONObject( "{error:{code:403,data:{content:\"Security token missing\"}},id:\"p\"}"); Collection<Object> result = data.toJson(); assertEquals(2, result.size()); Map<String, String> resultsById = getResultsById(result); JsonAssert.assertJsonEquals(resultWithKeyA.toString(), resultsById.get("a")); JsonAssert.assertJsonEquals(resultWithKeyP.toString(), resultsById.get("p")); } private Map<String, String> getResultsById(Collection<Object> result) { Map<String, String> resultsById = Maps.newHashMap(); for (Object o : result) { resultsById.put((String) JsonUtil.getProperty(o, "id"), JsonSerializer.serialize(o)); } return resultsById; } private Batch getBatch(Gadget gadget) { return gadget.getCurrentView().getPipelinedData().getBatch(expressions, new GadgetELResolver(gadget.getContext())); } @Test public void testHttpPreloadOfJsonObject() throws Exception { HttpResponse response = new HttpResponseBuilder() .setResponseString("{foo: 'bar'}") .create(); String expectedResult = "{result: {status: 200, content: {foo: 'bar'}}, id: 'p'}"; verifyHttpPreload(response, expectedResult); } @Test public void testHttpPreloadOfJsonArrayWithHeaders() throws Exception { HttpResponse response = new HttpResponseBuilder() .setResponseString("[1, 2]") .addHeader("content-type", "application/json") .addHeader("set-cookie", "cookiecookie") .addHeader("not-ok", "shouldn'tbehere") .create(); String expectedResult = "{result: {status: 200, headers:" + "{'content-type': ['application/json; charset=UTF-8'], 'set-cookie': ['cookiecookie']}," + "content: [1, 2]}, id: 'p'}"; verifyHttpPreload(response, expectedResult); } @Test public void testHttpPreloadOfJsonWithErrorCode() throws Exception { HttpResponse response = new HttpResponseBuilder() .setResponseString("not found") .addHeader("content-type", "text/html") .setHttpStatusCode(HttpResponse.SC_NOT_FOUND) .create(); String expectedResult = "{error: {code: 404, data:" + "{headers: {'content-type': ['text/html; charset=UTF-8']}," + "content: 'not found'}}, id: 'p'}"; verifyHttpPreload(response, expectedResult); } @Test public void testHttpPreloadWithBadJson() throws Exception { HttpResponse response = new HttpResponseBuilder() .setResponseString("notjson") .addHeader("content-type", "text/html") .create(); JSONObject result = new JSONObject(executeHttpPreload(response, XML_WITH_HTTP_REQUEST)); assertFalse(result.has("result")); JSONObject error = result.getJSONObject("error"); assertEquals(HttpResponse.SC_NOT_ACCEPTABLE, error.getInt("code")); } @Test public void testHttpPreloadOfText() throws Exception { HttpResponse response = new HttpResponseBuilder() .setResponseString("{foo: 'bar'}") .addHeader("content-type", "application/json") .create(); // Even though the response was actually JSON, @format=text, so the content // will be a block of text String expectedResult = "{result: {status: 200, headers:" + "{'content-type': ['application/json; charset=UTF-8']}," + "content: '{foo: \\'bar\\'}'}, id: 'p'}"; String resultString = executeHttpPreload(response, XML_WITH_HTTP_REQUEST_FOR_TEXT); JsonAssert.assertJsonEquals(expectedResult, resultString); } private void verifyHttpPreload(HttpResponse response, String expectedJson) throws Exception { String resultString = executeHttpPreload(response, XML_WITH_HTTP_REQUEST); JsonAssert.assertJsonEquals(expectedJson, resultString); } /** * Run an HTTP Preload test, returning the String result. */ private String executeHttpPreload(HttpResponse response, String xml) throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, xml); RecordingRequestPipeline pipeline = new RecordingRequestPipeline(response); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); assertEquals(1, tasks.size()); // Nothing fetched yet assertEquals(0, pipeline.requests.size()); Collection<Object> result = tasks.iterator().next().call().toJson(); assertEquals(1, result.size()); // Should have only fetched one request assertEquals(1, pipeline.requests.size()); HttpRequest request = pipeline.requests.get(0); assertEquals(HTTP_REQUEST_URL, request.getUri().toString()); assertEquals("POST", request.getMethod()); assertEquals(60, request.getCacheTtl()); return result.iterator().next().toString(); } @Test public void testHttpPreloadWithPostParams() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_WITH_HTTP_REQUEST_AND_PARAMS); String httpResult = "{foo: 'bar'}"; RecordingRequestPipeline pipeline = new RecordingRequestPipeline(httpResult); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); tasks.iterator().next().call(); // Should have only fetched one request assertEquals(1, pipeline.requests.size()); HttpRequest request = pipeline.requests.get(0); assertEquals(HTTP_REQUEST_URL, request.getUri().toString()); assertEquals("POST", request.getMethod()); assertEquals(PARAMS, request.getPostBodyAsString()); } @Test public void testHttpPreloadWithGetParams() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_WITH_HTTP_REQUEST_AND_GET_PARAMS); String httpResult = "{foo: 'bar'}"; RecordingRequestPipeline pipeline = new RecordingRequestPipeline(httpResult); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); tasks.iterator().next().call(); // Should have only fetched one request assertEquals(1, pipeline.requests.size()); HttpRequest request = pipeline.requests.get(0); assertEquals(HTTP_REQUEST_URL + '?' + PARAMS, request.getUri().toString()); assertEquals("GET", request.getMethod()); } /** * Verify that social preloads pay attention to view resolution by * using gadget.getCurrentView(). */ @Test public void testSocialPreloadViewResolution() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_IN_DEFAULT_CONTAINER); String socialResult = "[{id:'p', result:1}, {id:'a', result:2}]"; RecordingRequestPipeline pipeline = new RecordingRequestPipeline(socialResult); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; contextParams.put("st", "token"); Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) // Assume view resolution has behaved correctly .setCurrentView(spec.getView(GadgetSpec.DEFAULT_VIEW)); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); assertEquals(1, tasks.size()); } @Test public void testVariablePreload() throws Exception { GadgetSpec spec = new GadgetSpec(GADGET_URL, XML_WITH_VARIABLE); RecordingRequestPipeline pipeline = new RecordingRequestPipeline(""); PipelinedDataPreloader preloader = new PipelinedDataPreloader(pipeline, containerConfig); view = "profile"; contextParams.put("st", "token"); Gadget gadget = new Gadget() .setContext(context) .setSpec(spec) .setCurrentView(spec.getView("profile")); PipelinedData.Batch batch = getBatch(gadget); Collection<Callable<PreloadedData>> tasks = preloader.createPreloadTasks( context, batch); assertEquals(1, tasks.size()); // Nothing fetched yet assertEquals(0, pipeline.requests.size()); Collection<Object> result = tasks.iterator().next().call().toJson(); assertEquals(1, result.size()); JsonAssert.assertObjectEquals("{id: 'p', result: 2}", result.iterator().next()); } private static class RecordingRequestPipeline implements RequestPipeline { public final List<HttpRequest> requests = Lists.newArrayList(); private final HttpResponse response; public RecordingRequestPipeline(String content) { this(new HttpResponseBuilder().setResponseString(content).create()); } public RecordingRequestPipeline(HttpResponse response) { this.response = response; } public HttpResponse execute(HttpRequest request) { requests.add(request); return response; } } }