Skip to content

Commit

Permalink
Fix #970: AmountAttribute with invalid amount (#971)
Browse files Browse the repository at this point in the history
* Fix #970: AmountAttribute with invalid amount
  • Loading branch information
banterCZ authored Jan 2, 2024
1 parent 2a50517 commit 4b58e1a
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -285,29 +285,39 @@ private static Optional<Attribute> buildAmountAttribute(final OperationTemplateP
if (currency.isEmpty()) {
return Optional.empty();
}
final BigDecimal amountRaw;
try {
amountRaw = new BigDecimal(amount.get());
} catch (NumberFormatException ex) {
logger.warn("Invalid number format: {}, skipping the AMOUNT attribute!", amount);
return Optional.empty();
}

final Locale locale = LocaleContextHolder.getLocale();
final String currencyRaw = currency.get();
final String currencyFormatted = MonetaryConverter.formatCurrency(currencyRaw, locale);
final String amountFormatted = MonetaryConverter.formatAmount(amountRaw, currencyRaw, locale);
final String valueFormatted = MonetaryConverter.formatValue(amountRaw, currencyRaw, locale);
final AmountFormatted amountFormatted = createAmountFormatted(amount.get(), currencyRaw, "AMOUNT");
return Optional.of(AmountAttribute.builder()
.id(id)
.label(text)
.amount(amountRaw)
.amountFormatted(amountFormatted)
.amount(amountFormatted.amountRaw())
.amountFormatted(amountFormatted.amountFormatted())
.currency(currencyRaw)
.currencyFormatted(currencyFormatted)
.valueFormatted(valueFormatted)
.valueFormatted(amountFormatted.valueFormatted())
.build());
}

private static AmountFormatted createAmountFormatted(final String amount, final String currencyRaw, final String attribute) {
final Locale locale = LocaleContextHolder.getLocale();

try {
final BigDecimal amountRaw = new BigDecimal(amount);
final String amountFormatted = MonetaryConverter.formatAmount(amountRaw, currencyRaw, locale);
final String valueFormatted = MonetaryConverter.formatValue(amountRaw, currencyRaw, locale);
return new AmountFormatted(amountRaw, amountFormatted, valueFormatted);
} catch (NumberFormatException e) {
logger.warn("Invalid number format: {}, the raw value is not filled in into {} attribute!", amount, attribute);
logger.trace("Invalid number format: {}, the raw value is not filled in into {} attribute!", amount, attribute, e);
// fallback - pass 'not a number' directly to the formatted field
final String valueFormatted = amount + " " + currencyRaw;
return new AmountFormatted(null, amount, valueFormatted);
}
}

private static Optional<Attribute> buildAmountConversionAttribute(final OperationTemplateParam templateParam, final Map<String, String> params) {
final String id = templateParam.getId();
final String text = templateParam.getText();
Expand All @@ -326,39 +336,29 @@ private static Optional<Attribute> buildAmountConversionAttribute(final Operatio
.map(Boolean::parseBoolean)
.orElse(false);

final BigDecimal sourceAmountRaw;
final BigDecimal targetAmountRaw;
try {
sourceAmountRaw = new BigDecimal(sourceAmount.get());
targetAmountRaw = new BigDecimal(targetAmount.get());
} catch (NumberFormatException ex) {
logger.warn("Invalid number format: {}, skipping the AMOUNT_CONVERSION attribute!", sourceAmount);
return Optional.empty();
}

final Locale locale = LocaleContextHolder.getLocale();
final String sourceCurrencyRaw = sourceCurrency.get();
final String sourceCurrencyFormatted = MonetaryConverter.formatCurrency(sourceCurrencyRaw, locale);
final String sourceAmountFormatted = MonetaryConverter.formatAmount(sourceAmountRaw, sourceCurrencyRaw, locale);
final String sourceValueFormatted = MonetaryConverter.formatValue(sourceAmountRaw, sourceCurrencyRaw, locale);
final AmountFormatted sourceAmountFormatted = createAmountFormatted(sourceAmount.get(), sourceCurrencyRaw, "AMOUNT_CONVERSION");

final String targetCurrencyRaw = targetCurrency.get();
final AmountFormatted targetAmountFormatted = createAmountFormatted(targetAmount.get(), targetCurrencyRaw, "AMOUNT_CONVERSION");

final String sourceCurrencyFormatted = MonetaryConverter.formatCurrency(sourceCurrencyRaw, locale);
final String targetCurrencyFormatted = MonetaryConverter.formatCurrency(targetCurrencyRaw, locale);
final String targetAmountFormatted = MonetaryConverter.formatAmount(targetAmountRaw, targetCurrencyRaw, locale);
final String targetValueFormatted = MonetaryConverter.formatValue(targetAmountRaw, targetCurrencyRaw, locale);
return Optional.of(AmountConversionAttribute.builder()
.id(id)
.label(text)
.dynamic(dynamic)
.sourceAmount(sourceAmountRaw)
.sourceAmountFormatted(sourceAmountFormatted)
.sourceAmount(sourceAmountFormatted.amountRaw())
.sourceAmountFormatted(sourceAmountFormatted.amountFormatted())
.sourceCurrency(sourceCurrencyRaw)
.sourceCurrencyFormatted(sourceCurrencyFormatted)
.sourceValueFormatted(sourceValueFormatted)
.targetAmount(targetAmountRaw)
.targetAmountFormatted(targetAmountFormatted)
.sourceValueFormatted(sourceAmountFormatted.valueFormatted())
.targetAmount(targetAmountFormatted.amountRaw())
.targetAmountFormatted(targetAmountFormatted.amountFormatted())
.targetCurrency(targetCurrencyRaw)
.targetCurrencyFormatted(targetCurrencyFormatted)
.targetValueFormatted(targetValueFormatted)
.targetValueFormatted(targetAmountFormatted.valueFormatted())
.build());
}

Expand Down Expand Up @@ -397,7 +397,7 @@ private static Optional<String> fetchTemplateParamValue(final OperationTemplateP
return Optional.empty();
}
if (params == null) {
logger.warn("Params of OperationDetailResponse is null");
logger.warn("Params of OperationTemplateParam is null");
return Optional.empty();
}
return Optional.ofNullable(templateParams.get(key))
Expand All @@ -408,4 +408,6 @@ private static String fetchTemplateParamValueNullable(final OperationTemplatePar
return fetchTemplateParamValue(templateParam, params, key)
.orElse(null);
}

private record AmountFormatted(BigDecimal amountRaw, String amountFormatted, String valueFormatted) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,150 @@ void testConvertAttributes() throws Exception {
.build()), atributesIterator.next());
}

@Test
void testConvertAmount_notANumber() throws Exception {
final OperationDetailResponse operationDetail = createOperationDetailResponse();
operationDetail.setParameters(ImmutableMap.<String, String>builder()
.put("amount", "not a number")
.put("currency", "CZK")
.build());

final OperationTemplateEntity operationTemplate = new OperationTemplateEntity();
operationTemplate.setAttributes("""
[
{
"id": "operation.amount",
"type": "AMOUNT",
"text": "Amount",
"params": {
"amount": "amount",
"currency": "currency"
}
}
]""");

LocaleContextHolder.setLocale(new Locale("en"));
final Operation result = tested.convert(operationDetail, operationTemplate);

final List<Attribute> attributes = result.getFormData().getAttributes();

assertEquals(1, attributes.size());
final var atributesIterator = attributes.iterator();
assertEquals(AmountAttribute.builder()
.id("operation.amount")
.label("Amount")
.amount(null)
.amountFormatted("not a number")
.currency("CZK")
.currencyFormatted("CZK")
.valueFormatted("not a number CZK")
.build(), atributesIterator.next());
}

@Test
void testConvertAmountConversion_sourceNotANumber() throws Exception {
final OperationDetailResponse operationDetail = createOperationDetailResponse();
operationDetail.setParameters(ImmutableMap.<String, String>builder()
.put("sourceAmount", "source not a number")
.put("sourceCurrency", "EUR")
.put("targetAmount", "1710.98")
.put("targetCurrency", "USD")
.put("dynamic", "true")
.build());

final OperationTemplateEntity operationTemplate = new OperationTemplateEntity();
operationTemplate.setAttributes("""
[
{
"id": "operation.amountConversion",
"type": "AMOUNT_CONVERSION",
"text": "Amount Conversion",
"params": {
"dynamic": "dynamic",
"sourceAmount": "sourceAmount",
"sourceCurrency": "sourceCurrency",
"targetAmount": "targetAmount",
"targetCurrency": "targetCurrency"
}
}
]""");

LocaleContextHolder.setLocale(new Locale("en"));
final Operation result = tested.convert(operationDetail, operationTemplate);

final List<Attribute> attributes = result.getFormData().getAttributes();

assertEquals(1, attributes.size());
final var atributesIterator = attributes.iterator();
assertEquals(AmountConversionAttribute.builder()
.id("operation.amountConversion")
.label("Amount Conversion")
.dynamic(true)
.sourceAmount(null)
.sourceAmountFormatted("source not a number")
.sourceCurrency("EUR")
.sourceCurrencyFormatted("€")
.sourceValueFormatted("source not a number EUR")
.targetAmount(new BigDecimal("1710.98"))
.targetAmountFormatted("1,710.98")
.targetCurrency("USD")
.targetCurrencyFormatted("$")
.targetValueFormatted("$1,710.98")
.build(), atributesIterator.next());
}

@Test
void testConvertAmountConversion_targetNotANumber() throws Exception {
final OperationDetailResponse operationDetail = createOperationDetailResponse();
operationDetail.setParameters(ImmutableMap.<String, String>builder()
.put("sourceAmount", "1710.98")
.put("sourceCurrency", "USD")
.put("targetAmount", "target not a number")
.put("targetCurrency", "EUR")
.put("dynamic", "true")
.build());

final OperationTemplateEntity operationTemplate = new OperationTemplateEntity();
operationTemplate.setAttributes("""
[
{
"id": "operation.amountConversion",
"type": "AMOUNT_CONVERSION",
"text": "Amount Conversion",
"params": {
"dynamic": "dynamic",
"sourceAmount": "sourceAmount",
"sourceCurrency": "sourceCurrency",
"targetAmount": "targetAmount",
"targetCurrency": "targetCurrency"
}
}
]""");

LocaleContextHolder.setLocale(new Locale("en"));
final Operation result = tested.convert(operationDetail, operationTemplate);

final List<Attribute> attributes = result.getFormData().getAttributes();

assertEquals(1, attributes.size());
final var atributesIterator = attributes.iterator();
assertEquals(AmountConversionAttribute.builder()
.id("operation.amountConversion")
.label("Amount Conversion")
.dynamic(true)
.sourceAmount(new BigDecimal("1710.98"))
.sourceAmountFormatted("1,710.98")
.sourceCurrency("USD")
.sourceCurrencyFormatted("$")
.sourceValueFormatted("$1,710.98")
.targetAmount(null)
.targetAmountFormatted("target not a number")
.targetCurrency("EUR")
.targetCurrencyFormatted("€")
.targetValueFormatted("target not a number EUR")
.build(), atributesIterator.next());
}

@Test
void testConvertImageAttributeWithoutOriginalUrl() throws Exception {
final OperationDetailResponse operationDetail = createOperationDetailResponse();
Expand Down

0 comments on commit 4b58e1a

Please sign in to comment.