From 422c2d81869dacfad81299518ba273b88128ee59 Mon Sep 17 00:00:00 2001 From: Kummita Sriteja Date: Thu, 25 Jul 2019 21:09:18 +0200 Subject: [PATCH] Added library methods for of splitwise. Added library methods for of splitwise. --- .gitignore | 9 + README.md | 299 +++++++++++++++++- pom.xml | 46 +++ src/main/java/Splitwise.java | 349 +++++++++++++++++++++ src/main/java/constants/Strings.java | 14 + src/main/java/constants/URL.java | 39 +++ src/main/java/utils/OAuthUtil.java | 181 +++++++++++ src/main/java/utils/Splitwise10Api.java | 27 ++ src/test/java/SplitwiseTest.java | 141 +++++++++ src/test/java/constants/MockResponses.java | 99 ++++++ src/test/java/utils/OAuthUtilTest.java | 96 ++++++ 11 files changed, 1299 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/Splitwise.java create mode 100644 src/main/java/constants/Strings.java create mode 100644 src/main/java/constants/URL.java create mode 100644 src/main/java/utils/OAuthUtil.java create mode 100644 src/main/java/utils/Splitwise10Api.java create mode 100644 src/test/java/SplitwiseTest.java create mode 100644 src/test/java/constants/MockResponses.java create mode 100644 src/test/java/utils/OAuthUtilTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a487fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +target/ +.classpath +.project +.settings/ +*.iml +.idea/ +shippable/ +*.class +*.temp diff --git a/README.md b/README.md index a98f80e..02baa8c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,299 @@ # splitwise-java -Java SDK for Splitwise +**Java SDK for Splitwise** +*** +This is Java SDK for Splitwise. Currently the following [splitwise API](http://dev.splitwise.com/#introduction) calls +are supported, + +`get_current_user` +`get_user/:id` +`update_user/:id` +`get_groups` +`get_group/:id` +`create_group` +`delete_group/:id` +`add_user_to_group` +`remove_user_from_group` +`get_friends` +`get_friend/:id` +`create_friend` +`delete_friend/:id` +`get_expenses` +`get_expense/:id` +`delete_expense/:id` +`get_comments?expense_id=` +`get_notifications` +`get_currencies` +`get_categories` + +Pull requests and bugs are welcomed. + +**Authentication** +*** +Currently the authentication is performed using [ScribeJava](https://github.com/scribejava/scribejava) +library which supports both OAuth1.0 and OAuth2.0. In this library, **OAuth1.0** is included at this +point of time. Authentication using OAuth2.0 is expected in the future updates. + +**Registration** +*** +The library expects consumer key and consumer secret of Splitwise and can be fetched after registering +your application [here](https://secure.splitwise.com/oauth_clients). + +**Using the library** +*** + +**Authorization** + +As already mentioned, the library expects consumer key and consumer secret from Splitwise. As splitwise +uses OAuth authentication for access, the user should also fetch token verifier from the +authorization url. This is done in two steps, + +1. Initialize splitwise with consumer key and consumer secret. + +```$xslt +Splitwise splitwise = new Splitwise("", ""); +String authorizationURL = splitwise.getAuthorizationUrl(); +``` + +2. Open the authorizationURL in the browser. It is a simple process of logging into Splitwise and +giving the access. After giving the access, token verifier is shown in the url itself. Set the +access token using the verifier. + +```$xslt +splitwise.util.setAccessToken(""); +``` + +To skip the process of authenticating every time, the token details fetched above can be used to +set the token without actually going into authorizationURL. + +```$xslt +OAuth1AccessToken accessToken = (OAuth1AccessToken) splitwise.util.getAccessToken(); +splitwise.util.setAccessToken(accessToken.getToken(), + accessToken.getTokenSecret(), + accessToken.getRawResponse() + ); +``` + +However, the user must authorize at least once to get the token verifier. + +**Access data from splitwise** + +Once the authorization if performed and token details are fetched, they can be used to instantiate +Splitwise class and set the access token as shown above. + +```$xslt +Splitwise splitwise = new Splitwise("", ""); +splitwise.util.setAccessToken(@NotNull , @NotNull , ); +``` + +The complete list of methods the library supports are as follows, + +**Get Current User** + +`getCurrentUser()` can be used to fetch current user. It returns the user details in a JSON string. + +```$xslt +String userDetailsJson = splitwise.getCurrentUser(); +``` + +**Get User** + +`getUser(userId)` can be used to fetch user details. It returns user details in a JSON String. + +```$xslt +String userId = "324343"; +String userDetailsJson = splitwise.getUser(userId); +``` + +**Update User** + +`updateUser(userId, userDetails)` can be used to update the details of the splitwise user. The +user fields that can be updated can be seen [here](http://dev.splitwise.com/#update_user-id). + +```$xslt +Map userDetails = new HashMap(){{ + put("first_name", "Lorem"); + put("last_name", "Ipsum"); +}} +String userId = 324343 +String response = splitwise.updateUser(userId, userDetails); +``` + +**Get Groups** + +`getGroups()` can be used to fetch all the splitwise groups of the current user. It returns a +JSON string of the group details. + +```$xslt +String groupDetails = splitwise.getGroups(); +``` + +***Get Group*** + +`getGroup(groupId)` can be used to fetch details of a specific splitwise group. It returns a +JSON string of the group details. + +```$xslt +String groupId = "123"; +String groupDetails = splitwise.getGroup(groupId); +``` + +***Create Group*** + +`createGroup(groupDetails)` can be used to create splitwise group. `groupDetails` can contain +all the item shown [here](http://dev.splitwise.com/#create_group). + +```$xslt +Map groupDetails = new HashMap(){{ + put("name", "Lorem"); + put("group_type", "Apartment"); +}} +String response = splitwise.createGroup(groupDetails); +``` + +**Delete Group** + +`deleteGroup(groupId)` can be used to delete a splitwise group. + +```$xslt +String groupId = "123"; +String response = splitwise.deleteGroup(groupId); +``` + +***Add User to Group*** + +`addUserToGroup(userDetails)` can be used to add a user to a group. + +```$xslt +Map userDetails = new HashMap(){{ + put("first_name", "Lorem"); + put("last_name", "Ipsum"); + put("email", "hello@world.com"); + put("user_id", 324343); + put("group_id", 123); +}} +String response = splitwise.addUserToGroup(userDetails); +``` + +***Remove User from Group*** + +`removeUserFromGroup(groupId, userId)` can be used to remove a splitwise user from a splitwise group. + +```$xslt +int userId = 324343; +int groupId = 123; +String response = splitwise.removeUserFromGroup(groupId, userId); +``` + +***Get Friends*** + +`getFriends()` can be used to fetch all the splitwise friends of the current user. It returns a +JSON string containing all the friend details. + +```$xslt +String friends = splitwise.getFriends(); +``` + +***Get Friend*** + +`getFriend(userId)` can be used to fetch details of a splitwise friend. It returns a +JSON string containing all the friend details. + +```$xslt +String userId = "324343" +String friendDetails = splitwise.getFriend(userId); +``` + +***Create a friend*** + +`createFriend(firstName, lastName, email)` can be used to add a friend into Splitwise. + +```$xslt +String response = splitwise.createFriend("lorem", "ipsum", "hello@world.com"); +``` + +***Delete a friend*** + +`deleteFriend(friendId)` can be used to delete a friend from Splitwise. + +```$xslt +String friendId = 345; +String response = splitwise.deleteFriend(friendId); +``` + +***Get Expenses*** + +`getExpenses()` can be used to get all the expenses of the current user. It returns a +JSON string containing all the expenses. + +```$xslt +String expenses = splitwise.getExpenses(); +``` + +***Get Expense*** + +`getExpense(expenseId)` can be used to get details of a splitwise expense. It returns a +JSON string containing the expense details. + +```$xslt +String expenseId = "765"; +String expenseDetails = splitwise.getExpense(expenseId); +``` + +***Delete Expense*** + +`deleteExpense(expenseId)` can be used to delete a splitwise expense. + +```$xslt +String expenseId = "765"; +String response = splitwise.deleteExpense(expenseId); +``` + +***Get Comments on an Expense*** + +`getComments(expenseId)` can be used to get comments on a splitwise expense. It returns a +JSON string containing the expense details. + +```$xslt +String expenseId = "987"; +String comments = splitwise.getComments(expenseId); +``` + +***Get Notifications*** + +`getNotifications()` can be used to get unseen notifications of the current user. It returns a +JSON string containing all the unseen notifications. + +```$xslt +String notifications = splitwise.getNotifications(); +``` + +***Get Currencies*** + +`getCurrencies()` can be used to fetch the currencies supported by Splitwise. It returns a +JSON string containing all the currencies. + +```$xslt +String currencies = splitwise.getCurrencies(); +``` + +***Get Categories*** + +`getCategories()` can be used to fetch the categories in Splitwise. It returns a +JSON string containing all the categories. + +```$xslt +String categories = splitwise.getCategories(); +``` + +***Testing*** +*** + +The library includes test cases for some of the methods. It uses [Mockito](https://site.mockito.org/) +framework to mock the methods needed in unit test cases. + +Test cases for two vital classes `Splitwise` and `OAuthUtl` are included in the library, +`SplitwiseTest` and `OAuthUtilTest`. + +**License** +*** +MIT \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1a2ea78 --- /dev/null +++ b/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + com.sritejakv.api + splitwise + 1.0-SNAPSHOT + + + com.github.scribejava + scribejava-apis + 6.6.3 + + + + junit + junit + 4.12 + test + + + + org.mockito + mockito-core + 2.23.4 + test + + + + org.json + json + 20180813 + test + + + org.jetbrains + annotations-java5 + RELEASE + compile + + + + + \ No newline at end of file diff --git a/src/main/java/Splitwise.java b/src/main/java/Splitwise.java new file mode 100644 index 0000000..02cc870 --- /dev/null +++ b/src/main/java/Splitwise.java @@ -0,0 +1,349 @@ +import com.github.scribejava.core.model.OAuth1AccessToken; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Token; +import com.github.scribejava.core.model.Verb; +import constants.Strings; +import constants.URL; +import utils.OAuthUtil; +import utils.Splitwise10Api; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + + +/** + * Java SDK for Splitwise. Authentication is done using OAuth1.0 + */ +public class Splitwise { + private String consumerKey, consumerSecret; + protected OAuthUtil util; + + /** + * Constructor. + * @param consumerKey consumerKey provided by splitwise + * @param consumerSecret consumerSecret provided by splitwise + */ + public Splitwise(String consumerKey, String consumerSecret){ + this.consumerKey = consumerKey; + this.consumerSecret = consumerSecret; + this.util = new OAuthUtil().setApiKey(this.consumerKey) + .setApiSecret(this.consumerSecret) + .build(Splitwise10Api.instance()); + } + + /** + * Returns authorization url using which user can get token verifier. + * @return authorization url + * @throws InterruptedException + * @throws ExecutionException + * @throws IOException + */ + public String getAuthorizationUrl() throws InterruptedException, ExecutionException, IOException { + return this.util.getAuthorizationUrl(); + } + + /** + * Authenticates with splitwise with the verifier passed and returns the token details. + * @param verifier + * @return Token details provided by OAuth + * @throws InterruptedException + * @throws ExecutionException + * @throws IOException + */ + public Token getAccessToken(String verifier) throws InterruptedException, ExecutionException, IOException { + this.util.setAccessToken(verifier); + return this.util.getAccessToken(); + } + + /** + * Returns the JSON string of current user based on the consumerKey and consumerSecret. + * @return JSON string of user + * @throws Exception + */ + public String getCurrentUser() throws Exception { + Response response = this.util.makeRequest(String.format(URL.GET_CURRENT_USER, Strings.SPLITWISE_API_VERSION) + , Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Returns the JSON string of user based on the userId passed. + * @param userId Splitwise id of the user + * @return JSON string containing user details + * @throws Exception + */ + public String getUser(String userId) throws Exception { + String url = String.format(URL.GET_USER_WITH_ID, userId); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Update the parameters of the current user. + * @param id splitwise id of the user + * @param details details to be updated (http://dev.splitwise.com/?javascript#update_user-id) + * @return JSON response string from splitwise + * @throws Exception + */ + public String updateUser(String id, Map details) throws Exception { + String url = String.format(URL.UPDATE_USER_WITH_ID, id); + Response response = this.util.makeRequest(url, Verb.POST, details); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Returns the JSON string of the splitwise groups of the current user. + * @return JSON string containing the groups + * @throws Exception + */ + public String getGroups() throws Exception { + Response response = this.util.makeRequest(URL.GET_GROUPS, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Returns the JSON string of the group based on the group id passed. + * @param id splitwise group id + * @return JSON string containing group details + * @throws Exception + */ + public String getGroup(String id) throws Exception { + String url = String.format(URL.GET_GROUP_WITH_ID, id); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Creates a splitwise group. + * @param details details of the group (http://dev.splitwise.com/?javascript#create_group) + * @return JSON response from splitwise + * @throws Exception + */ + public String createGroup(Map details) throws Exception { + Response response = this.util.makeRequest(URL.CREATE_GROUP_URL, Verb.POST, details); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Delete splitwise group based on the id. + * @param id splitwise group id + * @return JSON response from splitwise + * @throws Exception + */ + public String deleteGroup(String id) throws Exception { + String url = String.format(URL.DELETE_GROUP_WITH_ID, id); + Response response = this.util.makeRequest(url, Verb.POST); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Add user to a splitwise group. + * @param userDetails details of the user to be added (http://dev.splitwise.com/?javascript#add_user_to_group) + * @return JSON response from splitwise + * @throws Exception + */ + //TODO test this + public String addUserToGroup(Map userDetails) throws Exception { + Response response = this.util.makeRequest(URL.ADD_USER_TO_GROUP, Verb.POST, userDetails); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Remove user from a splitwise group. + * @param groupId splitwise group id + * @param userId splitwise user id + * @return JSON response from splitwise + * @throws Exception + */ + //TODO test this + public String removeUserFromGroup(final String groupId, final String userId) throws Exception { + Map details = new HashMap(){{ + put(Strings.USER_ID, userId); + put(Strings.GROUP_ID, groupId); + }}; + Response response = this.util.makeRequest(URL.REMOVE_USER_FROM_GROUP, Verb.POST, details); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Returns splitiwse friends of the current user. + * @return JSON string containing friends + * @throws Exception + */ + public String getFriends() throws Exception { + Response response = this.util.makeRequest(URL.GET_FRIENDS, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Returns splitwise friend details based on the id passed. + * @param id splitwise user id of the friend + * @return JSON string containing friend details + * @throws Exception + */ + public String getFriend(String id) throws Exception { + String url = String.format(URL.GET_FRIEND, id); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Create a splitwise friend + * @param firstName first name of the friend + * @param lastName last name of the friend + * @param email email of the friend + * @return JSON response from splitwise + * @throws Exception + */ + public String createFriend(final String firstName, final String lastName, final String email) throws Exception { + Map friendDetails = new HashMap(){{ + put(Strings.USER_FIRST_NAME, firstName); + put(Strings.USER_LAST_NAME, lastName); + put(Strings.USER_EMAIL, email); + }}; + Response response = this.util.makeRequest(URL.CREATE_FRIEND, Verb.POST, friendDetails); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Delete a friend from splitwise. + * @param friendId splitwise user id + * @return JSON response string from splitwise + * @throws Exception + */ + public String deleteFriend(String friendId) throws Exception { + String url = String.format(URL.DELETE_FRIEND_WITH_ID, friendId); + Response response = this.util.makeRequest(url, Verb.POST); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get splitwise comments on an expense. + * @param expenseId splitwise expense id + * @return JSON string of comments on the expense + * @throws Exception + */ + public String getComments(String expenseId) throws Exception { + String url = String.format(URL.GET_COMMENTS_FOR_EXPENSE, expenseId); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get all expenses for the current user. + * @return JSON string containing user expenses + * @throws Exception + */ + public String getExpenses() throws Exception { + Response response = this.util.makeRequest(URL.GET_EXPENSES, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get user expense for an expense id. + * @param expenseId splitwise expense id + * @return JSON string containing expense details + * @throws Exception + */ + public String getExpense(String expenseId) throws Exception { + String url = String.format(URL.GET_EXPENSE_WITH_ID, expenseId); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Delete splitwise expense of current user + * @param expenseId splitwise expense id + * @return JSON string response from splitwise + * @throws Exception + */ + public String deleteExpense(String expenseId) throws Exception { + String url = String.format(URL.DELETE_EXPENSE_WITH_ID, expenseId); + Response response = this.util.makeRequest(url, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get unseen notifications of the current user. + * @return JSON string response containing user notifications + * @throws Exception + */ + public String getNotifications() throws Exception { + Response response = this.util.makeRequest(URL.GET_NOTIFICATIONS, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get currencies supported by splitwise + * @return JSON string containing splitwise currencies + * @throws Exception + */ + public String getCurrencies() throws Exception { + Response response = this.util.makeRequest(URL.GET_CURRENCIES, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + /** + * Get categories supported by splitwise + * @return JSON string containing splitwise categories + * @throws Exception + */ + public String getCategories() throws Exception { + Response response = this.util.makeRequest(URL.GET_CATEGORIES, Verb.GET); + if (response.getCode() == 200) + return response.getBody(); + return null; + } + + public static void main(String... args) throws Exception { + Splitwise splitwise = new Splitwise("", + ""); + String authorizationURL = splitwise.getAuthorizationUrl(); + splitwise.util.setAccessToken(""); + OAuth1AccessToken accessToken = (OAuth1AccessToken) splitwise.util.getAccessToken(); + splitwise.util.setAccessToken(accessToken.getToken(), + accessToken.getTokenSecret(), + accessToken.getRawResponse() + ); + System.out.println("Response \n" + splitwise.getCurrentUser()); + } +} diff --git a/src/main/java/constants/Strings.java b/src/main/java/constants/Strings.java new file mode 100644 index 0000000..eb28b72 --- /dev/null +++ b/src/main/java/constants/Strings.java @@ -0,0 +1,14 @@ +package constants; + +/** + * String constants used in the library. + */ +public class Strings { + public static final String SPLITWISE_API_VERSION = "v3.0"; + + public static final String USER_ID = "user_id"; + public static final String GROUP_ID = "group_id"; + public static final String USER_FIRST_NAME = "user_first_name"; + public static final String USER_LAST_NAME = "user_last_name"; + public static final String USER_EMAIL = "user_email"; +} diff --git a/src/main/java/constants/URL.java b/src/main/java/constants/URL.java new file mode 100644 index 0000000..c41112f --- /dev/null +++ b/src/main/java/constants/URL.java @@ -0,0 +1,39 @@ +package constants; + +/** + * Contains the Splitwise REST API URLs from (http://dev.splitwise.com/#introduction). + */ +public class URL { + public static final String BASE_URL = String.format("https://secure.splitwise.com/api/%s", + Strings.SPLITWISE_API_VERSION); + + //URLs for Users API + public static final String GET_CURRENT_USER = BASE_URL +"/get_current_user"; + public static final String GET_USER_WITH_ID = BASE_URL + "/get_user/%s"; + public static final String UPDATE_USER_WITH_ID = BASE_URL + "/update_user/%s"; + + //URLs for groups API + public static final String GET_GROUPS = BASE_URL + "/get_groups"; + public static final String GET_GROUP_WITH_ID = BASE_URL +"/get_group/%s"; + public static final String CREATE_GROUP_URL = BASE_URL +"/create_group"; + public static final String DELETE_GROUP_WITH_ID = BASE_URL +"/delete_group/%s"; + public static final String ADD_USER_TO_GROUP = BASE_URL + "/add_user_to_group"; + public static final String REMOVE_USER_FROM_GROUP = BASE_URL + "/remove_user_from_group"; + + //URLs for Friends API + public static final String GET_FRIENDS = BASE_URL + "/get_friends"; + public static final String GET_FRIEND = BASE_URL + "/get_friend/%s"; + public static final String CREATE_FRIEND = BASE_URL + "/create_friend"; + public static final String DELETE_FRIEND_WITH_ID = BASE_URL + "/delete_friend/%s"; + + //URLs for Expenses API + public static final String GET_EXPENSES = BASE_URL + "/get_expenses"; + public static final String GET_EXPENSE_WITH_ID = BASE_URL + "/get_expense/%s"; + public static final String DELETE_EXPENSE_WITH_ID = BASE_URL + "/delete_expense/%s"; + + //Others + public static final String GET_COMMENTS_FOR_EXPENSE = BASE_URL + "/get_comments?expense_id=%s"; + public static final String GET_NOTIFICATIONS = BASE_URL + "/get_notifications"; + public static final String GET_CURRENCIES = BASE_URL + "/get_currencies"; + public static final String GET_CATEGORIES = BASE_URL + "/get_categories"; +} diff --git a/src/main/java/utils/OAuthUtil.java b/src/main/java/utils/OAuthUtil.java new file mode 100644 index 0000000..47cdcf2 --- /dev/null +++ b/src/main/java/utils/OAuthUtil.java @@ -0,0 +1,181 @@ +package utils; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.builder.api.DefaultApi10a; +import com.github.scribejava.core.model.*; +import com.github.scribejava.core.oauth.OAuth10aService; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + + +/** + * Utility class that performs OAuth authentication. + */ +public class OAuthUtil { + private String apiKey; + private String apiSecret; + private OAuth10aService service; + private OAuth1RequestToken requestToken; + private OAuth1AccessToken accessToken; + private List httpErrorCodes = new ArrayList() {{ + add(400); + add(401); + add(403); + add(404); + add(500); + add(503); + }}; + + /** + * Set the consumer key of the API. + * @param apiKey consumer key + * @return OAuthUtil instance + */ + public OAuthUtil setApiKey(@NotNull String apiKey) { + this.apiKey = apiKey; + return this; + } + + /** + * Set the consumer secret of the API. + * @param apiSecret consumer secret + * @return OAuthUtil instance + */ + public OAuthUtil setApiSecret(@NotNull String apiSecret) { + this.apiSecret = apiSecret; + return this; + } + + /** + * Returns the authorization url from apiKey and apiSecret. + * @return authorization url + * @throws InterruptedException + * @throws ExecutionException + * @throws IOException + */ + public String getAuthorizationUrl() throws InterruptedException, ExecutionException, IOException { + this.requestToken = service.getRequestToken(); + String authUrl = service.getAuthorizationUrl(requestToken); + return authUrl; + } + + /** + * Sets the access token by using the token verifier received from authorization url. + * @param verifier token verifier + * @throws InterruptedException + * @throws ExecutionException + * @throws IOException + */ + public void setAccessToken(@NotNull String verifier) throws InterruptedException, ExecutionException, IOException { + this.accessToken = this.service.getAccessToken(requestToken, verifier); + } + + /** + * Set the access token details. + * @param token access token + * @param tokenSecret access token secret + * @param rawResponse raw response of the access token + */ + public void setAccessToken(@NotNull String token, @NotNull String tokenSecret, String rawResponse){ + this.accessToken = new OAuth1AccessToken(token, tokenSecret, rawResponse); + } + + /** + * Returns the access token details received. + * @return access token details received + */ + public Token getAccessToken(){ + return this.accessToken; + } + + /** + * Generates the API request, communicates to the API and returns the response. + * @param url URL to communicate + * @param httpMethod GET or POST + * @param data data to be sent if the httpMethod is POST + * @return response from the API + * @throws Exception + */ + public Response makeRequest(String url, Verb httpMethod, Object... data) throws Exception { + OAuthRequest request = new OAuthRequest(httpMethod, url); + if (data.length > 0 && data[0] instanceof Map){ + Map details = (Map) data[0]; + for (Map.Entry entry: details.entrySet()){ + request.addBodyParameter(entry.getKey(), entry.getValue()); + } + } + this.service.signRequest(this.accessToken, request); + + Response response = null; + try { + response = this.service.execute(request); + } catch (InterruptedException e) { + throw new Exception(String.format( + "Invalid response received - %s. " + + "Please check your client id and secret.", e.getMessage())); + } catch (ExecutionException e) { + throw new Exception(String.format( + "Invalid response received - %s. " + + "Please check your client id and secret.", e.getMessage())); + } catch (IOException e) { + throw new Exception(String.format( + "Invalid response received - %s. " + + "Please check your client id and secret.", e.getMessage())); + } catch (Exception e) { + throw new Exception(String.format( + "Invalid response received - %s. " + + "Please check your client id and secret.", e.getMessage())); + } + return parseResponse(response); + } + + /** + * Parse the response from the API + * @param response response from the API + * @return response if the response code is 200 + * @throws Exception throws exception for other response codes + */ + private Response parseResponse(@NotNull Response response) throws Exception { + if (httpErrorCodes.contains(response.getCode())){ + throw new Exception(String.format( + "Invalid response received with body - %s, message - %s. " + + "Please check your client id and secret. Response code - %s", response.getBody(), + response.getMessage(), + response.getCode())); + } + return response; + } + + /** + * Utility builder for OAuth authentication + * @param instance API instance containing OAuth details + * @return OAuthUtil instance + */ + public OAuthUtil build(DefaultApi10a instance){ + this.service = new ServiceBuilder(this.apiKey) + .apiSecret(this.apiSecret) + .build(instance); + return this; + } + + /** + * Utility builder for OAuth authentication + * @param apiKey consumerKey + * @param apiSecret consuerSecret + * @param instance API instance containing OAuth details + * @return OAuthUtil instance + */ + public OAuthUtil build(@NotNull String apiKey, @NotNull String apiSecret, DefaultApi10a instance){ + setApiKey(apiKey); + setApiSecret(apiSecret); + this.service = new ServiceBuilder(this.apiKey) + .apiSecret(this.apiSecret) + .build(instance); + return this; + } +} diff --git a/src/main/java/utils/Splitwise10Api.java b/src/main/java/utils/Splitwise10Api.java new file mode 100644 index 0000000..14b89b7 --- /dev/null +++ b/src/main/java/utils/Splitwise10Api.java @@ -0,0 +1,27 @@ +package utils; + +import com.github.scribejava.core.builder.api.DefaultApi10a; + +/** + * Class containing OAuth1.0 details of splitwise API. + */ +public class Splitwise10Api extends DefaultApi10a { + + private static final Splitwise10Api INSTANCE = new Splitwise10Api(); + + public String getRequestTokenEndpoint() { + return "https://secure.splitwise.com/oauth/request_token"; + } + + public String getAccessTokenEndpoint() { + return "https://secure.splitwise.com/oauth/access_token"; + } + + protected String getAuthorizationBaseUrl() { + return "https://secure.splitwise.com/oauth/authorize"; + } + + public static Splitwise10Api instance() { + return Splitwise10Api.INSTANCE; + } +} diff --git a/src/test/java/SplitwiseTest.java b/src/test/java/SplitwiseTest.java new file mode 100644 index 0000000..8907f85 --- /dev/null +++ b/src/test/java/SplitwiseTest.java @@ -0,0 +1,141 @@ +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import constants.MockResponses; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import utils.OAuthUtil; + +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SplitwiseTest { + //Fill in the apiKey and apiSecret before running the test cases. + private String apiKey = "sdfksdfds"; + private String apiSecret = "jfkdjfkd"; + + //Fill token, tokenSecret after authorization. + private String token = "kjfkdjksk"; + private String tokenSecret = "lkjdfimdskl;"; + + @InjectMocks + private Splitwise splitwise; + + @Before + public void setUp() { + splitwise = new Splitwise(apiKey, apiSecret); + splitwise.util.setAccessToken(token, tokenSecret, ""); + } + + @Test + public void getAuthorizationUrl() { + String authUrl = null; + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.getAuthorizationUrl()).thenReturn(MockResponses.MOCK_AUTHURL); + authUrl = splitwise.getAuthorizationUrl(); + URL auth = new URL(authUrl); + } catch (InterruptedException e) { + fail(e.getMessage()); + } catch (ExecutionException e) { + fail(e.getMessage()); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void getCurrentUser() { + Response mockResponse = new Response(200, "mockMessage", new HashMap(), + MockResponses.MOCK_USER); + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.makeRequest(any(String.class), any(Verb.class))).thenReturn(mockResponse); + String userDetails = splitwise.getCurrentUser(); + JSONObject userJson = new JSONObject(userDetails); + assertTrue(userJson.has("user")); + verify(mockUtil).makeRequest(any(String.class), any(Verb.class)); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void getFriends() { + Response mockResponse = new Response(200, "mockMessage", new HashMap(), + MockResponses.MOCK_FRIEND); + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.makeRequest(any(String.class), any(Verb.class))).thenReturn(mockResponse); + String friends = splitwise.getFriends(); + JSONObject userJson = new JSONObject(friends); + assertTrue(userJson.has("friends")); + verify(mockUtil).makeRequest(any(String.class), any(Verb.class)); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void getExpenses() { + Response mockResponse = new Response(200, "mockMessage", new HashMap(), + MockResponses.MOCK_EXPENSES); + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.makeRequest(any(String.class), any(Verb.class))).thenReturn(mockResponse); + String expenses = splitwise.getExpenses(); + JSONObject userJson = new JSONObject(expenses); + assertTrue(userJson.has("expenses")); + verify(mockUtil).makeRequest(any(String.class), any(Verb.class)); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void getCurrencies() { + Response mockResponse = new Response(200, "mockMessage", new HashMap(), + MockResponses.MOCK_CURRENCIES); + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.makeRequest(any(String.class), any(Verb.class))).thenReturn(mockResponse); + String currencies = splitwise.getCurrencies(); + JSONObject userJson = new JSONObject(currencies); + assertTrue(userJson.has("currencies")); + verify(mockUtil).makeRequest(any(String.class), any(Verb.class)); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void getCategories() { + Response mockResponse = new Response(200, "mockMessage", new HashMap(), + MockResponses.MOCK_CATGORIES); + OAuthUtil mockUtil = mock(OAuthUtil.class); + splitwise.util = mockUtil; + try { + when(mockUtil.makeRequest(any(String.class), any(Verb.class))).thenReturn(mockResponse); + String categories = splitwise.getCategories(); + JSONObject userJson = new JSONObject(categories); + assertTrue(userJson.has("categories")); + verify(mockUtil).makeRequest(any(String.class), any(Verb.class)); + } catch (Exception e) { + fail(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/constants/MockResponses.java b/src/test/java/constants/MockResponses.java new file mode 100644 index 0000000..9612558 --- /dev/null +++ b/src/test/java/constants/MockResponses.java @@ -0,0 +1,99 @@ +package constants; + +public class MockResponses { + + public static final String MOCK_AUTHURL = "https://secure.splitwise.com/oauth/authorize?" + + "oauth_token=jfsjflkdsjlfdsj"; + + public static final String MOCK_USER = "{\n" + + " \"user\": {\n" + + " \"id\": 1,\n" + + " \"first_name\": \"Ada\",\n" + + " \"last_name\": \"Lovelace\",\n" + + " \"picture\": {\n" + + " \"small\": \"image_url\",\n" + + " \"medium\": \"image_url\",\n" + + " \"large\": \"image_url\"\n" + + " },\n" + + " \"email\": \"ada@example.com\",\n" + + " \"registration_status\": \"confirmed\"\n" + + " }\n" + + " }\n" + + "}"; + + public static final String MOCK_FRIEND = "{\n" + + " \"friends\":[\n" + + " {\n" + + " \"id\": 1,\n" + + " \"first_name\": \"Ada\",\n" + + " \"last_name\": \"Lovelace\",\n" + + " \"picture\": {\n" + + " \"small\": \"image_url\",\n" + + " \"medium\": \"image_url\",\n" + + " \"large\": \"image_url\"\n" + + " },\n" + + " \"balance\":[\n" + + " {\n" + + " \"currency_code\":\"USD\",\n" + + " \"amount\":\"-1794.5\"\n" + + " },\n" + + " {\n" + + " \"currency_code\":\"AED\",\n" + + " \"amount\":\"7.5\"\n" + + " }\n" + + " ],\n" + + " \"groups\":[\n" + + " {\n" + + " \"group_id\":3018312,\n" + + " \"balance\":[\n" + + " {\n" + + " \"currency_code\":\"USD\",\n" + + " \"amount\":\"414.5\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"group_id\":2830896,\n" + + " \"balance\":[\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"group_id\":0,\n" + + " \"balance\":[\n" + + " {\n" + + " \"currency_code\":\"USD\",\n" + + " \"amount\":\"-2209.0\"\n" + + " },\n" + + " {\n" + + " \"currency_code\":\"AED\",\n" + + " \"amount\":\"7.5\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"updated_at\":\"2017-11-30T09:41:09Z\"\n" + + " }\n" + + " ]\n" + + "}"; + + public static final String MOCK_EXPENSES = "{ \"expenses\" : [" + + "]}"; + + public static final String MOCK_CURRENCIES = "{\n" + + " \"currencies\":[\n" + + " { \"currency_code\":\"USD\", \"unit\":\"$\" },\n" + + " { \"currency_code\":\"ARS\", \"unit\":\"$\" },\n" + + " { \"currency_code\":\"AUD\", \"unit\":\"$\" },\n" + + " { \"currency_code\":\"EUR\", \"unit\":\"€\" },\n" + + " { \"currency_code\":\"BRL\", \"unit\":\"R$\" },\n" + + " { \"currency_code\":\"CAD\", \"unit\":\"$\" },\n" + + " { \"currency_code\":\"CNY\", \"unit\":\"¥\" },\n" + + " { \"currency_code\":\"DKK\", \"unit\":\"kr\" },\n" + + " { \"currency_code\":\"GBP\", \"unit\":\"£\" },\n" + + " { \"currency_code\":\"INR\", \"unit\":\"₹\" },\n" + + " { \"currency_code\":\"ILS\", \"unit\":\"₪\" },\n" + + " { \"currency_code\":\"JPY\", \"unit\":\"¥\" }]}"; + + public static final String MOCK_CATGORIES = "{ \"categories\" : [" + + "]}"; +} diff --git a/src/test/java/utils/OAuthUtilTest.java b/src/test/java/utils/OAuthUtilTest.java new file mode 100644 index 0000000..7c11075 --- /dev/null +++ b/src/test/java/utils/OAuthUtilTest.java @@ -0,0 +1,96 @@ +package utils; + +import com.github.scribejava.core.model.OAuth1AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth10aService; +import constants.Strings; +import constants.URL; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class OAuthUtilTest { + + @InjectMocks + private OAuthUtil oAuthUtil; + + @Mock + private OAuth10aService mockService; + + private Response mockResponse = new Response(200, "mockMessage", new HashMap(), + new InputStream() { + @Override + public int read() throws IOException { + return 0; + } + }); + + private Response mockInvalidResponse = new Response(400, "mockInvalidMessage", + new HashMap(), + "Invalid Request."); + + private String mockApiToken = "jdfajiodflslm"; + private String mockApiSecret = "jipmewlfeifmdls"; + private String mockRawResponse = "{key: value}"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + oAuthUtil = new OAuthUtil(); + } + + @Test + public void makeRequest() throws Exception { + doAnswer(new Answer() { + + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + return null; + } + }).when(mockService).signRequest(any(OAuth1AccessToken.class), any(OAuthRequest.class)); + + when(mockService.execute(any(OAuthRequest.class))).thenReturn(mockResponse); + FieldSetter.setField(oAuthUtil, oAuthUtil.getClass().getDeclaredField("service"), mockService); + oAuthUtil.setAccessToken(mockApiToken, mockApiSecret, mockRawResponse); + Response resp = oAuthUtil.makeRequest(String.format(URL.GET_FRIENDS, Strings.SPLITWISE_API_VERSION), Verb.GET); + + assertThat(resp.getCode(), is(200)); + verify(mockService).signRequest(any(OAuth1AccessToken.class), any(OAuthRequest.class)); + verify(mockService).execute(any(OAuthRequest.class)); + } + + @Test(expected = Exception.class) + public void makeInvalidRequest() throws Exception { + doAnswer(new Answer() { + public Void answer(InvocationOnMock invocation) { + return null; + } + }).when(mockService).signRequest(any(OAuth1AccessToken.class), any(OAuthRequest.class)); + + when(mockService.execute(any(OAuthRequest.class))).thenReturn(mockInvalidResponse); + FieldSetter.setField(oAuthUtil, oAuthUtil.getClass().getDeclaredField("service"), mockService); + oAuthUtil.setAccessToken(mockApiToken, mockApiSecret, mockRawResponse); + Response resp = oAuthUtil.makeRequest(String.format(URL.GET_FRIENDS, Strings.SPLITWISE_API_VERSION), Verb.GET); + } +} \ No newline at end of file