test: CurrencyServiceTest completed?

This commit is contained in:
Piotr Dec 2024-05-24 16:25:19 +02:00
parent 6d8a2093b9
commit c87bcc8b54
Signed by: stawros
GPG key ID: F89F27AD8F881A91
6 changed files with 128 additions and 9 deletions

View file

@ -0,0 +1,13 @@
package eu.ztsh.wymiana.exception;
public class ExchangeFailedException extends RuntimeException{
public ExchangeFailedException() {
this("An exchange error has occurred");
}
public ExchangeFailedException(String message) {
super(message);
}
}

View file

@ -1,11 +1,19 @@
package eu.ztsh.wymiana.service;
import eu.ztsh.wymiana.exception.ExchangeFailedException;
import eu.ztsh.wymiana.exception.InsufficientFundsException;
import eu.ztsh.wymiana.model.Currency;
import eu.ztsh.wymiana.model.User;
import eu.ztsh.wymiana.validation.InstanceValidator;
import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RequiredArgsConstructor
@Service
public class CurrencyService {
@ -15,7 +23,47 @@ public class CurrencyService {
private final InstanceValidator validator;
public User exchange(CurrencyExchangeRequest request) {
throw new UnsupportedOperationException("Not implemented yet");
validator.validate(request);
return userService.get(request.pesel()).map(user -> {
if (!request.from().equalsIgnoreCase("PLN") && !request.to().equalsIgnoreCase("PLN")) {
throw new ExchangeFailedException("Either 'from' or 'to' has to be PLN");
}
// As we support only USD now, we need to limit second parameter too
// Begin: unlock other currencies
if (!request.from().equalsIgnoreCase("USD") && !request.to().equalsIgnoreCase("USD")) {
throw new ExchangeFailedException("Either 'from' or 'to' has to be USD");
}
// End: unlock other currencies
var exchanged = performExchange(user.currencies().get(request.from().toUpperCase()),
user.currencies().get(request.to().toUpperCase()),
Optional.ofNullable(request.toSell()).orElse(0D),
Optional.ofNullable(request.toBuy()).orElse(0D));
user.currencies().putAll(exchanged);
return userService.update(user);
})
.orElseThrow(ExchangeFailedException::new);
}
private Map<String, Currency> performExchange(Currency from, Currency to, double toSell, double toBuy) {
double exchangeRate;
double neededFromAmount;
double requestedToAmount;
if (from.symbol().equalsIgnoreCase("PLN")) {
exchangeRate = nbpService.getSellRate(to.symbol());
neededFromAmount = toBuy != 0 ? toBuy * exchangeRate : toSell;
requestedToAmount = toBuy != 0 ? toBuy : toSell / exchangeRate;
} else {
exchangeRate = nbpService.getSellRate(from.symbol());
neededFromAmount = toBuy != 0 ? toBuy / exchangeRate : toSell;
requestedToAmount = toBuy != 0 ? toBuy : toSell * exchangeRate;
}
if (neededFromAmount > from.amount()) {
throw new InsufficientFundsException();
}
var newFrom = new Currency(from.symbol(), from.amount() - neededFromAmount);
var newTo = new Currency(to.symbol(), to.amount() + requestedToAmount);
return Stream.of(newFrom, newTo).collect(Collectors.toMap(Currency::symbol, currency -> currency));
}
}

View file

@ -31,11 +31,11 @@ public class NbpService {
private final ConcurrentMap<String, RatesCache> cache = new ConcurrentHashMap<>(1);
public double getSellRate(String currency) {
return getCurrency(currency).sell();
return getCurrency(currency.toUpperCase()).sell();
}
public double getBuyRate(String currency) {
return getCurrency(currency).buy();
return getCurrency(currency.toUpperCase()).buy();
}
private synchronized RatesCache getCurrency(String currency) {

View file

@ -30,4 +30,8 @@ public class UserService {
return userRepository.findById(pesel).map(UserMapper::entityToPojo);
}
public User update(User user) {
throw new UnsupportedOperationException("Not implemented yet");
}
}

View file

@ -14,7 +14,8 @@ public class ValidExchangeRequestValidator implements
return false;
}
return !request.from().equals(request.to())
// Apart from @NotNull annotation we need to check if request.from() != null to avoid NPE in equals
return (request.from() != null && !request.from().equals(request.to()))
&& !((request.toBuy() == null && request.toSell() == null)
|| (request.toBuy() != null && request.toSell() != null))
&& ((request.toBuy() != null && request.toBuy() >= 0)