taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

AmountTest.kt (15052B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.common
     18 
     19 import android.os.Build
     20 import org.junit.Assert.assertEquals
     21 import org.junit.Assert.assertFalse
     22 import org.junit.Assert.assertTrue
     23 import org.junit.Test
     24 import java.text.DecimalFormatSymbols
     25 
     26 class AmountTest {
     27 
     28     @Test
     29     fun testFromJSONString() {
     30         var str = "TESTKUDOS:23.42"
     31         var amount = Amount.fromJSONString(str)
     32         assertEquals(str, amount.toJSONString())
     33         assertEquals("TESTKUDOS", amount.currency)
     34         assertEquals(23, amount.value)
     35         assertEquals((0.42 * 1e8).toInt(), amount.fraction)
     36 
     37         str = "EUR:500000000.00000001"
     38         amount = Amount.fromJSONString(str)
     39         assertEquals(str, amount.toJSONString())
     40         assertEquals("EUR", amount.currency)
     41         assertEquals(500000000, amount.value)
     42         assertEquals(1, amount.fraction)
     43 
     44         str = "EUR:1500000000.00000003"
     45         amount = Amount.fromJSONString(str)
     46         assertEquals(str, amount.toJSONString())
     47         assertEquals("EUR", amount.currency)
     48         assertEquals(1500000000, amount.value)
     49         assertEquals(3, amount.fraction)
     50     }
     51 
     52     @Test
     53     fun testToString() {
     54         amountToString(
     55             amount = Amount.fromString("KUDOS", "13.71"),
     56             spec = CurrencySpecification(
     57                 name = "Test (Taler Demostrator)",
     58                 numFractionalInputDigits = 2,
     59                 numFractionalNormalDigits = 2,
     60                 numFractionalTrailingZeroDigits = 2,
     61                 altUnitNames = mapOf(0 to "ク"),
     62             ),
     63             rawStr = "13.71",
     64             fraction = 71000000,
     65             specAmount = "13.71",
     66             noSpecAmount = "13.71",
     67             currency = "KUDOS",
     68             symbol = "ク",
     69         )
     70 
     71         amountToString(
     72             amount = Amount.fromString("TESTKUDOS", "23.42"),
     73             spec = CurrencySpecification(
     74                 name = "Test (Taler Unstable Demostrator)",
     75                 numFractionalInputDigits = 0,
     76                 numFractionalNormalDigits = 0,
     77                 numFractionalTrailingZeroDigits = 0,
     78                 altUnitNames = mapOf(0 to "テ"),
     79             ),
     80             rawStr = "23.42",
     81             fraction = 42000000,
     82             specAmount = "23.42",
     83             noSpecAmount = "23.42",
     84             currency = "TESTKUDOS",
     85             symbol = "テ",
     86         )
     87 
     88         amountToString(
     89             amount = Amount.fromString("TESTKUDOS", "23"),
     90             spec = CurrencySpecification(
     91                 name = "Test (Taler Unstable Demostrator)",
     92                 numFractionalInputDigits = 0,
     93                 numFractionalNormalDigits = 0,
     94                 numFractionalTrailingZeroDigits = 0,
     95                 altUnitNames = mapOf(0 to "テ"),
     96             ),
     97             rawStr = "23",
     98             fraction = 0,
     99             specAmount = "23",
    100             noSpecAmount = "23.00",
    101             currency = "TESTKUDOS",
    102             symbol = "テ",
    103         )
    104 
    105         amountToString(
    106             amount = Amount.fromString("BITCOINBTC", "0.00000001"),
    107             spec = CurrencySpecification(
    108                 name = "Bitcoin",
    109                 numFractionalInputDigits = 8,
    110                 numFractionalNormalDigits = 8,
    111                 numFractionalTrailingZeroDigits = 0,
    112                 altUnitNames = mapOf(
    113                     0 to "₿",
    114                     // TODO: uncomment when units get implemented
    115                     //  and then write tests for units, please
    116 //                -1 to "d₿",
    117 //                -2 to "c₿",
    118 //                -3 to "m₿",
    119 //                -6 to "µ₿",
    120 //                -8 to "sat",
    121                 ),
    122             ),
    123             rawStr = "0.00000001",
    124             fraction = 1,
    125             specAmount = "0.00000001",
    126             noSpecAmount = "0.00000001",
    127             currency = "BITCOINBTC",
    128             symbol = "₿",
    129         )
    130 
    131         val specEUR = CurrencySpecification(
    132             name = "EUR",
    133             numFractionalInputDigits = 2,
    134             numFractionalNormalDigits = 2,
    135             numFractionalTrailingZeroDigits = 2,
    136             altUnitNames = mapOf(0 to "€"),
    137         )
    138 
    139         amountToString(
    140             amount = Amount.fromString("EUR", "1500000000.00000003"),
    141             spec = specEUR,
    142             rawStr = "1500000000.00000003",
    143             fraction = 3,
    144             specAmount = "1,500,000,000.00000003",
    145             noSpecAmount = "1,500,000,000.00000003",
    146             currency = "EUR",
    147             symbol = "€",
    148         )
    149 
    150         amountToString(
    151             amount = Amount.fromString("EUR", "500000000.126"),
    152             spec = specEUR,
    153             rawStr = "500000000.126",
    154             fraction = 12600000,
    155             specAmount = "500,000,000.126",
    156             noSpecAmount = "500,000,000.126",
    157             currency = "EUR",
    158             symbol = "€",
    159         )
    160 
    161         amountToString(
    162             amount = Amount.fromString("NOSYMBOL", "13.24"),
    163             spec = CurrencySpecification(
    164                 name = "No symbol!",
    165                 numFractionalInputDigits = 2,
    166                 numFractionalNormalDigits = 2,
    167                 numFractionalTrailingZeroDigits = 2,
    168                 altUnitNames = mapOf(),
    169             ),
    170             rawStr = "13.24",
    171             fraction = 24000000,
    172             specAmount = "13.24",
    173             noSpecAmount = "13.24",
    174             currency = "NOSYMBOL",
    175             symbol = "NOSYMBOL",
    176         )
    177     }
    178 
    179     private fun amountToString(
    180         amount: Amount,
    181         spec: CurrencySpecification,
    182         rawStr: String,
    183         fraction: Int,
    184         specAmount: String,
    185         noSpecAmount: String,
    186         currency: String,
    187         symbol: String,
    188     ) {
    189         val symbols = DecimalFormatSymbols.getInstance()
    190         symbols.decimalSeparator = '.'
    191         symbols.groupingSeparator = ','
    192         symbols.monetaryDecimalSeparator = '.'
    193         if (Build.VERSION.SDK_INT >= 34) {
    194             symbols.monetaryGroupingSeparator = ','
    195         }
    196 
    197         // Only the raw amount
    198         assertEquals(rawStr, amount.amountStr)
    199         assertEquals(fraction, amount.fraction)
    200 
    201         // The amount without currency spec
    202         assertEquals("$noSpecAmount $currency", amount.toString(symbols = symbols))
    203         assertEquals(noSpecAmount, amount.toString(symbols = symbols, showSymbol = false))
    204         assertEquals("-$noSpecAmount $currency", amount.toString(symbols = symbols, negative = true))
    205         assertEquals("-$noSpecAmount", amount.toString(symbols = symbols, showSymbol = false, negative = true))
    206 
    207         // The amount with currency spec
    208         val withSpec = amount.withSpec(spec)
    209         assertEquals(specAmount, withSpec.toString(symbols = symbols, showSymbol = false))
    210         assertEquals(specAmount, withSpec.toString(symbols = symbols, showSymbol = false))
    211         assertEquals("-$specAmount", withSpec.toString(symbols = symbols, showSymbol = false, negative = true))
    212         assertEquals("-$specAmount", withSpec.toString(symbols = symbols, showSymbol = false, negative = true))
    213 
    214         // FIXME: this test is locale-dependent
    215          if (spec.symbol != null) {
    216             assertEquals("${symbol}$specAmount", withSpec.toString(symbols = symbols))
    217             assertEquals("-${symbol}$specAmount", withSpec.toString(symbols = symbols, negative = true))
    218          } else {
    219              assertEquals("$specAmount $currency", withSpec.toString(symbols = symbols))
    220              assertEquals("-$specAmount $currency", withSpec.toString(symbols = symbols, negative = true))
    221          }
    222     }
    223 
    224     @Test
    225     fun testFromJSONStringAcceptsMaxValuesRejectsAbove() {
    226         val maxValue = 4503599627370496
    227         val str = "TESTKUDOS123:$maxValue.99999999"
    228         val amount = Amount.fromJSONString(str)
    229         assertEquals(str, amount.toJSONString())
    230         assertEquals("TESTKUDOS123", amount.currency)
    231         assertEquals(maxValue, amount.value)
    232 
    233         // longer currency not accepted
    234         assertThrows<AmountParserException>("longer currency was accepted") {
    235             Amount.fromJSONString("TESTKUDOS1234:$maxValue.99999999")
    236         }
    237 
    238         // max value + 1 not accepted
    239         assertThrows<AmountParserException>("max value + 1 was accepted") {
    240             Amount.fromJSONString("TESTKUDOS123:${maxValue + 1}.99999999")
    241         }
    242 
    243         // max fraction + 1 not accepted
    244         assertThrows<AmountParserException>("max fraction + 1 was accepted") {
    245             Amount.fromJSONString("TESTKUDOS123:$maxValue.999999990")
    246         }
    247     }
    248 
    249     @Test
    250     fun testFromJSONStringRejections() {
    251         assertThrows<AmountParserException> {
    252             Amount.fromJSONString("TESTKUDOS:0,5")
    253         }
    254         assertThrows<AmountParserException> {
    255             Amount.fromJSONString("+TESTKUDOS:0.5")
    256         }
    257         assertThrows<AmountParserException> {
    258             Amount.fromJSONString("0.5")
    259         }
    260         assertThrows<AmountParserException> {
    261             Amount.fromJSONString(":0.5")
    262         }
    263         assertThrows<AmountParserException> {
    264             Amount.fromJSONString("EUR::0.5")
    265         }
    266         assertThrows<AmountParserException> {
    267             Amount.fromJSONString("EUR:.5")
    268         }
    269     }
    270 
    271     @Test
    272     fun testAddition() {
    273         assertEquals(
    274             Amount.fromJSONString("EUR:2"),
    275             Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
    276         )
    277         assertEquals(
    278             Amount.fromJSONString("EUR:3"),
    279             Amount.fromJSONString("EUR:1.5") + Amount.fromJSONString("EUR:1.5")
    280         )
    281         assertEquals(
    282             Amount.fromJSONString("EUR:500000000.00000002"),
    283             Amount.fromJSONString("EUR:500000000.00000001") + Amount.fromJSONString("EUR:0.00000001")
    284         )
    285         assertThrows<AmountOverflowException>("addition didn't overflow") {
    286             Amount.fromJSONString("EUR:4503599627370496.99999999") + Amount.fromJSONString("EUR:0.00000001")
    287         }
    288         assertThrows<AmountOverflowException>("addition didn't overflow") {
    289             Amount.fromJSONString("EUR:4000000000000000") + Amount.fromJSONString("EUR:4000000000000000")
    290         }
    291     }
    292 
    293     @Test
    294     fun testTimes() {
    295         assertEquals(
    296             Amount.fromJSONString("EUR:2"),
    297             Amount.fromJSONString("EUR:2") * 1
    298         )
    299         assertEquals(
    300             Amount.fromJSONString("EUR:2"),
    301             Amount.fromJSONString("EUR:1") * 2
    302         )
    303         assertEquals(
    304             Amount.fromJSONString("EUR:4.5"),
    305             Amount.fromJSONString("EUR:1.5") * 3
    306         )
    307         assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:1.11") * 0)
    308         assertEquals(Amount.fromJSONString("EUR:1.11"), Amount.fromJSONString("EUR:1.11") * 1)
    309         assertEquals(Amount.fromJSONString("EUR:2.22"), Amount.fromJSONString("EUR:1.11") * 2)
    310         assertEquals(Amount.fromJSONString("EUR:3.33"), Amount.fromJSONString("EUR:1.11") * 3)
    311         assertEquals(Amount.fromJSONString("EUR:4.44"), Amount.fromJSONString("EUR:1.11") * 4)
    312         assertEquals(Amount.fromJSONString("EUR:5.55"), Amount.fromJSONString("EUR:1.11") * 5)
    313         assertEquals(
    314             Amount.fromJSONString("EUR:1500000000.00000003"),
    315             Amount.fromJSONString("EUR:500000000.00000001") * 3
    316         )
    317         assertThrows<AmountOverflowException>("times didn't overflow") {
    318             Amount.fromJSONString("EUR:4000000000000000") * 2
    319         }
    320     }
    321 
    322     @Test
    323     fun testSubtraction() {
    324         assertEquals(
    325             Amount.fromJSONString("EUR:0"),
    326             Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
    327         )
    328         assertEquals(
    329             Amount.fromJSONString("EUR:1.5"),
    330             Amount.fromJSONString("EUR:3") - Amount.fromJSONString("EUR:1.5")
    331         )
    332         assertEquals(
    333             Amount.fromJSONString("EUR:500000000.00000001"),
    334             Amount.fromJSONString("EUR:500000000.00000002") - Amount.fromJSONString("EUR:0.00000001")
    335         )
    336         assertThrows<AmountOverflowException>("subtraction didn't underflow") {
    337             Amount.fromJSONString("EUR:23.42") - Amount.fromJSONString("EUR:42.23")
    338         }
    339         assertThrows<AmountOverflowException>("subtraction didn't underflow") {
    340             Amount.fromJSONString("EUR:0.5") - Amount.fromJSONString("EUR:0.50000001")
    341         }
    342     }
    343 
    344     @Test
    345     fun testIsZero() {
    346         assertTrue(Amount.zero("EUR").isZero())
    347         assertTrue(Amount.fromJSONString("EUR:0").isZero())
    348         assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
    349         assertTrue(Amount.fromJSONString("EUR:0.00000").isZero())
    350         assertTrue((Amount.fromJSONString("EUR:1.001") - Amount.fromJSONString("EUR:1.001")).isZero())
    351 
    352         assertFalse(Amount.fromJSONString("EUR:0.00000001").isZero())
    353         assertFalse(Amount.fromJSONString("EUR:1.0").isZero())
    354         assertFalse(Amount.fromJSONString("EUR:0001.0").isZero())
    355     }
    356 
    357     @Test
    358     fun testComparison() {
    359         assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0"))
    360         assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0.00000001"))
    361         assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:0.00000001"))
    362         assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:1"))
    363         assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:0"))
    364         assertEquals(Amount.fromJSONString("EUR:42"), Amount.fromJSONString("EUR:42"))
    365         assertEquals(
    366             Amount.fromJSONString("EUR:42.00000001"),
    367             Amount.fromJSONString("EUR:42.00000001")
    368         )
    369         assertTrue(Amount.fromJSONString("EUR:42.00000001") >= Amount.fromJSONString("EUR:42.00000001"))
    370         assertTrue(Amount.fromJSONString("EUR:42.00000002") >= Amount.fromJSONString("EUR:42.00000001"))
    371         assertTrue(Amount.fromJSONString("EUR:42.00000002") > Amount.fromJSONString("EUR:42.00000001"))
    372         assertTrue(Amount.fromJSONString("EUR:0.00000002") > Amount.fromJSONString("EUR:0.00000001"))
    373         assertTrue(Amount.fromJSONString("EUR:0.00000001") > Amount.fromJSONString("EUR:0"))
    374         assertTrue(Amount.fromJSONString("EUR:2") > Amount.fromJSONString("EUR:1"))
    375 
    376         assertThrows<IllegalStateException>("could compare amounts with different currencies") {
    377             Amount.fromJSONString("EUR:0.5") < Amount.fromJSONString("USD:0.50000001")
    378         }
    379     }
    380 
    381 }