diff --git a/README.md b/README.md index 47afe95..b6a9a0c 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ BinanceJ is released under the MIT license. ## Rate Limiting -BinanceJ does not perform rate limiting, so you are responsible for limiting your requests. Pay attention to the errors -you receive from Binance and check to see if they are a 429. If they are, you need to back off or face a temporary -ban. +BinanceJ will throw a `RateLimitExceededException` when a HTTP 429 error code comes back. This is an important +exception to catch and handle because if you do not back off they will issue a temporary ban. If you don't listen, +you'll be receiving `IpBannedException`s. ## API Coverage @@ -26,110 +26,77 @@ The following endpoints are currently covered: 5. `GET /api/v1/klines` 6. `GET /api/v3/ticker/price` -More will be added in future PRs as they become necessary to me or the people using the library. +More will be added in future PRs as they become necessary to me or the people using the library. -## Return Types +## HTTP Exceptions -All functions return an `Either` type. For those of you not in the know - an Either type represents a disjoint union. -Typically this means a "success" and "failure" case that must be handled uniquely. They work well with stream -processing and are a very natural way to delineate success and failure cleanly. Additionally it eliminates the pattern -of throwing exceptions on errors, and allows exceptions to be reserved for truly exceptional behavior as intended. +### Checked Exceptions -By convention I adapted the Haskell Either type style to this code. What this means is that the Either type's Right -value is the correct one (mnemonic: "right" as in correct) and the Left value is the `ServiceError`. +Any function in the API can throw a handful of exceptions related to HTTP: -### How do I extract the raw value from the Either type? +1. `IpBannedException`: Your IP has been banned after ignoring rate limit exceeded messages +2. `RateLimitExceededException`: Your IP has exceeded the rate limit and needs to slow down +3. `InternalServiceErrorException`: An error occurred on Binance's server side (see note below) +4. `MalformedRequestException`: A malformed request was sent, the error exists on the sender's side (check error code and message) -There are a few ways to do this using Ambivalent, but the most prevalent way in BinanceJ's tests is: +**Note**: A 504 error in general should not be treated as an error (though it is "exceptional" behavior). In the case of `InternalServiceErrorException`s it is critical to check the `code` member of the exception. -```java -Either val = clazz.getThing(); - -// Good path case -if(val.isRight()) { - TypeB thing = val.right().join(Function.identity(), Function.identity()); -} +The "more general" HTTP exceptions (`MalformedRequestException` and `InternalServiceErrorException`) have a code +component so you can check the specifics against the [Binance Error Documentation](https://github.com/binance-exchange/binance-official-api-docs/blob/master/errors.md). -``` +## Standard Exceptions -Alternatively you are welcome to use `Helpers.extractEitherValueSafely` method to make your code cleaner. +### Runtime Exceptions +In the event an unknown error occurs, the code will throw an `UnexpectedErrorException`. These are generally complete showstoppers. ## Examples ### Server alive check with ping +If the ping method does not throw an exception, the ping was successful. + ```java GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build(); -Either res = generalUtilities.ping(); -if(res.isRight()) { - // Successful ping -} - +generalUtilities.ping(); ``` ### Getting current server time ```java GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build(); -Either res = generalUtilities.getServerTime(); - -if(res.isRight()) { - ServerTime response = Helpers.extractEitherValueSafely(res.right()); - //... -} +ServerTime = generalUtilities.getServerTime(); ``` ### Getting Exchange Information ```java GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build(); -Either res = generalUtilities.getExchangeInfo(); - -if(res.isRight()) { - ExchangeInfo response = Helpers.extractEitherValueSafely(res.right()); - //... -} +ExchangeInfo res = generalUtilities.getExchangeInfo(); ``` ### Getting Candlestick data ```java MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build(); -Either> res = marketData.getCandlestickData("ETHBTC", Interval.ONE_MINUTE); - -if(res.isRight()) { - List data = Helpers.extractEitherValueSafely(res.right()); - //... -} +List res = marketData.getCandlestickData("ETHBTC", Interval.ONE_MINUTE); ``` ### Getting market depth ```java MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build(); -Either res = marketData.getOrderBookDepth("ETHBTC", 1000); - -if(res.isRight()) { - OrderBookDepth orderBookDepth = Helpers.extractEitherValueSafely(res.right()); - //... -} +OrderBookDepth res = marketData.getOrderBookDepth("ETHBTC", 1000); ``` ### Getting ticker price for an instrument ```java MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build(); -Either res = marketData.getTickerPriceForSymbol("ETHBTC"); - -if(res.isRight()) { - TickerPrice tickerPrice = Helpers.extractEitherValueSafely(res.right()); - //... -} +TickerPrice res = marketData.getTickerPriceForSymbol("ETHBTC"); ``` ## Contributing -Head over to our [CONTRIBUTING.md](CONTRIBUTING.md) to get started. All features are welcome as long as they are +Head over to our [CONTRIBUTING.md](CONTRIBUTING.md) to get started. All features are welcome as long as they are in scope of the API and following the contributing guide. - diff --git a/build.gradle b/build.gradle index 9b6a04d..262a8c0 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,6 @@ sourceCompatibility = 1.8 dependencies { compile group: 'org.projectlombok', name: 'lombok', version: '1.16.20' - compile group: 'com.codepoetics', name: 'ambivalence', version: '0.2' compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5' compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5' diff --git a/src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java b/src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java index e0cd54c..07c89ae 100644 --- a/src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java +++ b/src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java @@ -12,7 +12,7 @@ public abstract class BaseBinanceApi { protected final String secretKey; protected final CloseableHttpClient closeableHttpClient; - protected static final ObjectMapper mapper = Helpers.objectMapperBuilder(); + protected static final ObjectMapper mapper = HttpRequests.objectMapperBuilder(); public BaseBinanceApi(String apiKey, String secretKey) { this.apiKey = apiKey; diff --git a/src/main/java/com/sigmaflare/binancej/GeneralUtilities.java b/src/main/java/com/sigmaflare/binancej/GeneralUtilities.java index 4ab4d0a..9b79b1f 100644 --- a/src/main/java/com/sigmaflare/binancej/GeneralUtilities.java +++ b/src/main/java/com/sigmaflare/binancej/GeneralUtilities.java @@ -1,11 +1,12 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.sigmaflare.binancej.entities.ExchangeInfo; import com.sigmaflare.binancej.entities.Ping; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.Time; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.UnexpectedErrorException; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; @@ -19,7 +20,7 @@ import java.io.IOException; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT_FORMATTED; -import static com.sigmaflare.binancej.Helpers.buildGetRequestFromEndpoint; +import static com.sigmaflare.binancej.HttpRequests.buildGetRequestFromEndpoint; /** * GeneralUtilities is a container class for methods that interact with the infrastructure @@ -44,9 +45,15 @@ public class GeneralUtilities extends BaseBinanceApi { /** * Hits the ping endpoint to check if the service is alive. * - * @return empty Ping object if it returned 200, otherwise ServiceError + * @return Empty Ping object + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either ping() throws BinanceServiceUnreachableException { + public Ping ping() throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(PING_URL, apiKey); try { @@ -63,24 +70,30 @@ public class GeneralUtilities extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } - return Either.ofRight(new Ping()); + return new Ping(); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } /** * Gets the current server time on Binance's servers * - * @return A Time object if successful, otherwise an ServiceError object - * @throws BinanceServiceUnreachableException Throws when the request fails + * @return A Time object populated with the current server time + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either getServerTime() throws BinanceServiceUnreachableException { + public Time getServerTime() throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(TIME_URL, apiKey); try { @@ -97,24 +110,30 @@ public class GeneralUtilities extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } - return Either.ofRight(mapper.readValue(response, Time.class)); + return mapper.readValue(response, Time.class); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } /** * Retrieves exchange information * - * @return An ExchangeInfo when successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service cannot be reached + * @return An ExchangeInfo populated with exchange information + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either getExchangeInfo() throws BinanceServiceUnreachableException { + public ExchangeInfo getExchangeInfo() throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(EXCHANGE_INFO_URL, apiKey); try { @@ -131,14 +150,15 @@ public class GeneralUtilities extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } - return Either.ofRight(mapper.readValue(response, ExchangeInfo.class)); + return mapper.readValue(response, ExchangeInfo.class); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } diff --git a/src/main/java/com/sigmaflare/binancej/Helpers.java b/src/main/java/com/sigmaflare/binancej/HttpRequests.java similarity index 59% rename from src/main/java/com/sigmaflare/binancej/Helpers.java rename to src/main/java/com/sigmaflare/binancej/HttpRequests.java index b5a0d5b..aabd4e1 100644 --- a/src/main/java/com/sigmaflare/binancej/Helpers.java +++ b/src/main/java/com/sigmaflare/binancej/HttpRequests.java @@ -1,18 +1,21 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.LeftProjection; -import com.codepoetics.ambivalence.RightProjection; import com.fasterxml.jackson.databind.ObjectMapper; +import com.sigmaflare.binancej.entities.ServiceError; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; +import com.sigmaflare.binancej.exceptions.InternalServiceErrorException; +import com.sigmaflare.binancej.exceptions.IpBannedException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; +import com.sigmaflare.binancej.exceptions.RateLimitExceededException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import java.util.function.Function; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; -public final class Helpers { - private Helpers() { +public final class HttpRequests { + private HttpRequests() { } /** @@ -28,24 +31,26 @@ public final class Helpers { /** * Utilizes getBuilder to build a fully functional HttpGet request + * * @param endpoint The endpoint AFTER the base endpoint (e.g. /api/v1/xyz) - * @param apiKey The API key to use + * @param apiKey The API key to use * @return A configured HttpGet object */ public static HttpGet buildGetRequestFromEndpoint(String endpoint, String apiKey) { final String url = String.format("%s%s", BASE_ENDPOINT, endpoint); - return Helpers.getBuilder(url, apiKey); + return HttpRequests.getBuilder(url, apiKey); } /** * Utilizes postBuilder to build a fully functional HttpPost request + * * @param endpoint The endpoint AFTER the base endpoint (e.g. /api/v1/xyz) - * @param apiKey The API key to use + * @param apiKey The API key to use * @return A configured HttpPost object */ public static HttpPost buildPostRequestFromEndpoint(String endpoint, String apiKey) { final String url = String.format("%s%s", BASE_ENDPOINT, endpoint); - return Helpers.postBuilder(url, apiKey); + return HttpRequests.postBuilder(url, apiKey); } /** @@ -84,28 +89,24 @@ public final class Helpers { } /** - * Safely extracts the value from a LeftProjection of an Either - * - * @param val The LeftProjection to perform the extraction on - * @param The Left type - * @param The Right type - * @return The unwrapped L type object + * A simple function to consolidate the creation of common HTTP exceptions */ - @SuppressWarnings("unchecked") - public static L extractEitherValueSafely(LeftProjection val) { - return (L) val.join(Function.identity(), Function.identity()); - } + public static BinanceServiceException buildHttpException(int statusCode, ServiceError error) { + if(statusCode < 400) { + throw new IllegalArgumentException( + String.format("Status codes below 400 (statusCode = %d) do not have an exception", statusCode)); + } - /** - * Safely extracts the value from a RightProjection of an Either - * - * @param val The RightProjection to perform the extraction on - * @param The Left type - * @param The Right type - * @return The unwrapped R type object - */ - @SuppressWarnings("unchecked") - public static R extractEitherValueSafely(RightProjection val) { - return (R) val.join(Function.identity(), Function.identity()); + if (statusCode == 418) { + return new IpBannedException(error.getMessage()); + } else if (statusCode == 429) { + return new RateLimitExceededException(error.getMessage()); + } else if (statusCode == 504) { + return new InternalServiceErrorException(error.getCode(), error.getMessage()); + } else if (statusCode < 500) { + return new MalformedRequestException(error.getCode(), error.getMessage()); + } else { + return new InternalServiceErrorException(error.getCode(), error.getMessage()); + } } } diff --git a/src/main/java/com/sigmaflare/binancej/MarketData.java b/src/main/java/com/sigmaflare/binancej/MarketData.java index 185fb1b..6dc763b 100644 --- a/src/main/java/com/sigmaflare/binancej/MarketData.java +++ b/src/main/java/com/sigmaflare/binancej/MarketData.java @@ -1,13 +1,14 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.JavaType; import com.sigmaflare.binancej.entities.Candlestick; import com.sigmaflare.binancej.entities.Interval; import com.sigmaflare.binancej.entities.OrderBookDepth; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.TickerPrice; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.UnexpectedErrorException; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; @@ -18,12 +19,14 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT_FORMATTED; import static com.sigmaflare.binancej.Constant.SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED; -import static com.sigmaflare.binancej.Helpers.buildGetRequestFromEndpoint; +import static com.sigmaflare.binancej.HttpRequests.buildGetRequestFromEndpoint; @Slf4j public class MarketData extends BaseBinanceApi { @@ -31,6 +34,9 @@ public class MarketData extends BaseBinanceApi { private static final String CANDLESTICK_URL = "/api/v1/klines"; private static final String TICKER_PRICE_URL = "/api/v3/ticker/price"; + private static final ArrayList LIMITS = new ArrayList<>(Arrays.asList(5, 10, 20, 50, 100, 500, 1000)); + private static final String LIMIT_EXCEPTION_MESSAGE = "Limit must be in [5, 10, 20, 50, 100, 500, 1000]"; + @Builder MarketData(String apiKey, String secretKey) { super(apiKey, secretKey); @@ -40,15 +46,24 @@ public class MarketData extends BaseBinanceApi { super(apiKey, secretKey, closeableHttpClient); } + private void checkCandlestickLimit(int limit) { + if(limit < 0 || limit > 500) { + throw new IllegalArgumentException("Limit must be greater than 0 and less than or equal to 500"); + } + } + /** * Overloaded version of getOrderBookDepth that uses the default limit of 100 * * @param symbol The symbol * @return A populated OrderBookDepth if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException In the case the service is unreachable + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable */ - public Either getOrderBookDepth(String symbol) - throws BinanceServiceUnreachableException { + public OrderBookDepth getOrderBookDepth(String symbol) throws BinanceServiceException { return getOrderBookDepth(symbol, 100); } @@ -57,11 +72,19 @@ public class MarketData extends BaseBinanceApi { * * @param symbol The symbol * @param limit The record limit (default: 100, maximum 1000) - * @return A populated OrderBookDepth if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException In the case the service is unreachable + * @return A populated OrderBookDepth + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either getOrderBookDepth(String symbol, int limit) - throws BinanceServiceUnreachableException { + public OrderBookDepth getOrderBookDepth(String symbol, int limit) throws BinanceServiceException { + if(!LIMITS.contains(limit)) { + throw new IllegalArgumentException(LIMIT_EXCEPTION_MESSAGE); + } + String urlWithParams = String.format("%s?symbol=%s&limit=%d", ORDER_BOOK_URL, symbol, limit); final HttpGet request = buildGetRequestFromEndpoint(urlWithParams, apiKey); @@ -79,14 +102,15 @@ public class MarketData extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } - return Either.ofRight(mapper.readValue(response, OrderBookDepth.class)); + return mapper.readValue(response, OrderBookDepth.class); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } @@ -95,12 +119,16 @@ public class MarketData extends BaseBinanceApi { * * @param symbol The symbol * @param interval The interval - * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A list of candlesticks + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ - public Either> getCandleStickData(String symbol, Interval interval) - throws BinanceServiceUnreachableException { + public List getCandleStickData(String symbol, Interval interval) + throws BinanceServiceException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); @@ -122,11 +150,17 @@ public class MarketData extends BaseBinanceApi { * @param interval The interval * @param limit The output limit * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ - public Either> getCandleStickData(String symbol, Interval interval, int limit) - throws BinanceServiceUnreachableException { + public List getCandleStickData(String symbol, Interval interval, int limit) + throws BinanceServiceException { + + checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); @@ -149,13 +183,17 @@ public class MarketData extends BaseBinanceApi { * @param interval The interval * @param time The start/end time * @param isStartTime Indicates whether the time is a start or end time - * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A list of candlesticks + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ - public Either> + public List getCandleStickData(String symbol, Interval interval, long time, boolean isStartTime) - throws BinanceServiceUnreachableException { + throws BinanceServiceException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); @@ -190,13 +228,19 @@ public class MarketData extends BaseBinanceApi { * @param limit The output limit * @param time The timeframe * @param isStartTime indicates whether time is a startTime (true) or endTime (false) - * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A list of candlesticks + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ - public Either> + public List getCandleStickData(String symbol, Interval interval, int limit, long time, boolean isStartTime) - throws BinanceServiceUnreachableException { + throws BinanceServiceException { + + checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); @@ -233,13 +277,19 @@ public class MarketData extends BaseBinanceApi { * @param limit The output limit * @param startTime The start timeframe * @param endTime The end timeframe - * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A list of candlesticks + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ - public Either> + public List getCandleStickData(String symbol, Interval interval, int limit, long startTime, long endTime) - throws BinanceServiceUnreachableException { + throws BinanceServiceException { + + checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); @@ -263,11 +313,16 @@ public class MarketData extends BaseBinanceApi { * Retrieves the current ticker price for the specified symbol * * @param symbol The symbol - * @return A TickerPrice if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A populated list of TickerPrice objects + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either getTickerPriceForSymbol(String symbol) - throws BinanceServiceUnreachableException { + public TickerPrice getTickerPriceForSymbol(String symbol) + throws BinanceServiceException { if (symbol == null) { throw new IllegalArgumentException("Symbol must not be null"); } @@ -289,14 +344,15 @@ public class MarketData extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } - return Either.ofRight(mapper.readValue(response, TickerPrice.class)); + return mapper.readValue(response, TickerPrice.class); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } @@ -304,10 +360,15 @@ public class MarketData extends BaseBinanceApi { * Retrieves ticker prices for all supported Binance symbols. This is good to use in terms of cost if you need * more than one symbol's current ticker price. * - * @return Either a list of TickerPrice objects if successful, or an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A populated list of TickerPrice objects + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - public Either> getTickerPrices() throws BinanceServiceUnreachableException { + public List getTickerPrices() throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(TICKER_PRICE_URL, apiKey); try { @@ -324,15 +385,16 @@ public class MarketData extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, TickerPrice.class); - return Either.ofRight(mapper.readValue(response, type)); + return mapper.readValue(response, type); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } @@ -341,11 +403,16 @@ public class MarketData extends BaseBinanceApi { * as glorified URL builders. * * @param endpoint The endpoint to use that has the associated parameters populated - * @return A list of candlesticks if successful, otherwise an ServiceError - * @throws BinanceServiceUnreachableException If the service is unreachable + * @return A list of candlesticks + * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned + * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded + * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed + * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side + * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable + * @throws UnexpectedErrorException If an unexpected exception occurs */ - private Either> - getCandleStickDataFromUrl(String endpoint) throws BinanceServiceUnreachableException { + private List + getCandleStickDataFromUrl(String endpoint) throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(endpoint, apiKey); try { @@ -362,15 +429,16 @@ public class MarketData extends BaseBinanceApi { String response = EntityUtils.toString(httpEntity); - if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { - return Either.ofLeft(mapper.readValue(response, ServiceError.class)); + if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { + ServiceError error = mapper.readValue(response, ServiceError.class); + throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Candlestick.class); - return Either.ofRight(mapper.readValue(response, type)); + return mapper.readValue(response, type); } } catch (IOException e) { - throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); + throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } } diff --git a/src/main/java/com/sigmaflare/binancej/entities/ServiceError.java b/src/main/java/com/sigmaflare/binancej/entities/ServiceError.java index c821ced..0b795da 100644 --- a/src/main/java/com/sigmaflare/binancej/entities/ServiceError.java +++ b/src/main/java/com/sigmaflare/binancej/entities/ServiceError.java @@ -13,9 +13,9 @@ import lombok.NonNull; @NoArgsConstructor public class ServiceError { @JsonProperty("code") - public int code; + private int code; @NonNull @JsonProperty("msg") - public String message; + private String message; } diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java b/src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java index 558f913..3c88a66 100644 --- a/src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java +++ b/src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java @@ -6,6 +6,6 @@ import lombok.Getter; @Getter @AllArgsConstructor public class BinanceServiceUnreachableException extends BinanceServiceException { - public String message; - public Throwable cause; + private final String message; + private final Throwable cause; } diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/InternalServiceErrorException.java b/src/main/java/com/sigmaflare/binancej/exceptions/InternalServiceErrorException.java new file mode 100644 index 0000000..4b65e18 --- /dev/null +++ b/src/main/java/com/sigmaflare/binancej/exceptions/InternalServiceErrorException.java @@ -0,0 +1,11 @@ +package com.sigmaflare.binancej.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class InternalServiceErrorException extends BinanceServiceException { + private final int code; + private final String message; +} diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/IpBannedException.java b/src/main/java/com/sigmaflare/binancej/exceptions/IpBannedException.java new file mode 100644 index 0000000..4a1a484 --- /dev/null +++ b/src/main/java/com/sigmaflare/binancej/exceptions/IpBannedException.java @@ -0,0 +1,10 @@ +package com.sigmaflare.binancej.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class IpBannedException extends BinanceServiceException { + private final String message; +} diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/MalformedRequestException.java b/src/main/java/com/sigmaflare/binancej/exceptions/MalformedRequestException.java new file mode 100644 index 0000000..c2fc3ec --- /dev/null +++ b/src/main/java/com/sigmaflare/binancej/exceptions/MalformedRequestException.java @@ -0,0 +1,11 @@ +package com.sigmaflare.binancej.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MalformedRequestException extends BinanceServiceException { + private final int code; + private final String message; +} diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/RateLimitExceededException.java b/src/main/java/com/sigmaflare/binancej/exceptions/RateLimitExceededException.java new file mode 100644 index 0000000..394d351 --- /dev/null +++ b/src/main/java/com/sigmaflare/binancej/exceptions/RateLimitExceededException.java @@ -0,0 +1,10 @@ +package com.sigmaflare.binancej.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class RateLimitExceededException extends BinanceServiceException { + private final String message; +} diff --git a/src/main/java/com/sigmaflare/binancej/exceptions/UnexpectedErrorException.java b/src/main/java/com/sigmaflare/binancej/exceptions/UnexpectedErrorException.java new file mode 100644 index 0000000..7eb69d3 --- /dev/null +++ b/src/main/java/com/sigmaflare/binancej/exceptions/UnexpectedErrorException.java @@ -0,0 +1,7 @@ +package com.sigmaflare.binancej.exceptions; + +public class UnexpectedErrorException extends RuntimeException { + public UnexpectedErrorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java b/src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java index fbbf304..aefa7b6 100644 --- a/src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java +++ b/src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java @@ -1,12 +1,13 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.Candlestick; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.Interval; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.commons.io.FileUtils; import org.apache.http.ProtocolVersion; @@ -26,15 +27,15 @@ import java.util.List; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CandlestickMethodTests { - private static ObjectMapper mapper = Helpers.objectMapperBuilder(); + private static ObjectMapper mapper = HttpRequests.objectMapperBuilder(); private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/klines?symbol=TEST&interval=8h"); - private final String urlWithLimit = String.format("%s%s", url, "&limit=1000"); + private final String urlWithLimit = String.format("%s%s", url, "&limit=250"); private final String urlWithStartTime = String.format("%s%s", url, "&startTime=1234"); private final String urlWithEndTime = String.format("%s%s", url, "&endTime=1234"); private final String urlWithAllParameters = String.format("%s%s", urlWithLimit, "&startTime=1234&endTime=1234"); @@ -82,7 +83,7 @@ public class CandlestickMethodTests { } @Test - public void testCandlestickServiceReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -100,16 +101,38 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); + List res = marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithLimitReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithLimitReturnsSuccessfully() throws Exception { + CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); + CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); + + when(mockedCloseableHttpResponse.getStatusLine()) + .thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), + 200, "test")); + + when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimit, "1234")))) + .thenReturn(mockedCloseableHttpResponse); + + BasicHttpEntity httpEntity = new BasicHttpEntity(); + httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); + + when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); + + MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); + + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 250); + + assertEquals(candlesticks, res); + } + + @Test(expected = IllegalArgumentException.class) + public void testCandlestickServiceWithLimitOutOfBoundsThrows() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -127,16 +150,14 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000); + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 60000); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithLimitAndStartTimeReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithLimitAndStartTimeReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -154,16 +175,39 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000, 1234L,true); + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 250, 1234L, true); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); + } + + @Test(expected = IllegalArgumentException.class) + public void testCandlestickServiceWithLimitOutOfBoundsAndStartTimeThrows() throws Exception { + CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); + CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); + + when(mockedCloseableHttpResponse.getStatusLine()) + .thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), + 200, "test")); + + when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimitAndStartTime, "1234")))) + .thenReturn(mockedCloseableHttpResponse); + + BasicHttpEntity httpEntity = new BasicHttpEntity(); + httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); + + when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); + + MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); + + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 750, 1234L, true); + + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithLimitAndEndTimeReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithLimitAndEndTimeReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -181,16 +225,14 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000, 1234L,false); + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 250, 1234L, false); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithAllParametersReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithAllParametersReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -208,17 +250,15 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = + List res = marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, - 1000, 1234L,1234L); + 250, 1234L, 1234L); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithStartTimeReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithStartTimeReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -236,16 +276,14 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L,true); + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L, true); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test - public void testCandlestickServiceWithEndTimeReturnsSuccessfully() - throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithEndTimeReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -263,15 +301,14 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = - marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L,false); + List res = + marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L, false); - assertTrue(res.isRight()); - assertEquals(candlesticks, Helpers.extractEitherValueSafely(res.right())); + assertEquals(candlesticks, res); } @Test(expected = BinanceServiceUnreachableException.class) - public void testCandlestickServiceWithoutHttpEntityThrows() throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceWithoutHttpEntityThrows() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -288,7 +325,7 @@ public class CandlestickMethodTests { } @Test - public void testCandlestickServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testCandlestickServiceBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -309,10 +346,18 @@ public class CandlestickMethodTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = + + try { marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); + fail(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } + + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); } } diff --git a/src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java b/src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java index 1842eb2..d64e059 100644 --- a/src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java +++ b/src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java @@ -1,11 +1,12 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.ExchangeInfo; import com.sigmaflare.binancej.entities.ServiceError; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.commons.io.FileUtils; import org.apache.http.ProtocolVersion; @@ -23,13 +24,13 @@ import java.nio.charset.StandardCharsets; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ExchangeInfoMethodTests { - private static ObjectMapper mapper = Helpers.objectMapperBuilder(); + private static ObjectMapper mapper = HttpRequests.objectMapperBuilder(); private String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/exchangeInfo"); private ExchangeInfo exchangeInfo; @@ -44,7 +45,7 @@ public class ExchangeInfoMethodTests { } @Test - public void testExchangeInfoServiceReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testExchangeInfoServiceReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -65,14 +66,13 @@ public class ExchangeInfoMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.getExchangeInfo(); + ExchangeInfo res = generalUtilities.getExchangeInfo(); - assertTrue(res.isRight()); - assertEquals(exchangeInfo, Helpers.extractEitherValueSafely(res.right())); + assertEquals(exchangeInfo, res); } @Test(expected = BinanceServiceUnreachableException.class) - public void testExchangeInfoServiceWithNoHttpEntityFails() throws IOException, BinanceServiceUnreachableException { + public void testExchangeInfoServiceWithNoHttpEntityFails() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -90,7 +90,7 @@ public class ExchangeInfoMethodTests { } @Test - public void testExchangeInfoServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testExchangeInfoServiceBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -112,9 +112,17 @@ public class ExchangeInfoMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.getExchangeInfo(); - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); + try { + generalUtilities.getExchangeInfo(); + fail(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } + + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } } } diff --git a/src/test/java/com/sigmaflare/binancej/HttpErrorThrowerTests.java b/src/test/java/com/sigmaflare/binancej/HttpErrorThrowerTests.java new file mode 100644 index 0000000..b1d889d --- /dev/null +++ b/src/test/java/com/sigmaflare/binancej/HttpErrorThrowerTests.java @@ -0,0 +1,42 @@ +package com.sigmaflare.binancej; + +import com.sigmaflare.binancej.entities.ServiceError; +import com.sigmaflare.binancej.exceptions.InternalServiceErrorException; +import com.sigmaflare.binancej.exceptions.IpBannedException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; +import com.sigmaflare.binancej.exceptions.RateLimitExceededException; +import org.junit.Test; + +public class HttpErrorThrowerTests { + @Test(expected = IpBannedException.class) + public void testIpBannedOnHttpCode418() throws Exception { + throw HttpRequests.buildHttpException(418, ServiceError.builder().message("TEST").code(418).build()); + } + + @Test(expected = RateLimitExceededException.class) + public void testRateLimitOnHttpCode421() throws Exception { + throw HttpRequests.buildHttpException(429, ServiceError.builder().message("TEST").code(429).build()); + } + + @Test(expected = MalformedRequestException.class) + public void testMalformedRequestOnHttpCode400() throws Exception { + throw HttpRequests.buildHttpException(400, ServiceError.builder().message("TEST").code(400).build()); + } + + @Test(expected = InternalServiceErrorException.class) + public void testInternalServiceErrorOnHttpCode500() throws Exception { + throw HttpRequests.buildHttpException(500, ServiceError.builder().message("TEST").code(500).build()); + } + + @Test(expected = InternalServiceErrorException.class) + public void testInternalServiceErrorOnHttpCode504() throws Exception { + throw HttpRequests.buildHttpException(504, ServiceError.builder().message("TEST").code(504).build()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInternalServiceThrowsOn200Ok() throws Exception { + throw HttpRequests.buildHttpException(200, ServiceError.builder().message("TEST").code(200).build()); + } + + +} diff --git a/src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java b/src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java index 098dc29..8146bfc 100644 --- a/src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java +++ b/src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java @@ -1,11 +1,12 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.OrderBookDepth; import com.sigmaflare.binancej.entities.OrderBookPricing; import com.sigmaflare.binancej.entities.ServiceError; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.commons.io.FileUtils; import org.apache.http.ProtocolVersion; @@ -24,13 +25,13 @@ import java.nio.charset.StandardCharsets; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class OrderBookDepthTests { - private static ObjectMapper mapper = Helpers.objectMapperBuilder(); + private static ObjectMapper mapper = HttpRequests.objectMapperBuilder(); private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/depth?symbol=ETHBTC&limit=100"); private final String urlWithLimit = String.format("%s%s", BASE_ENDPOINT, "/api/v1/depth?symbol=ETHBTC&limit=1000"); @@ -70,7 +71,7 @@ public class OrderBookDepthTests { @Test - public void testOrderBookDepthReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testOrderBookDepthReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -92,15 +93,43 @@ public class OrderBookDepthTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either res = marketData.getOrderBookDepth("ETHBTC"); + OrderBookDepth res = marketData.getOrderBookDepth("ETHBTC"); - assertTrue(res.isRight()); - assertEquals(orderBookDepth, Helpers.extractEitherValueSafely(res.right())); + assertEquals(orderBookDepth, res); + + } + + @Test(expected = IllegalArgumentException.class) + public void testOrderBookDepthWithLimitOutOfBounds() throws Exception { + CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); + CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); + + when(mockedCloseableHttpResponse.getStatusLine()) + .thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), + 200, "test")); + + when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) + .thenReturn(mockedCloseableHttpResponse); + + BasicHttpEntity httpEntity = new BasicHttpEntity(); + + // This changes from the usual way we do this because we do post-processing in the deserializer and the JSON + // will no longer match the stringified OrderBookDepth. + httpEntity.setContent(new ByteArrayInputStream(orderBookDepthJson.getBytes())); + + + when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); + + MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); + + OrderBookDepth res = marketData.getOrderBookDepth("ETHBTC", 1500); + + assertEquals(orderBookDepth, res); } @Test(expected = BinanceServiceUnreachableException.class) - public void testOrderBookDepthWithNoHttpEntityFails() throws IOException, BinanceServiceUnreachableException { + public void testOrderBookDepthWithNoHttpEntityFails() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -117,7 +146,7 @@ public class OrderBookDepthTests { } @Test - public void testOrderBookDepthWithBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testOrderBookDepthWithBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -139,9 +168,17 @@ public class OrderBookDepthTests { when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); - Either res = marketData.getOrderBookDepth("ETHBTC", 1000); + try { + marketData.getOrderBookDepth("ETHBTC", 1000); + fail(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } + + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); } } diff --git a/src/test/java/com/sigmaflare/binancej/PingMethodTests.java b/src/test/java/com/sigmaflare/binancej/PingMethodTests.java index 0acc5b2..291976d 100644 --- a/src/test/java/com/sigmaflare/binancej/PingMethodTests.java +++ b/src/test/java/com/sigmaflare/binancej/PingMethodTests.java @@ -1,11 +1,11 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.ServiceError; -import com.sigmaflare.binancej.entities.Ping; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.http.ProtocolVersion; import org.apache.http.client.methods.CloseableHttpResponse; @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,7 +30,7 @@ public class PingMethodTests { private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/ping"); @Test - public void testServiceAliveReturnsSuccess() throws IOException, BinanceServiceUnreachableException { + public void testServiceAliveReturnsSuccess() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -50,14 +50,12 @@ public class PingMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.ping(); - - assertTrue(res.isRight()); - + // Ping returns an object that is an empty Ping - all we care is that is doesn't throw + generalUtilities.ping(); } @Test(expected = BinanceServiceUnreachableException.class) - public void testServiceFailsToReturnHttpEntity() throws IOException, BinanceServiceUnreachableException { + public void testServiceFailsToReturnHttpEntity() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -75,7 +73,7 @@ public class PingMethodTests { } @Test - public void testServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testServiceBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -97,9 +95,15 @@ public class PingMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.ping(); - - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); + try { + generalUtilities.ping(); + fail(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } } } diff --git a/src/test/java/com/sigmaflare/binancej/TickerPriceTests.java b/src/test/java/com/sigmaflare/binancej/TickerPriceTests.java index bc92fd3..ee56474 100644 --- a/src/test/java/com/sigmaflare/binancej/TickerPriceTests.java +++ b/src/test/java/com/sigmaflare/binancej/TickerPriceTests.java @@ -1,11 +1,12 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.TickerPrice; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.commons.io.FileUtils; import org.apache.http.ProtocolVersion; @@ -24,13 +25,13 @@ import java.util.List; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class TickerPriceTests { - private static final ObjectMapper mapper = Helpers.objectMapperBuilder(); + private static final ObjectMapper mapper = HttpRequests.objectMapperBuilder(); private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v3/ticker/price"); private final String specificUrl = String.format("%s%s", url, "?symbol=TEST"); @@ -61,7 +62,7 @@ public class TickerPriceTests { } @Test - public void testTickerPriceForSymbolReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testTickerPriceForSymbolReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -80,14 +81,13 @@ public class TickerPriceTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either res = marketData.getTickerPriceForSymbol("TEST"); + TickerPrice res = marketData.getTickerPriceForSymbol("TEST"); - assertTrue(res.isRight()); - assertEquals(tickerPrice, Helpers.extractEitherValueSafely(res.right())); + assertEquals(tickerPrice, res); } @Test - public void testTickerPricesSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testTickerPricesSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -106,15 +106,13 @@ public class TickerPriceTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = marketData.getTickerPrices(); + List res = marketData.getTickerPrices(); - assertTrue(res.isRight()); - assertEquals(tickerPrices, Helpers.extractEitherValueSafely(res.right())); + assertEquals(tickerPrices, res); } @Test(expected = BinanceServiceUnreachableException.class) - public void testTickerPriceForSymbolWithoutHttpEntityThrows() - throws IOException, BinanceServiceUnreachableException { + public void testTickerPriceForSymbolWithoutHttpEntityThrows() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -131,8 +129,7 @@ public class TickerPriceTests { } @Test(expected = BinanceServiceUnreachableException.class) - public void testTickerPricesWithoutHttpEntityThrows() - throws IOException, BinanceServiceUnreachableException { + public void testTickerPricesWithoutHttpEntityThrows() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -149,8 +146,7 @@ public class TickerPriceTests { } @Test - public void testTickerPriceForSymbolWithBadApiKeyReturns400() - throws IOException, BinanceServiceUnreachableException { + public void testTickerPriceForSymbolWithBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -171,14 +167,21 @@ public class TickerPriceTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either res = marketData.getTickerPriceForSymbol("TEST"); - - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); + try { + marketData.getTickerPriceForSymbol("TEST"); + fail(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } + + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } } @Test - public void testTickerPricesBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testTickerPricesBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -199,9 +202,15 @@ public class TickerPriceTests { MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); - Either> res = marketData.getTickerPrices(); + try { + marketData.getTickerPrices(); + } catch (BinanceServiceException e) { + if (!(e instanceof MalformedRequestException)) { + fail(); + } - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } } } diff --git a/src/test/java/com/sigmaflare/binancej/TimeMethodTests.java b/src/test/java/com/sigmaflare/binancej/TimeMethodTests.java index 846e897..22e88ae 100644 --- a/src/test/java/com/sigmaflare/binancej/TimeMethodTests.java +++ b/src/test/java/com/sigmaflare/binancej/TimeMethodTests.java @@ -1,10 +1,11 @@ package com.sigmaflare.binancej; -import com.codepoetics.ambivalence.Either; import com.fasterxml.jackson.databind.ObjectMapper; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.Time; +import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; +import com.sigmaflare.binancej.exceptions.MalformedRequestException; import com.sigmaflare.binancej.matchers.GetMatcher; import org.apache.http.ProtocolVersion; import org.apache.http.client.methods.CloseableHttpResponse; @@ -19,7 +20,7 @@ import java.nio.charset.StandardCharsets; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -29,7 +30,7 @@ public class TimeMethodTests { private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/time"); @Test - public void testTimeReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { + public void testTimeReturnsSuccessfully() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -52,14 +53,13 @@ public class TimeMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.getServerTime(); + Time res = generalUtilities.getServerTime(); - assertTrue(res.isRight()); - assertEquals(time, Helpers.extractEitherValueSafely(res.right())); + assertEquals(time, res); } @Test(expected = BinanceServiceUnreachableException.class) - public void testTimeNoEntityReturnedFails() throws IOException, BinanceServiceUnreachableException { + public void testTimeNoEntityReturnedFails() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -77,7 +77,7 @@ public class TimeMethodTests { } @Test - public void testTimeBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { + public void testTimeBadApiKeyReturns400() throws Exception { CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); @@ -100,9 +100,16 @@ public class TimeMethodTests { GeneralUtilities generalUtilities = new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); - Either res = generalUtilities.getServerTime(); - - assertTrue(res.isLeft()); - assertEquals(serviceError, Helpers.extractEitherValueSafely(res.left())); + try { + generalUtilities.getServerTime(); + fail(); + } catch(BinanceServiceException e) { + if(!(e instanceof MalformedRequestException)) { + fail(); + } + + assertEquals(400, ((MalformedRequestException) e).getCode()); + assertEquals("Bad API Key", e.getMessage()); + } } }