From 2dbab70c8c3625b7c3f9bb2d4f242173113cf47a Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 17 Dec 2020 10:27:54 -0800 Subject: [PATCH] Create utility endpoint for currency conversion --- .../textsecuregcm/WhisperServerService.java | 11 +- .../PaymentsServiceConfiguration.java | 17 +++ .../controllers/PaymentsController.java | 17 ++- .../currency/CurrencyConversionManager.java | 117 ++++++++++++++++ .../textsecuregcm/currency/FixerClient.java | 75 +++++++++++ .../textsecuregcm/currency/FtxClient.java | 68 ++++++++++ .../entities/CurrencyConversionEntity.java | 30 +++++ .../CurrencyConversionEntityList.java | 29 ++++ .../CurrencyConversionManagerTest.java | 126 ++++++++++++++++++ .../controllers/PaymentsControllerTest.java | 34 ++++- 10 files changed, 515 insertions(+), 9 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManager.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/currency/FtxClient.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntity.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntityList.java create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManagerTest.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 8d543163f..24a793204 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -40,6 +40,7 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontMeterRegistry; +import java.net.http.HttpClient; import java.security.Security; import java.time.Duration; import java.util.ArrayList; @@ -87,6 +88,9 @@ import org.whispersystems.textsecuregcm.controllers.SecureBackupController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.StickerController; import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController; +import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; +import org.whispersystems.textsecuregcm.currency.FixerClient; +import org.whispersystems.textsecuregcm.currency.FtxClient; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter; import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter; @@ -364,6 +368,11 @@ public class WhisperServerService extends Application paymentCurrencies; + public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException { return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray()); } + public String getFixerApiKey() { + return fixerApiKey; + } + + public List getPaymentCurrencies() { + return paymentCurrencies; + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java index 76bd99a9c..ef1b95b68 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java @@ -6,22 +6,27 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; -import io.dropwizard.auth.Auth; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import io.dropwizard.auth.Auth; + @Path("/v1/payments") public class PaymentsController { private final ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator; + private final CurrencyConversionManager currencyManager; - public PaymentsController(ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator) { + public PaymentsController(CurrencyConversionManager currencyManager, ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator) { + this.currencyManager = currencyManager; this.paymentsServiceCredentialGenerator = paymentsServiceCredentialGenerator; } @@ -32,4 +37,12 @@ public class PaymentsController { public ExternalServiceCredentials getAuth(@Auth Account account) { return paymentsServiceCredentialGenerator.generateFor(account.getUuid().toString()); } + + @Timed + @GET + @Path("/conversions") + @Produces(MediaType.APPLICATION_JSON) + public CurrencyConversionEntityList getConversions(@Auth Account account) { + return currencyManager.getCurrencyConversions().orElseThrow(); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManager.java new file mode 100644 index 000000000..ffed2f98f --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManager.java @@ -0,0 +1,117 @@ +package org.whispersystems.textsecuregcm.currency; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntity; +import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; +import org.whispersystems.textsecuregcm.util.Util; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.dropwizard.lifecycle.Managed; + +public class CurrencyConversionManager implements Managed { + + private static final Logger logger = LoggerFactory.getLogger(CurrencyConversionManager.class); + + private static final long FIXER_INTERVAL = TimeUnit.HOURS.toMillis(2); + private static final long FTX_INTERVAL = TimeUnit.MINUTES.toMillis(5); + + private final FixerClient fixerClient; + private final FtxClient ftxClient; + private final List currencies; + + private AtomicReference cached = new AtomicReference<>(null); + + private long fixerUpdatedTimestamp; + private long ftxUpdatedTimestamp; + + private Map cachedFixerValues; + private Map cachedFtxValues; + + public CurrencyConversionManager(FixerClient fixerClient, FtxClient ftxClient, List currencies) { + this.fixerClient = fixerClient; + this.ftxClient = ftxClient; + this.currencies = currencies; + } + + public Optional getCurrencyConversions() { + return Optional.ofNullable(cached.get()); + } + + @Override + public void start() throws Exception { + new Thread(() -> { + for (;;) { + try { + updateCacheIfNecessary(); + } catch (Throwable t) { + logger.warn("Error updating currency conversions", t); + } + + Util.sleep(15000); + } + }).start(); + } + + @Override + public void stop() throws Exception { + + } + + @VisibleForTesting + void updateCacheIfNecessary() throws IOException { + if (System.currentTimeMillis() - fixerUpdatedTimestamp > FIXER_INTERVAL || cachedFixerValues == null) { + this.cachedFixerValues = new HashMap<>(fixerClient.getConversionsForBase("USD")); + this.fixerUpdatedTimestamp = System.currentTimeMillis(); + } + + if (System.currentTimeMillis() - ftxUpdatedTimestamp > FTX_INTERVAL || cachedFtxValues == null) { + Map cachedFtxValues = new HashMap<>(); + + for (String currency : currencies) { + cachedFtxValues.put(currency, ftxClient.getSpotPrice(currency, "USD")); + } + + this.cachedFtxValues = cachedFtxValues; + this.ftxUpdatedTimestamp = System.currentTimeMillis(); + } + + List entities = new LinkedList<>(); + + for (Map.Entry currency : cachedFtxValues.entrySet()) { + double usdValue = currency.getValue(); + + Map values = new HashMap<>(); + values.put("USD", usdValue); + + for (Map.Entry conversion : cachedFixerValues.entrySet()) { + values.put(conversion.getKey(), conversion.getValue() * usdValue); + } + + entities.add(new CurrencyConversionEntity(currency.getKey(), values)); + } + + + this.cached.set(new CurrencyConversionEntityList(entities, Math.min(fixerUpdatedTimestamp, ftxUpdatedTimestamp))); + } + + @VisibleForTesting + void setFixerUpdatedTimestamp(long timestamp) { + this.fixerUpdatedTimestamp = timestamp; + } + + @VisibleForTesting + void setFtxUpdatedTimestamp(long timestamp) { + this.ftxUpdatedTimestamp = timestamp; + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java new file mode 100644 index 000000000..577628631 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java @@ -0,0 +1,75 @@ +package org.whispersystems.textsecuregcm.currency; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.whispersystems.textsecuregcm.util.SystemMapper; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Map; + +public class FixerClient { + + private final String apiKey; + private final HttpClient client; + + public FixerClient(HttpClient client, String apiKey) { + this.apiKey = apiKey; + this.client = client; + } + + public Map getConversionsForBase(String base) throws FixerException { + try { + URI uri = URI.create("https://data.fixer.io/api/latest?access_key=" + apiKey + "&base=" + base); + + HttpResponse response = client.send(HttpRequest.newBuilder() + .GET() + .uri(uri) + .build(), + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new FixerException("Bad response: " + response.statusCode() + " " + response.toString()); + } + + FixerResponse parsedResponse = SystemMapper.getMapper().readValue(response.body(), FixerResponse.class); + + if (parsedResponse.success) return parsedResponse.rates; + else throw new FixerException("Got failed response!"); + } catch (IOException | InterruptedException e) { + throw new FixerException(e); + } + } + + private static class FixerResponse { + + @JsonProperty + private boolean success; + + @JsonProperty + private long timestamp; + + @JsonProperty + private String base; + + @JsonProperty + private String date; + + @JsonProperty + private Map rates; + + } + + public static class FixerException extends IOException { + public FixerException(String message) { + super(message); + } + + public FixerException(Exception exception) { + super(exception); + } + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/currency/FtxClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FtxClient.java new file mode 100644 index 000000000..edf9bedd1 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FtxClient.java @@ -0,0 +1,68 @@ +package org.whispersystems.textsecuregcm.currency; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.whispersystems.textsecuregcm.util.SystemMapper; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class FtxClient { + + private final HttpClient client; + + public FtxClient(HttpClient client) { + this.client = client; + } + + public double getSpotPrice(String currency, String base) throws FtxException{ + try { + URI uri = URI.create("https://ftx.com/api/markets/" + currency + "/" + base); + + HttpResponse response = client.send(HttpRequest.newBuilder() + .GET() + .uri(uri) + .build(), + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new FtxException("Bad response: " + response.statusCode() + " " + response.toString()); + } + + FtxResponse parsedResponse = SystemMapper.getMapper().readValue(response.body(), FtxResponse.class); + + return parsedResponse.result.price; + + } catch (IOException | InterruptedException e) { + throw new FtxException(e); + } + } + + private static class FtxResponse { + + @JsonProperty + private FtxResult result; + + } + + private static class FtxResult { + + @JsonProperty + private double price; + + } + + public static class FtxException extends IOException { + public FtxException(String message) { + super(message); + } + + public FtxException(Exception exception) { + super(exception); + } + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntity.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntity.java new file mode 100644 index 000000000..300f39e9c --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntity.java @@ -0,0 +1,30 @@ +package org.whispersystems.textsecuregcm.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class CurrencyConversionEntity { + + @JsonProperty + private String base; + + @JsonProperty + private Map conversions; + + public CurrencyConversionEntity(String base, Map conversions) { + this.base = base; + this.conversions = conversions; + } + + public CurrencyConversionEntity() {} + + public String getBase() { + return base; + } + + public Map getConversions() { + return conversions; + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntityList.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntityList.java new file mode 100644 index 000000000..91c101615 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/CurrencyConversionEntityList.java @@ -0,0 +1,29 @@ +package org.whispersystems.textsecuregcm.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class CurrencyConversionEntityList { + + @JsonProperty + private List currencies; + + @JsonProperty + private long timestamp; + + public CurrencyConversionEntityList(List currencies, long timestamp) { + this.currencies = currencies; + this.timestamp = timestamp; + } + + public CurrencyConversionEntityList() {} + + public List getCurrencies() { + return currencies; + } + + public long getTimestamp() { + return timestamp; + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManagerTest.java new file mode 100644 index 000000000..cef12d74e --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/currency/CurrencyConversionManagerTest.java @@ -0,0 +1,126 @@ +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; + +public class CurrencyConversionManagerTest { + + @Test + public void testCurrencyCalculations() throws IOException { + 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)); + + 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(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); + } + + @Test + public void testCurrencyCalculationsTimeoutNoRun() throws IOException { + 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)); + + CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO")); + + manager.updateCacheIfNecessary(); + + when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(3.50); + + 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(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); + } + + @Test + public void testCurrencyCalculationsFtxTimeoutWithRun() throws IOException { + 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)); + + CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, ftxClient, List.of("FOO")); + + manager.updateCacheIfNecessary(); + + when(ftxClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(3.50); + manager.setFtxUpdatedTimestamp(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) - TimeUnit.SECONDS.toMillis(1)); + 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(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); + } + + + @Test + public void testCurrencyCalculationsFixerTimeoutWithRun() throws IOException { + 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)); + + 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)); + + manager.setFixerUpdatedTimestamp(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) - TimeUnit.SECONDS.toMillis(1)); + 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(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); + + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java index 27f1258f6..f64196ffd 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java @@ -6,9 +6,6 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.google.common.collect.ImmutableSet; -import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; -import io.dropwizard.testing.junit.ResourceTestRule; -import org.assertj.core.api.Assertions; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.ClassRule; @@ -17,19 +14,28 @@ import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.controllers.PaymentsController; +import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; +import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntity; +import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import javax.ws.rs.core.Response; +import java.util.List; +import java.util.Map; +import java.util.Optional; - +import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; +import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class PaymentsControllerTest { private static final ExternalServiceCredentialGenerator paymentsCredentialGenerator = mock(ExternalServiceCredentialGenerator.class); + private static final CurrencyConversionManager currencyManager = mock(CurrencyConversionManager.class); private final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password"); @@ -38,13 +44,14 @@ public class PaymentsControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new PaymentsController(paymentsCredentialGenerator)) + .addResource(new PaymentsController(currencyManager, paymentsCredentialGenerator)) .build(); @Before public 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()))); } @Test @@ -83,4 +90,19 @@ public class PaymentsControllerTest { assertThat(response.getStatus()).isEqualTo(401); } + @Test + public void testGetCurrencyConversions() { + CurrencyConversionEntityList conversions = + resources.getJerseyTest() + .target("/v1/payments/conversions") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .get(CurrencyConversionEntityList.class); + + + 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); + } + }