Use BigDecimal instead of Double for currency rate calculations (#134)

use BigDecimal instead of double for accuracy
This commit is contained in:
Sophiah Ho
2021-09-10 17:15:57 -04:00
committed by GitHub
parent 489519a982
commit feb59deb28
10 changed files with 251 additions and 53 deletions

View File

@@ -1,18 +1,18 @@
package org.whispersystems.textsecuregcm.currency;
import org.junit.Test;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
public class CurrencyConversionManagerTest {
@Test
@@ -20,8 +20,12 @@ public class CurrencyConversionManagerTest {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(2.35);
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of("EUR", 0.822876, "FJD", 2.0577,"FKP", 0.743446));
when(ftxClient.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"));
@@ -32,10 +36,67 @@ public class CurrencyConversionManagerTest {
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(4);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(2.35);
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(1.9337586000000002);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(4.8355950000000005);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(1.7470981);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("2.35"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("1.9337586"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("4.835595"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("1.7470981"));
}
@Test
public void testCurrencyCalculations_noTrailingZeros() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.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"),
"FKP", new BigDecimal("50.0000"),
"CAD", new BigDecimal("700.000")
));
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO"));
manager.updateCacheIfNecessary();
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(5);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("1"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("0.2"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("3"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("50"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("CAD")).isEqualTo(new BigDecimal("700"));
}
@Test
public void testCurrencyCalculations_accuracy() throws IOException {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.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"));
manager.updateCacheIfNecessary();
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(4);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("0.999999"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("0.999999999999"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("0.000000999999"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("0.999999"));
}
@Test
@@ -43,14 +104,18 @@ public class CurrencyConversionManagerTest {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(2.35);
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of("EUR", 0.822876, "FJD", 2.0577,"FKP", 0.743446));
when(ftxClient.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"));
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(3.50);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
manager.updateCacheIfNecessary();
@@ -59,10 +124,10 @@ public class CurrencyConversionManagerTest {
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(4);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(2.35);
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(1.9337586000000002);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(4.8355950000000005);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(1.7470981);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("2.35"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("1.9337586"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("4.835595"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("1.7470981"));
}
@Test
@@ -70,14 +135,18 @@ public class CurrencyConversionManagerTest {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(2.35);
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of("EUR", 0.822876, "FJD", 2.0577,"FKP", 0.743446));
when(ftxClient.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"));
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(3.50);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
manager.setFtxUpdatedTimestamp(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) - TimeUnit.SECONDS.toMillis(1));
manager.updateCacheIfNecessary();
@@ -86,10 +155,10 @@ public class CurrencyConversionManagerTest {
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(4);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(3.5);
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(2.8800660000000002);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(7.20195);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(2.602061);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("3.5"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("2.880066"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("7.20195"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("2.602061"));
}
@@ -98,15 +167,23 @@ public class CurrencyConversionManagerTest {
FixerClient fixerClient = mock(FixerClient.class);
FtxClient ftxClient = mock(FtxClient.class);
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(2.35);
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of("EUR", 0.822876, "FJD", 2.0577,"FKP", 0.743446));
when(ftxClient.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"));
manager.updateCacheIfNecessary();
when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(3.50);
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of("EUR", 0.922876, "FJD", 2.0577,"FKP", 0.743446));
when(ftxClient.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));
manager.updateCacheIfNecessary();
@@ -116,10 +193,10 @@ public class CurrencyConversionManagerTest {
assertThat(conversions.getCurrencies().size()).isEqualTo(1);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().size()).isEqualTo(4);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(2.35);
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(2.1687586000000003);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(4.8355950000000005);
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(1.7470981);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("2.35"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("EUR")).isEqualTo(new BigDecimal("2.1687586"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FJD")).isEqualTo(new BigDecimal("4.835595"));
assertThat(conversions.getCurrencies().get(0).getConversions().get("FKP")).isEqualTo(new BigDecimal("1.7470981"));
}

View File

@@ -0,0 +1,36 @@
package org.whispersystems.textsecuregcm.currency;
import org.junit.jupiter.api.Test;
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 java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
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;
public class FixerClientTest {
@Test
public void testGetConversionsForBase() throws IOException, InterruptedException {
HttpResponse<String> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(200);
when(httpResponse.body()).thenReturn(jsonFixture("fixtures/fixer.res.json"));
HttpClient httpClient = mock(HttpClient.class);
when(httpClient.send(any(HttpRequest.class), any(BodyHandler.class))).thenReturn(httpResponse);
FixerClient fixerClient = new FixerClient(httpClient, "foo");
Map<String, BigDecimal> conversions = fixerClient.getConversionsForBase("EUR");
assertThat(conversions.get("CAD")).isEqualTo(new BigDecimal("1.560132"));
}
}

View File

@@ -0,0 +1,33 @@
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"));
}
}

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -52,7 +53,17 @@ class PaymentsControllerTest {
@BeforeEach
void setup() {
when(paymentsCredentialGenerator.generateFor(eq(AuthHelper.VALID_UUID.toString()))).thenReturn(validCredentials);
when(currencyManager.getCurrencyConversions()).thenReturn(Optional.of(new CurrencyConversionEntityList(List.of(new CurrencyConversionEntity("FOO", Map.of("USD", 2.35, "EUR", 1.89)), new CurrencyConversionEntity("BAR", Map.of("USD", 1.50, "EUR", 0.98))), System.currentTimeMillis())));
when(currencyManager.getCurrencyConversions()).thenReturn(Optional.of(
new CurrencyConversionEntityList(List.of(
new CurrencyConversionEntity("FOO", Map.of(
"USD", new BigDecimal("2.35"),
"EUR", new BigDecimal("1.89")
)),
new CurrencyConversionEntity("BAR", Map.of(
"USD", new BigDecimal("1.50"),
"EUR", new BigDecimal("0.98")
))
), System.currentTimeMillis())));
}
@Test
@@ -103,7 +114,19 @@ class PaymentsControllerTest {
assertThat(conversions.getCurrencies().size()).isEqualTo(2);
assertThat(conversions.getCurrencies().get(0).getBase()).isEqualTo("FOO");
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(2.35);
assertThat(conversions.getCurrencies().get(0).getConversions().get("USD")).isEqualTo(new BigDecimal("2.35"));
}
@Test
void testGetCurrencyConversions_Json() {
String json =
resources.getJerseyTest()
.target("/v1/payments/conversions")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
.get(String.class);
assertThat(json.contains("{\"USD\":2.35,\"EUR\":1.89}"));
}
}