Skip to content

Fix signUp and linkWith with an anonymous user #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 44 additions & 56 deletions Parse/src/main/java/com/parse/ParseUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -475,26 +475,28 @@ private void restoreAnonymity(Map<String, String> anonymousData) {

@Override
/* package */ Task<Void> saveAsync(String sessionToken, Task<Void> toAwait) {
synchronized (mutex) {
Task<Void> task;
if (isLazy()) {
task = resolveLazinessAsync(toAwait).makeVoid();
} else {
task = super.saveAsync(sessionToken, toAwait);
}
return saveAsync(sessionToken, isLazy(), toAwait);
}

return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
// If the user is the currently logged in user, we persist all data to disk
if (isCurrentUser()) {
cleanUpAuthData();
return saveCurrentUserAsync(ParseUser.this);
}
return Task.forResult(null);
}
});
/* package for tests */ Task<Void> saveAsync(String sessionToken, boolean isLazy, Task<Void> toAwait) {
Task<Void> task;
if (isLazy) {
task = resolveLazinessAsync(toAwait);
} else {
task = super.saveAsync(sessionToken, toAwait);
}

return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
// If the user is the currently logged in user, we persist all data to disk
if (isCurrentUser()) {
cleanUpAuthData();
return saveCurrentUserAsync(ParseUser.this);
}
return Task.forResult(null);
}
});
}

@Override
Expand Down Expand Up @@ -622,6 +624,7 @@ public Task<Void> then(Task<Void> task) throws Exception {
checkForChangesToMutableContainers();
user.checkForChangesToMutableContainers();

boolean isLazy = user.isLazy();
final String oldUsername = user.getUsername();
final String oldPassword = user.getPassword();
final Map<String, String> anonymousData = user.getAuthData(ParseAnonymousUtils.AUTH_TYPE);
Expand All @@ -631,21 +634,26 @@ public Task<Void> then(Task<Void> task) throws Exception {
user.setPassword(getPassword());
revert();

return user.saveAsync(sessionToken, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
return user.saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
if (task.isCancelled() || task.isFaulted()) { // Error
synchronized (user.mutex) {
if (oldUsername != null) {
user.setUsername(oldUsername);
} else {
user.revert(KEY_USERNAME);
}
if (oldPassword != null) {
user.setPassword(oldPassword);
} else {
user.revert(KEY_PASSWORD);
}
user.restoreAnonymity(anonymousData);
}
return task;
} else { // Success
user.revert(KEY_PASSWORD);
revert(KEY_PASSWORD);
}

Expand Down Expand Up @@ -1165,7 +1173,7 @@ public Task<Void> then(Task<Void> task) throws Exception {
user.stripAnonymity();
user.putAuthData(authType, authData);

return user.resolveLazinessAsync(task).makeVoid();
return user.resolveLazinessAsync(task);
}
}
}).continueWithTask(new Continuation<Void, Task<ParseUser>>() {
Expand Down Expand Up @@ -1224,20 +1232,14 @@ private Task<Void> linkWithAsync(
final Map<String, String> authData,
final Task<Void> toAwait,
final String sessionToken) {
final Map<String, String> oldAnonymousData = getAuthData(ParseAnonymousUtils.AUTH_TYPE);
synchronized (mutex) {
final boolean isLazy = isLazy();
final Map<String, String> oldAnonymousData = getAuthData(ParseAnonymousUtils.AUTH_TYPE);

stripAnonymity();
putAuthData(authType, authData);

// TODO(grantland): Should we really be getting the current user's sessionToken?
// What if we're not the current user?
// TODO(mengyan): Delete getCurrentSessionTokenAsync or delete the inject sessionToken
return ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<Void>>() {
@Override
public Task<Void> then(Task<String> task) throws Exception {
return saveAsync(sessionToken, toAwait);
}
}).continueWithTask(new Continuation<Void, Task<Void>>() {
return saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
synchronized (mutex) {
Expand Down Expand Up @@ -1285,32 +1287,22 @@ private void logOutWith(String authType) {
* @return A {@code Task} that will resolve to the current user. If this is a SignUp it'll be this
* {@code ParseUser} instance, otherwise it'll be a new {@code ParseUser} instance.
*/
/* package for tests */ Task<ParseUser> resolveLazinessAsync(Task<Void> toAwait) {
/* package for tests */ Task<Void> resolveLazinessAsync(Task<Void> toAwait) {
synchronized (mutex) {
if (!isLazy()) {
return Task.forResult(null);
}
if (getAuthData().size() == 0) { // TODO(grantland): Could we just check isDirty(KEY_AUTH_DATA)?
// If there are no linked services, treat this as a SignUp.
return signUpAsync(toAwait).onSuccess(new Continuation<Void, ParseUser>() {
@Override
public ParseUser then(Task<Void> task) throws Exception {
synchronized (mutex) {
return ParseUser.this;
}
}
});
return signUpAsync(toAwait);
}

final ParseOperationSet operations = startSave();

// Otherwise, treat this as a SignUpOrLogIn
return toAwait.onSuccessTask(new Continuation<Void, Task<ParseUser>>() {
return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<ParseUser> then(Task<Void> task) throws Exception {
return getUserController().logInAsync(getState(), operations).onSuccessTask(new Continuation<ParseUser.State, Task<ParseUser>>() {
public Task<Void> then(Task<Void> task) throws Exception {
return getUserController().logInAsync(getState(), operations).onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
@Override
public Task<ParseUser> then(Task<ParseUser.State> task) throws Exception {
public Task<Void> then(Task<ParseUser.State> task) throws Exception {
final ParseUser.State result = task.getResult();

Task<ParseUser.State> resultTask;
Expand All @@ -1319,29 +1311,25 @@ public Task<ParseUser> then(Task<ParseUser.State> task) throws Exception {
if (Parse.isLocalDatastoreEnabled() && !result.isNew()) {
resultTask = Task.forResult(result);
} else {
resultTask = handleSaveResultAsync(result, operations).onSuccess(new Continuation<Void, ParseUser.State>() {
resultTask = handleSaveResultAsync(result,
operations).onSuccess(new Continuation<Void, ParseUser.State>() {
@Override
public ParseUser.State then(Task<Void> task) throws Exception {
return result;
}
});
}
return resultTask.onSuccessTask(new Continuation<ParseUser.State, Task<ParseUser>>() {
return resultTask.onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
@Override
public Task<ParseUser> then(Task<ParseUser.State> task) throws Exception {
public Task<Void> then(Task<ParseUser.State> task) throws Exception {
ParseUser.State result = task.getResult();
if (!result.isNew()) {
// If the result is not a new user, treat this as a fresh logIn with complete
// serverData, and switch the current user to the new user.
final ParseUser newUser = ParseObject.from(result);
return saveCurrentUserAsync(newUser).onSuccess(new Continuation<Void, ParseUser>() {
@Override
public ParseUser then(Task<Void> task) throws Exception {
return newUser;
}
});
return saveCurrentUserAsync(newUser);
}
return Task.forResult(ParseUser.this);
return task.makeVoid();
}
});
}
Expand Down
58 changes: 29 additions & 29 deletions Parse/src/test/java/com/parse/ParseUserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
Expand Down Expand Up @@ -217,10 +218,11 @@ public void testSignUpAsyncWithMergeInDiskAnonymousUser() throws Exception {
ParseUser currentUser = mock(ParseUser.class);
when(currentUser.getUsername()).thenReturn("oldUserName");
when(currentUser.getPassword()).thenReturn("oldPassword");
when(currentUser.isLazy()).thenReturn(false);
when(currentUser.isLinked(ParseAnonymousUtils.AUTH_TYPE)).thenReturn(true);
when(currentUser.getSessionToken()).thenReturn("oldSessionToken");
when(currentUser.getAuthData()).thenReturn(new HashMap<String, Map<String, String>>());
when(currentUser.saveAsync(anyString(), Matchers.<Task<Void>>any()))
when(currentUser.saveAsync(anyString(), eq(false), Matchers.<Task<Void>>any()))
.thenReturn(Task.<Void>forResult(null));
ParseUser.State state = new ParseUser.State.Builder()
.put("oldKey", "oldValue")
Expand Down Expand Up @@ -248,7 +250,8 @@ public void testSignUpAsyncWithMergeInDiskAnonymousUser() throws Exception {
verify(currentUser, times(1)).setUsername("userName");
verify(currentUser, times(1)).setPassword("password");
// Make sure we save currentUser
verify(currentUser, times(1)).saveAsync(eq("oldSessionToken"), Matchers.<Task<Void>>any());
verify(currentUser, times(1))
.saveAsync(eq("oldSessionToken"), eq(false), Matchers.<Task<Void>>any());
// Make sure we merge currentUser with user after save
assertEquals("oldValue", user.get("oldKey"));
// Make sure set currentUser
Expand All @@ -262,14 +265,15 @@ public void testSignUpAsyncWithMergeInDiskAnonymousUserSaveFailure() throws Exce
Map<String, String> oldAnonymousAuthData = new HashMap<>();
oldAnonymousAuthData.put("oldKey", "oldToken");
currentUser.putAuthData(ParseAnonymousUtils.AUTH_TYPE, oldAnonymousAuthData);
ParseUser partialMockCurrentUser = spy(currentUser);
ParseUser partialMockCurrentUser = spy(currentUser); // Spy since we need mutex
when(partialMockCurrentUser.getUsername()).thenReturn("oldUserName");
when(partialMockCurrentUser.getPassword()).thenReturn("oldPassword");
when(partialMockCurrentUser.getSessionToken()).thenReturn("oldSessionToken");
when(partialMockCurrentUser.isLazy()).thenReturn(false);
ParseException saveException = new ParseException(ParseException.OTHER_CAUSE, "");
doReturn(Task.<Void>forError(saveException))
.when(partialMockCurrentUser)
.saveAsync(anyString(), Matchers.<Task<Void>>any());
.saveAsync(anyString(), eq(false), Matchers.<Task<Void>>any());
ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class);
when(currentUserController.getAsync(anyBoolean()))
.thenReturn(Task.forResult(partialMockCurrentUser));
Expand All @@ -294,7 +298,7 @@ public void testSignUpAsyncWithMergeInDiskAnonymousUserSaveFailure() throws Exce
verify(partialMockCurrentUser, times(1)).copyChangesFrom(eq(user));
// Make sure we save currentUser
verify(partialMockCurrentUser, times(1))
.saveAsync(eq("oldSessionToken"), Matchers.<Task<Void>>any());
.saveAsync(eq("oldSessionToken"), eq(false), Matchers.<Task<Void>>any());
// Make sure we restore old username and password after save fails
verify(partialMockCurrentUser, times(1)).setUsername("oldUserName");
verify(partialMockCurrentUser, times(1)).setPassword("oldPassword");
Expand Down Expand Up @@ -574,7 +578,7 @@ public void testLinkWithAsyncWithSaveAsyncSuccess() throws Exception {
ParseUser partialMockUser = spy(user);
doReturn(Task.<Void>forResult(null))
.when(partialMockUser)
.saveAsync(anyString(), Matchers.<Task<Void>>any());
.saveAsync(anyString(), eq(false), Matchers.<Task<Void>>any());
String authType = "facebook";
Map<String, String> authData = new HashMap<>();
authData.put("token", "test");
Expand All @@ -586,7 +590,8 @@ public void testLinkWithAsyncWithSaveAsyncSuccess() throws Exception {
// Make sure new authData is added
assertSame(authData, partialMockUser.getAuthData().get("facebook"));
// Make sure we save the user
verify(partialMockUser, times(1)).saveAsync(eq("sessionTokenAgain"), Matchers.<Task<Void>>any());
verify(partialMockUser, times(1))
.saveAsync(eq("sessionTokenAgain"), eq(false), Matchers.<Task<Void>>any());
// Make sure synchronizeAuthData() is called
verify(provider, times(1)).restoreAuthentication(authData);
}
Expand All @@ -609,7 +614,7 @@ public void testLinkWithAsyncWithSaveAsyncFailure() throws Exception {
Exception saveException = new Exception();
doReturn(Task.<Void>forError(saveException))
.when(partialMockUser)
.saveAsync(anyString(), Matchers.<Task<Void>>any());
.saveAsync(anyString(), eq(false), Matchers.<Task<Void>>any());
String facebookAuthType = "facebook";
Map<String, String> facebookAuthData = new HashMap<>();
facebookAuthData.put("facebookToken", "facebookTest");
Expand All @@ -621,7 +626,8 @@ public void testLinkWithAsyncWithSaveAsyncFailure() throws Exception {
// Make sure new authData is added
assertSame(facebookAuthData, partialMockUser.getAuthData().get("facebook"));
// Make sure we save the user
verify(partialMockUser, times(1)).saveAsync(eq("sessionTokenAgain"), Matchers.<Task<Void>>any());
verify(partialMockUser, times(1))
.saveAsync(eq("sessionTokenAgain"), eq(false), Matchers.<Task<Void>>any());
// Make sure old authData is restored
assertSame(anonymousAuthData, partialMockUser.getAuthData().get(ParseAnonymousUtils.AUTH_TYPE));
// Verify exception
Expand All @@ -632,13 +638,6 @@ public void testLinkWithAsyncWithSaveAsyncFailure() throws Exception {

//region testResolveLazinessAsync

@Test
public void testResolveLazinessAsyncWithNotLazyUser() throws Exception {
ParseUser user = new ParseUser();

ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
}

@Test
public void testResolveLazinessAsyncWithAuthDataAndNotNewUser() throws Exception {
ParseUser user = new ParseUser();
Expand All @@ -660,14 +659,16 @@ public void testResolveLazinessAsyncWithAuthDataAndNotNewUser() throws Exception
.thenReturn(Task.<Void>forResult(null));
ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController);

ParseUser userAfterResolveLaziness =
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
ArgumentCaptor<ParseUser> userAfterResolveLazinessCaptor =
ArgumentCaptor.forClass(ParseUser.class);

// Make sure we logIn the lazy user
verify(userController, times(1)).logInAsync(
any(ParseUser.State.class), any(ParseOperationSet.class));
// Make sure we save currentUser
verify(currentUserController, times(1)).setAsync(any(ParseUser.class));
verify(currentUserController, times(1)).setAsync(userAfterResolveLazinessCaptor.capture());
ParseUser userAfterResolveLaziness = userAfterResolveLazinessCaptor.getValue();
// Make sure user's data is correct
assertEquals("newSessionToken", userAfterResolveLaziness.getSessionToken());
assertEquals("newValue", userAfterResolveLaziness.get("newKey"));
Expand Down Expand Up @@ -697,21 +698,18 @@ public void testResolveLazinessAsyncWithAuthDataAndNewUser() throws Exception {
ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class);
ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController);

ParseUser userAfterResolveLaziness =
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));

// Make sure we logIn the lazy user
verify(userController, times(1)).logInAsync(
any(ParseUser.State.class), any(ParseOperationSet.class));
// Make sure we do not save currentUser
verify(currentUserController, never()).setAsync(any(ParseUser.class));
// Make sure userAfterResolveLaziness's data is correct
assertEquals("newSessionToken", userAfterResolveLaziness.getSessionToken());
assertEquals("newValue", userAfterResolveLaziness.get("newKey"));
assertEquals("newSessionToken", user.getSessionToken());
assertEquals("newValue", user.get("newKey"));
// Make sure userAfterResolveLaziness is not lazy
assertFalse(userAfterResolveLaziness.isLazy());
// Make sure we do not create new user
assertSame(user, userAfterResolveLaziness);
assertFalse(user.isLazy());
}

@Test
Expand Down Expand Up @@ -739,8 +737,9 @@ public void testResolveLazinessAsyncWithAuthDataAndNotNewUserAndLDSEnabled() thr
// Enable LDS
Parse.enableLocalDatastore(null);

ParseUser userAfterResolveLaziness =
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
ParseTaskUtils.wait(user.resolveLazinessAsync(Task.<Void>forResult(null)));
ArgumentCaptor<ParseUser> userAfterResolveLazinessCaptor =
ArgumentCaptor.forClass(ParseUser.class);

// Make sure we logIn the lazy user
verify(userController, times(1)).logInAsync(
Expand All @@ -749,7 +748,8 @@ public void testResolveLazinessAsyncWithAuthDataAndNotNewUserAndLDSEnabled() thr
// field should be cleaned
assertEquals("password", user.getPassword());
// Make sure we do not save currentUser
verify(currentUserController, times(1)).setAsync(any(ParseUser.class));
verify(currentUserController, times(1)).setAsync(userAfterResolveLazinessCaptor.capture());
ParseUser userAfterResolveLaziness = userAfterResolveLazinessCaptor.getValue();
// Make sure userAfterResolveLaziness's data is correct
assertEquals("newSessionToken", userAfterResolveLaziness.getSessionToken());
assertEquals("newValue", userAfterResolveLaziness.get("newKey"));
Expand Down