Mholloway has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/334371 )

Change subject: Get user ID after logging in or creating an account
......................................................................

Get user ID after logging in or creating an account

The MW API doesn't return user id on login or account creation success.
We have to get it separately.  Currently, we don't do this, and the user id
in the app is always 0.

It seems the numeric user ID is needed for EventLogging, so we'd better
get it.

For the login case, this adds another paramter to the request that's
already made after login to retrieve group memberships, so that the user
ID is also returned.

For account creation, a simple new UserIdClient is added to make the
follow-up request.

TODO: Update the user ID with the new UserIdClient when the app language
state changes, since a user's numeric ID varies by wiki.  This will come
in the next patch in this series.

Bug: T149915
Change-Id: I3dc7871805edf51d2de6598dd5a5e67140f416b5
---
M app/src/main/java/org/wikipedia/createaccount/CreateAccountActivity.java
M app/src/main/java/org/wikipedia/createaccount/CreateAccountClient.java
M app/src/main/java/org/wikipedia/createaccount/CreateAccountSuccessResult.java
D app/src/main/java/org/wikipedia/login/GroupMembershipClient.java
M app/src/main/java/org/wikipedia/login/LoginClient.java
M app/src/main/java/org/wikipedia/login/User.java
A app/src/main/java/org/wikipedia/login/UserExtendedInfoClient.java
A app/src/main/java/org/wikipedia/login/UserIdClient.java
M 
app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
M app/src/main/java/org/wikipedia/useroption/dataclient/UserInfo.java
A app/src/test/java/org/wikipedia/login/UserIdClientTest.java
A app/src/test/res/raw/user_info.json
12 files changed, 373 insertions(+), 178 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia 
refs/changes/71/334371/1

diff --git 
a/app/src/main/java/org/wikipedia/createaccount/CreateAccountActivity.java 
b/app/src/main/java/org/wikipedia/createaccount/CreateAccountActivity.java
index f101268..a63c8d7 100644
--- a/app/src/main/java/org/wikipedia/createaccount/CreateAccountActivity.java
+++ b/app/src/main/java/org/wikipedia/createaccount/CreateAccountActivity.java
@@ -27,6 +27,8 @@
 import org.wikipedia.captcha.CaptchaResult;
 import org.wikipedia.dataclient.WikiSite;
 import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.login.UserIdClient;
+import org.wikipedia.useroption.dataclient.UserInfo;
 import org.wikipedia.util.FeedbackUtil;
 import org.wikipedia.util.log.L;
 import org.wikipedia.views.NonEmptyValidator;
@@ -262,21 +264,24 @@
                 captchaHandler.isActive() ? captchaHandler.captchaWord() : 
"null",
                 new CreateAccountClient.Callback() {
                     @Override
-                    public void success(@NonNull Call<CreateAccountResponse> 
call, @NonNull CreateAccountSuccessResult result) {
+                    public void success(@NonNull Call<CreateAccountResponse> 
call,
+                                        @NonNull final 
CreateAccountSuccessResult result) {
                         if (!progressDialog.isShowing()) {
                             // no longer attached to activity!
                             return;
                         }
-                        createAccountResult = result;
-                        progressDialog.dismiss();
-                        captchaHandler.cancelCaptcha();
-                        funnel.logSuccess();
-                        hideSoftKeyboard(CreateAccountActivity.this);
-                        Intent resultIntent = new Intent();
-                        resultIntent.putExtra("username", 
result.getUsername());
-                        resultIntent.putExtra("password", 
passwordEdit.getText().toString());
-                        setResult(RESULT_ACCOUNT_CREATED, resultIntent);
-                        finish();
+                        getUserId(new UserIdCallback() {
+                            @Override
+                            public void success(@NonNull 
Call<MwQueryResponse<UserInfo>> call, int userId) {
+                                finishWithUserResult(result, userId);
+                            }
+
+                            @Override
+                            public void failure(@NonNull 
Call<MwQueryResponse<UserInfo>> call, @NonNull Throwable caught) {
+                                finishWithUserResult(result, 0);
+                                L.e("Failed to obtain new user ID", caught);
+                            }
+                        });
                     }
 
                     @Override
@@ -311,4 +316,41 @@
         }
         super.onStop();
     }
+
+    // Get the new user ID, which the createaccount API doesn't provide to us 
on success :(
+    // TODO: See about getting this into the API success response so we dont' 
have to make another call.
+    private void getUserId(@NonNull final UserIdCallback cb) {
+        UserIdClient infoClient = new UserIdClient();
+        infoClient.request(wiki, new UserIdClient.Callback() {
+            @Override
+            public void success(@NonNull Call<MwQueryResponse<UserInfo>> call, 
int userId) {
+                cb.success(call, userId);
+            }
+
+            @Override
+            public void failure(@NonNull Call<MwQueryResponse<UserInfo>> call, 
@NonNull Throwable caught) {
+                cb.failure(call, caught);
+            }
+        });
+    }
+
+    private void finishWithUserResult(@NonNull CreateAccountSuccessResult 
result, int userId) {
+        Intent resultIntent = new Intent();
+        resultIntent.putExtra("username", result.getUsername());
+        resultIntent.putExtra("password", passwordEdit.getText().toString());
+        resultIntent.putExtra("userId", userId);
+        setResult(RESULT_ACCOUNT_CREATED, resultIntent);
+
+        createAccountResult = result;
+        progressDialog.dismiss();
+        captchaHandler.cancelCaptcha();
+        funnel.logSuccess();
+        hideSoftKeyboard(CreateAccountActivity.this);
+        finish();
+    }
+
+    private interface UserIdCallback {
+        void success(@NonNull Call<MwQueryResponse<UserInfo>> call, int 
userId);
+        void failure(@NonNull Call<MwQueryResponse<UserInfo>> call, @NonNull 
Throwable caught);
+    }
 }
diff --git 
a/app/src/main/java/org/wikipedia/createaccount/CreateAccountClient.java 
b/app/src/main/java/org/wikipedia/createaccount/CreateAccountClient.java
index 45ede87..e673fce 100644
--- a/app/src/main/java/org/wikipedia/createaccount/CreateAccountClient.java
+++ b/app/src/main/java/org/wikipedia/createaccount/CreateAccountClient.java
@@ -49,8 +49,7 @@
                 if (response.isSuccessful()) {
                     if (response.body().hasResult()) {
                         CreateAccountResponse result = response.body();
-                        String status = result.status();
-                        if ("PASS".equals(status)) {
+                        if ("PASS".equals(result.status())) {
                             cb.success(call, new 
CreateAccountSuccessResult(result.user()));
                         } else {
                             cb.failure(call, new 
CreateAccountException(result.message()));
diff --git 
a/app/src/main/java/org/wikipedia/createaccount/CreateAccountSuccessResult.java 
b/app/src/main/java/org/wikipedia/createaccount/CreateAccountSuccessResult.java
index 61827a6..835b75b 100644
--- 
a/app/src/main/java/org/wikipedia/createaccount/CreateAccountSuccessResult.java
+++ 
b/app/src/main/java/org/wikipedia/createaccount/CreateAccountSuccessResult.java
@@ -2,16 +2,17 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
 
-public class CreateAccountSuccessResult extends CreateAccountResult implements 
Parcelable {
-    private final String username;
+class CreateAccountSuccessResult extends CreateAccountResult implements 
Parcelable {
+    private String username;
 
-    public CreateAccountSuccessResult(String username) {
+    CreateAccountSuccessResult(@NonNull String username) {
         super("PASS", "Account created");
         this.username = username;
     }
 
-    public String getUsername() {
+    String getUsername() {
         return username;
     }
 
@@ -21,7 +22,7 @@
         parcel.writeString(username);
     }
 
-    protected CreateAccountSuccessResult(Parcel in) {
+    private CreateAccountSuccessResult(Parcel in) {
         super(in);
         username = in.readString();
     }
diff --git a/app/src/main/java/org/wikipedia/login/GroupMembershipClient.java 
b/app/src/main/java/org/wikipedia/login/GroupMembershipClient.java
deleted file mode 100644
index dcad9e4..0000000
--- a/app/src/main/java/org/wikipedia/login/GroupMembershipClient.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.wikipedia.login;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import com.google.gson.annotations.SerializedName;
-
-import org.wikipedia.dataclient.WikiSite;
-import org.wikipedia.dataclient.mwapi.MwQueryResponse;
-import org.wikipedia.dataclient.retrofit.MwCachedService;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-import retrofit2.http.POST;
-import retrofit2.http.Query;
-
-/**
- * Retrofit DataClient to retrieve implicit group membership information for a 
specific user.
- */
-class GroupMembershipClient {
-    @NonNull private final MwCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
-
-    @Nullable private Call<MwQueryResponse<UserMemberships>> groupCall;
-
-    interface GroupMembershipCallback {
-        void success(@NonNull Set<String> result);
-        void error(@NonNull Throwable caught);
-    }
-
-    public void request(@NonNull final WikiSite wiki, @NonNull final String 
userName,
-                        @NonNull final GroupMembershipCallback cb) {
-        cancel();
-
-        groupCall = cachedService.service(wiki).listUsers(userName);
-        groupCall.enqueue(new Callback<MwQueryResponse<UserMemberships>>() {
-            @Override
-            public void onResponse(Call<MwQueryResponse<UserMemberships>> call,
-                                   Response<MwQueryResponse<UserMemberships>> 
response) {
-                if (response.isSuccessful()) {
-                    final MwQueryResponse<UserMemberships> body = 
response.body();
-                    final UserMemberships query = body.query();
-                    if (query != null) {
-                        cb.success(query.getGroupsFor(userName));
-                    } else if (body.getError() != null) {
-                        cb.error(new LoginClient.LoginFailedException(
-                                "Failed to retrieve group membership data. "
-                                        + body.getError().toString()));
-                    } else {
-                        cb.error(new LoginClient.LoginFailedException(
-                                "Unexpected error trying to retrieve group 
membership data. "
-                                        + body.toString()));
-                    }
-                } else {
-                    cb.error(new 
LoginClient.LoginFailedException(response.message()));
-                }
-            }
-
-            @Override
-            public void onFailure(Call<MwQueryResponse<UserMemberships>> call, 
Throwable caught) {
-                cb.error(caught);
-            }
-        });
-    }
-
-    public void cancel() {
-        cancelTokenRequest();
-    }
-
-    private void cancelTokenRequest() {
-        if (groupCall == null) {
-            return;
-        }
-        groupCall.cancel();
-        groupCall = null;
-    }
-
-    private interface Service {
-
-        /** Request the implicit groups a user belongs to. */
-        @NonNull
-        @POST("w/api.php?format=json&formatversion=2&action=query"
-                + "&list=users&usprop=implicitgroups")
-        Call<MwQueryResponse<UserMemberships>> listUsers(
-                @Query("ususers") @NonNull String userName);
-    }
-
-    private static final class UserMemberships {
-        @SuppressWarnings("MismatchedReadAndWriteOfArray") 
@SerializedName("users") @NonNull
-        private List<ListUsersResponse> users = Collections.emptyList();
-
-        @NonNull Set<String> getGroupsFor(@NonNull String userName) {
-            if (!users.isEmpty()) {
-                for (ListUsersResponse user : users) {
-                    final Set<String> groups = user.getGroupsFor(userName);
-                    if (groups != null) {
-                        return groups;
-                    }
-                }
-            }
-            return Collections.emptySet();
-        }
-
-        private static final class ListUsersResponse {
-            @SerializedName("name") @Nullable private String name;
-
-            @SerializedName("implicitgroups") @Nullable private String[] 
implicitGroups;
-
-            @Nullable Set<String> getGroupsFor(@NonNull String userName) {
-                if (userName.equals(name) && implicitGroups != null) {
-                    Set<String> groups = new HashSet<>();
-                    groups.addAll(Arrays.asList(implicitGroups));
-                    return Collections.unmodifiableSet(groups);
-                } else {
-                    return null;
-                }
-            }
-        }
-    }
-}
diff --git a/app/src/main/java/org/wikipedia/login/LoginClient.java 
b/app/src/main/java/org/wikipedia/login/LoginClient.java
index 6909692..46d09f9 100644
--- a/app/src/main/java/org/wikipedia/login/LoginClient.java
+++ b/app/src/main/java/org/wikipedia/login/LoginClient.java
@@ -95,7 +95,7 @@
                             // The server could do some transformations on 
user names, e.g. on some
                             // wikis is uppercases the first letter.
                             String actualUserName = 
loginResult.getUser().getUsername();
-                            getGroupMemberships(wiki, actualUserName, 
loginResult, cb);
+                            getExtendedInfo(wiki, actualUserName, loginResult, 
cb);
                         } else if ("UI".equals(loginResult.getStatus())) {
                             //TODO: Don't just assume this is a 2FA UI result
                             cb.twoFactorPrompt(new 
LoginFailedException(loginResult.getMessage()), loginToken);
@@ -118,20 +118,21 @@
         });
     }
 
-    private void getGroupMemberships(@NonNull WikiSite wiki, @NonNull String 
userName,
-                                     @NonNull final LoginResult loginResult,
-                                     @NonNull final LoginCallback cb) {
-        GroupMembershipClient groupClient = new GroupMembershipClient();
-        groupClient.request(wiki, userName, new 
GroupMembershipClient.GroupMembershipCallback() {
+    private void getExtendedInfo(@NonNull WikiSite wiki, @NonNull String 
userName,
+                                 @NonNull final LoginResult loginResult, 
@NonNull final LoginCallback cb) {
+        UserExtendedInfoClient infoClient = new UserExtendedInfoClient();
+        infoClient.request(wiki, userName, new 
UserExtendedInfoClient.GroupMembershipCallback() {
             @Override
-            public void success(@NonNull Set<String> groups) {
+            public void success(@NonNull 
Call<MwQueryResponse<UserExtendedInfoClient.QueryResult>> call,
+                                int id, @NonNull Set<String> groups) {
                 final User user = loginResult.getUser();
-                User.setUser(new User(user, groups));
+                User.setUser(new User(user, id, groups));
                 cb.success(loginResult);
             }
 
             @Override
-            public void error(@NonNull Throwable caught) {
+            public void error(@NonNull 
Call<MwQueryResponse<UserExtendedInfoClient.QueryResult>> call,
+                              @NonNull Throwable caught) {
                 L.e("Login suceeded but getting group information failed. " + 
caught);
                 cb.error(caught);
             }
@@ -222,7 +223,7 @@
                 User user = null;
                 String userMessage = null;
                 if ("PASS".equals(status)) {
-                    user = new User(userName, password, 0);
+                    user = new User(userName, password);
                 } else if ("FAIL".equals(status)) {
                     userMessage = message;
                 } else if ("UI".equals(status)) {
diff --git a/app/src/main/java/org/wikipedia/login/User.java 
b/app/src/main/java/org/wikipedia/login/User.java
index b379c60..bbfa036 100644
--- a/app/src/main/java/org/wikipedia/login/User.java
+++ b/app/src/main/java/org/wikipedia/login/User.java
@@ -51,12 +51,12 @@
     private final int userID;
     @NonNull private final Set<String> groups;
 
-    public User(@NonNull String username, @NonNull String password, int 
userID) {
-        this(username, password, userID, null);
+    public User(@NonNull String username, @NonNull String password) {
+        this(username, password, 0, null);
     }
 
-    public User(@NonNull User other, @Nullable Set<String> groups) {
-        this(other.username, other.password, other.userID, groups);
+    public User(@NonNull User other, int id, @Nullable Set<String> groups) {
+        this(other.username, other.password, id, groups);
     }
 
     public User(@NonNull String username, @NonNull String password, int userID,
diff --git a/app/src/main/java/org/wikipedia/login/UserExtendedInfoClient.java 
b/app/src/main/java/org/wikipedia/login/UserExtendedInfoClient.java
new file mode 100644
index 0000000..848c976
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/login/UserExtendedInfoClient.java
@@ -0,0 +1,129 @@
+package org.wikipedia.login;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.dataclient.retrofit.MwCachedService;
+import org.wikipedia.useroption.dataclient.UserInfo;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+
+/**
+ * Retrofit DataClient to retrieve implicit user info and group membership 
information for a specific user.
+ */
+class UserExtendedInfoClient {
+    @NonNull private final MwCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+
+    @Nullable private Call<MwQueryResponse<QueryResult>> groupCall;
+
+    interface GroupMembershipCallback {
+        void success(@NonNull Call<MwQueryResponse<QueryResult>> call, int id, 
@NonNull Set<String> groups);
+        void error(@NonNull Call<MwQueryResponse<QueryResult>> call, @NonNull 
Throwable caught);
+    }
+
+    public void request(@NonNull final WikiSite wiki, @NonNull final String 
userName,
+                        @NonNull final GroupMembershipCallback cb) {
+        cancel();
+
+        groupCall = cachedService.service(wiki).request(userName);
+        groupCall.enqueue(new Callback<MwQueryResponse<QueryResult>>() {
+            @Override
+            public void onResponse(Call<MwQueryResponse<QueryResult>> call,
+                                   Response<MwQueryResponse<QueryResult>> 
response) {
+                if (response.isSuccessful()) {
+                    final MwQueryResponse<QueryResult> body = response.body();
+                    final QueryResult query = body.query();
+                    if (response.body().success()) {
+                        cb.success(call, query.id(), 
query.getGroupsFor(userName));
+                    } else if (response.body().hasError()) {
+                        cb.error(call, new LoginClient.LoginFailedException(
+                                "Failed to retrieve group membership data. " + 
body.getError().toString()));
+                    } else {
+                        cb.error(call, new LoginClient.LoginFailedException(
+                                "Unexpected error trying to retrieve group 
membership data. " + body.toString()));
+                    }
+                } else {
+                    cb.error(call, new 
LoginClient.LoginFailedException(response.message()));
+                }
+            }
+
+            @Override
+            public void onFailure(Call<MwQueryResponse<QueryResult>> call, 
Throwable caught) {
+                cb.error(call, caught);
+            }
+        });
+    }
+
+    public void cancel() {
+        cancelTokenRequest();
+    }
+
+    private void cancelTokenRequest() {
+        if (groupCall == null) {
+            return;
+        }
+        groupCall.cancel();
+        groupCall = null;
+    }
+
+    private interface Service {
+
+        /** Request the implicit groups a user belongs to. */
+        @NonNull
+        
@POST("w/api.php?action=query&format=json&formatversion=2&meta=userinfo&list=users&usprop=implicitgroups")
+        Call<MwQueryResponse<QueryResult>> request(@Query("ususers") @NonNull 
String userName);
+    }
+
+    static final class QueryResult {
+        @SuppressWarnings("MismatchedReadAndWriteOfArray") 
@SerializedName("users") @NonNull
+        private List<ListUsersResponse> users = Collections.emptyList();
+
+        @SuppressWarnings("unused") @SerializedName("userinfo") private 
UserInfo userInfo;
+
+        int id() {
+            return userInfo.id();
+        }
+
+        @NonNull Set<String> getGroupsFor(@NonNull String userName) {
+            if (!users.isEmpty()) {
+                for (ListUsersResponse user : users) {
+                    final Set<String> groups = user.getGroupsFor(userName);
+                    if (groups != null) {
+                        return groups;
+                    }
+                }
+            }
+            return Collections.emptySet();
+        }
+
+        private static final class ListUsersResponse {
+            @SerializedName("name") @Nullable private String name;
+
+            @SerializedName("implicitgroups") @Nullable private String[] 
implicitGroups;
+
+            @Nullable Set<String> getGroupsFor(@NonNull String userName) {
+                if (userName.equals(name) && implicitGroups != null) {
+                    Set<String> groups = new HashSet<>();
+                    groups.addAll(Arrays.asList(implicitGroups));
+                    return Collections.unmodifiableSet(groups);
+                } else {
+                    return null;
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/login/UserIdClient.java 
b/app/src/main/java/org/wikipedia/login/UserIdClient.java
new file mode 100644
index 0000000..7c3b3d0
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/login/UserIdClient.java
@@ -0,0 +1,61 @@
+package org.wikipedia.login;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.mwapi.MwApiException;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.dataclient.retrofit.MwCachedService;
+import org.wikipedia.dataclient.retrofit.RetrofitException;
+import org.wikipedia.useroption.dataclient.UserInfo;
+
+import java.io.IOException;
+
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.http.GET;
+
+public class UserIdClient {
+    @NonNull private final MwCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+
+    public interface Callback {
+        void success(@NonNull Call<MwQueryResponse<UserInfo>> call, int 
userId);
+        void failure(@NonNull Call<MwQueryResponse<UserInfo>> call, @NonNull 
Throwable caught);
+    }
+
+    public Call<MwQueryResponse<UserInfo>> request(@NonNull WikiSite wiki, 
@NonNull Callback cb) {
+        return request(cachedService.service(wiki), cb);
+    }
+
+    public Call<MwQueryResponse<UserInfo>> request(@NonNull Service service, 
@NonNull final Callback cb) {
+        Call<MwQueryResponse<UserInfo>> call = service.request();
+        call.enqueue(new retrofit2.Callback<MwQueryResponse<UserInfo>>() {
+            @Override
+            public void onResponse(Call<MwQueryResponse<UserInfo>> call, 
Response<MwQueryResponse<UserInfo>> response) {
+                if (response.isSuccessful()) {
+                    if (response.body().success()) {
+                        cb.success(call, response.body().query().id());
+                    } else if (response.body().hasError()) {
+                        cb.failure(call, new 
MwApiException(response.body().getError()));
+                    } else {
+                        cb.failure(call, new IOException("An unknown error 
occurred."));
+                    }
+                } else {
+                    cb.failure(call, RetrofitException.httpError(response, 
cachedService.retrofit()));
+                }
+            }
+
+            @Override
+            public void onFailure(Call<MwQueryResponse<UserInfo>> call, 
Throwable caught) {
+                cb.failure(call, caught);
+            }
+        });
+        return call;
+    }
+
+    @VisibleForTesting interface Service {
+        
@GET("w/api.php?action=query&format=json&formatversion=2&meta=userinfo")
+        @NonNull Call<MwQueryResponse<UserInfo>> request();
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
 
b/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
index 94145d1..cd43db0 100644
--- 
a/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
+++ 
b/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
@@ -118,7 +118,7 @@
     }
 
     private static class PostResponse extends MwPostResponse {
-        private String options;
+        @SuppressWarnings("unused") private String options;
 
         public String result() {
             return options;
@@ -136,10 +136,9 @@
     }
 
     private static class QueryUserInfo {
-        @SerializedName("userinfo")
-        private UserInfo userInfo;
+        @SuppressWarnings("unused") @SerializedName("userinfo") private 
UserInfo userInfo;
 
-        public UserInfo userInfo() {
+        UserInfo userInfo() {
             return userInfo;
         }
     }
diff --git 
a/app/src/main/java/org/wikipedia/useroption/dataclient/UserInfo.java 
b/app/src/main/java/org/wikipedia/useroption/dataclient/UserInfo.java
index 9e01b25..7ea9748 100644
--- a/app/src/main/java/org/wikipedia/useroption/dataclient/UserInfo.java
+++ b/app/src/main/java/org/wikipedia/useroption/dataclient/UserInfo.java
@@ -1,6 +1,7 @@
 package org.wikipedia.useroption.dataclient;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 import com.google.gson.annotations.SerializedName;
 
@@ -11,36 +12,31 @@
 import java.util.Map;
 
 public class UserInfo {
-    @SerializedName("name")
-    private String username;
-    private int id;
+    @SuppressWarnings("unused") @SerializedName("userinfo") private UserInfo 
userInfo;
+    @SuppressWarnings("unused") @SerializedName("name") private String 
username;
+    @SuppressWarnings("unused") private int id;
 
     // Object type is any JSON type.
-    @NonNull private Map<String, ?> options;
+    @SuppressWarnings("unused") @Nullable private Map<String, ?> options;
 
     public int id() {
         return id;
     }
 
-    @NonNull
-    public String username() {
-        return username;
-    }
-
-    @NonNull
-    public Collection<UserOption> userjsOptions() {
+    @NonNull public Collection<UserOption> userjsOptions() {
         Collection<UserOption> ret = new ArrayList<>();
-        for (Map.Entry<String, ?> entry : options.entrySet()) {
-            if (entry.getKey().startsWith("userjs-")) {
-                ret.add(new UserOption(entry.getKey(), (String) 
entry.getValue()));
+        if (options != null) {
+            for (Map.Entry<String, ?> entry : options.entrySet()) {
+                if (entry.getKey().startsWith("userjs-")) {
+                    ret.add(new UserOption(entry.getKey(), (String) 
entry.getValue()));
+                }
             }
         }
         return ret;
     }
 
     // Auto-generated
-    @Override
-    public String toString() {
+    @Override public String toString() {
         return "UserInfo{"
                 + "username='" + username + '\''
                 + ", id=" + id
diff --git a/app/src/test/java/org/wikipedia/login/UserIdClientTest.java 
b/app/src/test/java/org/wikipedia/login/UserIdClientTest.java
new file mode 100644
index 0000000..b304981
--- /dev/null
+++ b/app/src/test/java/org/wikipedia/login/UserIdClientTest.java
@@ -0,0 +1,84 @@
+package org.wikipedia.login;
+
+import android.support.annotation.NonNull;
+
+import com.google.gson.stream.MalformedJsonException;
+
+import org.junit.Test;
+import org.wikipedia.dataclient.mwapi.MwApiException;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.dataclient.retrofit.RetrofitException;
+import org.wikipedia.test.MockWebServerTest;
+import org.wikipedia.useroption.dataclient.UserInfo;
+
+import retrofit2.Call;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class UserIdClientTest extends MockWebServerTest {
+    private UserIdClient subject = new UserIdClient();
+
+    @Test public void testRequestSuccess() throws Throwable {
+        enqueueFromFile("user_info.json");
+
+        UserIdClient.Callback cb = mock(UserIdClient.Callback.class);
+        Call<MwQueryResponse<UserInfo>> call = request(cb);
+
+        server().takeRequest();
+        assertCallbackSuccess(call, cb);
+    }
+
+    @Test public void testRequestResponseApiError() throws Throwable {
+        enqueueFromFile("api_error.json");
+
+        UserIdClient.Callback cb = mock(UserIdClient.Callback.class);
+        Call<MwQueryResponse<UserInfo>> call = request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, MwApiException.class);
+    }
+
+    @Test public void testRequestResponse404() throws Throwable {
+        enqueue404();
+
+        UserIdClient.Callback cb = mock(UserIdClient.Callback.class);
+        Call<MwQueryResponse<UserInfo>> call = request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, RetrofitException.class);
+    }
+
+    @Test public void testRequestResponseMalformed() throws Throwable {
+        server().enqueue("┏━┓ ︵  /(^.^/)");
+
+        UserIdClient.Callback cb = mock(UserIdClient.Callback.class);
+        Call<MwQueryResponse<UserInfo>> call = request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, MalformedJsonException.class);
+    }
+
+    private void assertCallbackSuccess(@NonNull 
Call<MwQueryResponse<UserInfo>> call,
+                                       @NonNull UserIdClient.Callback cb) {
+        verify(cb).success(eq(call), any(Integer.class));
+        //noinspection unchecked
+        verify(cb, never()).failure(any(Call.class), any(Throwable.class));
+    }
+
+    private void assertCallbackFailure(@NonNull 
Call<MwQueryResponse<UserInfo>> call,
+                                       @NonNull UserIdClient.Callback cb,
+                                       @NonNull Class<? extends Throwable> 
throwable) {
+        //noinspection unchecked
+        verify(cb, never()).success(any(Call.class), any(Integer.class));
+        verify(cb).failure(eq(call), isA(throwable));
+    }
+
+    private Call<MwQueryResponse<UserInfo>> request(@NonNull 
UserIdClient.Callback cb) {
+        return subject.request(service(UserIdClient.Service.class), cb);
+    }
+}
diff --git a/app/src/test/res/raw/user_info.json 
b/app/src/test/res/raw/user_info.json
new file mode 100644
index 0000000..afe22cc
--- /dev/null
+++ b/app/src/test/res/raw/user_info.json
@@ -0,0 +1,9 @@
+{
+  "batchcomplete": true,
+  "query": {
+    "userinfo": {
+      "id": 24531888,
+      "name": "MHolloway (WMF)"
+    }
+  }
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/334371
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3dc7871805edf51d2de6598dd5a5e67140f416b5
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Mholloway <mhollo...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to