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 }