@@ -6,7 +6,9 @@ import android.net.Uri
66import android.util.Base64.URL_SAFE
77import android.util.Base64.decode
88import androidx.arch.core.executor.testing.InstantTaskExecutorRule
9+ import ee.ria.DigiDoc.R
910import ee.ria.DigiDoc.webEid.WebEidAuthService
11+ import ee.ria.DigiDoc.webEid.WebEidSignService
1012import kotlinx.coroutines.ExperimentalCoroutinesApi
1113import kotlinx.coroutines.async
1214import kotlinx.coroutines.flow.first
@@ -32,12 +34,15 @@ class WebEidViewModelTest {
3234 @Mock
3335 private lateinit var authService: WebEidAuthService
3436
37+ @Mock
38+ private lateinit var signService: WebEidSignService
39+
3540 private lateinit var viewModel: WebEidViewModel
3641
3742 @Before
3843 fun setup () {
3944 MockitoAnnotations .openMocks(this )
40- viewModel = WebEidViewModel (authService)
45+ viewModel = WebEidViewModel (authService, signService )
4146 }
4247
4348 @Test
@@ -127,20 +132,13 @@ class WebEidViewModelTest {
127132 }
128133
129134 @Test
130- fun webEidViewModel_handleSign_parsesSignUriAndSetsStateFlow () {
131- val uri =
132- Uri .parse(
133- " web-eid-mobile://sign#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25pbmdfY2VydGlmaWNhdGUiLCJoYXNoIjoiaGFzaCIsImhhc2hfZnVuY3Rpb24iOiJoYXNoX2Z1bmN0aW9uIn0" ,
134- )
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)
135+ @OptIn(ExperimentalCoroutinesApi ::class )
136+ fun webEidViewModel_handleAuth_emitDialogErrorWhenGenericException () {
137+ runTest(UnconfinedTestDispatcher ()) {
138+ val uri = Uri .parse(" web-eid-mobile://auth#{}" )
139+ viewModel.handleAuth(uri)
140+ assertEquals(R .string.web_eid_invalid_auth_request_error, viewModel.dialogError.value)
141+ }
144142 }
145143
146144 @Test
@@ -169,7 +167,38 @@ class WebEidViewModelTest {
169167 assert (emittedUri.fragment != null )
170168 val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
171169 val jsonPayload = JSONObject (decodedPayload)
172- val authToken = jsonPayload.getJSONObject(" auth-token" )
170+ val authToken = jsonPayload.getJSONObject(" auth_token" )
171+ assertEquals(" web-eid:1.0" , authToken.getString(" format" ))
172+ }
173+ }
174+
175+ @Test
176+ @OptIn(ExperimentalCoroutinesApi ::class )
177+ fun webEidViewModel_handleWebEidAuthResult_buildsAuthTokenWithoutSigningCert () {
178+ runTest(UnconfinedTestDispatcher ()) {
179+ val cert = byteArrayOf(1 , 2 , 3 )
180+ val signingCert = byteArrayOf(9 , 9 , 9 )
181+ val signature = byteArrayOf(4 , 5 , 6 )
182+ val uri =
183+ Uri .parse(
184+ " web-eid-mobile://auth#eyJjaGFsbGVuZ2UiOiJ0ZXN0LWNoYWxsZW5nZS0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsImxvZ2luX3VyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vcmVzcG9uc2UiLCJnZXRfc2lnbmluZ19jZXJ0aWZpY2F0ZSI6ZmFsc2V9" ,
185+ )
186+ whenever(authService.buildAuthToken(cert, null , signature))
187+ .thenReturn(JSONObject ().put(" format" , " web-eid:1.0" ))
188+ val deferred =
189+ async {
190+ viewModel.relyingPartyResponseEvents.first()
191+ }
192+ viewModel.handleAuth(uri)
193+ viewModel.handleWebEidAuthResult(cert, signingCert, signature)
194+
195+ verify(authService).buildAuthToken(cert, null , signature)
196+ val emittedUri = deferred.await()
197+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
198+ assert (emittedUri.fragment != null )
199+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
200+ val jsonPayload = JSONObject (decodedPayload)
201+ val authToken = jsonPayload.getJSONObject(" auth_token" )
173202 assertEquals(" web-eid:1.0" , authToken.getString(" format" ))
174203 }
175204 }
@@ -205,4 +234,228 @@ class WebEidViewModelTest {
205234 assertEquals(" Unexpected error" , jsonPayload.getString(" message" ))
206235 }
207236 }
237+
238+ @Test
239+ fun webEidViewModel_handleCertificate_parsesCertificateUriAndSetsStateFlow () {
240+ runTest {
241+ val uri =
242+ Uri .parse(
243+ " web-eid-mobile://cert#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIn0" ,
244+ )
245+ viewModel.handleCertificate(uri)
246+ val authRequest = viewModel.authRequest.value
247+ val signRequest = viewModel.signRequest.value
248+ assert (authRequest == null )
249+ assert (signRequest != null )
250+ assertEquals(" https://example.com/response" , signRequest?.responseUri)
251+ assertEquals(null , signRequest?.hash)
252+ assertEquals(null , signRequest?.hashFunction)
253+ }
254+ }
255+
256+ @Test
257+ @OptIn(ExperimentalCoroutinesApi ::class )
258+ fun webEidViewModel_handleCertificate_emitDialogErrorWhenGenericException () {
259+ runTest(UnconfinedTestDispatcher ()) {
260+ val uri = Uri .parse(" web-eid-mobile://cert#{}" )
261+ viewModel.handleCertificate(uri)
262+ assertEquals(
263+ R .string.web_eid_invalid_sign_request_error,
264+ viewModel.dialogError.value,
265+ )
266+ }
267+ }
268+
269+ @Test
270+ fun webEidViewModel_handleSign_parsesSignUriAndSetsStateFlow () {
271+ runTest {
272+ val uri =
273+ Uri .parse(
274+ " web-eid-mobile://sign#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25pbmdfY2VydGlmaWNhdGUiLCJoYXNoIjoiaGFzaCIsImhhc2hfZnVuY3Rpb24iOiJoYXNoX2Z1bmN0aW9uIn0" ,
275+ )
276+ viewModel.handleSign(uri)
277+ val authRequest = viewModel.authRequest.value
278+ val signRequest = viewModel.signRequest.value
279+ assert (authRequest == null )
280+ assert (signRequest != null )
281+ assertEquals(" https://example.com/response" , signRequest?.responseUri)
282+ assertEquals(" hash" , signRequest?.hash)
283+ assertEquals(" hash_function" , signRequest?.hashFunction)
284+ }
285+ }
286+
287+ @Test
288+ @OptIn(ExperimentalCoroutinesApi ::class )
289+ fun webEidViewModel_handleSign_emitErrorResponseEventWhenWebEidException () {
290+ runTest(UnconfinedTestDispatcher ()) {
291+ val uri =
292+ Uri .parse(
293+ " web-eid-mobile://sign#" +
294+ " eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25lcnNlcnQiLCJoYXNoIjoiIn0" ,
295+ )
296+
297+ val deferred =
298+ async {
299+ viewModel.relyingPartyResponseEvents.first()
300+ }
301+
302+ viewModel.handleSign(uri)
303+
304+ val emittedUri = deferred.await()
305+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
306+ assert (emittedUri.fragment != null )
307+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
308+ val jsonPayload = JSONObject (decodedPayload)
309+ assertEquals(" ERR_WEBEID_MOBILE_INVALID_REQUEST" , jsonPayload.getString(" code" ))
310+ assertEquals(
311+ " Invalid signing request: missing hash or hash_function" ,
312+ jsonPayload.getString(" message" ),
313+ )
314+ }
315+ }
316+
317+ @Test
318+ @OptIn(ExperimentalCoroutinesApi ::class )
319+ fun webEidViewModel_handleSign_emitDialogErrorWhenGenericException () {
320+ runTest(UnconfinedTestDispatcher ()) {
321+ val uri = Uri .parse(" web-eid-mobile://sign#{}" )
322+ viewModel.handleSign(uri)
323+ assertEquals(R .string.web_eid_invalid_sign_request_error, viewModel.dialogError.value)
324+ }
325+ }
326+
327+ @Test
328+ @OptIn(ExperimentalCoroutinesApi ::class )
329+ fun webEidViewModel_handleUnknown_emitDialogError () {
330+ runTest(UnconfinedTestDispatcher ()) {
331+ val uri = Uri .parse(" web-eid-mobile://unknown#{}" )
332+ viewModel.handleUnknown(uri)
333+ assertEquals(
334+ R .string.web_eid_invalid_sign_request_error,
335+ viewModel.dialogError.value,
336+ )
337+ }
338+ }
339+
340+ @Test
341+ @OptIn(ExperimentalCoroutinesApi ::class )
342+ fun webEidViewModel_handleWebEidCertificateResult_buildsCertificatePayloadAndEmitsResponseEvent () {
343+ runTest(UnconfinedTestDispatcher ()) {
344+ val signingCert = byteArrayOf(1 , 2 , 3 )
345+ val uri =
346+ Uri .parse(
347+ " web-eid-mobile://sign#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25pbmdfY2VydGlmaWNhdGUiLCJoYXNoIjoiaGFzaCIsImhhc2hfZnVuY3Rpb24iOiJoYXNoX2Z1bmN0aW9uIn0" ,
348+ )
349+ viewModel.handleSign(uri)
350+
351+ whenever(signService.buildCertificatePayload(signingCert))
352+ .thenReturn(JSONObject ().put(" certificate" , " mock-cert" ))
353+
354+ val deferred =
355+ async {
356+ viewModel.relyingPartyResponseEvents.first()
357+ }
358+
359+ viewModel.handleWebEidCertificateResult(signingCert)
360+
361+ verify(signService).buildCertificatePayload(signingCert)
362+ val emittedUri = deferred.await()
363+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
364+ assert (emittedUri.fragment != null )
365+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
366+ val jsonPayload = JSONObject (decodedPayload)
367+ val certificateValue = jsonPayload.getString(" certificate" )
368+ assertEquals(" mock-cert" , certificateValue)
369+ }
370+ }
371+
372+ @Test
373+ @OptIn(ExperimentalCoroutinesApi ::class )
374+ fun webEidViewModel_handleWebEidCertificateResult_emitErrorResponseEventWhenException () {
375+ runTest(UnconfinedTestDispatcher ()) {
376+ val signingCert = byteArrayOf(1 , 2 , 3 )
377+ val uri =
378+ Uri .parse(
379+ " web-eid-mobile://sign#eyJyZXNwb25zZV91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3Jlc3BvbnNlIiwic2lnbl9jZXJ0aWZpY2F0ZSI6InNpZ25pbmdfY2VydGlmaWNhdGUiLCJoYXNoIjoiaGFzaCIsImhhc2hfZnVuY3Rpb24iOiJoYXNoX2Z1bmN0aW9uIn0" ,
380+ )
381+ viewModel.handleSign(uri)
382+
383+ whenever(signService.buildCertificatePayload(signingCert))
384+ .thenThrow(RuntimeException (" Test exception" ))
385+
386+ val deferred =
387+ async {
388+ viewModel.relyingPartyResponseEvents.first()
389+ }
390+
391+ viewModel.handleWebEidCertificateResult(signingCert)
392+
393+ verify(signService).buildCertificatePayload(signingCert)
394+ val emittedUri = deferred.await()
395+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
396+ assert (emittedUri.fragment != null )
397+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
398+ val jsonPayload = JSONObject (decodedPayload)
399+ assertEquals(" ERR_WEBEID_MOBILE_UNKNOWN_ERROR" , jsonPayload.getString(" code" ))
400+ assertEquals(" Unexpected error" , jsonPayload.getString(" message" ))
401+ }
402+ }
403+
404+ @Test
405+ @OptIn(ExperimentalCoroutinesApi ::class )
406+ fun webEidViewModel_handleWebEidSignResult_buildsSignPayloadAndEmitsResponseEvent () {
407+ runTest(UnconfinedTestDispatcher ()) {
408+ val signingCert = " mock-sign-cert"
409+ val signature = byteArrayOf(1 , 2 , 3 )
410+ val responseUri = " https://example.com/response"
411+
412+ whenever(signService.buildSignPayload(signingCert, signature))
413+ .thenReturn(JSONObject ().put(" signature" , " mock-signature" ))
414+
415+ val deferred =
416+ async {
417+ viewModel.relyingPartyResponseEvents.first()
418+ }
419+
420+ viewModel.handleWebEidSignResult(signingCert, signature, responseUri)
421+
422+ verify(signService).buildSignPayload(signingCert, signature)
423+ val emittedUri = deferred.await()
424+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
425+ assert (emittedUri.fragment != null )
426+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
427+ val jsonPayload = JSONObject (decodedPayload)
428+ val signValue = jsonPayload.getString(" signature" )
429+ assertEquals(" mock-signature" , signValue)
430+ }
431+ }
432+
433+ @Test
434+ @OptIn(ExperimentalCoroutinesApi ::class )
435+ fun webEidViewModel_handleWebEidSignResult_emitErrorResponseEventWhenException () {
436+ runTest(UnconfinedTestDispatcher ()) {
437+ val signingCert = " mock-sign-cert"
438+ val signature = byteArrayOf(1 , 2 , 3 )
439+ val responseUri = " https://example.com/response"
440+
441+ whenever(signService.buildSignPayload(signingCert, signature))
442+ .thenThrow(RuntimeException (" Test exception" ))
443+
444+ val deferred =
445+ async {
446+ viewModel.relyingPartyResponseEvents.first()
447+ }
448+
449+ viewModel.handleWebEidSignResult(signingCert, signature, responseUri)
450+
451+ verify(signService).buildSignPayload(signingCert, signature)
452+ val emittedUri = deferred.await()
453+ assert (emittedUri.toString().startsWith(" https://example.com/response#" ))
454+ assert (emittedUri.fragment != null )
455+ val decodedPayload = String (decode(emittedUri.fragment, URL_SAFE ))
456+ val jsonPayload = JSONObject (decodedPayload)
457+ assertEquals(" ERR_WEBEID_MOBILE_UNKNOWN_ERROR" , jsonPayload.getString(" code" ))
458+ assertEquals(" Unexpected error" , jsonPayload.getString(" message" ))
459+ }
460+ }
208461}
0 commit comments