/*
* Copyright (c) 2015-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.parse;
import com.parse.http.ParseHttpRequest;
import com.parse.http.ParseHttpResponse;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.skyscreamer.jsonassert.JSONCompareMode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import bolts.Task;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
// For org.json
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION)
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class ParseRESTCommandTest {
private static ParseHttpResponse newMockParseHttpResponse(int statusCode, JSONObject body) {
return newMockParseHttpResponse(statusCode, body.toString());
}
private static ParseHttpResponse newMockParseHttpResponse(int statusCode, String body) {
ParseHttpResponse mockResponse = new ParseHttpResponse.Builder()
.setStatusCode(statusCode)
.setTotalSize((long) body.length())
.setContent(new ByteArrayInputStream(body.getBytes()))
.build();
return mockResponse;
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() throws Exception {
ParseRequest.setDefaultInitialRetryDelay(1L);
ParseRESTCommand.server = new URL("https://api.parse.com/1");
}
@After
public void tearDown() throws Exception {
ParseRequest.setDefaultInitialRetryDelay(ParseRequest.DEFAULT_INITIAL_RETRY_DELAY);
ParseCorePlugins.getInstance().reset();
ParseRESTCommand.server = null;
}
@Test
public void testInitializationWithDefaultParseServerURL() throws Exception {
ParseRESTCommand.server = new URL("https://api.parse.com/1/");
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath("events/Appopened")
.build();
assertEquals("https://api.parse.com/1/events/Appopened", command.url);
}
@Test
public void testPermanentFailures() throws Exception {
JSONObject json = new JSONObject();
json.put("code", 1337);
json.put("error", "mock error");
ParseHttpResponse response = newMockParseHttpResponse(400, json);
ParseHttpClient client = mock(ParseHttpClient.class);
when(client.execute(any(ParseHttpRequest.class))).thenReturn(response);
ParseRESTCommand command = new ParseRESTCommand.Builder()
.method(ParseHttpRequest.Method.GET)
.installationId("fake_installation_id")
.build();
Task<JSONObject> task = command.executeAsync(client);
task.waitForCompletion();
verify(client, times(1)).execute(any(ParseHttpRequest.class));
assertTrue(task.isFaulted());
assertEquals(1337, ((ParseException) task.getError()).getCode());
assertEquals("mock error", task.getError().getMessage());
}
@Test
public void testTemporaryFailures() throws Exception {
JSONObject json = new JSONObject();
json.put("code", 1337);
json.put("error", "mock error");
ParseHttpResponse response1 = newMockParseHttpResponse(500, json);
ParseHttpResponse response2 = newMockParseHttpResponse(500, json);
ParseHttpResponse response3 = newMockParseHttpResponse(500, json);
ParseHttpResponse response4 = newMockParseHttpResponse(500, json);
ParseHttpResponse response5 = newMockParseHttpResponse(500, json);
ParseHttpClient client = mock(ParseHttpClient.class);
when(client.execute(any(ParseHttpRequest.class))).thenReturn(
response1,
response2,
response3,
response4,
response5
);
ParseRESTCommand command = new ParseRESTCommand.Builder()
.method(ParseHttpRequest.Method.GET)
.installationId("fake_installation_id")
.build();
Task<JSONObject> task = command.executeAsync(client);
task.waitForCompletion();
verify(client, times(5)).execute(any(ParseHttpRequest.class));
assertTrue(task.isFaulted());
assertEquals(1337, ((ParseException) task.getError()).getCode());
assertEquals("mock error", task.getError().getMessage());
}
/**
* Test to verify that handle 401 unauthorized
*/
@Test
public void test401Unauthorized() throws Exception {
JSONObject json = new JSONObject();
json.put("error", "unauthorized");
ParseHttpResponse response = newMockParseHttpResponse(401, json);
ParseHttpClient client = mock(ParseHttpClient.class);
when(client.execute(any(ParseHttpRequest.class))).thenReturn(response);
ParseRESTCommand command = new ParseRESTCommand.Builder()
.method(ParseHttpRequest.Method.GET)
.installationId("fake_installation_id")
.build();
Task<JSONObject> task = command.executeAsync(client);
task.waitForCompletion();
verify(client, times(1)).execute(any(ParseHttpRequest.class));
assertTrue(task.isFaulted());
assertEquals(0, ((ParseException) task.getError()).getCode());
assertEquals("unauthorized", task.getError().getMessage());
}
@Test
public void testToDeterministicString() throws Exception {
// Make test json
JSONArray nestedJSONArray = new JSONArray()
.put(true)
.put(1)
.put("test");
JSONObject nestedJSON = new JSONObject()
.put("bool", false)
.put("int", 2)
.put("string", "test");
JSONObject json = new JSONObject()
.put("json", nestedJSON)
.put("jsonArray", nestedJSONArray)
.put("bool", true)
.put("int", 3)
.put("string", "test");
String jsonString = ParseRESTCommand.toDeterministicString(json);
JSONObject jsonAgain = new JSONObject(jsonString);
assertEquals(json, jsonAgain, JSONCompareMode.NON_EXTENSIBLE);
}
@Test
public void testToJSONObject() throws Exception {
// Make test command
String httpPath = "www.parse.com";
JSONObject jsonParameters = new JSONObject()
.put("count", 1)
.put("limit", 1);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
JSONObject json = command.toJSONObject();
assertEquals(httpPath, json.getString("httpPath"));
assertEquals("POST", json.getString("httpMethod"));
assertEquals(jsonParameters, json.getJSONObject("parameters"), JSONCompareMode.NON_EXTENSIBLE);
assertEquals(sessionToken, json.getString("sessionToken"));
assertEquals(localId, json.getString("localId"));
}
@Test
public void testGetCacheKey() throws Exception {
// Make test command
String httpPath = "www.parse.com";
JSONObject jsonParameters = new JSONObject()
.put("count", 1)
.put("limit", 1);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
String cacheKey = command.getCacheKey();
assertTrue(cacheKey.contains("ParseRESTCommand"));
assertTrue(cacheKey.contains(ParseHttpRequest.Method.POST.toString()));
assertTrue(cacheKey.contains(ParseDigestUtils.md5(httpPath)));
String str =
ParseDigestUtils.md5(ParseRESTCommand.toDeterministicString(jsonParameters) + sessionToken);
assertTrue(cacheKey.contains(str));
}
@Test
public void testGetCacheKeyWithNoJSONParameters() throws Exception {
// Make test command
String httpPath = "www.parse.com";
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
String cacheKey = command.getCacheKey();
assertTrue(cacheKey.contains("ParseRESTCommand"));
assertTrue(cacheKey.contains(ParseHttpRequest.Method.POST.toString()));
assertTrue(cacheKey.contains(ParseDigestUtils.md5(httpPath)));
assertTrue(cacheKey.contains(ParseDigestUtils.md5(sessionToken)));
}
@Test
public void testReleaseLocalIds() {
// Register LocalIdManager
LocalIdManager localIdManager = mock(LocalIdManager.class);
when(localIdManager.createLocalId()).thenReturn("localIdAgain");
ParseCorePlugins.getInstance().registerLocalIdManager(localIdManager);
// Make test command
ParseObject object = new ParseObject("Test");
object.put("key", "value");
String httpPath = "www.parse.com";
JSONObject jsonParameters = PointerOrLocalIdEncoder.get().encodeRelatedObject(object);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
command.releaseLocalIds();
verify(localIdManager, times(1)).releaseLocalIdOnDisk(localId);
verify(localIdManager, times(1)).releaseLocalIdOnDisk("localIdAgain");
}
@Test
public void testResolveLocalIdsWithNoObjectId() {
// Register LocalIdManager
LocalIdManager localIdManager = mock(LocalIdManager.class);
when(localIdManager.createLocalId()).thenReturn("localIdAgain");
ParseCorePlugins.getInstance().registerLocalIdManager(localIdManager);
// Make test command
ParseObject object = new ParseObject("Test");
object.put("key", "value");
String httpPath = "www.parse.com";
JSONObject jsonParameters = PointerOrLocalIdEncoder.get().encodeRelatedObject(object);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Tried to serialize a command referencing a new, unsaved object.");
command.resolveLocalIds();
// Make sure we try to get the objectId
verify(localIdManager, times(1)).getObjectId("localIdAgain");
}
@Test
public void testResolveLocalIds() throws Exception {
// Register LocalIdManager
LocalIdManager localIdManager = mock(LocalIdManager.class);
when(localIdManager.createLocalId()).thenReturn("localIdAgain");
when(localIdManager.getObjectId("localIdAgain")).thenReturn("objectIdAgain");
when(localIdManager.getObjectId("localId")).thenReturn("objectId");
ParseCorePlugins.getInstance().registerLocalIdManager(localIdManager);
// Make test command
ParseObject object = new ParseObject("Test");
object.put("key", "value");
String httpPath = "classes";
JSONObject jsonParameters = PointerOrLocalIdEncoder.get().encodeRelatedObject(object);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
command.resolveLocalIds();
verify(localIdManager, times(1)).getObjectId("localIdAgain");
verify(localIdManager, times(1)).getObjectId("localId");
// Make sure localId in jsonParameters has been replaced with objectId
assertFalse(jsonParameters.has("localId"));
assertEquals("objectIdAgain", jsonParameters.getString("objectId"));
// Make sure localId in command has been replaced with objectId
assertNull(command.getLocalId());
// Make sure httpMethod has been changed
assertEquals(ParseHttpRequest.Method.PUT, command.method);
// Make sure objectId has been added to httpPath
assertTrue(command.httpPath.contains("objectId"));
}
@Test
public void testRetainLocalIds() throws Exception {
// Register LocalIdManager
LocalIdManager localIdManager = mock(LocalIdManager.class);
when(localIdManager.createLocalId()).thenReturn("localIdAgain");
ParseCorePlugins.getInstance().registerLocalIdManager(localIdManager);
// Make test command
ParseObject object = new ParseObject("Test");
object.put("key", "value");
String httpPath = "classes";
JSONObject jsonParameters = PointerOrLocalIdEncoder.get().encodeRelatedObject(object);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.POST)
.sessionToken(sessionToken)
.localId(localId)
.build();
command.retainLocalIds();
verify(localIdManager, times(1)).retainLocalIdOnDisk("localIdAgain");
verify(localIdManager, times(1)).retainLocalIdOnDisk(localId);
}
@Test
public void testNewBodyWithNoJSONParameters() throws Exception {
// Make test command
String httpPath = "www.parse.com";
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.method(ParseHttpRequest.Method.GET)
.sessionToken(sessionToken)
.localId(localId)
.build();
thrown.expect(IllegalArgumentException.class);
String message = String.format("Trying to execute a %s command without body parameters.",
ParseHttpRequest.Method.GET.toString());
thrown.expectMessage(message);
command.newBody(null);
}
@Test
public void testNewBody() throws Exception {
// Make test command
String httpPath = "www.parse.com";
JSONObject jsonParameters = new JSONObject()
.put("count", 1)
.put("limit", 1);
String sessionToken = "sessionToken";
String localId = "localId";
ParseRESTCommand command = new ParseRESTCommand.Builder()
.httpPath(httpPath)
.jsonParameters(jsonParameters)
.method(ParseHttpRequest.Method.GET)
.sessionToken(sessionToken)
.localId(localId)
.build();
ParseByteArrayHttpBody body = (ParseByteArrayHttpBody) command.newBody(null);
// Verify body content is correct
JSONObject json = new JSONObject(new String(ParseIOUtils.toByteArray(body.getContent())));
assertEquals(1, json.getInt("count"));
assertEquals(1, json.getInt("limit"));
assertEquals(ParseHttpRequest.Method.GET.toString(), json.getString("_method"));
// Verify body content-type is correct
assertEquals("application/json", body.getContentType());
}
@Test
public void testFromJSONObject() throws Exception {
// Make test command
String httpPath = "www.parse.com";
JSONObject jsonParameters = new JSONObject()
.put("count", 1)
.put("limit", 1);
String sessionToken = "sessionToken";
String localId = "localId";
String httpMethod = "POST";
JSONObject commandJSON = new JSONObject()
.put("httpPath", httpPath)
.put("parameters", jsonParameters)
.put("httpMethod", httpMethod)
.put("sessionToken", sessionToken)
.put("localId", localId);
ParseRESTCommand command = ParseRESTCommand.fromJSONObject(commandJSON);
assertEquals(httpPath, command.httpPath);
assertEquals(httpMethod, command.method.toString());
assertEquals(sessionToken, command.getSessionToken());
assertEquals(localId, command.getLocalId());
assertEquals(jsonParameters, command.jsonParameters, JSONCompareMode.NON_EXTENSIBLE);
}
@Test
public void testOnResponseCloseNetworkStreamWithNormalResponse() throws Exception {
// Mock response stream
int statusCode = 200;
JSONObject bodyJson = new JSONObject();
bodyJson.put("key", "value");
String bodyStr = bodyJson.toString();
ByteArrayInputStream bodyStream = new ByteArrayInputStream(bodyStr.getBytes());
InputStream mockResponseStream = spy(bodyStream);
doNothing()
.when(mockResponseStream)
.close();
// Mock response
ParseHttpResponse mockResponse = new ParseHttpResponse.Builder()
.setStatusCode(statusCode)
.setTotalSize((long) bodyStr.length())
.setContent(mockResponseStream)
.build();
ParseRESTCommand command = new ParseRESTCommand.Builder().build();
JSONObject json = ParseTaskUtils.wait(command.onResponseAsync(mockResponse, null));
verify(mockResponseStream, times(1)).close();
assertEquals(bodyJson, json, JSONCompareMode.NON_EXTENSIBLE);
}
@Test
public void testOnResposneCloseNetworkStreamWithIOException() throws Exception {
// Mock response stream
int statusCode = 200;
InputStream mockResponseStream = mock(InputStream.class);
doNothing()
.when(mockResponseStream)
.close();
IOException readException = new IOException("Error");
doThrow(readException)
.when(mockResponseStream)
.read();
doThrow(readException)
.when(mockResponseStream)
.read(any(byte[].class));
// Mock response
ParseHttpResponse mockResponse = new ParseHttpResponse.Builder()
.setStatusCode(statusCode)
.setContent(mockResponseStream)
.build();
ParseRESTCommand command = new ParseRESTCommand.Builder().build();
// We can not use ParseTaskUtils here since it will replace the original exception with runtime
// exception
Task<JSONObject> responseTask = command.onResponseAsync(mockResponse, null);
responseTask.waitForCompletion();
assertTrue(responseTask.isFaulted());
assertTrue(responseTask.getError() instanceof IOException);
assertEquals("Error", responseTask.getError().getMessage());
verify(mockResponseStream, times(1)).close();
}
}