From a6e9d0e77c7b73c739dc425307233185298edcd8 Mon Sep 17 00:00:00 2001 From: m2049r Date: Sun, 10 Dec 2023 14:24:35 +0100 Subject: [PATCH] use yadio for pricing "exotic" fiat (#921) * yadio api * add more currencies * fix es typo * bump version & fix cicleci build --- .circleci/config.yml | 2 +- app/build.gradle | 4 +- .../xmrwallet/XmrWalletApplication.java | 6 + .../service/exchange/api/ExchangeApi.java | 1 + .../service/exchange/ecb/ExchangeApiImpl.java | 24 +- .../exchange/kraken/ExchangeApiImpl.java | 5 + .../ExchangeApiImpl.java | 39 ++- .../exchange/krakenFiat/ExchangeRateImpl.java | 43 +++ .../exchange/yadio/ExchangeApiImpl.java | 105 +++++++ .../ExchangeRateImpl.java | 33 +-- .../m2049r/xmrwallet/util/ServiceHelper.java | 17 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values/arrays.xml | 44 ++- app/src/main/res/values/strings.xml | 36 --- .../exchange/yadio/ExchangeRateTest.java | 263 ++++++++++++++++++ build.gradle | 2 +- 16 files changed, 537 insertions(+), 89 deletions(-) rename app/src/main/java/com/m2049r/xmrwallet/service/exchange/{krakenEcb => krakenFiat}/ExchangeApiImpl.java (70%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeRateImpl.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeApiImpl.java rename app/src/main/java/com/m2049r/xmrwallet/service/exchange/{krakenEcb => yadio}/ExchangeRateImpl.java (65%) create mode 100644 app/src/test/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateTest.java diff --git a/.circleci/config.yml b/.circleci/config.yml index b939051a..a880d432 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: working_directory: ~/code docker: - - image: cimg/android:2022.03-ndk + - image: cimg/android:2023.12-ndk environment: JVM_OPTS: -Xmx3200m steps: diff --git a/app/build.gradle b/app/build.gradle index e862fb3d..48f93d69 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { compileSdk 33 minSdkVersion 21 targetSdkVersion 33 - versionCode 3308 - versionName "3.3.8 'Pocket Change'" + versionCode 3310 + versionName "3.3.10 'Argentina'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { diff --git a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java index 3762fb18..3943d23b 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java +++ b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java @@ -22,18 +22,24 @@ import android.content.res.Configuration; import android.os.Build; import androidx.annotation.NonNull; +import androidx.annotation.OptIn; import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStateManagerControl; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.NetCipherHelper; import com.m2049r.xmrwallet.util.NightmodeHelper; +import com.m2049r.xmrwallet.util.ServiceHelper; + +import java.util.Arrays; import timber.log.Timber; public class XmrWalletApplication extends Application { @Override + @OptIn(markerClass = FragmentStateManagerControl.class) public void onCreate() { super.onCreate(); FragmentManager.enableNewStateManager(false); diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java index 7b7972a0..b63c32c5 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java @@ -33,5 +33,6 @@ public interface ExchangeApi { void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, @NonNull final ExchangeCallback callback); + String getName(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java index e355ff5d..cae72758 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java @@ -67,9 +67,9 @@ public class ExchangeApiImpl implements ExchangeApi { // data is daily and is refreshed around 16:00 CET every working day } - public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) { - return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) && - (calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR)); + @Override + public String getName() { + return "ecb"; } @Override @@ -121,12 +121,12 @@ public class ExchangeApiImpl implements ExchangeApi { final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl); httpRequest.enqueue(new okhttp3.Callback() { @Override - public void onFailure(final Call call, final IOException ex) { + public void onFailure(@NonNull final Call call, @NonNull final IOException ex) { callback.onError(ex); } @Override - public void onResponse(final Call call, final Response response) throws IOException { + public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException { if (response.isSuccessful()) { try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); @@ -178,11 +178,12 @@ public class ExchangeApiImpl implements ExchangeApi { Element cube = (Element) node; if (cube.hasAttribute("time")) { // a time Cube final Date time = DATE_FORMAT.parse(cube.getAttribute("time")); + assert time != null; date.setTime(time); } else if (cube.hasAttribute("currency") && cube.hasAttribute("rate")) { // a rate Cube String currency = cube.getAttribute("currency"); - double rate = Double.valueOf(cube.getAttribute("rate")); + double rate = Double.parseDouble(cube.getAttribute("rate")); entries.put(currency, rate); } // else an empty Cube - ignore } @@ -191,13 +192,10 @@ public class ExchangeApiImpl implements ExchangeApi { Timber.d(ex); } synchronized (this) { - if (date != null) { - fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET")); - fxDate = date; - fxEntries.clear(); - fxEntries.putAll(entries); - } - // else don't change what we have + fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET")); + fxDate = date; + fxEntries.clear(); + fxEntries.putAll(entries); } } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java index ab36259e..2be00103 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java @@ -51,6 +51,11 @@ public class ExchangeApiImpl implements ExchangeApi { this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker")); } + @Override + public String getName() { + return "kraken"; + } + @Override public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, @NonNull final ExchangeCallback callback) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeApiImpl.java similarity index 70% rename from app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java rename to app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeApiImpl.java index fcc7c678..889359a2 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeApiImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 m2049r@monerujo.io + * Copyright (c) 2019-2023 m2049r@monerujo.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ // https://developer.android.com/training/basics/network-ops/xml -package com.m2049r.xmrwallet.service.exchange.krakenEcb; +package com.m2049r.xmrwallet.service.exchange.krakenFiat; import androidx.annotation.NonNull; @@ -24,23 +24,39 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.util.ServiceHelper; import timber.log.Timber; /* - Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB + Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the yadio */ public class ExchangeApiImpl implements ExchangeApi { static public final String BASE_FIAT = "EUR"; + final ExchangeApi krakenApi = new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(); + + private ExchangeApi getFiatApi(String symbol) { + return ServiceHelper.getFiatApi(symbol); + } + + @Override + public String getName() { + return krakenApi.getName() + "+"; + } + + public String getRealName(String fiatService) { + return getName() + fiatService; + } + @Override public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, @NonNull final ExchangeCallback callback) { Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency); if (baseCurrency.equals(quoteCurrency)) { Timber.d("BASE=QUOTE=1"); - callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0)); + callback.onSuccess(new ExchangeRateImpl(getName(), baseCurrency, quoteCurrency, 1.0)); return; } @@ -52,24 +68,21 @@ public class ExchangeApiImpl implements ExchangeApi { final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency; - final ExchangeApi krakenApi = - new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(); krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() { @Override public void onSuccess(final ExchangeRate krakenRate) { Timber.d("kraken = %f", krakenRate.getRate()); - final ExchangeApi ecbApi = - new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(); - ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() { + final ExchangeApi fiatApi = getFiatApi(quote); + fiatApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() { @Override - public void onSuccess(final ExchangeRate ecbRate) { - Timber.d("ECB = %f", ecbRate.getRate()); - double rate = ecbRate.getRate() * krakenRate.getRate(); + public void onSuccess(final ExchangeRate fiatRate) { + Timber.d("FIAT = %f", fiatRate.getRate()); + double rate = fiatRate.getRate() * krakenRate.getRate(); Timber.d("Q=%s QC=%s", quote, quoteCurrency); if (!quote.equals(quoteCurrency)) rate = 1.0d / rate; Timber.d("rate = %f", rate); final ExchangeRate exchangeRate = - new ExchangeRateImpl(baseCurrency, quoteCurrency, rate); + new ExchangeRateImpl(getRealName(fiatApi.getName()), baseCurrency, quoteCurrency, rate); callback.onSuccess(exchangeRate); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeRateImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeRateImpl.java new file mode 100644 index 00000000..f4c94029 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenFiat/ExchangeRateImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2023 m2049r et al. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.service.exchange.krakenFiat; + +import androidx.annotation.NonNull; + +import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; + +import lombok.Getter; + +class ExchangeRateImpl implements ExchangeRate { + @Getter + private final String serviceName; + @Getter + private final String baseCurrency; + @Getter + private final String quoteCurrency; + @Getter + private final double rate; + + ExchangeRateImpl(@NonNull final String serviceName, @NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) { + super(); + this.serviceName = serviceName; + this.baseCurrency = baseCurrency; + this.quoteCurrency = quoteCurrency; + this.rate = rate; + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeApiImpl.java new file mode 100644 index 00000000..738a2721 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeApiImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 m2049r@monerujo.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://developer.android.com/training/basics/network-ops/xml + +package com.m2049r.xmrwallet.service.exchange.yadio; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; +import com.m2049r.xmrwallet.util.NetCipherHelper; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.HttpUrl; +import okhttp3.Response; +import timber.log.Timber; + +public class ExchangeApiImpl implements ExchangeApi { + @NonNull + private final HttpUrl baseUrl; + + //so we can inject the mockserver url + @VisibleForTesting + public ExchangeApiImpl(@NonNull final HttpUrl baseUrl) { + this.baseUrl = baseUrl; + } + + public ExchangeApiImpl() { + this(HttpUrl.parse("https://api.yadio.io/convert/1/eur")); + } + + @Override + public String getName() { + return "yadio"; + } + + @Override + public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, + @NonNull final ExchangeCallback callback) { + if (!baseCurrency.equals("EUR")) { + callback.onError(new IllegalArgumentException("Only EUR supported as base")); + return; + } + + if (baseCurrency.equals(quoteCurrency)) { + callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, 0)); + return; + } + + final HttpUrl url = baseUrl.newBuilder() + .addPathSegments(quoteCurrency.substring(0, 3)) + .build(); + final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url); + + httpRequest.enqueue(new okhttp3.Callback() { + @Override + public void onFailure(@NonNull final Call call, @NonNull final IOException ex) { + callback.onError(ex); + } + + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException { + if (response.isSuccessful()) { + try { + assert response.body() != null; + final JSONObject json = new JSONObject(response.body().string()); + if (json.has("error")) { + Timber.d("%d: %s", response.code(), json.getString("error")); + callback.onError(new ExchangeException(response.code(), json.getString("error"))); + return; + } + double rate = json.getDouble("rate"); + long timestamp = json.getLong("timestamp"); + callback.onSuccess(new ExchangeRateImpl(quoteCurrency, rate, timestamp)); + } catch (JSONException ex) { + callback.onError(ex); + } + } else { + callback.onError(new ExchangeException(response.code(), response.message())); + } + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateImpl.java similarity index 65% rename from app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java rename to app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateImpl.java index 48b8ef0b..49a936a6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateImpl.java @@ -14,41 +14,34 @@ * limitations under the License. */ -package com.m2049r.xmrwallet.service.exchange.krakenEcb; +package com.m2049r.xmrwallet.service.exchange.yadio; import androidx.annotation.NonNull; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; +import java.util.Date; + +import lombok.Getter; + class ExchangeRateImpl implements ExchangeRate { - private final String baseCurrency; + @Getter + private final String baseCurrency = "EUR"; + @Getter private final String quoteCurrency; + @Getter private final double rate; + private final Date date; @Override public String getServiceName() { - return "kraken+ecb"; + return "yadio.io"; } - @Override - public String getBaseCurrency() { - return baseCurrency; - } - - @Override - public String getQuoteCurrency() { - return quoteCurrency; - } - - @Override - public double getRate() { - return rate; - } - - ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) { + ExchangeRateImpl(@NonNull final String quoteCurrency, double rate, final long timestamp) { super(); - this.baseCurrency = baseCurrency; this.quoteCurrency = quoteCurrency; this.rate = rate; + this.date = timestamp > 0 ? new Date(timestamp) : new Date(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java index 27622916..271a0e63 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java @@ -1,9 +1,16 @@ package com.m2049r.xmrwallet.util; +import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import lombok.Getter; +import lombok.NonNull; import okhttp3.HttpUrl; public class ServiceHelper { @@ -19,6 +26,14 @@ public class ServiceHelper { } static public ExchangeApi getExchangeApi() { - return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(); + return new com.m2049r.xmrwallet.service.exchange.krakenFiat.ExchangeApiImpl(); + } + + static private final ExchangeApi[] fiatApis = new ExchangeApi[]{ + new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(), + new com.m2049r.xmrwallet.service.exchange.yadio.ExchangeApiImpl()}; + + static public ExchangeApi getFiatApi(String symbol) { + return (symbol.length() == 3) ? fiatApis[0] : fiatApis[1]; } } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9ed714cd..00cf3afa 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -433,7 +433,7 @@ Monto trabado hasta el bloque %1$d (faltan %2$d bloques ≈ %3$,.2f días) - Modo calle activado\nSólo se ºmostrarán transacciones nuevas + Modo calle activado\nSólo se mostrarán transacciones nuevas To reduce waiting time on repeated spending, Monerujo can create spare change at the expense of higher fees. It\'ll try to create and maintain at least 6 coins of the selected amount. Create Change diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index ec8b9ad5..f9ee14d7 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -10,4 +10,46 @@ Oled - \ No newline at end of file + + EUR + USD + JPY + GBP + CHF + CAD + + AUD + BGN + BRL + CNY + CZK + DKK + HKD + HUF + IDR + ILS + INR + ISK + KRW + MXN + MYR + NOK + NZD + PHP + PLN + RON + SEK + SGD + THB + TRY + ZAR + + + ARS​ + IRR​ + LBP​ + RUB​ + VES​ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3fe8097a..dd982786 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -282,42 +282,6 @@ Yes, do that! No thanks! - - EUR - USD - JPY - GBP - CHF - CAD - - AUD - BGN - BRL - CNY - CZK - DKK - HKD - HRK - HUF - IDR - ILS - INR - ISK - KRW - MXN - MYR - NOK - NZD - PHP - PLN - RON - SEK - SGD - THB - TRY - ZAR - - Create new wallet Restore view-only wallet Restore wallet from private keys diff --git a/app/src/test/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateTest.java b/app/src/test/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateTest.java new file mode 100644 index 00000000..4fc77c19 --- /dev/null +++ b/app/src/test/java/com/m2049r/xmrwallet/service/exchange/yadio/ExchangeRateTest.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2017-2023 m2049r et al. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.service.exchange.yadio; + +import static org.junit.Assert.assertEquals; + +import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; +import com.m2049r.xmrwallet.util.NetCipherHelper; + +import net.jodah.concurrentunit.Waiter; + +import org.json.JSONException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Date; +import java.util.concurrent.TimeoutException; + +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + + +public class ExchangeRateTest { + + private MockWebServer mockWebServer; + + private ExchangeApi exchangeApi; + + private Waiter waiter; + + @Mock + ExchangeCallback mockExchangeCallback; + + @Before + public void setUp() throws Exception { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + + waiter = new Waiter(); + + MockitoAnnotations.initMocks(this); + NetCipherHelper.Request.mockClient = new OkHttpClient(); + exchangeApi = new ExchangeApiImpl(mockWebServer.url("/")); + } + + @After + public void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + @Test + public void queryExchangeRate_shouldBeGetMethod() + throws InterruptedException, TimeoutException { + + exchangeApi.queryExchangeRate("EUR", "USD", mockExchangeCallback); + + RecordedRequest request = mockWebServer.takeRequest(); + assertEquals("GET", request.getMethod()); + } + + @Test + public void queryExchangeRate_shouldBeEUR() + throws InterruptedException, TimeoutException { + + exchangeApi.queryExchangeRate("CHF", "USD", new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.fail(); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.assertTrue(e instanceof IllegalArgumentException); + waiter.resume(); + } + + }); + waiter.await(); + } + + + @Test + public void queryExchangeRate_shouldBeOneForEur() + throws InterruptedException, TimeoutException { + + exchangeApi.queryExchangeRate("EUR", "EUR", new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.assertEquals(1.0, exchangeRate.getRate()); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.fail(); + waiter.resume(); + } + + }); + waiter.await(); + } + + @Test + public void queryExchangeRate_wasSuccessfulShouldRespondWithUsdRate() + throws InterruptedException, JSONException, TimeoutException { + final String base = "EUR"; + final String quote = "USD"; + final double rate = 1.1043; + + MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse(quote, rate)); + mockWebServer.enqueue(jsonMockResponse); + + exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.assertEquals(base, exchangeRate.getBaseCurrency()); + waiter.assertEquals(quote, exchangeRate.getQuoteCurrency()); + waiter.assertEquals(rate, exchangeRate.getRate()); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.fail(e); + waiter.resume(); + } + }); + waiter.await(); + } + + @Test + public void queryExchangeRate_wasSuccessfulShouldRespondWithAudRate() + throws InterruptedException, JSONException, TimeoutException { + final String base = "EUR"; + final String quote = "AUD"; + final double rate = 99.114651; + + MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse(quote, rate)); + mockWebServer.enqueue(jsonMockResponse); + + exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.assertEquals(base, exchangeRate.getBaseCurrency()); + waiter.assertEquals(quote, exchangeRate.getQuoteCurrency()); + waiter.assertEquals(rate, exchangeRate.getRate()); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.fail(e); + waiter.resume(); + } + }); + waiter.await(); + } + + + @Test + public void queryExchangeRate_wasSuccessfulShouldRespondWithZarRate() + throws InterruptedException, JSONException, TimeoutException { + final String base = "EUR"; + final String quote = "ZAR"; + final double rate = 99.114651; + + MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse(quote, rate)); + mockWebServer.enqueue(jsonMockResponse); + + exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.assertEquals(base, exchangeRate.getBaseCurrency()); + waiter.assertEquals(quote, exchangeRate.getQuoteCurrency()); + waiter.assertEquals(rate, exchangeRate.getRate()); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.fail(e); + waiter.resume(); + } + }); + waiter.await(); + } + + @Test + public void queryExchangeRate_wasNotSuccessfulShouldCallOnError() + throws InterruptedException, JSONException, TimeoutException { + mockWebServer.enqueue(new MockResponse().setResponseCode(500)); + exchangeApi.queryExchangeRate("EUR", "USD", new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.fail(); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.assertTrue(e instanceof ExchangeException); + waiter.assertTrue(((ExchangeException) e).getCode() == 500); + waiter.resume(); + } + + }); + waiter.await(); + } + + @Test + public void queryExchangeRate_unknownAssetShouldCallOnError() + throws InterruptedException, JSONException, TimeoutException { + MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse("X", 0)); + mockWebServer.enqueue(jsonMockResponse); + exchangeApi.queryExchangeRate("EUR", "ABC", new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + waiter.fail(); + waiter.resume(); + } + + @Override + public void onError(final Exception e) { + waiter.assertTrue(e instanceof ExchangeException); + ExchangeException ex = (ExchangeException) e; + waiter.assertEquals(ex.getCode(), 200); + waiter.assertEquals(ex.getErrorMsg(), "currency not found"); + waiter.resume(); + } + }); + waiter.await(); + } + + static public String createMockExchangeRateResponse(String quote, double rate) { + if (!quote.equals("X")) { + return "{\"request\":{\"amount\":1,\"from\":\"EUR\",\"to\":\"" + quote + "\"},\"result\":" + rate + ",\"rate\":" + rate + ",\"timestamp\":" + new Date().getTime() + "}"; + } + return "{\"error\":\"currency not found\"}"; + } +} diff --git a/build.gradle b/build.gradle index 4dfc678d..8c293a5a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.0' + classpath 'com.android.tools.build:gradle:8.1.2' } }