ConfigSessionTest.kt (3954B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2026 Taler Systems S.A. 4 * SPDX-License-Identifier: GPL-3.0-or-later 5 */ 6 package net.taler.merchantpos.config 7 8 import android.app.Application 9 10 import androidx.test.core.app.ApplicationProvider 11 12 import net.taler.common.EventObserver 13 import net.taler.common.createDefaultHttpClient 14 import net.taler.merchantlib.MerchantConfig 15 16 import org.junit.After 17 import org.junit.Assert.assertEquals 18 import org.junit.Assert.assertNotNull 19 import org.junit.Assert.assertNull 20 import org.junit.Test 21 import org.junit.runner.RunWith 22 import org.robolectric.RobolectricTestRunner 23 import org.robolectric.annotation.Config 24 25 import kotlinx.coroutines.CoroutineScope 26 import kotlinx.coroutines.Dispatchers 27 import kotlinx.coroutines.SupervisorJob 28 import kotlinx.coroutines.cancel 29 30 @RunWith(RobolectricTestRunner::class) 31 @Config(application = Application::class, sdk = [28]) 32 class ConfigSessionTest 33 { 34 private val app = ApplicationProvider.getApplicationContext<Application>() 35 private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) 36 private val client = createDefaultHttpClient(logging = false) 37 private val manager = ConfigManager(app, scope, client) 38 private val base = "https://example.invalid/instances/test" 39 40 private fun configure() 41 { 42 manager.debugApplyFixture( 43 PosConfig(categories = emptyList(), products = emptyList()), 44 MerchantConfig(base, "secret-token:current"), 45 "CHF", 46 ) 47 } 48 49 @After 50 fun cleanup() 51 { 52 scope.cancel() 53 client.close() 54 app 55 .getSharedPreferences("taler-merchant-terminal", 0) 56 .edit() 57 .clear() 58 .commit() 59 } 60 61 @Test 62 fun `token prefix probe and login failures leave the active merchant configured`() 63 { 64 configure() 65 manager.handleUnauthorized("$base/private/pos", "Bearer current") 66 manager.handleUnauthorized("$base/private/token", "Bearer secret-token:current") 67 manager.handleUnauthorized("$base/challenge/challenge/confirm", null) 68 assertNotNull(manager.merchantConfig) 69 assertNull(manager.sessionExpired.value) 70 } 71 72 @Test 73 fun `late responses from another merchant or old credentials cannot expire current session`() 74 { 75 configure() 76 manager.handleUnauthorized( 77 "https://elsewhere.invalid/instances/test/private/orders", 78 "Bearer secret-token:current" 79 ) 80 manager.handleUnauthorized("${base}2/private/orders", "Bearer secret-token:current") 81 manager.handleUnauthorized("$base/private/orders", "Bearer secret-token:old") 82 manager.handleUnauthorized("$base/private/orders", null) 83 assertNotNull(manager.merchantConfig) 84 assertNull(manager.sessionExpired.value) 85 } 86 87 @Test 88 fun `real session rejection clears credentials and is consumed once across observer recreation`() 89 { 90 configure() 91 var notifications = 0 92 val first = EventObserver<Unit> { notifications++ } 93 manager.sessionExpired.observeForever(first) 94 manager.handleUnauthorized("$base/private/orders?limit=20", "Bearer secret-token:current") 95 assertNull(manager.merchantConfig) 96 assertEquals("", (manager.config as net.taler.merchantpos.config.Config.New).accessToken) 97 assertEquals(1, notifications) 98 manager.sessionExpired.removeObserver(first) 99 val recreated = EventObserver<Unit> { notifications++ } 100 manager.sessionExpired.observeForever(recreated) 101 assertEquals(1, notifications) 102 manager.handleUnauthorized("$base/private/orders", "Bearer secret-token:current") 103 assertEquals(1, notifications) 104 configure() 105 manager.handleUnauthorized("$base/private/orders", "Bearer secret-token:current") 106 assertEquals(2, notifications) 107 manager.sessionExpired.removeObserver(recreated) 108 } 109 }