@@ -26,6 +26,7 @@ import to.bitkit.models.PubkySessionBackupKind
2626import to.bitkit.models.PubkySessionBackupV1
2727import to.bitkit.services.PubkyService
2828import to.bitkit.test.BaseUnitTest
29+ import to.bitkit.utils.AppError
2930import kotlin.test.assertEquals
3031import kotlin.test.assertFalse
3132import kotlin.test.assertNotNull
@@ -84,7 +85,7 @@ class PubkyRepoTest : BaseUnitTest() {
8485
8586 @Test
8687 fun `startAuthentication should reset state on failure` () = test {
87- whenever(pubkyService.startAuth()).thenThrow( RuntimeException ( " Auth failed" ))
88+ whenever(pubkyService.startAuth()).thenAnswer { throw TestAppError ( " Auth failed" ) }
8889
8990 val result = sut.startAuthentication()
9091
@@ -145,7 +146,7 @@ class PubkyRepoTest : BaseUnitTest() {
145146
146147 @Test
147148 fun `completeAuthentication should reset state on failure` () = test {
148- whenever(pubkyService.completeAuth()).thenThrow( RuntimeException ( " Failed" ))
149+ whenever(pubkyService.completeAuth()).thenAnswer { throw TestAppError ( " Failed" ) }
149150
150151 val result = sut.completeAuthentication()
151152
@@ -193,7 +194,7 @@ class PubkyRepoTest : BaseUnitTest() {
193194 assertNotNull(existingProfile)
194195
195196 val pk = checkNotNull(sut.publicKey.value) { " publicKey should be set after authentication" }
196- whenever(pubkyService.getProfile(pk)).thenThrow( RuntimeException ( " Network error" ))
197+ whenever(pubkyService.getProfile(pk)).thenAnswer { throw TestAppError ( " Network error" ) }
197198
198199 sut.loadProfile()
199200
@@ -266,8 +267,8 @@ class PubkyRepoTest : BaseUnitTest() {
266267 fun `deleteProfile should fail when signOut fails` () = test {
267268 authenticateForTesting()
268269 whenever(keychain.loadString(Keychain .Key .PAYKIT_SESSION .name)).thenReturn(" test_secret" )
269- whenever(pubkyService.signOut()).thenThrow( RuntimeException ( " Sign out failed" ))
270- whenever(pubkyService.forceSignOut()).thenThrow( RuntimeException ( " Force sign out failed" ))
270+ whenever(pubkyService.signOut()).thenAnswer { throw TestAppError ( " Sign out failed" ) }
271+ whenever(pubkyService.forceSignOut()).thenAnswer { throw TestAppError ( " Force sign out failed" ) }
271272
272273 val result = sut.deleteProfile()
273274
@@ -284,7 +285,11 @@ class PubkyRepoTest : BaseUnitTest() {
284285 whenever(keychain.loadString(Keychain .Key .PUBKY_SECRET_KEY .name)).thenReturn(secretKey)
285286 whenever(pubkyService.sessionList(expiredSession, Env .contactsBasePath)).thenReturn(emptyList())
286287 whenever(pubkyService.sessionList(newSession, Env .contactsBasePath)).thenReturn(emptyList())
287- whenever(pubkyService.sessionDelete(expiredSession, Env .profilePath)).thenThrow(RuntimeException (" Expired" ))
288+ whenever(
289+ pubkyService.sessionDelete(expiredSession, Env .profilePath)
290+ ).thenAnswer {
291+ throw TestAppError (" Expired" )
292+ }
288293 whenever(pubkyService.signIn(secretKey)).thenReturn(newSession)
289294 whenever(pubkyService.importSession(newSession)).thenReturn(VALID_SELF_KEY )
290295
@@ -303,7 +308,11 @@ class PubkyRepoTest : BaseUnitTest() {
303308 whenever(keychain.loadString(Keychain .Key .PAYKIT_SESSION .name)).thenReturn(expiredSession)
304309 whenever(keychain.loadString(Keychain .Key .PUBKY_SECRET_KEY .name)).thenReturn(null )
305310 whenever(pubkyService.sessionList(expiredSession, Env .contactsBasePath)).thenReturn(emptyList())
306- whenever(pubkyService.sessionDelete(expiredSession, Env .profilePath)).thenThrow(RuntimeException (" Expired" ))
311+ whenever(
312+ pubkyService.sessionDelete(expiredSession, Env .profilePath)
313+ ).thenAnswer {
314+ throw TestAppError (" Expired" )
315+ }
307316
308317 val result = sut.deleteProfileWithSessionRetry()
309318
@@ -315,7 +324,7 @@ class PubkyRepoTest : BaseUnitTest() {
315324 @Test
316325 fun `signOut should force sign out when server sign out fails` () = test {
317326 authenticateForTesting()
318- whenever(pubkyService.signOut()).thenThrow( RuntimeException ( " Server error" ))
327+ whenever(pubkyService.signOut()).thenAnswer { throw TestAppError ( " Server error" ) }
319328
320329 val result = sut.signOut()
321330
@@ -455,6 +464,20 @@ class PubkyRepoTest : BaseUnitTest() {
455464 verifyBlocking(keychain) { upsertString(Keychain .Key .PAYKIT_SESSION .name, session) }
456465 }
457466
467+ @Test
468+ fun `initialize should delete stale saved session when re-sign-in is unavailable` () = test {
469+ val session = " stale_session"
470+ whenever(keychain.loadString(Keychain .Key .PAYKIT_SESSION .name)).thenReturn(session)
471+ whenever(keychain.loadString(Keychain .Key .PUBKY_SECRET_KEY .name)).thenReturn(null )
472+ whenever(pubkyService.importSession(session)).thenAnswer { throw TestAppError (" Expired" ) }
473+
474+ sut.initialize()
475+
476+ assertTrue(sut.sessionRestorationFailed.value)
477+ assertFalse(sut.isAuthenticated.value)
478+ verifyBlocking(keychain) { delete(Keychain .Key .PAYKIT_SESSION .name) }
479+ }
480+
458481 @Test
459482 fun `refreshSessionIfPossible should refresh session when local secret key exists` () = test {
460483 val secretKey = " local_secret"
@@ -511,6 +534,21 @@ class PubkyRepoTest : BaseUnitTest() {
511534 verifyBlocking(keychain) { upsertString(Keychain .Key .PAYKIT_SESSION .name, " external_session" ) }
512535 }
513536
537+ @Test
538+ fun `restoreSessionBackupState should keep current session when backup has no pubky state` () = test {
539+ authenticateForTesting(publicKey = VALID_SELF_KEY )
540+ clearInvocations(pubkyService, keychain)
541+
542+ val result = sut.restoreSessionBackupState(null )
543+
544+ assertTrue(result.isSuccess)
545+ assertTrue(sut.isAuthenticated.value)
546+ assertEquals(VALID_SELF_KEY , sut.publicKey.value)
547+ verifyBlocking(pubkyService, never()) { forceSignOut() }
548+ verifyBlocking(keychain, never()) { delete(Keychain .Key .PAYKIT_SESSION .name) }
549+ verifyBlocking(keychain, never()) { delete(Keychain .Key .PUBKY_SECRET_KEY .name) }
550+ }
551+
514552 @Test
515553 fun `loadContacts should populate contacts on success` () = test {
516554 authenticateForTesting()
@@ -651,7 +689,7 @@ class PubkyRepoTest : BaseUnitTest() {
651689 val pk = checkNotNull(sut.publicKey.value)
652690 val strippedPk = pk.removePrefix(" pubky" )
653691 whenever(pubkyService.fetchFileString(" pubky://$strippedPk${Env .contactsBasePath}$contactKey " ))
654- .thenThrow( RuntimeException ( " Network error" ))
692+ .thenAnswer { throw TestAppError ( " Network error" ) }
655693
656694 sut.loadContacts()
657695
@@ -666,7 +704,7 @@ class PubkyRepoTest : BaseUnitTest() {
666704 authenticateForTesting()
667705 whenever(keychain.loadString(Keychain .Key .PAYKIT_SESSION .name)).thenReturn(" test_secret" )
668706 whenever(pubkyService.sessionList(" test_secret" , Env .contactsBasePath))
669- .thenThrow( RuntimeException ( " Directory Not Found (404)" ))
707+ .thenAnswer { throw TestAppError ( " Directory Not Found (404)" ) }
670708
671709 sut.loadContacts()
672710
@@ -695,7 +733,7 @@ class PubkyRepoTest : BaseUnitTest() {
695733 val strippedKey = contactKey.removePrefix(" pubky" )
696734 val contactProfile = mock<CorePubkyProfile >()
697735 whenever(pubkyService.fetchFileString(" pubky://$strippedKey${Env .profilePath} " ))
698- .thenThrow( RuntimeException ( " Missing bitkit profile" ))
736+ .thenAnswer { throw TestAppError ( " Missing bitkit profile" ) }
699737 whenever(contactProfile.name).thenReturn(" Bob" )
700738 whenever(contactProfile.bio).thenReturn(" Bio" )
701739 whenever(pubkyService.getProfile(contactKey)).thenReturn(contactProfile)
@@ -711,8 +749,8 @@ class PubkyRepoTest : BaseUnitTest() {
711749 val contactKey = VALID_CONTACT_KEY_A
712750 val strippedKey = contactKey.removePrefix(" pubky" )
713751 whenever(pubkyService.fetchFileString(" pubky://$strippedKey${Env .profilePath} " ))
714- .thenThrow( RuntimeException ( " Missing bitkit profile" ))
715- whenever(pubkyService.getProfile(contactKey)).thenThrow( RuntimeException ( " Profile not found" ))
752+ .thenAnswer { throw TestAppError ( " Missing bitkit profile" ) }
753+ whenever(pubkyService.getProfile(contactKey)).thenAnswer { throw TestAppError ( " Profile not found" ) }
716754
717755 val result = sut.fetchContactProfile(contactKey)
718756
@@ -892,5 +930,7 @@ class PubkyRepoTest : BaseUnitTest() {
892930 }
893931}
894932
933+ private class TestAppError (message : String ) : AppError(message)
934+
895935private fun String.ensurePubkyPrefixForTest (): String =
896936 if (startsWith(" pubky" )) this else " pubky$this "
0 commit comments