From c83fae1ec508a1a360d07c12d53e205586f48465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Thu, 24 May 2018 12:25:03 -0400 Subject: [PATCH 1/7] - Added a failing test for https://github.com/parse-community/Parse-SDK-Android/issues/827 - The eventually queue should be cleared at each test execution but Parse.getEventuallyQueue().clear() crashes, that's why line 1585 is commented --- .../test/java/com/parse/ParseUserTest.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index b5fe54fac..fd49293d3 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -8,10 +8,13 @@ */ package com.parse; +import android.Manifest; import android.os.Parcel; +import android.util.Log; import org.json.JSONObject; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -20,14 +23,22 @@ import org.mockito.ArgumentCaptor; import org.mockito.Matchers; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import java.net.URL; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import bolts.Capture; +import bolts.Continuation; import bolts.Task; import static org.junit.Assert.assertEquals; @@ -1541,4 +1552,102 @@ private static void setLazy(ParseUser user) { anonymousAuthData.put("anonymousToken", "anonymousTest"); user.putAuthData(ParseAnonymousUtils.AUTH_TYPE, anonymousAuthData); } + + //region testSaveEventuallyWhenSessionIsInvalid + + @Test + public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { + + ParseRESTCommand.server = new URL("https://api.parse.com/1"); + Parse.enableLocalDatastore(RuntimeEnvironment.application); + + Parse.Configuration configuration = new Parse.Configuration.Builder(RuntimeEnvironment.application) + .build(); + ParsePlugins plugins = mock(ParsePlugins.class); + when(plugins.configuration()).thenReturn(configuration); + when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application); + ParsePlugins.set(plugins); + + ShadowApplication application = Shadows.shadowOf(RuntimeEnvironment.application); + application.grantPermissions(Manifest.permission.ACCESS_NETWORK_STATE); + + ParseUser.State userState = new ParseUser.State.Builder() + .objectId("test") + .sessionToken("r:sessionToken") + .build(); + ParseUser user = ParseObject.from(userState); + + ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); + when(currentUserController.getAsync(anyBoolean())).thenReturn(Task.forResult(user)); + when(currentUserController.getAsync()).thenReturn(Task.forResult(user)); + ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); + + //Parse.getEventuallyQueue().clear(); + + user.put("field", "data"); + + JSONObject mockResponse = new JSONObject(); + mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date())); + ParseHttpClient restClient = + ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 200, "OK"); + when(plugins.restClient()).thenReturn(restClient); + + final CountDownLatch saveCountDown1 = new CountDownLatch(1); + final Capture exceptionCapture = new Capture<>(); + user.saveInBackground().continueWith(new Continuation() { + @Override + public Void then(Task task) throws Exception { + exceptionCapture.set(task.getError()); + saveCountDown1.countDown(); + return null; + } + }); + assertTrue(saveCountDown1.await(5, TimeUnit.SECONDS)); + assertNull(exceptionCapture.get()); + + user.put("field", "other data"); + + mockResponse = new JSONObject(); + mockResponse.put("error", "invalid session token"); + mockResponse.put("code", 209); + restClient = + ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 400, "Bad Request"); + when(plugins.restClient()).thenReturn(restClient); + + final CountDownLatch saveEventuallyCountDown = new CountDownLatch(1); + user.saveEventually().continueWith(new Continuation() { + @Override + public Void then(Task task) throws Exception { + exceptionCapture.set(task.getError()); + saveEventuallyCountDown.countDown(); + return null; + } + }); + assertTrue(saveEventuallyCountDown.await(5, TimeUnit.SECONDS)); + assertTrue(exceptionCapture.get() instanceof ParseException); + assertEquals(ParseException.INVALID_SESSION_TOKEN, ((ParseException)exceptionCapture.get()).getCode()); + assertEquals("invalid session token", exceptionCapture.get().getMessage()); + + user.put("field", "another data"); + + mockResponse = new JSONObject(); + mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date())); + restClient = + ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 200, "OK"); + when(plugins.restClient()).thenReturn(restClient); + + final CountDownLatch saveCountDown2 = new CountDownLatch(1); + user.saveInBackground().continueWith(new Continuation() { + @Override + public Void then(Task task) throws Exception { + exceptionCapture.set(task.getError()); + saveCountDown2.countDown(); + return null; + } + }); + assertTrue(saveCountDown2.await(5, TimeUnit.SECONDS)); + assertNull(exceptionCapture.get()); + } + + //endregion } From 3e55ffbd61b86a42e2d15f5eb1ea92b7eb4a001e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Thu, 24 May 2018 13:41:25 -0400 Subject: [PATCH 2/7] Removed unused imports --- Parse/src/test/java/com/parse/ParseUserTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index fd49293d3..ac5ad2f1f 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -10,11 +10,9 @@ import android.Manifest; import android.os.Parcel; -import android.util.Log; import org.json.JSONObject; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; From 65934bf7e3e88f3275b65ef92dfbfc08bfddf317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Tue, 29 May 2018 11:18:34 -0400 Subject: [PATCH 3/7] Fixed ParseUserTest.testSaveEventuallyWhenSessionIsInvalid() so it reproduce exactly issue https://github.com/parse-community/Parse-SDK-Android/issues/827 --- Parse/src/test/java/com/parse/ParseUserTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index ac5ad2f1f..2e97b9835 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -1557,7 +1557,10 @@ private static void setLazy(ParseUser user) { public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { ParseRESTCommand.server = new URL("https://api.parse.com/1"); - Parse.enableLocalDatastore(RuntimeEnvironment.application); + + ParseObject.registerSubclass(EventuallyPin.class); + ParseObject.registerSubclass(ParsePin.class); + Parse.setLocalDatastore(new OfflineStore(RuntimeEnvironment.application)); Parse.Configuration configuration = new Parse.Configuration.Builder(RuntimeEnvironment.application) .build(); @@ -1580,8 +1583,6 @@ public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { when(currentUserController.getAsync()).thenReturn(Task.forResult(user)); ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); - //Parse.getEventuallyQueue().clear(); - user.put("field", "data"); JSONObject mockResponse = new JSONObject(); From ff0064bdc96205c2fa7e43317f7b15757dff783b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Tue, 29 May 2018 16:37:17 -0400 Subject: [PATCH 4/7] Fixed server response mocking in ParseUserTest.testSaveEventuallyWhenSessionIsInvalid() --- .../test/java/com/parse/ParseTestUtils.java | 18 ++++++++++++------ .../src/test/java/com/parse/ParseUserTest.java | 10 ++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Parse/src/test/java/com/parse/ParseTestUtils.java b/Parse/src/test/java/com/parse/ParseTestUtils.java index 3fcc8ade0..c2ad8d362 100644 --- a/Parse/src/test/java/com/parse/ParseTestUtils.java +++ b/Parse/src/test/java/com/parse/ParseTestUtils.java @@ -41,14 +41,20 @@ public static void setTestParseUser() { public static ParseHttpClient mockParseHttpClientWithResponse( JSONObject content, int statusCode, String reasonPhrase) throws IOException { + ParseHttpClient client = mock(ParseHttpClient.class); + updateMockParseHttpClientWithResponse(client, content, statusCode, reasonPhrase); + return client; + } + + public static ParseHttpClient updateMockParseHttpClientWithResponse( + ParseHttpClient client, JSONObject content, int statusCode, String reasonPhrase) throws IOException { byte[] contentBytes = content.toString().getBytes(); ParseHttpResponse response = new ParseHttpResponse.Builder() - .setContent(new ByteArrayInputStream(contentBytes)) - .setStatusCode(statusCode) - .setTotalSize(contentBytes.length) - .setContentType("application/json") - .build(); - ParseHttpClient client = mock(ParseHttpClient.class); + .setContent(new ByteArrayInputStream(contentBytes)) + .setStatusCode(statusCode) + .setTotalSize(contentBytes.length) + .setContentType("application/json") + .build(); when(client.execute(any(ParseHttpRequest.class))).thenReturn(response); return client; } diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index 2e97b9835..a7fb99760 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -1609,9 +1609,8 @@ public Void then(Task task) throws Exception { mockResponse = new JSONObject(); mockResponse.put("error", "invalid session token"); mockResponse.put("code", 209); - restClient = - ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 400, "Bad Request"); - when(plugins.restClient()).thenReturn(restClient); + ParseTestUtils.updateMockParseHttpClientWithResponse( + restClient, mockResponse, 400, "Bad Request"); final CountDownLatch saveEventuallyCountDown = new CountDownLatch(1); user.saveEventually().continueWith(new Continuation() { @@ -1631,9 +1630,8 @@ public Void then(Task task) throws Exception { mockResponse = new JSONObject(); mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date())); - restClient = - ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 200, "OK"); - when(plugins.restClient()).thenReturn(restClient); + ParseTestUtils.updateMockParseHttpClientWithResponse( + restClient, mockResponse, 200, "OK"); final CountDownLatch saveCountDown2 = new CountDownLatch(1); user.saveInBackground().continueWith(new Continuation() { From 6dea925e3751f2e981f55f5a6572ece808d7387b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Sat, 9 Jun 2018 23:50:13 -0400 Subject: [PATCH 5/7] Failing test for #827 fails when the deadlock at ParseObject.java:L1604 occurs --- Parse/src/main/java/com/parse/Parse.java | 10 ++- .../test/java/com/parse/ParseTestUtils.java | 37 +++++++++- .../test/java/com/parse/ParseUserTest.java | 70 ++++++++++--------- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/Parse/src/main/java/com/parse/Parse.java b/Parse/src/main/java/com/parse/Parse.java index 6cd1355f8..09198967f 100644 --- a/Parse/src/main/java/com/parse/Parse.java +++ b/Parse/src/main/java/com/parse/Parse.java @@ -248,6 +248,10 @@ public static boolean isLocalDatastoreEnabled() { * @param configuration The configuration for your application. */ public static void initialize(Configuration configuration) { + initialize(configuration, null); + } + + static void initialize(Configuration configuration, ParsePlugins parsePlugins) { if (isInitialized()) { PLog.w(TAG, "Parse is already initialized"); return; @@ -256,7 +260,11 @@ public static void initialize(Configuration configuration) { // isLocalDataStoreEnabled() to perform additional behavior. isLocalDatastoreEnabled = configuration.localDataStoreEnabled; - ParsePlugins.initialize(configuration.context, configuration); + if (parsePlugins == null) { + ParsePlugins.initialize(configuration.context, configuration); + } else { + ParsePlugins.set(parsePlugins); + } try { ParseRESTCommand.server = new URL(configuration.server); diff --git a/Parse/src/test/java/com/parse/ParseTestUtils.java b/Parse/src/test/java/com/parse/ParseTestUtils.java index c2ad8d362..bb38065b5 100644 --- a/Parse/src/test/java/com/parse/ParseTestUtils.java +++ b/Parse/src/test/java/com/parse/ParseTestUtils.java @@ -8,12 +8,15 @@ */ package com.parse; +import android.content.Context; + import com.parse.http.ParseHttpRequest; import com.parse.http.ParseHttpResponse; import org.json.JSONObject; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import bolts.Task; @@ -46,7 +49,7 @@ public static ParseHttpClient mockParseHttpClientWithResponse( return client; } - public static ParseHttpClient updateMockParseHttpClientWithResponse( + static void updateMockParseHttpClientWithResponse( ParseHttpClient client, JSONObject content, int statusCode, String reasonPhrase) throws IOException { byte[] contentBytes = content.toString().getBytes(); ParseHttpResponse response = new ParseHttpResponse.Builder() @@ -56,6 +59,36 @@ public static ParseHttpClient updateMockParseHttpClientWithResponse( .setContentType("application/json") .build(); when(client.execute(any(ParseHttpRequest.class))).thenReturn(response); - return client; + } + + static ParsePlugins mockParsePlugins(Parse.Configuration configuration) { + ParsePlugins parsePlugins = mock(ParsePlugins.class); + when(parsePlugins.applicationId()).thenReturn(configuration.applicationId); + when(parsePlugins.clientKey()).thenReturn(configuration.clientKey); + when(parsePlugins.configuration()).thenReturn(configuration); + Context applicationContext = configuration.context.getApplicationContext(); + when(parsePlugins.applicationContext()).thenReturn(applicationContext); + File parseDir = createFileDir(applicationContext.getDir("Parse", Context.MODE_PRIVATE)); + when(parsePlugins.installationId()) + .thenReturn( + new InstallationId(new File(parseDir, "installationId"))); + when(parsePlugins.getParseDir()) + .thenReturn(parseDir); + when(parsePlugins.getCacheDir()) + .thenReturn(createFileDir( + new File(applicationContext.getCacheDir(), "com.parse"))); + when(parsePlugins.getFilesDir()) + .thenReturn(createFileDir( + new File(applicationContext.getFilesDir(), "com.parse"))); + return parsePlugins; + } + + private static File createFileDir(File file) { + if (!file.exists()) { + if (!file.mkdirs()) { + return file; + } + } + return file; } } diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index a7fb99760..5e582725a 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -1555,41 +1555,35 @@ private static void setLazy(ParseUser user) { @Test public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { - - ParseRESTCommand.server = new URL("https://api.parse.com/1"); - - ParseObject.registerSubclass(EventuallyPin.class); - ParseObject.registerSubclass(ParsePin.class); - Parse.setLocalDatastore(new OfflineStore(RuntimeEnvironment.application)); - - Parse.Configuration configuration = new Parse.Configuration.Builder(RuntimeEnvironment.application) - .build(); - ParsePlugins plugins = mock(ParsePlugins.class); - when(plugins.configuration()).thenReturn(configuration); - when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application); - ParsePlugins.set(plugins); - - ShadowApplication application = Shadows.shadowOf(RuntimeEnvironment.application); - application.grantPermissions(Manifest.permission.ACCESS_NETWORK_STATE); - - ParseUser.State userState = new ParseUser.State.Builder() - .objectId("test") - .sessionToken("r:sessionToken") + Shadows.shadowOf(RuntimeEnvironment.application) + .grantPermissions(Manifest.permission.ACCESS_NETWORK_STATE); + Parse.Configuration configuration = + new Parse.Configuration.Builder(RuntimeEnvironment.application) + .applicationId(BuildConfig.APPLICATION_ID) + .server("https://api.parse.com/1") + .enableLocalDataStore() .build(); - ParseUser user = ParseObject.from(userState); - - ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); - when(currentUserController.getAsync(anyBoolean())).thenReturn(Task.forResult(user)); - when(currentUserController.getAsync()).thenReturn(Task.forResult(user)); - ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); - + ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration); + JSONObject mockResponse = new JSONObject(); + mockResponse.put("objectId", "objectId"); + mockResponse.put("email", "email@parse.com"); + mockResponse.put("username", "username"); + mockResponse.put("sessionToken", "r:sessionToken"); + mockResponse.put("createdAt", ParseDateFormat.getInstance().format(new Date(1000))); + mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(2000))); + ParseHttpClient restClient = ParseTestUtils.mockParseHttpClientWithResponse( + mockResponse,200, "OK"); + when(plugins.restClient()) + .thenReturn(restClient); + Parse.initialize(configuration, plugins); + + ParseUser user = ParseUser.logIn("username", "password"); user.put("field", "data"); - JSONObject mockResponse = new JSONObject(); - mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date())); - ParseHttpClient restClient = - ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 200, "OK"); - when(plugins.restClient()).thenReturn(restClient); + mockResponse = new JSONObject(); + mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(3000))); + ParseTestUtils.updateMockParseHttpClientWithResponse( + restClient, mockResponse, 200, "OK"); final CountDownLatch saveCountDown1 = new CountDownLatch(1); final Capture exceptionCapture = new Capture<>(); @@ -1626,10 +1620,16 @@ public Void then(Task task) throws Exception { assertEquals(ParseException.INVALID_SESSION_TOKEN, ((ParseException)exceptionCapture.get()).getCode()); assertEquals("invalid session token", exceptionCapture.get().getMessage()); + // Simulate reboot + Parse.destroy(); + Parse.initialize(configuration, plugins); + + user = ParseUser.getCurrentUser(); + assertEquals("other data", user.get("field")); user.put("field", "another data"); mockResponse = new JSONObject(); - mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date())); + mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(4000))); ParseTestUtils.updateMockParseHttpClientWithResponse( restClient, mockResponse, 200, "OK"); @@ -1644,6 +1644,10 @@ public Void then(Task task) throws Exception { }); assertTrue(saveCountDown2.await(5, TimeUnit.SECONDS)); assertNull(exceptionCapture.get()); + + ParseCorePlugins.getInstance().getCurrentUserController().clearFromDisk(); + ParseCorePlugins.getInstance().getCurrentInstallationController().clearFromDisk(); + Parse.destroy(); } //endregion From 605e8a25933c4352f0cd8ef2190f63dd1447308c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Wed, 13 Jun 2018 14:20:36 -0400 Subject: [PATCH 6/7] * Suggestion of fix for #827 * Improved test for #827 : - Generalize to any server error that is different to CONNECTION_FAILED (not only INVALID_SESSION_TOKEN) - Added isDirty() checks - fixed test tearDown --- Parse/src/main/java/com/parse/Parse.java | 4 ++ .../src/main/java/com/parse/ParseObject.java | 36 +++++++++------ .../test/java/com/parse/ParseUserTest.java | 44 ++++++++++++------- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/Parse/src/main/java/com/parse/Parse.java b/Parse/src/main/java/com/parse/Parse.java index 09198967f..248f387c3 100644 --- a/Parse/src/main/java/com/parse/Parse.java +++ b/Parse/src/main/java/com/parse/Parse.java @@ -319,6 +319,8 @@ public Void then(Task task) throws Exception { } static void destroy() { + ParseObject.unregisterParseSubclasses(); + ParseEventuallyQueue queue; synchronized (MUTEX) { queue = eventuallyQueue; @@ -330,6 +332,8 @@ static void destroy() { ParseCorePlugins.getInstance().reset(); ParsePlugins.reset(); + + setLocalDatastore(null); } /** diff --git a/Parse/src/main/java/com/parse/ParseObject.java b/Parse/src/main/java/com/parse/ParseObject.java index 6c4046bb6..551c90010 100644 --- a/Parse/src/main/java/com/parse/ParseObject.java +++ b/Parse/src/main/java/com/parse/ParseObject.java @@ -1420,6 +1420,20 @@ private ParseRESTObjectCommand currentSaveEventuallyCommand( final ParseObject.State result, final ParseOperationSet operationsBeforeSave) { Task task = Task.forResult(null); + /* + * If this object is in the offline store, then we need to make sure that we pull in any dirty + * changes it may have before merging the server data into it. + */ + final OfflineStore store = Parse.getLocalDatastore(); + if (store != null) { + task = task.onSuccessTask(new Continuation>() { + @Override + public Task then(Task task) throws Exception { + return store.fetchLocallyAsync(ParseObject.this).makeVoid(); + } + }); + } + final boolean success = result != null; synchronized (mutex) { // Find operationsBeforeSave in the queue so that we can remove it and move to the next @@ -1433,24 +1447,18 @@ private ParseRESTObjectCommand currentSaveEventuallyCommand( // Merge the data from the failed save into the next save. ParseOperationSet nextOperation = opIterator.next(); nextOperation.mergeFrom(operationsBeforeSave); + if (store != null) { + task = task.onSuccessTask(new Continuation>() { + @Override + public Task then(Task task) throws Exception { + return store.updateDataForObjectAsync(ParseObject.this); + } + }); + } return task; } } - /* - * If this object is in the offline store, then we need to make sure that we pull in any dirty - * changes it may have before merging the server data into it. - */ - final OfflineStore store = Parse.getLocalDatastore(); - if (store != null) { - task = task.onSuccessTask(new Continuation>() { - @Override - public Task then(Task task) throws Exception { - return store.fetchLocallyAsync(ParseObject.this).makeVoid(); - } - }); - } - // fetchLocallyAsync will return an error if this object isn't in the LDS yet and that's ok task = task.continueWith(new Continuation() { @Override diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index 5e582725a..690b2cbee 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -24,9 +24,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; -import java.net.URL; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -77,9 +75,19 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { super.tearDown(); - ParseObject.unregisterSubclass(ParseUser.class); - ParseObject.unregisterSubclass(ParseSession.class); - Parse.disableLocalDatastore(); + if (ParsePlugins.get() != null) { + ParseCurrentInstallationController installationController = + ParseCorePlugins.getInstance().getCurrentInstallationController(); + if (installationController != null) { + installationController.clearFromDisk(); + } + ParseCurrentUserController userController = + ParseCorePlugins.getInstance().getCurrentUserController(); + if (userController != null) { + userController.clearFromDisk(); + } + } + Parse.destroy(); } @Test @@ -1551,10 +1559,10 @@ private static void setLazy(ParseUser user) { user.putAuthData(ParseAnonymousUtils.AUTH_TYPE, anonymousAuthData); } - //region testSaveEventuallyWhenSessionIsInvalid + //region testSaveEventuallyWhenServerError @Test - public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { + public void testSaveEventuallyWhenServerError() throws Exception { Shadows.shadowOf(RuntimeEnvironment.application) .grantPermissions(Manifest.permission.ACCESS_NETWORK_STATE); Parse.Configuration configuration = @@ -1578,7 +1586,10 @@ public void testSaveEventuallyWhenSessionIsInvalid() throws Exception { Parse.initialize(configuration, plugins); ParseUser user = ParseUser.logIn("username", "password"); + assertFalse(user.isDirty()); + user.put("field", "data"); + assertTrue(user.isDirty()); mockResponse = new JSONObject(); mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(3000))); @@ -1597,12 +1608,14 @@ public Void then(Task task) throws Exception { }); assertTrue(saveCountDown1.await(5, TimeUnit.SECONDS)); assertNull(exceptionCapture.get()); + assertFalse(user.isDirty()); user.put("field", "other data"); + assertTrue(user.isDirty()); mockResponse = new JSONObject(); - mockResponse.put("error", "invalid session token"); - mockResponse.put("code", 209); + mockResponse.put("error", "Save is not allowed"); + mockResponse.put("code", 141); ParseTestUtils.updateMockParseHttpClientWithResponse( restClient, mockResponse, 400, "Bad Request"); @@ -1617,14 +1630,17 @@ public Void then(Task task) throws Exception { }); assertTrue(saveEventuallyCountDown.await(5, TimeUnit.SECONDS)); assertTrue(exceptionCapture.get() instanceof ParseException); - assertEquals(ParseException.INVALID_SESSION_TOKEN, ((ParseException)exceptionCapture.get()).getCode()); - assertEquals("invalid session token", exceptionCapture.get().getMessage()); + assertEquals(ParseException.SCRIPT_ERROR, ((ParseException)exceptionCapture.get()).getCode()); + assertEquals("Save is not allowed", exceptionCapture.get().getMessage()); + assertTrue(user.isDirty()); // Simulate reboot Parse.destroy(); Parse.initialize(configuration, plugins); user = ParseUser.getCurrentUser(); + assertTrue(user.isDirty()); + assertEquals("other data", user.get("field")); user.put("field", "another data"); @@ -1642,12 +1658,10 @@ public Void then(Task task) throws Exception { return null; } }); + assertTrue(saveCountDown2.await(5, TimeUnit.SECONDS)); assertNull(exceptionCapture.get()); - - ParseCorePlugins.getInstance().getCurrentUserController().clearFromDisk(); - ParseCorePlugins.getInstance().getCurrentInstallationController().clearFromDisk(); - Parse.destroy(); + assertFalse(user.isDirty()); } //endregion From 0f5e5af7c64dacfeefa945a19a288bf0dab733ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude=20Joseph-Ange=CC=81lique?= Date: Wed, 13 Jun 2018 16:05:19 -0400 Subject: [PATCH 7/7] Working fix suggestion for #827 --- Parse/src/main/java/com/parse/ParseObject.java | 8 ++++++-- Parse/src/test/java/com/parse/ParseInstallationTest.java | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseObject.java b/Parse/src/main/java/com/parse/ParseObject.java index 551c90010..e6d36c2ef 100644 --- a/Parse/src/main/java/com/parse/ParseObject.java +++ b/Parse/src/main/java/com/parse/ParseObject.java @@ -1448,10 +1448,14 @@ public Task then(Task task) throws Exception { ParseOperationSet nextOperation = opIterator.next(); nextOperation.mergeFrom(operationsBeforeSave); if (store != null) { - task = task.onSuccessTask(new Continuation>() { + task = task.continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { - return store.updateDataForObjectAsync(ParseObject.this); + if (task.isFaulted()) { + return Task.forResult(null); + } else { + return store.updateDataForObjectAsync(ParseObject.this); + } } }); } diff --git a/Parse/src/test/java/com/parse/ParseInstallationTest.java b/Parse/src/test/java/com/parse/ParseInstallationTest.java index a8a8a4233..d4c4c7b27 100644 --- a/Parse/src/test/java/com/parse/ParseInstallationTest.java +++ b/Parse/src/test/java/com/parse/ParseInstallationTest.java @@ -146,7 +146,7 @@ public void testMissingRequiredFieldWhenSaveAsync() throws Exception { ParseInstallation installation = ParseInstallation.getCurrentInstallation(); assertNotNull(installation); installation.put("key", "value"); - installation.saveAsync(sessionToken, toAwait); + ParseTaskUtils.wait(installation.saveAsync(sessionToken, toAwait)); verify(controller).getAsync(); verify(objController, times(2)).saveAsync( any(ParseObject.State.class), @@ -187,7 +187,7 @@ public void testObjectNotFoundWhenSaveAsync() throws Exception { assertNotNull(installation); installation.setState(state); installation.put("key", "value"); - installation.saveAsync(sessionToken, toAwait); + ParseTaskUtils.wait(installation.saveAsync(sessionToken, toAwait)); verify(controller).getAsync(); verify(objController, times(2)).saveAsync(