taler-android

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

OrderManagerTest.kt (7371B)


      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.merchantpos.order
     18 
     19 import android.app.Application
     20 import androidx.lifecycle.LiveData
     21 import androidx.lifecycle.Observer
     22 import androidx.test.core.app.ApplicationProvider.getApplicationContext
     23 import androidx.test.ext.junit.runners.AndroidJUnit4
     24 import kotlinx.coroutines.runBlocking
     25 import net.taler.common.Amount
     26 import net.taler.merchantlib.MerchantConfig
     27 import net.taler.merchantpos.R
     28 import net.taler.merchantpos.config.Category
     29 import net.taler.merchantpos.config.ConfigProduct
     30 import net.taler.merchantpos.config.PosConfig
     31 import org.junit.Assert.assertEquals
     32 import org.junit.Assert.assertFalse
     33 import org.junit.Assert.assertNotNull
     34 import org.junit.Assert.assertNull
     35 import org.junit.Assert.assertTrue
     36 import org.junit.Test
     37 import org.junit.runner.RunWith
     38 import org.robolectric.annotation.Config
     39 import org.robolectric.shadows.ShadowLooper.shadowMainLooper
     40 import java.util.concurrent.CountDownLatch
     41 import java.util.concurrent.TimeUnit
     42 
     43 @Config(sdk = [28]) // API 29 needs at least Java 9
     44 @RunWith(AndroidJUnit4::class)
     45 class OrderManagerTest {
     46 
     47     private val app: Application = getApplicationContext()
     48     private val orderManager = OrderManager(app)
     49     private val posConfig = PosConfig(
     50         merchantConfig = MerchantConfig(
     51             baseUrl = "http://example.org",
     52             apiKey = "sandbox"
     53         ),
     54         categories = listOf(
     55             Category(1, "one"),
     56             Category(2, "two")
     57         ),
     58         products = listOf(
     59             ConfigProduct(
     60                 description = "foo",
     61                 price = Amount("KUDOS", 1, 0),
     62                 categories = listOf(1)
     63             ),
     64             ConfigProduct(
     65                 description = "bar",
     66                 price = Amount("KUDOS", 1, 50000),
     67                 categories = listOf(2)
     68             )
     69         )
     70     )
     71 
     72     @Test
     73     fun `config test missing categories`() = runBlocking {
     74         val config = posConfig.copy(categories = emptyList())
     75         val result = orderManager.onConfigurationReceived(config, "KUDOS", null)
     76         assertEquals(app.getString(R.string.config_error_category), result)
     77     }
     78 
     79     @Test
     80     fun `currency mismatch product is accepted but unavailable`() = runBlocking {
     81         val products = listOf(posConfig.products[0].copy(price = Amount("WRONGCUR", 1, 0)))
     82         val config = posConfig.copy(products = products)
     83         val result = orderManager.onConfigurationReceived(config, "KUDOS", null)
     84         shadowMainLooper().idle()
     85 
     86         assertNull(result)
     87         val product = orderManager.products.awaitValue().single()
     88         assertEquals("WRONGCUR", product.price.currency)
     89         assertTrue(product.currencyMismatch)
     90         assertFalse(product.availableToSell)
     91     }
     92 
     93 //    TODO: re-enable test based on orderManager.categories contents!
     94 //      challenge: LiveData is not easily observable in unit tests
     95 //    @Test
     96 //    fun `config test unknown category ID`() = runBlocking {
     97 //        val products = listOf(posConfig.products[0].copy(categories = listOf(42)))
     98 //        val config = posConfig.copy(products = products)
     99 //        val result = orderManager.onConfigurationReceived(config, "KUDOS", null)
    100 //        val expectedStr = app.getString(
    101 //            R.string.config_error_product_category_id, "foo", 42
    102 //        )
    103 //        assertEquals(expectedStr, result)
    104 //    }
    105 
    106     @Test
    107     fun `config test valid config gets accepted`() = runBlocking {
    108         val result = orderManager.onConfigurationReceived(posConfig, "KUDOS", null)
    109         assertNull(result)
    110     }
    111 
    112     @Test
    113     fun `all objects is selected by default and shown first`() = runBlocking {
    114         orderManager.onConfigurationReceived(posConfig, "KUDOS", null)
    115         shadowMainLooper().idle()
    116 
    117         val categories = orderManager.categories.awaitValue()
    118         val products = orderManager.products.awaitValue()
    119 
    120         assertNotNull(categories)
    121         assertNotNull(products)
    122         assertEquals(app.getString(R.string.product_category_all_objects), categories[0].name)
    123         assertTrue(categories[0].selected)
    124         assertFalse(categories[1].selected)
    125         assertEquals(posConfig.products, products)
    126     }
    127 
    128     @Test
    129     fun `uncategorized is appended at the end when needed`() = runBlocking {
    130         val uncategorizedProduct = ConfigProduct(
    131             description = "baz",
    132             price = Amount("KUDOS", 2, 0),
    133             categories = emptyList()
    134         )
    135         val config = posConfig.copy(products = posConfig.products + uncategorizedProduct)
    136 
    137         orderManager.onConfigurationReceived(config, "KUDOS", null)
    138         shadowMainLooper().idle()
    139 
    140         val categories = orderManager.categories.awaitValue()
    141         val uncategorized = categories.last()
    142 
    143         assertEquals(app.getString(R.string.product_category_uncategorized), uncategorized.name)
    144         orderManager.setCurrentCategory(uncategorized)
    145         shadowMainLooper().idle()
    146         assertEquals(listOf(uncategorizedProduct), orderManager.products.awaitValue())
    147     }
    148 
    149     @Test
    150     fun `legacy default category is hidden and mapped to uncategorized`() = runBlocking {
    151         val defaultCategory = Category(3, "Default")
    152         val defaultProduct = ConfigProduct(
    153             description = "legacy",
    154             price = Amount("KUDOS", 3, 0),
    155             categories = listOf(3)
    156         )
    157         val config = posConfig.copy(
    158             categories = posConfig.categories + defaultCategory,
    159             products = posConfig.products + defaultProduct
    160         )
    161 
    162         orderManager.onConfigurationReceived(config, "KUDOS", null)
    163         shadowMainLooper().idle()
    164 
    165         val categories = orderManager.categories.awaitValue()
    166         assertFalse(categories.any { it.name == "Default" })
    167         assertEquals(app.getString(R.string.product_category_uncategorized), categories.last().name)
    168 
    169         orderManager.setCurrentCategory(categories.last())
    170         shadowMainLooper().idle()
    171         assertEquals(listOf(defaultProduct), orderManager.products.awaitValue())
    172     }
    173 
    174 }
    175 
    176 private fun <T> LiveData<T>.awaitValue(timeout: Long = 2, unit: TimeUnit = TimeUnit.SECONDS): T {
    177     val latch = CountDownLatch(1)
    178     var result: T? = null
    179     val observer = object : Observer<T> {
    180         override fun onChanged(value: T) {
    181             result = value
    182             latch.countDown()
    183             removeObserver(this)
    184         }
    185     }
    186     observeForever(observer)
    187     if (this.value != null) {
    188         result = this.value
    189         removeObserver(observer)
    190     } else if (!latch.await(timeout, unit)) {
    191         removeObserver(observer)
    192         throw AssertionError("LiveData value was never set.")
    193     }
    194     return requireNotNull(result)
    195 }