commit 722edf1c0594b85e2edfebfdb919463676b4b7f1 parent c462122af43bc7514e120a662588c49166c97187 Author: Joel-Haeberli <haebu@rubigen.ch> Date: Sat, 4 May 2024 11:13:48 +0200 fix: http requests from wallee app Diffstat:
15 files changed, 291 insertions(+), 139 deletions(-)
diff --git a/simulation/c2ec-simulation b/simulation/c2ec-simulation Binary files differ. diff --git a/simulation/config.yaml b/simulation/config.yaml @@ -1,5 +1,6 @@ disable-delays: false c2ec-base-url: "http://localhost:8080" +parallel-withdrawals: 1 provider-backend-payment-delay: 1000 terminal-accept-card-delay: 5000 terminal-provider: "Simulation" diff --git a/simulation/main.go b/simulation/main.go @@ -34,18 +34,26 @@ func main() { } kill := make(chan error) - toTerminal := make(chan *SimulatedPhysicalInteraction, 10) - toWallet := make(chan *SimulatedPhysicalInteraction, 10) finish := make(chan interface{}) + // start simulated wire watch + go WireWatch(finish, kill) - // start simulated terminal - go Terminal(toTerminal, toWallet, kill) + go func() { + for i := 0; i < CONFIG.ParallelWithdrawals; i++ { - // start simulated wallet - go Wallet(toWallet, toTerminal, kill) + fmt.Println("Starting withdrawal", i) - // start simulated wire watch - go WireWatch(finish, kill) + toTerminal := make(chan *SimulatedPhysicalInteraction, 10) + toWallet := make(chan *SimulatedPhysicalInteraction, 10) + + // start simulated terminal + go Terminal(toTerminal, toWallet, kill) + + // start simulated wallet + go Wallet(toWallet, toTerminal, kill) + + } + }() for err := range kill { if err == nil { @@ -79,8 +87,9 @@ func c2ecAlive() bool { } type SimulationConfig struct { - DisableDelays bool `yaml:"disable-delays"` - C2ecBaseUrl string `yaml:"c2ec-base-url"` + DisableDelays bool `yaml:"disable-delays"` + ParallelWithdrawals int `yaml:"parallel-withdrawals"` + C2ecBaseUrl string `yaml:"c2ec-base-url"` // simulates the terminal talking to its backend system and executing the payment. ProviderBackendPaymentDelay int `yaml:"provider-backend-payment-delay"` // simulates the user presenting his card to the terminal diff --git a/wallee-c2ec/.idea/deploymentTargetSelector.xml b/wallee-c2ec/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="deploymentTargetSelector"> + <selectionStates> + <SelectionState runConfigName="app"> + <option name="selectionMode" value="DROPDOWN" /> + </SelectionState> + </selectionStates> + </component> +</project> +\ No newline at end of file diff --git a/wallee-c2ec/.idea/misc.xml b/wallee-c2ec/.idea/misc.xml @@ -1,6 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="ExternalStorageConfigurationManager" enabled="true" /> - <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClient.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClient.kt @@ -10,17 +10,30 @@ import java.util.Optional interface TerminalClient { - fun terminalsConfig(): Optional<TerminalApiConfig> + fun terminalsConfig( + callback: (Optional<TerminalApiConfig>) -> Unit + ) - fun setupWithdrawal(setupReq: TerminalWithdrawalSetup): Optional<TerminalWithdrawalSetupResponse> + fun setupWithdrawal( + setupReq: TerminalWithdrawalSetup, + callback: (Optional<TerminalWithdrawalSetupResponse>) -> Unit + ) fun retrieveWithdrawalStatus( wopid: String, longPollMs: Int, - oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.PENDING - ): Optional<BankWitdrawalOperationStatus> + oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.PENDING, + callback: (Optional<BankWitdrawalOperationStatus>) -> Unit + ) - fun sendConfirmationRequest(encodedWopid: String, confirmationRequest: TerminalWithdrawalConfirmationRequest) + fun sendConfirmationRequest( + encodedWopid: String, + confirmationRequest: TerminalWithdrawalConfirmationRequest, + callback: (Boolean) -> Unit + ) - fun abortWithdrawal(encodedWopid: String) + fun abortWithdrawal( + encodedWopid: String, + callback: (Boolean) -> Unit + ) } \ No newline at end of file diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientImplementation.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientImplementation.kt @@ -8,14 +8,19 @@ import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetup import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetupResponse import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus import com.squareup.moshi.Moshi -import okhttp3.HttpUrl +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.newSingleThreadContext +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import okio.IOException import java.util.Optional +import java.util.concurrent.Executors class TerminalClientException( val status: Int, @@ -26,23 +31,24 @@ class TerminalClientImplementation ( private val config: TalerTerminalConfig ): TerminalClient { + private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + private val client: OkHttpClient = OkHttpClient.Builder() .addInterceptor(C2ECBasicAuthInterceptor(config)) .build() - private fun baseUrlBuilder() = HttpUrl.Builder() - .encodedPath(config.terminalApiBaseUrl) + private fun baseUrlBuilder() = config.terminalApiBaseUrl.toHttpUrl() - private fun terminalsConfigUrl() = baseUrlBuilder() + private fun terminalsConfigUrl() = baseUrlBuilder().newBuilder() .addPathSegment("config") .build() - private fun setupWithdrawalUrl() = baseUrlBuilder() + private fun setupWithdrawalUrl() = baseUrlBuilder().newBuilder() .addPathSegment("withdrawals") .build() - private fun withdrawalsByWopid(encodedWopid: String) = baseUrlBuilder() + private fun withdrawalsByWopid(encodedWopid: String) = baseUrlBuilder().newBuilder() .addPathSegment("withdrawals") .addPathSegment(encodedWopid) .build() @@ -59,16 +65,21 @@ class TerminalClientImplementation ( .addPathSegment("abort") .build() - override fun terminalsConfig(): Optional<TerminalApiConfig> { + override fun terminalsConfig( + callback: (Optional<TerminalApiConfig>) -> Unit + ) = dispatcher.executor.execute { val req = Request.Builder() .get() .url(terminalsConfigUrl()) .build() val response = client.newCall(req).execute() - return parseOrEmpty(response) + callback(parseOrEmpty(response)) } - override fun setupWithdrawal(setupReq: TerminalWithdrawalSetup): Optional<TerminalWithdrawalSetupResponse> { + override fun setupWithdrawal( + setupReq: TerminalWithdrawalSetup, + callback: (Optional<TerminalWithdrawalSetupResponse>) -> Unit + ) = dispatcher.executor.execute { val reqBody = serializer(TerminalWithdrawalSetup::class.java) .toJson(setupReq) @@ -78,15 +89,15 @@ class TerminalClientImplementation ( .url(setupWithdrawalUrl()) .build() val response = client.newCall(req).execute() - return parseOrEmpty(response) + callback(parseOrEmpty(response)) } override fun retrieveWithdrawalStatus( wopid: String, longPollMs: Int, - oldState: WithdrawalOperationStatus - ): Optional<BankWitdrawalOperationStatus> { - + oldState: WithdrawalOperationStatus, + callback: (Optional<BankWitdrawalOperationStatus>) -> Unit + ) = dispatcher.executor.execute { val req = Request.Builder() .get() .url(withdrawalsByWopid(wopid) @@ -97,14 +108,14 @@ class TerminalClientImplementation ( ) .build() val response = client.newCall(req).execute() - return parseOrEmpty(response) + callback(parseOrEmpty(response)) } override fun sendConfirmationRequest( encodedWopid: String, - confirmationRequest: TerminalWithdrawalConfirmationRequest - ) { - + confirmationRequest: TerminalWithdrawalConfirmationRequest, + callback: (Boolean) -> Unit + ) = dispatcher.executor.execute { val reqBody = serializer(TerminalWithdrawalConfirmationRequest::class.java) .toJson(confirmationRequest) .toRequestBody("application/json".toMediaType()) @@ -112,18 +123,31 @@ class TerminalClientImplementation ( .post(reqBody) .url(withdrawalsConfirm(encodedWopid)) .build() - val response = client.newCall(req).execute() - if (response.code != 204) { - abortWithdrawal(encodedWopid) + try { + val response = client.newCall(req).execute() + if (response.code != 204) { + abortWithdrawal(encodedWopid) {} + } + callback(true) + } catch (ex: IOException) { + callback(false) } } - override fun abortWithdrawal(encodedWopid: String) { + override fun abortWithdrawal( + encodedWopid: String, + callback: (Boolean) -> Unit + ) = dispatcher.executor.execute { val req = Request.Builder() .delete() .url(withdrawalsAbort(encodedWopid)) .build() - client.newCall(req).execute() + try { + client.newCall(req).execute() + callback(true) + } catch (ex: IOException) { + callback(false) + } } private inline fun <reified T: Any> parseOrEmpty(response: Response): Optional<T> { diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt @@ -13,60 +13,72 @@ import java.util.Optional class TerminalClientMock: TerminalClient { - override fun terminalsConfig() = + override fun terminalsConfig(callback: (Optional<TerminalApiConfig>) -> Unit) = callback.invoke( Optional.of( TerminalApiConfig( - "taler-terminal", + "taler-terminal", "0:0:0", "Wallee", "CHF", "wallee-transaction" ) ) + ) - override fun setupWithdrawal(setupReq: TerminalWithdrawalSetup) = + override fun setupWithdrawal( + setupReq: TerminalWithdrawalSetup, + callback: (Optional<TerminalWithdrawalSetupResponse>) -> Unit + ) = callback.invoke( Optional.of( TerminalWithdrawalSetupResponse( mockWopidOrReservePubKey() ) ) + ) override fun retrieveWithdrawalStatus( wopid: String, longPollMs: Int, - oldState: WithdrawalOperationStatus - ): Optional<BankWitdrawalOperationStatus> { + oldState: WithdrawalOperationStatus, + callback: (Optional<BankWitdrawalOperationStatus>) -> Unit + ) { Thread.sleep(longPollMs.toLong()) - return Optional.of( - BankWitdrawalOperationStatus( - WithdrawalOperationStatus.SELECTED, - Amount(10,0), - Amount(10,0), - Amount(0,0), - Amount(0,0), - "wallee-transaction", - "http://mock.com/api", - "http://mock.com/api", - "", - Array(0) {""}, - mockWopidOrReservePubKey(), - "payto://IBAN/CH1111111111111", - aborted = false, - selectionDone = true, - transferDone = false + callback.invoke( + Optional.of( + BankWitdrawalOperationStatus( + WithdrawalOperationStatus.SELECTED, + Amount(10,0), + Amount(10,0), + Amount(0,0), + Amount(0,0), + "wallee-transaction", + "http://mock.com/api", + "http://mock.com/api", + "", + Array(0) {""}, + mockWopidOrReservePubKey(), + "payto://IBAN/CH1111111111111", + aborted = false, + selectionDone = true, + transferDone = false + ) ) ) } override fun sendConfirmationRequest( encodedWopid: String, - confirmationRequest: TerminalWithdrawalConfirmationRequest + confirmationRequest: TerminalWithdrawalConfirmationRequest, + callback: (Boolean) -> Unit ) { println("sending confirmation request") } - override fun abortWithdrawal(encodedWopid: String) { + override fun abortWithdrawal( + encodedWopid: String, + callback: (Boolean) -> Unit + ) { println("aborting withdrawal") } @@ -75,6 +87,5 @@ class TerminalClientMock: TerminalClient { val wopid = ByteArray(32) SecureRandom().nextBytes(wopid) return encodeCrock(wopid) - } } \ No newline at end of file diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/wallee/WalleeResponseHandler.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/wallee/WalleeResponseHandler.kt @@ -38,7 +38,7 @@ class WalleeResponseHandler( println("C2EC-COMPLETION-RESPONSE: ${response.transactionCompletion}") - model.updateWalleeTransactionCompletion(response) + model.updateWalleeTransactionCompletion(response, activity) } override fun checkApiServiceCompatibilityReply( diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt @@ -8,6 +8,10 @@ import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -26,9 +30,12 @@ fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () -> Text(model.uiState.value.amountError, color = Color.Red) TextField( - value = model.uiState.value.amountInput, + model.uiState.value.amountStr, onValueChange = { - model.validateInputOrEmptyField(it) + println(it) + if (!model.validateInput(it)) { + model.resetAmountStr() + } }, label = { Text(text = "Enter amount") }, keyboardOptions = KeyboardOptions( @@ -37,15 +44,13 @@ fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () -> ) ) + Text(text = model.uiState.value.amountStr) + Button(onClick = { println("clicked 'pay'") - val success = model.setupWithdrawal() - if (!success) { - activity.finish() - } - model.withdrawalOperationFailed() + model.setupWithdrawal(activity) navigateToWhenAmountEntered() - } /*, enabled = (model.uiState.value.amount.value > 0 || model.uiState.value.amount.frac > 0)*/) { + }, enabled = model.validAmount(model.uiState.value.amountStr)) { Text(text = "pay") } diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt @@ -2,6 +2,7 @@ package ch.bfh.habej2.wallee_c2ec.withdrawal import android.app.Activity import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -9,6 +10,15 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig +val exchanges = listOf( + TalerTerminalConfig( + "C2EC-Test", + "http://taler-c2ec.ti.bfh.ch", + "Wallee-2", + "1A92pgloFR8WIgr0LDA+s9hbkO4EgyJlHj+3dQ9IJ9U=" + ) +) + @Composable fun ExchangeSelectionScreen( model: WithdrawalViewModel, @@ -23,18 +33,16 @@ fun ExchangeSelectionScreen( Text(text = "Choose the exchange to withdraw from") - // TODO let user select exchanges from config here - // config must contain display name, credentials (generated by cli) - // and the base url of the c2ec bank-integration api - - Button(onClick = { - model.exchangeUpdated( - activity, - TalerTerminalConfig("","","","") - ) - onNavigateToWithdrawal() - }) { - Text(text = "withdraw") + Row { + exchanges.forEach { + Text(text = it.displayName) + Button(onClick = { + model.chooseExchange(it, activity) + onNavigateToWithdrawal() + }) { + Text(text = "withdraw") + } + } } Button(onClick = { activity.finish() }) { diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt @@ -19,10 +19,6 @@ fun RegisterWithdrawalScreen( val uiState by model.uiState.collectAsState() val activity = (LocalContext.current as Activity) - model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { - activity.finish() - } - Column( horizontalAlignment = Alignment.CenterHorizontally ) { @@ -38,6 +34,10 @@ fun RegisterWithdrawalScreen( Text(text = "abort") } } + + model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { + activity.finish() + } } private fun formatTalerUri(exchangeBankIntegrationApiPath: String, encodedWopid: String) = diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalViewModel.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalViewModel.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClient +import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClientImplementation import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClientMock import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalConfirmationRequest @@ -40,8 +41,8 @@ interface WithdrawalOperationState{ val requestUid: String val exchangeBankIntegrationApiUrl: String val encodedWopid: String - val amountInput: String val amount: Amount + val amountStr: String val amountError: String val currency: String val payed: Boolean @@ -53,8 +54,8 @@ private class MutableWithdrawalOperationState: WithdrawalOperationState { override val requestUid: String by derivedStateOf{UUID.randomUUID().toString()} override var exchangeBankIntegrationApiUrl: String by mutableStateOf("") override var encodedWopid: String by mutableStateOf("") - override var amountInput: String by mutableStateOf("") override var amount: Amount by mutableStateOf(Amount(0,0)) + override var amountStr: String by mutableStateOf("") override var amountError: String by mutableStateOf("") override var currency: String by mutableStateOf("") override var payed: Boolean by mutableStateOf(false) @@ -66,48 +67,61 @@ class WithdrawalViewModel( vararg closeables: Closeable ) : ViewModel(*closeables) { + private var exchangeSelected = false private var terminalClient: TerminalClient? = null private val _uiState = MutableStateFlow(MutableWithdrawalOperationState()) val uiState: StateFlow<WithdrawalOperationState> = _uiState - fun exchangeUpdated(activity: Activity, cfg: TalerTerminalConfig) { + fun chooseExchange(cfg: TalerTerminalConfig, activity: Activity) { - terminalClient = TerminalClientMock() // TerminalClientImplementation(cfg) + if (exchangeSelected) { + println("exchange cannot be changed after selection.") + return + } + + //terminalClient = TerminalClientImplementation(cfg) + terminalClient = TerminalClientMock() _uiState.value = MutableWithdrawalOperationState() // reset withdrawal operation - val optionalApiCfg = terminalClient!!.terminalsConfig() - if (!optionalApiCfg.isPresent) { - println("unable to fetch config from c2ec") - activity.finish() + + terminalClient!!.terminalsConfig { + if (!it.isPresent) { + activity.finish() + } + this@WithdrawalViewModel.updateCurrency(it.get().currency) } - updateCurrency(optionalApiCfg.get().currency) + exchangeSelected = true } - fun setupWithdrawal(): Boolean { + fun setupWithdrawal(activity: Activity) { val setupReq = TerminalWithdrawalSetup( _uiState.value.requestUid, _uiState.value.amount ) - val res = terminalClient!!.setupWithdrawal(setupReq) - if (!res.isPresent) { - return false + viewModelScope.launch { + terminalClient!!.setupWithdrawal(setupReq) { + if (!it.isPresent) { + activity.finish() + } + println("retrieved WOPID from c2ec: ${it.get().withdrawalId}") + _uiState.value.encodedWopid = it.get().withdrawalId + } } - - println("retrieved WOPID from c2ec: ${res.get().withdrawalId}") - _uiState.value.encodedWopid = res.get().withdrawalId - return true } - fun validateInputOrEmptyField(amount: String) { + fun validateInput(amount: String): Boolean { + println("validating amount input: $amount") val validAmount = parseAmount(amount) if (validAmount.isPresent) { _uiState.value.amountError = "" + _uiState.value.amountStr = amount _uiState.value.amount = validAmount.get() + return true } else { - _uiState.value.amountInput = "" _uiState.value.amountError = "invalid amount (format: X[.X])" + return false } } @@ -120,7 +134,7 @@ class WithdrawalViewModel( _uiState.value.transaction = transaction } - fun updateWalleeTransactionCompletion(completion: TransactionCompletionResponse) { + fun updateWalleeTransactionCompletion(completion: TransactionCompletionResponse, activity: Activity) { _uiState.value.transactionCompletion = completion if (completion.state == State.FAILED) { @@ -130,7 +144,7 @@ class WithdrawalViewModel( completion.transactionCompletion.lineItems[0].id completion.state.name - confirmationRequest() + confirmationRequest(activity) } fun startAuthorizationWhenReadyOrAbort( @@ -139,31 +153,39 @@ class WithdrawalViewModel( ) { viewModelScope.launch { - val result = terminalClient!!.retrieveWithdrawalStatus(uiState.value.encodedWopid, 30000) - if (result.isPresent) { - onSuccess() - } else { - withdrawalOperationFailed() - onFailure() + terminalClient!!.retrieveWithdrawalStatus(uiState.value.encodedWopid, 30000) { + if (it.isPresent) { + onSuccess() + } else { + withdrawalOperationFailed() + onFailure() + } } } } - fun confirmationRequest() { - viewModelScope.launch{ + private fun confirmationRequest(activity: Activity) { + viewModelScope.launch { terminalClient!!.sendConfirmationRequest( _uiState.value.encodedWopid, TerminalWithdrawalConfirmationRequest("", Amount(0,0)) - ) + ) { + if (!it) { + withdrawalOperationFailed() + activity.finish() + } + } } } fun withdrawalOperationFailed() { viewModelScope.launch { - terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) + terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) {} } } + fun validAmount(inp: String) = Regex( "\\d+(\\.\\d+)?").matches(inp) + /** * Format expected X[.X], X an integer */ @@ -179,7 +201,10 @@ class WithdrawalViewModel( val fracStr = inp.split(".")[1] return try { val value = valueStr.toInt() - val frac = fracStr.toInt() + var frac = 0 + if (fracStr.isNotEmpty()) { + frac = fracStr.toInt() + } Optional.of(Amount(value, frac)) } catch (ex: NumberFormatException) { println(ex.message) @@ -195,4 +220,9 @@ class WithdrawalViewModel( Optional.empty() } } + + fun resetAmountStr() { + println("resetting amountStr. was ${_uiState.value.amountStr}") + _uiState.value.amountStr = "" + } } \ No newline at end of file diff --git a/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/AmountPatternTest.kt b/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/AmountPatternTest.kt @@ -0,0 +1,56 @@ +package ch.bfh.habej2.wallee_c2ec + +import org.junit.Test + +import org.junit.Assert.* + +class AmountPatternTest { + + private val regex = Regex( "\\d+(\\.\\d+)?") + + @Test + fun valid_amount_pattern_test() { + + val validPatterns = listOf( + "0.5", + "0.05", + "1.34533434", + "45243.3254245235235335", + "1000", + "9383" + ) + + validPatterns.map { + assertTrue(regex.matches(it)) + } + } + + @Test + fun invalid_amount_pattern_test() { + + val invalidPattern = listOf( + "100.", + "12344.234523.34", + ".12344.234523", + ".", + ".932487", + "8989.2223..", + "2323..342" + ) + + invalidPattern.map { + assertFalse(regex.matches(it)) + } + } + + @Test + fun how_does_split_work() { + + val str = "1." + val first = str.split(".")[0] + val second = str.split(".")[1] + + assertEquals("1", first) + assertEquals("", second) + } +} +\ No newline at end of file diff --git a/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/ExampleUnitTest.kt b/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/ExampleUnitTest.kt @@ -1,17 +0,0 @@ -package ch.bfh.habej2.wallee_c2ec - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} -\ No newline at end of file