22
33package ee.ria.DigiDoc.viewmodel
44
5- import android.app.Activity
65import android.net.Uri
6+ import android.util.Base64.URL_SAFE
7+ import android.util.Base64.decode
78import androidx.arch.core.executor.testing.InstantTaskExecutorRule
89import ee.ria.DigiDoc.webEid.WebEidAuthService
9- import ee.ria.DigiDoc.webEid.domain.model.WebEidAuthRequest
10- import kotlinx.coroutines.flow.MutableStateFlow
10+ import kotlinx.coroutines.ExperimentalCoroutinesApi
11+ import kotlinx.coroutines.async
12+ import kotlinx.coroutines.flow.first
13+ import kotlinx.coroutines.test.UnconfinedTestDispatcher
14+ import kotlinx.coroutines.test.runTest
1115import org.json.JSONObject
16+ import org.junit.Assert.assertEquals
1217import org.junit.Before
1318import org.junit.Rule
1419import org.junit.Test
1520import org.junit.runner.RunWith
1621import org.mockito.Mock
17- import org.mockito.Mockito.never
18- import org.mockito.Mockito.`when`
1922import org.mockito.MockitoAnnotations
2023import org.mockito.junit.MockitoJUnitRunner
21- import org.mockito.kotlin.any
2224import org.mockito.kotlin.verify
25+ import org.mockito.kotlin.whenever
2326
2427@RunWith(MockitoJUnitRunner ::class )
2528class WebEidViewModelTest {
@@ -29,129 +32,177 @@ class WebEidViewModelTest {
2932 @Mock
3033 private lateinit var authService: WebEidAuthService
3134
32- @Mock
33- private lateinit var activity: Activity
34-
3535 private lateinit var viewModel: WebEidViewModel
3636
3737 @Before
3838 fun setup () {
3939 MockitoAnnotations .openMocks(this )
40-
41- `when `(authService.authRequest).thenReturn(MutableStateFlow (null ))
42- `when `(authService.signRequest).thenReturn(MutableStateFlow (null ))
43- `when `(authService.errorState).thenReturn(MutableStateFlow (null ))
44- `when `(authService.redirectUri).thenReturn(MutableStateFlow (null ))
45-
4640 viewModel = WebEidViewModel (authService)
4741 }
4842
4943 @Test
50- fun handleAuth_callsParseAuthUri () {
51- val uri = Uri .parse(" web-eid-mobile://auth#dummyData" )
52- viewModel.handleAuth(uri)
53- verify(authService).parseAuthUri(uri)
44+ fun webEidViewModel_handleAuth_parsesAuthUriAndSetsStateFlow () {
45+ runTest {
46+ val uri =
47+ Uri .parse(
48+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsImxvZ2luX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vcmVzcG9uc2UiLCJnZXRfc2lnbmluZ19jZXJ0aWZpY2F0ZSI6dHJ1ZX0" ,
49+ )
50+ viewModel.handleAuth(uri)
51+ val authRequest = viewModel.authRequest.value
52+ val signRequest = viewModel.signRequest.value
53+ assert (authRequest != null )
54+ assert (signRequest == null )
55+ assertEquals(" test-challenge-00000000000000000000000000000" , authRequest?.challenge)
56+ assertEquals(" https://example.com/response" , authRequest?.loginUri)
57+ assertEquals(" https://example.com" , authRequest?.origin)
58+ assertEquals(true , authRequest?.getSigningCertificate)
59+ }
5460 }
5561
5662 @Test
57- fun handleSign_callsParseSignUri () {
58- val uri = Uri .parse(" web-eid-mobile://sign#dummyData" )
59- viewModel.handleSign(uri)
60- verify(authService).parseSignUri(uri)
63+ fun webEidViewModel_handleAuth_emitErrorResponseEventWhenChallengeMinLength () {
64+ val uri =
65+ Uri .parse(
66+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwibG9naW5fdXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZXNwb25zZSIsImdldF9zaWduaW5nX2NlcnRpZmljYXRlIjp0cnVlfQ" ,
67+ )
68+ webEidViewModel_handleAuth_emitErrorResponseEventWhenInvalidChallenge(uri)
6169 }
6270
6371 @Test
64- fun reset_callsResetValues () {
65- viewModel.reset()
66- verify(authService).resetValues()
72+ fun webEidViewModel_handleAuth_emitErrorResponseEventWhenChallengeMaxLength () {
73+ val uri =
74+ Uri .parse(
75+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJsb2dpbl91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwiZ2V0X3NpZ25pbmdfY2VydGlmaWNhdGUiOnRydWV9" ,
76+ )
77+ webEidViewModel_handleAuth_emitErrorResponseEventWhenInvalidChallenge(uri)
6778 }
6879
69- @Test
70- fun redirectUri_isExposedFromAuthService () {
71- val redirectFlow = MutableStateFlow (" https://example.com#encodedPayload" )
72- `when `(authService.redirectUri).thenReturn(redirectFlow)
73- val vm = WebEidViewModel (authService)
74- assert (vm.redirectUri.value == " https://example.com#encodedPayload" )
80+ @OptIn(ExperimentalCoroutinesApi ::class )
81+ private fun webEidViewModel_handleAuth_emitErrorResponseEventWhenInvalidChallenge (uri : Uri ) {
82+ runTest(UnconfinedTestDispatcher ()) {
83+ val deferred =
84+ async {
85+ viewModel.relyingPartyResponseEvents.first()
86+ }
87+
88+ viewModel.handleAuth(uri)
89+
90+ val emittedUri = deferred.await()
91+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
92+ assert (emittedUri.fragment != null )
93+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
94+ val jsonPayload = JSONObject (decodedPayload)
95+ assertEquals(" ERR_WEBEID_MOBILE_INVALID_REQUEST" , jsonPayload.getString(" code" ))
96+ assertEquals(" Invalid challenge length" , jsonPayload.getString(" message" ))
97+ }
7598 }
7699
77100 @Test
78- fun redirectUri_updatesWhenServiceUpdates () {
79- val redirectFlow = MutableStateFlow <String ?>(null )
80- `when `(authService.redirectUri).thenReturn(redirectFlow)
81- val vm = WebEidViewModel (authService)
82- redirectFlow.value = " https://example.com#updatedPayload"
83- assert (vm.redirectUri.value == " https://example.com#updatedPayload" )
101+ @OptIn(ExperimentalCoroutinesApi ::class )
102+ fun webEidViewModel_handleAuth_emitErrorResponseEventWhenOriginMaxLength () {
103+ runTest(UnconfinedTestDispatcher ()) {
104+ val uri =
105+ Uri .parse(
106+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsImxvZ2luX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS54eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eC5jb20vcmVzcG9uc2UiLCJnZXRfc2lnbmluZ19jZXJ0aWZpY2F0ZSI6dHJ1ZX0" ,
107+ )
108+ val deferred =
109+ async {
110+ viewModel.relyingPartyResponseEvents.first()
111+ }
112+
113+ viewModel.handleAuth(uri)
114+
115+ val emittedUri = deferred.await()
116+ assert (
117+ emittedUri.toString().startsWith(
118+ " https://example.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.com/response#" ,
119+ ),
120+ )
121+ assert (emittedUri.fragment != null )
122+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
123+ val jsonPayload = JSONObject (decodedPayload)
124+ assertEquals(" ERR_WEBEID_MOBILE_INVALID_REQUEST" , jsonPayload.getString(" code" ))
125+ assertEquals(" Invalid origin length" , jsonPayload.getString(" message" ))
126+ }
84127 }
85128
86129 @Test
87- fun handleWebEidAuthResult_callsBuildAuthToken_whenPayloadValid () {
88- val cert = byteArrayOf(1 , 2 , 3 )
89- val signature = byteArrayOf(4 , 5 , 6 )
90- val challenge = " test-challenge"
91- val loginUri = " https://example.com/login"
92- val getSigningCertificate = true
93- val origin = " https://example.com"
94-
95- val authRequest =
96- WebEidAuthRequest (
97- challenge = challenge,
98- loginUri = loginUri,
99- getSigningCertificate = getSigningCertificate,
100- origin = origin,
130+ fun webEidViewModel_handleSign_parsesSignUriAndSetsStateFlow () {
131+ val uri =
132+ Uri .parse(
133+ " web-eid-mobile://sign#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25pbmdfY2VydGlmaWNhdGUiLCJoYXNoIjoiaGFzaCIsImhhc2hfZnVuY3Rpb24iOiJoYXNoX2Z1bmN0aW9uIn0" ,
101134 )
102- `when `(authService.authRequest).thenReturn(MutableStateFlow (authRequest))
103-
104- val token = JSONObject ().put(" mock" , " token" )
105- `when `(authService.buildAuthToken(cert, signature, challenge)).thenReturn(token)
106-
107- viewModel = WebEidViewModel (authService)
108-
109- viewModel.handleWebEidAuthResult(cert, signature, activity)
110-
111- verify(authService).buildAuthToken(cert, signature, challenge)
112- verify(activity).startActivity(any())
113- verify(activity).finish()
135+ viewModel.handleSign(uri)
136+ val authRequest = viewModel.authRequest.value
137+ val signRequest = viewModel.signRequest.value
138+ assert (authRequest == null )
139+ assert (signRequest != null )
140+ assertEquals(" https://example.com/response" , signRequest?.responseUri)
141+ assertEquals(" signing_certificate" , signRequest?.signCertificate)
142+ assertEquals(" hash" , signRequest?.hash)
143+ assertEquals(" hash_function" , signRequest?.hashFunction)
114144 }
115145
116146 @Test
117- fun handleWebEidAuthResult_doesNothing_whenChallengeMissing () {
118- val cert = byteArrayOf(1 )
119- val signature = byteArrayOf(2 )
120-
121- val authRequest =
122- WebEidAuthRequest (
123- challenge = " " ,
124- loginUri = " https://example.com" ,
125- getSigningCertificate = true ,
126- origin = " https://example.com" ,
127- )
128- `when `(authService.authRequest).thenReturn(MutableStateFlow (authRequest))
129-
130- viewModel = WebEidViewModel (authService)
131- viewModel.handleWebEidAuthResult(cert, signature, activity)
132-
133- verify(authService, never()).buildAuthToken(any(), any(), any())
134- verify(activity, never()).startActivity(any())
147+ @OptIn(ExperimentalCoroutinesApi ::class )
148+ fun webEidViewModel_handleWebEidAuthResult_buildsAuthTokenAndEmitsResponseEvent () {
149+ runTest(UnconfinedTestDispatcher ()) {
150+ val cert = byteArrayOf(1 , 2 , 3 )
151+ val signingCert = byteArrayOf(9 , 9 , 9 )
152+ val signature = byteArrayOf(4 , 5 , 6 )
153+ val uri =
154+ Uri .parse(
155+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsImxvZ2luX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vcmVzcG9uc2UiLCJnZXRfc2lnbmluZ19jZXJ0aWZpY2F0ZSI6dHJ1ZX0" ,
156+ )
157+ whenever(authService.buildAuthToken(cert, signingCert, signature))
158+ .thenReturn(JSONObject ().put(" format" , " web-eid:1.0" ))
159+ val deferred =
160+ async {
161+ viewModel.relyingPartyResponseEvents.first()
162+ }
163+ viewModel.handleAuth(uri)
164+ viewModel.handleWebEidAuthResult(cert, signingCert, signature)
165+
166+ verify(authService).buildAuthToken(cert, signingCert, signature)
167+ val emittedUri = deferred.await()
168+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
169+ assert (emittedUri.fragment != null )
170+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
171+ val jsonPayload = JSONObject (decodedPayload)
172+ val authToken = jsonPayload.getJSONObject(" auth-token" )
173+ assertEquals(" web-eid:1.0" , authToken.getString(" format" ))
174+ }
135175 }
136176
137177 @Test
138- fun handleWebEidAuthResult_doesNothing_whenLoginUriMissing () {
139- val cert = byteArrayOf(1 )
140- val signature = byteArrayOf(2 )
141-
142- val authRequest =
143- WebEidAuthRequest (
144- challenge = " abc" ,
145- loginUri = " " ,
146- getSigningCertificate = true ,
147- origin = " https://example.com" ,
148- )
149- `when `(authService.authRequest).thenReturn(MutableStateFlow (authRequest))
150-
151- viewModel = WebEidViewModel (authService)
152- viewModel.handleWebEidAuthResult(cert, signature, activity)
153-
154- verify(authService, never()).buildAuthToken(any(), any(), any())
155- verify(activity, never()).startActivity(any())
178+ @OptIn(ExperimentalCoroutinesApi ::class )
179+ fun webEidViewModel_handleWebEidAuthResult_emitErrorResponseEventWhenException () {
180+ runTest(UnconfinedTestDispatcher ()) {
181+ val cert = byteArrayOf(1 , 2 , 3 )
182+ val signingCert = byteArrayOf(9 , 9 , 9 )
183+ val signature = byteArrayOf(4 , 5 , 6 )
184+ val uri =
185+ Uri .parse(
186+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsImxvZ2luX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vcmVzcG9uc2UiLCJnZXRfc2lnbmluZ19jZXJ0aWZpY2F0ZSI6dHJ1ZX0" ,
187+ )
188+ whenever(authService.buildAuthToken(cert, signingCert, signature))
189+ .thenThrow(RuntimeException (" Test exception" ))
190+ val deferred =
191+ async {
192+ viewModel.relyingPartyResponseEvents.first()
193+ }
194+ viewModel.handleAuth(uri)
195+
196+ viewModel.handleWebEidAuthResult(cert, signingCert, signature)
197+
198+ verify(authService).buildAuthToken(cert, signingCert, signature)
199+ val emittedUri = deferred.await()
200+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
201+ assert (emittedUri.fragment != null )
202+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
203+ val jsonPayload = JSONObject (decodedPayload)
204+ assertEquals(" ERR_WEBEID_MOBILE_UNKNOWN_ERROR" , jsonPayload.getString(" code" ))
205+ assertEquals(" Unexpected error" , jsonPayload.getString(" message" ))
206+ }
156207 }
157208}
0 commit comments