diff --git a/Source/Singulink.Globalization.Currency/SortedMoneySet.cs b/Source/Singulink.Globalization.Currency/SortedMoneySet.cs index 9a0bdc5..fd04cb9 100644 --- a/Source/Singulink.Globalization.Currency/SortedMoneySet.cs +++ b/Source/Singulink.Globalization.Currency/SortedMoneySet.cs @@ -299,6 +299,28 @@ public void SetAmount(decimal amount, Currency currency) _amountLookup[currency] = amount; } + /// + /// Subtracts the specified value from this set. Zero amounts are not trimmed from the set. + /// + /// + /// Default values that are not associated with any currency are ignored. + /// + public void Subtract(Money value) => Add(-value); + + /// + /// Subtracts the specified currency and amount from this set. + /// + public void Subtract(decimal amount, string currencyCode) => Add(-amount, currencyCode); + + /// + /// Adds the specified currency and amount to this set. + /// + public void Subtract(decimal amount, Currency currency) + { + EnsureCurrencyAllowed(currency, nameof(currency)); + AddInternal(amount, currency); + } + /// /// Copies the values in this set to a new immutable set that uses the same registry as this set. /// diff --git a/Tests/Singulink.Globalization.Currency.Tests/SortedMoneySetTests/SubtractTests.cs b/Tests/Singulink.Globalization.Currency.Tests/SortedMoneySetTests/SubtractTests.cs new file mode 100644 index 0000000..a1e406f --- /dev/null +++ b/Tests/Singulink.Globalization.Currency.Tests/SortedMoneySetTests/SubtractTests.cs @@ -0,0 +1,124 @@ +using Shouldly; + +namespace Singulink.Globalization.Tests.SortedMoneySetTests; + +[TestClass] +public class SubtractTests +{ + private static readonly Money Usd100 = new(100m, "USD"); + private static readonly Money Cad50 = new(50m, "CAD"); + private static readonly Money Eur25 = new(25m, "EUR"); + private static readonly Money Aud75 = new(75m, "AUD"); + private static readonly ImmutableSortedMoneySet ImmutableSet = [Usd100, Cad50, Eur25]; + + private readonly SortedMoneySet _set = ImmutableSet.ToSet(); + + // public void Subtract(Money value) tests + + [TestMethod] + public void Money_CurrencyExists_SubtractsPositiveAmountAndReturnsPositiveAmount() + { + _set.Subtract(Usd100); + _set.Count.ShouldBe(3); + _set.ShouldBe([new(0m, "USD"), Cad50, Eur25]); + } + + [TestMethod] + public void MoneyCurrencyExists_SubtractsZeroAmountAndReturnsPositiveAmount() + { + _set.Subtract(new(-0m, "EUR")); + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } + + [TestMethod] + public void MoneyCurrencyNotInSet_AddsValueToSet() + { + _set.Subtract(-Aud75); + _set.ShouldBe([Usd100, Cad50, Eur25, Aud75]); + } + + [TestMethod] + public void MoneyCurrencyDisallowed_ThrowsArgumentException() + { + var value = new Money(100, new Currency("Blah blah blah", "BBB", "$$", 2)); + + Should.Throw(() => _set.Subtract(value)) + .Message.ShouldBe($"The currency '{value.Currency}' is not present in the set's currency registry. (Parameter 'value')"); + + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } + + // public void Subtract(decimal amount, string currencyCode) tests + + [TestMethod] + public void CurrencyCodeExists_SubtractsPositiveAmountAndReturnsPositiveAmount() + { + _set.Subtract(100m, "USD"); + _set.Count.ShouldBe(3); + _set.ShouldBe([new(0m, "USD"), Cad50, Eur25]); + } + + [TestMethod] + public void CurrencyCodeExists_SubtractsZeroAmountAndReturnsPositiveAmount() + { + _set.Subtract(-0m, "EUR"); + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } + + [TestMethod] + public void CurrencyCodeNotInSet_AddsValueToSet() + { + _set.Subtract(-75m, "AUD"); + _set.ShouldBe([Usd100, Cad50, Eur25, Aud75]); + } + + [TestMethod] + public void CurrencyCodeDisallowed_ThrowsArgumentException() + { + Should.Throw(() => _set.Subtract(100m, "Blah blah blah")) + .Message.ShouldBe("Currency code 'Blah blah blah' not found in the 'System' registry. (Parameter 'currencyCode')"); + + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } + + // public void Subtract(decimal amount, Currency currency) tests + + [TestMethod] + public void CurrencyExists_SubtractsPositiveAmountAndReturnsPositiveAmount() + { + _set.Subtract(-100m, Currency.Get("USD")); + _set.Count.ShouldBe(3); + _set.ShouldBe([new(0m, "USD"), Cad50, Eur25]); + } + + [TestMethod] + public void CurrencyExists_SubtractsZeroAmountAndReturnsPositiveAmount() + { + _set.Subtract(-0m, Currency.Get("EUR")); + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } + + [TestMethod] + public void CurrencyNotInSet_AddsValueToSet() + { + _set.Subtract(75m, Currency.Get("AUD")); + _set.ShouldBe([Usd100, Cad50, Eur25, Aud75]); + } + + [TestMethod] + public void CurrencyDisallowed_ThrowsArgumentException() + { + var disallowedCurrency = new Currency("XXX", "Non-existent currency", "X", 2); + + Should.Throw(() => _set.Subtract(100m, disallowedCurrency)) + .Message.ShouldBe($"The currency '{disallowedCurrency}' is not present in the set's currency registry. (Parameter 'currency')"); + + _set.Count.ShouldBe(3); + _set.ShouldBe([Usd100, Cad50, Eur25]); + } +} \ No newline at end of file