feat(workshop): add bonus exercises for custom providers and currencies
Introduce optional bonus tasks in part4 covering: - CSV-based exchange rate provider - historical exchange rate provider with date handling - custom currency (DKP) with bidirectional conversion Provide exercise scaffolding and tests for all scenarios. Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
This commit is contained in:
parent
4f915afb06
commit
f307005c6d
11 changed files with 679 additions and 7 deletions
|
|
@ -154,9 +154,11 @@ BigDecimal amount = new BigDecimal("10.123") // gültig oder nicht?
|
|||
|
||||
<div class="my-6"></div>
|
||||
|
||||
- CHF: 2 Nachkommastellen
|
||||
- JPY: keine Nachkommastellen
|
||||
- TND: 3 Nachkommastellen
|
||||
<ul v-click>
|
||||
<li>CHF: 2 Nachkommastellen</li>
|
||||
<li>JPY: keine Nachkommastellen</li>
|
||||
<li>TND: 3 Nachkommastellen</li>
|
||||
</ul>
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -234,8 +236,7 @@ Was ist JSR-354?
|
|||
- Spezifikation: Java Community Process (JCP)
|
||||
- Referenzimplementierung: Moneta
|
||||
- Entwickelt und gepflegt von der Java-Community
|
||||
- Spec Lead JSR-354: Werner Keil
|
||||
- Lead der Referenzimplementierung (Moneta): Anatole Tresch
|
||||
- Spec Leads: Anatole Tresch, Werner Keil und Otavio Santana
|
||||
|
||||
👉 Open Source und gemeinschaftlich weiterentwickelt
|
||||
|
||||
|
|
@ -676,7 +677,7 @@ MonetaryAmount amountCHF = Money.of(10, "CHF");
|
|||
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setTermCurrency("EUR")
|
||||
.set(LocalDateTime.class, LocalDateTime.of(2026, 3, 13, 14, 3, 27))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 3, 13))
|
||||
.build();
|
||||
|
||||
CurrencyConversion conversion = MonetaryConversions.getConversion(query);
|
||||
|
|
@ -992,6 +993,66 @@ Warenkorb / Bestellung
|
|||
|
||||
---
|
||||
|
||||
# Bonusaufgabe 1
|
||||
|
||||
Eigener Exchange Rate Provider
|
||||
|
||||
Package: `swiss.fihlon.workshop.money.part4`
|
||||
|
||||
- Implementiere einen eigenen `ExchangeRateProvider`
|
||||
- Lade Wechselkurse aus `exchange-rates.csv`
|
||||
- CSV enthält:
|
||||
- Quellwährung
|
||||
- Zielwährung
|
||||
- Wechselkurs
|
||||
- Stelle Umrechnungen zwischen den Währungen bereit
|
||||
- Noch **ohne** Historisierung
|
||||
|
||||
Relevante Interfaces: `ExchangeRateProvider`
|
||||
|
||||
👉 Ziel: eigener Provider mit lokal geladenen Kursen
|
||||
|
||||
---
|
||||
|
||||
# Bonus 2
|
||||
|
||||
Historische Kurse
|
||||
|
||||
Package: `swiss.fihlon.workshop.money.part4`
|
||||
|
||||
- Erweitere deinen Provider aus Bonus 1
|
||||
- Verwende `exchange-rates-hist.csv`
|
||||
- CSV enthält zusätzlich:
|
||||
- Datum pro Wechselkurs
|
||||
- Wähle den passenden Kurs für ein angefragtes Datum
|
||||
- Fallback: sinnvoller Standard (z. B. letzter verfügbarer Kurs)
|
||||
|
||||
Relevante Interfaces: `ExchangeRateProvider`
|
||||
|
||||
👉 Ziel: zeitabhängige Wechselkurse unterstützen
|
||||
|
||||
---
|
||||
|
||||
# Bonus 3
|
||||
|
||||
Eigene Währung
|
||||
|
||||
Package: `swiss.fihlon.workshop.money.part4`
|
||||
|
||||
- Implementiere die eigene Währung `DKP`
|
||||
- Vorgegebener Umrechnungsfaktor:
|
||||
- 10 CHF = 1 DKP
|
||||
- Baue einen eigenen Converter
|
||||
- Unterstütze:
|
||||
- CHF → DKP
|
||||
- DKP → CHF
|
||||
|
||||
Relevante Interfaces: `CurrencyUnit` und `ExchangeRateProvider`
|
||||
|
||||
👉 Ziel: eigene Währung ins Modell integrieren
|
||||
|
||||
---
|
||||
|
||||
# Voraussetzungen
|
||||
|
||||
Java-Versionen
|
||||
|
|
@ -1058,4 +1119,4 @@ layout: center
|
|||
layout: center
|
||||
---
|
||||
|
||||
# **Danke!**
|
||||
# Danke!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import javax.money.convert.ExchangeRateProvider;
|
||||
import javax.money.convert.ProviderContext;
|
||||
|
||||
/**
|
||||
* <p>Bonus exercise 1 for implementing a local CSV-based exchange rate provider with JSR-354.</p>
|
||||
*
|
||||
* <p>The provider is intended for workshop usage and should read rates from the classpath resource
|
||||
* {@code exchange-rates.csv}.</p>
|
||||
*
|
||||
* <p>The implementation is intentionally incomplete and should be finished by making tests pass.</p>
|
||||
*/
|
||||
public class CsvExchangeRateProvider implements ExchangeRateProvider {
|
||||
|
||||
/**
|
||||
* <p>Name of the classpath CSV resource containing exchange rates.</p>
|
||||
*/
|
||||
public static final String RESOURCE_NAME = "exchange-rates.csv";
|
||||
|
||||
/**
|
||||
* <p>Create a new provider instance.</p>
|
||||
*
|
||||
* <p>The provider should load exchange rates from {@link #RESOURCE_NAME}.</p>
|
||||
*/
|
||||
public CsvExchangeRateProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return the provider context for this exchange rate provider.</p>
|
||||
*
|
||||
* @return provider context including a useful provider name
|
||||
*/
|
||||
@Override
|
||||
public ProviderContext getContext() {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Look up an exchange rate for the given conversion query.</p>
|
||||
*
|
||||
* @param conversionQuery conversion query containing base and term currency
|
||||
* @return matching exchange rate, or {@code null} if no matching rate exists
|
||||
*/
|
||||
@Override
|
||||
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a currency conversion for the given conversion query.</p>
|
||||
*
|
||||
* <p>The returned conversion should allow direct amount conversion via {@code amount.with(conversion)}.</p>
|
||||
*
|
||||
* @param conversionQuery conversion query containing conversion details
|
||||
* @return currency conversion for the query
|
||||
*/
|
||||
@Override
|
||||
public CurrencyConversion getCurrencyConversion(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import javax.money.convert.ExchangeRateProvider;
|
||||
import javax.money.convert.ProviderContext;
|
||||
|
||||
/**
|
||||
* <p>Bonus exercise 2 for implementing a local CSV-based historical exchange rate provider with JSR-354.</p>
|
||||
*
|
||||
* <p>The provider is intended for workshop usage and should read rates from the classpath resource
|
||||
* {@code exchange-rates-hist.csv}.</p>
|
||||
*
|
||||
* <p>The lookup should use base currency, term currency, and a date passed in the query context.</p>
|
||||
*
|
||||
* <p>The implementation is intentionally incomplete and should be finished by making tests pass.</p>
|
||||
*/
|
||||
public class CsvHistoricalExchangeRateProvider implements ExchangeRateProvider {
|
||||
|
||||
/**
|
||||
* <p>Name of the classpath CSV resource containing historical exchange rates.</p>
|
||||
*/
|
||||
public static final String RESOURCE_NAME = "exchange-rates-hist.csv";
|
||||
|
||||
/**
|
||||
* <p>Query key type for historical lookups.</p>
|
||||
*/
|
||||
public static final Class<LocalDate> DATE_QUERY_KEY = LocalDate.class;
|
||||
|
||||
/**
|
||||
* <p>Create a new provider instance.</p>
|
||||
*
|
||||
* <p>The provider should load exchange rates from {@link #RESOURCE_NAME}.</p>
|
||||
*/
|
||||
public CsvHistoricalExchangeRateProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return the provider context for this exchange rate provider.</p>
|
||||
*
|
||||
* @return provider context including a useful provider name
|
||||
*/
|
||||
@Override
|
||||
public ProviderContext getContext() {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Look up a historical exchange rate for the given conversion query.</p>
|
||||
*
|
||||
* <p>The query is expected to contain a {@link LocalDate} under {@link #DATE_QUERY_KEY}.</p>
|
||||
*
|
||||
* <p>If no exact match exists for the requested date, the provider should use the last available
|
||||
* rate before that date.</p>
|
||||
*
|
||||
* <p>The method should return {@code null} only when no suitable earlier rate exists.</p>
|
||||
*
|
||||
* @param conversionQuery conversion query containing base currency, term currency, and date
|
||||
* @return matching historical exchange rate with fallback to the latest earlier date, or {@code null}
|
||||
*/
|
||||
@Override
|
||||
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a currency conversion for the given conversion query.</p>
|
||||
*
|
||||
* <p>The conversion should use the same lookup rules as {@link #getExchangeRate(ConversionQuery)}:
|
||||
* exact date first, otherwise the last available earlier rate.</p>
|
||||
*
|
||||
* <p>The returned conversion should allow direct amount conversion via {@code amount.with(conversion)}.</p>
|
||||
*
|
||||
* @param conversionQuery conversion query containing conversion details
|
||||
* @return currency conversion for the query
|
||||
*/
|
||||
@Override
|
||||
public CurrencyConversion getCurrencyConversion(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import javax.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* <p>Bonus exercise 3 helper for creating the custom DukePoints currency.</p>
|
||||
*
|
||||
* <p>The goal is to model {@code DKP} as a custom {@link CurrencyUnit} that can be used with monetary amounts.</p>
|
||||
*/
|
||||
public class DukePointsCurrencyFactory {
|
||||
|
||||
/**
|
||||
* <p>Currency code for DukePoints.</p>
|
||||
*/
|
||||
public static final String DKP = "DKP";
|
||||
|
||||
/**
|
||||
* <p>Create the custom DukePoints currency unit.</p>
|
||||
*
|
||||
* @return custom currency unit with currency code {@code DKP}
|
||||
*/
|
||||
public CurrencyUnit createDkpCurrency() {
|
||||
return new DukePointsCurrencyUnit();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import javax.money.CurrencyContext;
|
||||
import javax.money.CurrencyContextBuilder;
|
||||
import javax.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* <p>This is a minimal custom CurrencyUnit implementation used for workshop purposes.</p>
|
||||
*
|
||||
* <p>The implementation is provided to focus on currency integration and conversion logic.</p>
|
||||
*/
|
||||
public final class DukePointsCurrencyUnit implements CurrencyUnit {
|
||||
|
||||
private static final String CURRENCY_CODE = DukePointsCurrencyFactory.DKP;
|
||||
private static final int DEFAULT_FRACTION_DIGITS = 2;
|
||||
private static final int NUMERIC_CODE = 999;
|
||||
private static final CurrencyContext CONTEXT =
|
||||
CurrencyContextBuilder.of(DukePointsCurrencyUnit.class.getSimpleName()).build();
|
||||
|
||||
@Override
|
||||
public String getCurrencyCode() {
|
||||
return CURRENCY_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumericCode() {
|
||||
return NUMERIC_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultFractionDigits() {
|
||||
return DEFAULT_FRACTION_DIGITS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrencyContext getContext() {
|
||||
return CONTEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CurrencyUnit otherCurrency) {
|
||||
return getCurrencyCode().compareTo(otherCurrency.getCurrencyCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCurrencyCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof CurrencyUnit other)) {
|
||||
return false;
|
||||
}
|
||||
return getCurrencyCode().equals(other.getCurrencyCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getCurrencyCode().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import javax.money.convert.ExchangeRateProvider;
|
||||
import javax.money.convert.ProviderContext;
|
||||
|
||||
/**
|
||||
* <p>Bonus exercise 3 for implementing local exchange rates between CHF and DukePoints.</p>
|
||||
*
|
||||
* <p>The provider should support deterministic conversion for {@code CHF -> DKP} and {@code DKP -> CHF}.</p>
|
||||
*
|
||||
* <p>The implementation is intentionally incomplete and should be finished by making tests pass.</p>
|
||||
*/
|
||||
public class DukePointsExchangeRateProvider implements ExchangeRateProvider {
|
||||
|
||||
/**
|
||||
* <p>Create a new exchange rate provider for DukePoints.</p>
|
||||
*/
|
||||
public DukePointsExchangeRateProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return provider context metadata for this provider.</p>
|
||||
*
|
||||
* @return provider context with a useful provider name
|
||||
*/
|
||||
@Override
|
||||
public ProviderContext getContext() {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return the exchange rate for the given conversion query.</p>
|
||||
*
|
||||
* <p>Supported pairs are {@code CHF -> DKP} and {@code DKP -> CHF}.</p>
|
||||
*
|
||||
* @param conversionQuery query containing base and term currency
|
||||
* @return matching exchange rate, or {@code null} for unsupported currency pairs
|
||||
*/
|
||||
@Override
|
||||
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a currency conversion for the given conversion query.</p>
|
||||
*
|
||||
* <p>The returned conversion should allow direct conversion using {@code amount.with(conversion)}.</p>
|
||||
*
|
||||
* @param conversionQuery query containing base and term currency
|
||||
* @return currency conversion based on the local DukePoints rules
|
||||
*/
|
||||
@Override
|
||||
public CurrencyConversion getCurrencyConversion(ConversionQuery conversionQuery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
}
|
||||
19
src/main/resources/exchange-rates-hist.csv
Normal file
19
src/main/resources/exchange-rates-hist.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
date,base,term,rate
|
||||
2026-04-15,CHF,EUR,0.92
|
||||
2026-04-15,CHF,USD,1.09
|
||||
2026-04-15,EUR,CHF,1.09
|
||||
2026-04-15,EUR,USD,1.18
|
||||
2026-04-15,USD,CHF,0.92
|
||||
2026-04-15,USD,EUR,0.85
|
||||
2026-04-16,CHF,EUR,0.93
|
||||
2026-04-16,CHF,USD,1.10
|
||||
2026-04-16,EUR,CHF,1.08
|
||||
2026-04-16,EUR,USD,1.19
|
||||
2026-04-16,USD,CHF,0.91
|
||||
2026-04-16,USD,EUR,0.84
|
||||
2026-04-17,CHF,EUR,0.94
|
||||
2026-04-17,CHF,USD,1.11
|
||||
2026-04-17,EUR,CHF,1.06
|
||||
2026-04-17,EUR,USD,1.20
|
||||
2026-04-17,USD,CHF,0.90
|
||||
2026-04-17,USD,EUR,0.83
|
||||
|
9
src/main/resources/exchange-rates.csv
Normal file
9
src/main/resources/exchange-rates.csv
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
base,term,rate
|
||||
CHF,EUR,0.93
|
||||
CHF,USD,1.10
|
||||
CHF,GBP,0.82
|
||||
EUR,CHF,1.08
|
||||
USD,CHF,0.91
|
||||
GBP,CHF,1.22
|
||||
EUR,USD,1.18
|
||||
USD,EUR,0.85
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import javax.money.Monetary;
|
||||
import javax.money.MonetaryAmount;
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.ConversionQueryBuilder;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import org.javamoney.moneta.Money;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CsvExchangeRateProviderTest {
|
||||
|
||||
private final CsvExchangeRateProvider provider = new CsvExchangeRateProvider();
|
||||
|
||||
@Test
|
||||
void shouldReturnProviderContextWithUsefulName() {
|
||||
String providerName = provider.getContext().getProviderName();
|
||||
|
||||
assertThat(providerName).isNotBlank();
|
||||
assertThat(providerName).containsIgnoringCase("csv");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExchangeRateForChfToEur() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.93");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExchangeRateForUsdToEur() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("USD"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.85");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullForUnknownCurrencyPair() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("EUR"))
|
||||
.setTermCurrency(Monetary.getCurrency("GBP"))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertAmountUsingExchangeRate() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.build();
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
|
||||
MonetaryAmount convertedAmount = chfAmount.multiply(exchangeRate.getFactor().numberValueExact(BigDecimal.class));
|
||||
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("93.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateCurrencyConversion() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.build();
|
||||
|
||||
CurrencyConversion conversion = provider.getCurrencyConversion(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
MonetaryAmount convertedAmount = chfAmount.with(conversion);
|
||||
|
||||
assertThat(convertedAmount.getCurrency().getCurrencyCode()).isEqualTo("EUR");
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("93.00");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import javax.money.Monetary;
|
||||
import javax.money.MonetaryAmount;
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.ConversionQueryBuilder;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import org.javamoney.moneta.Money;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CsvHistoricalExchangeRateProviderTest {
|
||||
|
||||
private final CsvHistoricalExchangeRateProvider provider = new CsvHistoricalExchangeRateProvider();
|
||||
|
||||
@Test
|
||||
void shouldReturnExchangeRateForChfToEurOn20260415() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 15))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.92");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExchangeRateForChfToEurOn20260417() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 17))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.94");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExchangeRateForUsdToChfOn20260416() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("USD"))
|
||||
.setTermCurrency(Monetary.getCurrency("CHF"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 16))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.91");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnLastAvailableRateWhenDateIsMissing() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("EUR"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 18))
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNotNull();
|
||||
assertThat(exchangeRate.getFactor().numberValueExact(BigDecimal.class)).isEqualByComparingTo("0.94");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertAmountUsingHistoricalRate() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("USD"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 17))
|
||||
.build();
|
||||
ExchangeRate exchangeRate = provider.getExchangeRate(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
|
||||
MonetaryAmount convertedAmount = chfAmount.multiply(exchangeRate.getFactor().numberValueExact(BigDecimal.class));
|
||||
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("111.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateCurrencyConversionForHistoricalRate() {
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(Monetary.getCurrency("USD"))
|
||||
.set(LocalDate.class, LocalDate.of(2026, 4, 17))
|
||||
.build();
|
||||
|
||||
CurrencyConversion conversion = provider.getCurrencyConversion(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
MonetaryAmount convertedAmount = chfAmount.with(conversion);
|
||||
|
||||
assertThat(convertedAmount.getCurrency().getCurrencyCode()).isEqualTo("USD");
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("111.00");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package swiss.fihlon.workshop.money.part4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import javax.money.CurrencyUnit;
|
||||
import javax.money.Monetary;
|
||||
import javax.money.MonetaryAmount;
|
||||
import javax.money.convert.ConversionQuery;
|
||||
import javax.money.convert.ConversionQueryBuilder;
|
||||
import javax.money.convert.CurrencyConversion;
|
||||
import javax.money.convert.ExchangeRate;
|
||||
import org.javamoney.moneta.Money;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DukePointsExchangeRateProviderTest {
|
||||
|
||||
private final DukePointsCurrencyFactory currencyFactory = new DukePointsCurrencyFactory();
|
||||
private final DukePointsExchangeRateProvider exchangeRateProvider = new DukePointsExchangeRateProvider();
|
||||
|
||||
@Test
|
||||
void shouldCreateDkpCurrency() {
|
||||
CurrencyUnit dkp = currencyFactory.createDkpCurrency();
|
||||
|
||||
assertThat(dkp).isNotNull();
|
||||
assertThat(dkp.getCurrencyCode()).isEqualTo("DKP");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertChfToDkp() {
|
||||
CurrencyUnit dkp = currencyFactory.createDkpCurrency();
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(dkp)
|
||||
.build();
|
||||
ExchangeRate exchangeRate = exchangeRateProvider.getExchangeRate(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
|
||||
MonetaryAmount convertedAmount = chfAmount.multiply(exchangeRate.getFactor().numberValueExact(BigDecimal.class));
|
||||
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("10.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertDkpToChf() {
|
||||
CurrencyUnit dkp = currencyFactory.createDkpCurrency();
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(dkp)
|
||||
.setTermCurrency(Monetary.getCurrency("CHF"))
|
||||
.build();
|
||||
ExchangeRate exchangeRate = exchangeRateProvider.getExchangeRate(query);
|
||||
MonetaryAmount dkpAmount = Money.of(5, dkp);
|
||||
|
||||
MonetaryAmount convertedAmount = dkpAmount.multiply(exchangeRate.getFactor().numberValueExact(BigDecimal.class));
|
||||
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("50.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullForUnsupportedCurrencyPair() {
|
||||
CurrencyUnit dkp = currencyFactory.createDkpCurrency();
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("EUR"))
|
||||
.setTermCurrency(dkp)
|
||||
.build();
|
||||
|
||||
ExchangeRate exchangeRate = exchangeRateProvider.getExchangeRate(query);
|
||||
|
||||
assertThat(exchangeRate).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertUsingCurrencyConversion() {
|
||||
CurrencyUnit dkp = currencyFactory.createDkpCurrency();
|
||||
ConversionQuery query = ConversionQueryBuilder.of()
|
||||
.setBaseCurrency(Monetary.getCurrency("CHF"))
|
||||
.setTermCurrency(dkp)
|
||||
.build();
|
||||
CurrencyConversion conversion = exchangeRateProvider.getCurrencyConversion(query);
|
||||
MonetaryAmount chfAmount = Money.of(100, "CHF");
|
||||
|
||||
MonetaryAmount convertedAmount = chfAmount.with(conversion);
|
||||
|
||||
assertThat(convertedAmount.getCurrency().getCurrencyCode()).isEqualTo("DKP");
|
||||
assertThat(convertedAmount.getNumber().numberValueExact(BigDecimal.class)).isEqualByComparingTo("10.00");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue