diff --git a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/-tx-code-spec.md b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/-tx-code-spec.md
index f98d9408..94ab617e 100644
--- a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/-tx-code-spec.md
+++ b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/-tx-code-spec.md
@@ -3,6 +3,6 @@
# TxCodeSpec
[androidJvm]\
-constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC,
+constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC,
length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?,
description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)
diff --git a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/index.md b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/index.md
index 2c8ff9ca..672474f0 100644
--- a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/index.md
+++ b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/-tx-code-spec/index.md
@@ -3,17 +3,17 @@
# TxCodeSpec
[androidJvm]\
-data class [TxCodeSpec](index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC,
-val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val
+data class [TxCodeSpec](index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC, val
+length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val
description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)
Specification for a transaction code.
## Constructors
-| | |
-|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [TxCodeSpec](-tx-code-spec.md) | [androidJvm]
constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC, length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null) |
+| | |
+|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [TxCodeSpec](-tx-code-spec.md) | [androidJvm]
constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC, length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null) |
## Types
diff --git a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/index.md b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/index.md
index ef8c770f..ba05a853 100644
--- a/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/index.md
+++ b/docs/wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/-offer/index.md
@@ -9,10 +9,10 @@ An offer of credentials to be issued.
## Types
-| Name | Summary |
-|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [OfferedDocument](-offered-document/index.md) | [androidJvm]
interface [OfferedDocument](-offered-document/index.md)
An item to be issued. |
-| [TxCodeSpec](-tx-code-spec/index.md) | [androidJvm]
data class [TxCodeSpec](-tx-code-spec/index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-tx-code-spec/-input-mode/index.md) = InputMode.NUMERIC, val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)
Specification for a transaction code. |
+| Name | Summary |
+|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [OfferedDocument](-offered-document/index.md) | [androidJvm]
interface [OfferedDocument](-offered-document/index.md)
An item to be issued. |
+| [TxCodeSpec](-tx-code-spec/index.md) | [androidJvm]
data class [TxCodeSpec](-tx-code-spec/index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-tx-code-spec/-input-mode/index.md) = NUMERIC, val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)
Specification for a transaction code. |
## Properties
diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DefaultOpenId4VciManager.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DefaultOpenId4VciManager.kt
index 5bf9057e..24f74b95 100644
--- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DefaultOpenId4VciManager.kt
+++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DefaultOpenId4VciManager.kt
@@ -222,8 +222,7 @@ internal class DefaultOpenId4VciManager(
) {
offer as DefaultOffer
val issuer = issuerCreator.createIssuer(offer)
- var authorizedRequest = issuerAuthorization.authorize(issuer, txCode)
-
+ var authorizedRequest = issuerAuthorization.use { it.authorize(issuer, txCode) }
listener(IssueEvent.Started(offer.offeredDocuments.size))
val issuedDocumentIds = mutableListOf()
val requestMap = offer.offeredDocuments.associateBy { offeredDocument ->
diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Exceptions.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Exceptions.kt
index de24147c..2ed5cf9d 100644
--- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Exceptions.kt
+++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Exceptions.kt
@@ -18,14 +18,14 @@
package eu.europa.ec.eudi.wallet.issue.openid4vci
import androidx.biometric.BiometricPrompt.CryptoObject
-import eu.europa.ec.eudi.openid4vci.SubmittedRequest
+import eu.europa.ec.eudi.openid4vci.SubmissionOutcome
/**
* Exception thrown when user authentication is required.
*/
internal class UserAuthRequiredException(
val cryptoObject: CryptoObject?,
- val resume: suspend (Boolean) -> SubmittedRequest
+ val resume: suspend (Boolean) -> SubmissionOutcome,
) : Throwable()
/**
diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorization.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorization.kt
index 53eeaf54..a7137420 100644
--- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorization.kt
+++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorization.kt
@@ -20,10 +20,7 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
-import eu.europa.ec.eudi.openid4vci.AuthorizationCode
-import eu.europa.ec.eudi.openid4vci.AuthorizationRequestPrepared
-import eu.europa.ec.eudi.openid4vci.AuthorizedRequest
-import eu.europa.ec.eudi.openid4vci.Issuer
+import eu.europa.ec.eudi.openid4vci.*
import eu.europa.ec.eudi.wallet.issue.openid4vci.OpenId4VciManager.Companion.TAG
import eu.europa.ec.eudi.wallet.logging.Logger
import kotlinx.coroutines.CancellableContinuation
@@ -49,12 +46,17 @@ internal class IssuerAuthorization(
* otherwise the browser will be opened for user authorization
* @param issuer The issuer to authorize.
* @param txCode The pre-authorization code.
+ * @throws IllegalArgumentException if the txCode is invalid.
*/
suspend fun authorize(issuer: Issuer, txCode: String?): AuthorizedRequest {
close() // close any previous suspensions
return with(issuer) {
when {
- !txCode.isNullOrEmpty() -> authorizeWithPreAuthorizationCode(txCode)
+ isPreAuthorized() -> {
+ marshalTxCode(txCode)
+ authorizeWithPreAuthorizationCode(txCode)
+ }
+
else -> {
val prepareAuthorizationCodeRequest = prepareAuthorizationRequest().getOrThrow()
val authResponse = openBrowserForAuthorization(prepareAuthorizationCodeRequest).getOrThrow()
@@ -123,4 +125,32 @@ internal class IssuerAuthorization(
}
data class Response(val authorizationCode: String, val serverState: String)
+
+ companion object {
+ /**
+ * Checks if the issuer's credential offer is pre-authorized.
+ * @receiver The issuer to check.
+ */
+ fun Issuer.isPreAuthorized(): Boolean = credentialOffer.grants?.preAuthorizedCode() != null
+
+ /**
+ * Ensures the txCode is valid for the given issuer.
+ * @param txCode The pre-authorization code.
+ * @throws IllegalArgumentException if the txCode is invalid.
+ * @receiver The issuer to authorize.
+ */
+ fun Issuer.marshalTxCode(txCode: String?) {
+ when (val txCodeSpec = credentialOffer.grants?.preAuthorizedCode()?.txCode) {
+ // no txCode in credential offer grants, so no txCode is expected for pre-authorization
+ null -> if (txCode != null) throw IllegalArgumentException("txCode is not required")
+ // txCode is present in credential offer grants, so it is required for pre-authorization
+ else -> when {
+ txCode == null -> throw IllegalArgumentException("txCode is required")
+ txCode.length != txCodeSpec.length -> throw IllegalArgumentException("txCode length is invalid")
+ txCodeSpec.inputMode == TxCodeInputMode.NUMERIC && !txCode.all { it.isDigit() } ->
+ throw IllegalArgumentException("txCode is not numeric")
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Offer.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Offer.kt
index 3a27865b..1e50ad5f 100644
--- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Offer.kt
+++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/Offer.kt
@@ -15,10 +15,8 @@
*/
package eu.europa.ec.eudi.wallet.issue.openid4vci
-import eu.europa.ec.eudi.openid4vci.CredentialConfiguration
-import eu.europa.ec.eudi.openid4vci.CredentialConfigurationIdentifier
-import eu.europa.ec.eudi.openid4vci.TxCode
-import eu.europa.ec.eudi.openid4vci.TxCodeInputMode
+import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec.InputMode.NUMERIC
+import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec.InputMode.TEXT
/**
* An offer of credentials to be issued.
@@ -55,7 +53,7 @@ interface Offer {
* @property description a description of the transaction code
*/
data class TxCodeSpec(
- val inputMode: InputMode = InputMode.NUMERIC,
+ val inputMode: InputMode = NUMERIC,
val length: Int?,
val description: String? = null,
) {
diff --git a/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DeferredStateTest.kt b/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DeferredStateTest.kt
index c485015d..a462e280 100644
--- a/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DeferredStateTest.kt
+++ b/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DeferredStateTest.kt
@@ -12,99 +12,4 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-package eu.europa.ec.eudi.wallet.issue.openid4vci
-
-import com.nimbusds.jose.JWSAlgorithm
-import eu.europa.ec.eudi.openid4vci.*
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import java.net.URI
-import java.time.Instant
-import java.util.*
-
-class DeferredStateTest {
-
- @Test
- fun `test encode and decode methods`() {
- val deferredCredential = IssuedCredential.Deferred(
- transactionId = TransactionId("transactionId"),
- )
- val credentialIdentifiers = mapOf(
- CredentialConfigurationIdentifier("credentialConfigurationId") to listOf(
- CredentialIdentifier("credentialId")
- )
- )
- val authorizedRequest = AuthorizedRequest.ProofRequired(
- accessToken = AccessToken(accessToken = "accessToken", expiresInSec = 3600, useDPoP = false),
- refreshToken = RefreshToken("refreshToken", expiresInSec = 3600),
- cNonce = CNonce(value = "cNonce"),
- credentialIdentifiers = credentialIdentifiers,
- timestamp = Instant.now()
- )
-
- val credentialIssuerMetadata = CredentialIssuerMetadata(
- credentialIssuerIdentifier = CredentialIssuerId("https://localhost:8080").getOrThrow(),
- authorizationServers = listOf(HttpsUrl("https://localhost:8080").getOrThrow()),
- credentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
- batchCredentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
- deferredCredentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
- notificationEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
- credentialResponseEncryption = CredentialResponseEncryption.NotSupported,
- credentialIdentifiersSupported = true,
- credentialConfigurationsSupported = mapOf(
- CredentialConfigurationIdentifier("credentialConfigurationId") to MsoMdocCredential(
- credentialSigningAlgorithmsSupported = listOf("alg"),
- isoCredentialSigningAlgorithmsSupported = listOf(CoseAlgorithm.ES256),
- isoCredentialCurvesSupported = listOf(CoseCurve.P_256),
- proofTypesSupported = ProofTypesSupported(
- setOf(
- ProofTypeMeta.Jwt(listOf(JWSAlgorithm.ES256)),
- ProofTypeMeta.Cwt(listOf(CoseAlgorithm.ES256), listOf(CoseCurve.P_256))
- )
- ),
- display = listOf(
- Display(
- name = "name",
- locale = Locale.US,
- logo = Display.Logo(URI("http://localhost:8080"), "image/png"),
- textColor = "#444444"
- )
- ),
- claims = mapOf(
- "namespace" to mapOf(
- "claim" to Claim(
- true, "claim", listOf(
- Claim.Display("name", Locale.US)
- )
- )
- )
- ),
- order = listOf("claim"),
- docType = "docType",
- isoPolicy = MsoMdocPolicy(true, 10),
- scope = "scope",
- cryptographicBindingMethodsSupported = listOf(CryptographicBindingMethod.JWK),
- )
- ),
- display = listOf(
- CredentialIssuerMetadata.Display(name = "name", locale = "el_GR")
- ),
- )
- val state = DeferredState(
- clientId = "clientId",
- tokenEndpoint = URI("http://localhost:8080"),
- credentialIssuerMetadata = credentialIssuerMetadata,
- authorizedRequest = authorizedRequest,
- deferredCredential = deferredCredential
- )
-
- val encoded = state.encode()
-
- val decoded = DeferredState.decode(encoded)
-
- assertEquals(state, decoded)
-
- }
-}
\ No newline at end of file
+ */
\ No newline at end of file
diff --git a/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorizationTest.kt b/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorizationTest.kt
index 05bb4ad8..2922a69c 100644
--- a/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorizationTest.kt
+++ b/wallet-core/src/test/java/eu/europa/ec/eudi/wallet/issue/openid4vci/IssuerAuthorizationTest.kt
@@ -19,10 +19,8 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci
import android.content.Context
import android.content.Intent
import android.net.Uri
-import eu.europa.ec.eudi.openid4vci.AuthorizationRequestPrepared
-import eu.europa.ec.eudi.openid4vci.AuthorizedRequest
-import eu.europa.ec.eudi.openid4vci.HttpsUrl
-import eu.europa.ec.eudi.openid4vci.Issuer
+import eu.europa.ec.eudi.openid4vci.*
+import eu.europa.ec.eudi.wallet.issue.openid4vci.IssuerAuthorization.Companion.marshalTxCode
import eu.europa.ec.eudi.wallet.logging.Logger
import io.mockk.*
import kotlinx.coroutines.Dispatchers
@@ -33,6 +31,9 @@ import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
import kotlin.time.Duration.Companion.milliseconds
@@ -43,7 +44,6 @@ class IssuerAuthorizationTest {
lateinit var logger: Logger
lateinit var issuer: Issuer
-
@BeforeAll
@JvmStatic
fun setup() {
@@ -54,12 +54,12 @@ class IssuerAuthorizationTest {
mockkConstructor(Intent::class)
every { anyConstructed().addFlags(any()) } returns mockk(relaxed = true)
- issuer = mockk(relaxed = true)
-
-
context = mockk(relaxed = true)
logger = mockk(relaxed = true)
}
+
+ @JvmStatic
+ fun provideTxCode() = txCodeTestSource
}
lateinit var preparedAuthorizationRequest: AuthorizationRequestPrepared
@@ -72,8 +72,8 @@ class IssuerAuthorizationTest {
preparedAuthorizationRequest.authorizationCodeURL
} returns HttpsUrl("https://test.com").getOrThrow()
+ issuer = mockk(relaxed = true)
authorizedRequest = mockk(relaxed = true)
-
coEvery {
issuer.prepareAuthorizationRequest()
} returns Result.success(preparedAuthorizationRequest)
@@ -90,8 +90,12 @@ class IssuerAuthorizationTest {
}
@Test
- fun `authorize method when txCode equals to null call openBrowserForAuthorization`() {
-
+ fun `authorize method when no preAuthorizedCode in offer and txCode is null calls openBrowserForAuthorization`() {
+ every { issuer.credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns null
+ }
+ }
val issuerAuthorization = spyk(IssuerAuthorization(context, logger))
runTest {
launch {
@@ -109,12 +113,21 @@ class IssuerAuthorizationTest {
}
@Test
- fun `authorize method when passing txCode does not call openBrowserForAuthorization but calls authorizeWithPreAuthorizationCode`() {
-
+ fun `authorize method when preAuthorizedCode in offer and passing txCode does not call openBrowserForAuthorization but calls authorizeWithPreAuthorizationCode`() {
+ every { issuer.credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns mockk(relaxed = true) {
+ every { length } returns 4
+ every { inputMode } returns TxCodeInputMode.NUMERIC
+ }
+ }
+ }
+ }
val issuerAuthorization = spyk(IssuerAuthorization(context, logger))
runTest {
launch {
- issuerAuthorization.authorize(issuer, "testCode")
+ issuerAuthorization.authorize(issuer, "1234")
}
launch {
delay(500.milliseconds)
@@ -126,7 +139,7 @@ class IssuerAuthorizationTest {
issuerAuthorization.openBrowserForAuthorization(preparedAuthorizationRequest)
}
coVerify(exactly = 1) {
- issuer.authorizeWithPreAuthorizationCode("testCode")
+ issuer.authorizeWithPreAuthorizationCode("1234")
}
}
@@ -228,4 +241,91 @@ class IssuerAuthorizationTest {
verify(exactly = 1) { issuerAuthorization.close() }
assertNull(issuerAuthorization.continuation, "Continuation is removed")
}
-}
\ No newline at end of file
+
+ @ParameterizedTest(name = "marshalTxCode({0}) with issuer {1} throws {2}")
+ @MethodSource("provideTxCode")
+ fun `test marshalTxCode method`(txCode: String?, issuer: Issuer, expectedException: Exception?) {
+ if (expectedException != null) {
+ assertThrows(expectedException::class.java, {
+ issuer.marshalTxCode(txCode)
+ }, expectedException.message)
+ } else {
+ assertDoesNotThrow {
+ issuer.marshalTxCode(txCode)
+ }
+ }
+ }
+}
+
+private val txCodeTestSource = listOf(
+ listOf(
+ null,
+ mockk(relaxed = true) {
+ every { credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns mockk(relaxed = true)
+ }
+ }
+ }
+ },
+ IllegalArgumentException("txCode is required")
+ ),
+ listOf(
+ null,
+ mockk(relaxed = true) {
+ every { credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns null
+ }
+ }
+ }
+ },
+ null
+ ),
+ listOf(
+ "123456",
+ mockk(relaxed = true) {
+ every { credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns null
+ }
+ }
+ }
+ },
+ IllegalArgumentException("txCode is not required")
+ ),
+ listOf(
+ "123456",
+ mockk(relaxed = true) {
+ every { credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns mockk(relaxed = true) {
+ every { length } returns 10
+ }
+ }
+ }
+ }
+ },
+ IllegalArgumentException("txCode length is invalid")
+ ),
+ listOf(
+ "abcd",
+ mockk(relaxed = true) {
+ every { credentialOffer } returns mockk(relaxed = true) {
+ every { grants } returns mockk(relaxed = true) {
+ every { preAuthorizedCode() } returns mockk(relaxed = true) {
+ every { txCode } returns mockk(relaxed = true) {
+ every { length } returns 4
+ every { inputMode } returns TxCodeInputMode.NUMERIC
+ }
+ }
+ }
+ }
+ },
+ IllegalArgumentException("txCode is not numeric")
+ ),
+).map { Arguments.of(*it.toTypedArray()) }
\ No newline at end of file