Introduce an alternative exchange rate data provider

This commit is contained in:
Jon Chambers
2022-11-10 10:25:06 -05:00
committed by GitHub
parent 80a3a8a43c
commit d3f0ab8c6d
10 changed files with 291 additions and 174 deletions

View File

@@ -0,0 +1,61 @@
package org.whispersystems.textsecuregcm.currency;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
import org.junit.jupiter.api.Test;
class CoinMarketCapClientTest {
private static final String RESPONSE_JSON = """
{
"status": {
"timestamp": "2022-11-09T17:15:06.356Z",
"error_code": 0,
"error_message": null,
"elapsed": 41,
"credit_count": 1,
"notice": null
},
"data": {
"id": 7878,
"symbol": "MOB",
"name": "MobileCoin",
"amount": 1,
"last_updated": "2022-11-09T17:14:00.000Z",
"quote": {
"USD": {
"price": 0.6625319895827952,
"last_updated": "2022-11-09T17:14:00.000Z"
}
}
}
}
""";
@Test
void parseResponse() throws JsonProcessingException {
final CoinMarketCapClient.CoinMarketCapResponse parsedResponse = CoinMarketCapClient.parseResponse(RESPONSE_JSON);
assertEquals(7878, parsedResponse.priceConversionResponse().id());
assertEquals("MOB", parsedResponse.priceConversionResponse().symbol());
final Map<String, CoinMarketCapClient.PriceConversionQuote> quote =
parsedResponse.priceConversionResponse().quote();
assertEquals(1, quote.size());
assertEquals(new BigDecimal("0.6625319895827952"), quote.get("USD").price());
}
@Test
void extractConversionRate() throws IOException {
final CoinMarketCapClient.CoinMarketCapResponse parsedResponse = CoinMarketCapClient.parseResponse(RESPONSE_JSON);
assertEquals(new BigDecimal("0.6625319895827952"), CoinMarketCapClient.extractConversionRate(parsedResponse, "USD"));
assertThrows(IOException.class, () -> CoinMarketCapClient.extractConversionRate(parsedResponse, "CAD"));
}
}

View File

@@ -7,27 +7,35 @@ import static org.mockito.Mockito.when;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
class CurrencyConversionManagerTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@Test
void testCurrencyCalculations() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.822876"),
"FJD", new BigDecimal("2.0577"),
"FKP", new BigDecimal("0.743446")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), Clock.systemUTC());
manager.updateCacheIfNecessary();
@@ -45,9 +53,9 @@ class CurrencyConversionManagerTest {
@Test
void testCurrencyCalculations_noTrailingZeros() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("1.00000"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("1.00000"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.200000"),
"FJD", new BigDecimal("3.00000"),
@@ -55,7 +63,8 @@ class CurrencyConversionManagerTest {
"CAD", new BigDecimal("700.000")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), Clock.systemUTC());
manager.updateCacheIfNecessary();
@@ -74,16 +83,17 @@ class CurrencyConversionManagerTest {
@Test
void testCurrencyCalculations_accuracy() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("0.999999"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("0.999999"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("1.000001"),
"FJD", new BigDecimal("0.000001"),
"FKP", new BigDecimal("1")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), Clock.systemUTC());
manager.updateCacheIfNecessary();
@@ -102,20 +112,21 @@ class CurrencyConversionManagerTest {
@Test
void testCurrencyCalculationsTimeoutNoRun() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.822876"),
"FJD", new BigDecimal("2.0577"),
"FKP", new BigDecimal("0.743446")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), Clock.systemUTC());
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
manager.updateCacheIfNecessary();
@@ -131,23 +142,26 @@ class CurrencyConversionManagerTest {
}
@Test
void testCurrencyCalculationsFtxTimeoutWithRun() throws IOException {
void testCurrencyCalculationsCoinMarketCapTimeoutWithRun() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.822876"),
"FJD", new BigDecimal("2.0577"),
"FKP", new BigDecimal("0.743446")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), Clock.systemUTC());
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
manager.setFtxUpdatedTimestamp(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) - TimeUnit.SECONDS.toMillis(1));
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
connection.sync().del(CurrencyConversionManager.COIN_MARKET_CAP_SHARED_CACHE_CURRENT_KEY));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
manager.updateCacheIfNecessary();
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();
@@ -165,27 +179,37 @@ class CurrencyConversionManagerTest {
@Test
void testCurrencyCalculationsFixerTimeoutWithRun() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.822876"),
"FJD", new BigDecimal("2.0577"),
"FKP", new BigDecimal("0.743446")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
final Instant currentTime = Instant.now().truncatedTo(ChronoUnit.MILLIS);
final Clock clock = mock(Clock.class);
when(clock.instant()).thenReturn(currentTime);
when(clock.millis()).thenReturn(currentTime.toEpochMilli());
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
List.of("FOO"), clock);
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
"EUR", new BigDecimal("0.922876"),
"FJD", new BigDecimal("2.0577"),
"FKP", new BigDecimal("0.743446")
));
manager.setFixerUpdatedTimestamp(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) - TimeUnit.SECONDS.toMillis(1));
final Instant afterFixerExpiration = currentTime.plus(CurrencyConversionManager.FIXER_REFRESH_INTERVAL).plusMillis(1);
when(clock.instant()).thenReturn(afterFixerExpiration);
when(clock.millis()).thenReturn(afterFixerExpiration.toEpochMilli());
manager.updateCacheIfNecessary();
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();

View File

@@ -1,33 +0,0 @@
package org.whispersystems.textsecuregcm.currency;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import org.junit.jupiter.api.Test;
public class FtxClientTest {
@Test
public void testGetSpotPrice() throws IOException, InterruptedException {
HttpResponse<String> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(200);
when(httpResponse.body()).thenReturn(jsonFixture("fixtures/ftx.res.json"));
HttpClient httpClient = mock(HttpClient.class);
when(httpClient.send(any(HttpRequest.class), any(BodyHandler.class))).thenReturn(httpResponse);
FtxClient ftxClient = new FtxClient(httpClient);
BigDecimal spotPrice = ftxClient.getSpotPrice("FOO", "BAR");
assertThat(spotPrice).isEqualTo(new BigDecimal("0.8017"));
}
}