messenger-android

Android graphical user interfaces for GNUnet Messenger
Log | Files | Refs | README | LICENSE

commit 947e1f909628169e1c40cfe54a6a980f28ec8890
parent 3719c2bb4b598acc28ea137ed8f03a7c8981ba18
Author: t3sserakt <t3ss@posteo.de>
Date:   Fri,  2 Jan 2026 10:36:10 +0100

 database peer test

Diffstat:
M.gitignore | 1+
MGNUnetMessenger/app/src/androidTest/java/org/gnunet/gnunetmessenger/ipc/GnunetChatRemoteTest.kt | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
MGNUnetMessenger/app/src/main/aidl/org/gnunet/gnunetmessenger/ipc/IGnunetChat.aidl | 1+
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt | 4++--
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt | 33+++++++++++++++++++++++++++------
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt | 10++++++++++
6 files changed, 204 insertions(+), 42 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,3 +3,4 @@ GNUnetMessenger/app/release/ GNUnetMessenger/.idea/*.xml +/.idea/ diff --git a/GNUnetMessenger/app/src/androidTest/java/org/gnunet/gnunetmessenger/ipc/GnunetChatRemoteTest.kt b/GNUnetMessenger/app/src/androidTest/java/org/gnunet/gnunetmessenger/ipc/GnunetChatRemoteTest.kt @@ -37,9 +37,11 @@ class GnunetChatRemoteTest { } @After - fun tearDown() { + fun tearDown() = runTest { // sauber vom Service abmelden gnunetChat.unbind() + // Wait for unbind to complete and clean up + delay(1000) } @Test @@ -53,7 +55,7 @@ class GnunetChatRemoteTest { } // 2. Wait for handle to be initialized with pointer from async operation - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(5_000) { while (handle.pointer == 0L) { delay(100) @@ -78,7 +80,7 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(5_000) { while (handle.pointer == 0L) { delay(100) @@ -99,7 +101,7 @@ class GnunetChatRemoteTest { accounts += acc } - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(3_000) { while (accounts.none { it.name == name }) { delay(50) @@ -117,9 +119,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -137,7 +141,7 @@ class GnunetChatRemoteTest { accounts += acc } - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(5_000) { while (accounts.size < accountNames.size) { delay(50) @@ -158,9 +162,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -181,9 +187,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -201,7 +209,7 @@ class GnunetChatRemoteTest { 0 // Return GNUNet_OK (Int as required by interface) } - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(3_000) { while (groups.none { it.name == groupName }) { delay(50) @@ -220,11 +228,16 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } + + // Wait a bit for any initial messages to clear + delay(500) val contacts = mutableListOf<String>() @@ -233,15 +246,18 @@ class GnunetChatRemoteTest { 0 // Return GNUNet_OK (Int as required by interface) } - withContext(Dispatchers.Default) { - withTimeout(3_000) { - while (contacts.isEmpty()) { + // Wait longer for contacts to arrive - server needs to process the iteration + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + var attempts = 0 + while (contacts.isEmpty() && attempts < 100) { delay(50) + attempts++ } } } - assertTrue("Should receive at least one contact", contacts.isNotEmpty()) + assertTrue("Should receive at least one contact, got: $contacts", contacts.isNotEmpty()) } @Test @@ -251,9 +267,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -268,7 +286,7 @@ class GnunetChatRemoteTest { attributes.add(key to value) } - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(3_000) { while (attributes.none { it.first == testKey }) { delay(50) @@ -287,9 +305,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -298,7 +318,7 @@ class GnunetChatRemoteTest { gnunetChat.createAccount(handle, accountName) // Wait for message to be received - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(3_000) { while (messageLog.isEmpty()) { delay(50) @@ -320,9 +340,11 @@ class GnunetChatRemoteTest { } // Wait for handle to be initialized - withTimeout(5_000) { - while (handle.pointer == 0L) { - delay(100) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } } } @@ -332,7 +354,7 @@ class GnunetChatRemoteTest { lobbyUri = uri } - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default.limitedParallelism(1)) { withTimeout(3_000) { while (lobbyUri.isEmpty()) { delay(50) @@ -342,4 +364,111 @@ class GnunetChatRemoteTest { assertTrue("Lobby URI should not be empty", lobbyUri.isNotEmpty()) } + + @Test + fun testResetClearsAllData() = runTest { + val handle = gnunetChat.startChat(MessengerApp()) { ctx, msg -> + messageLog.add(ctx to msg) + } + + // Wait for handle to be initialized + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (handle.pointer == 0L) { + delay(100) + } + } + } + + // Create some test data + val accountName = "TestAccountReset" + val groupName = "TestGroupReset" + val profileName = "TestProfileReset" + + // Create account + val createResult = gnunetChat.createAccount(handle, accountName) + assertEquals("Account creation should succeed", GnunetReturnValue.OK, createResult) + + // Create group + val group = gnunetChat.createGroup(handle, groupName) + assertNotNull("Group should be created", group) + assertEquals(groupName, group.name) + + // Set profile name + gnunetChat.setProfileName(handle, profileName) + assertEquals("Profile name should be set", profileName, gnunetChat.getProfileName(handle)) + + // Verify data exists before reset + val accountsBefore = mutableListOf<ChatAccount>() + gnunetChat.iterateAccounts(handle) { acc -> + accountsBefore += acc + } + + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(3_000) { + while (accountsBefore.none { it.name == accountName }) { + delay(50) + } + } + } + + assertTrue("Account should exist before reset", + accountsBefore.any { it.name == accountName }) + + val groupsBefore = mutableListOf<ChatGroup>() + gnunetChat.iterateGroups(handle) { grp -> + groupsBefore += grp + 0 + } + + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(3_000) { + while (groupsBefore.none { it.name == groupName }) { + delay(50) + } + } + } + + assertTrue("Group should exist before reset", + groupsBefore.any { it.name == groupName }) + + // Perform reset + gnunetChat.reset() + + // Note: The local ChatHandle object's pointer is not automatically set to 0 + // because it's a local variable. The reset clears server state, but we need + // to start a new session with a fresh handle. + + // Start a new session after reset + val newHandle = gnunetChat.startChat(MessengerApp()) { ctx, msg -> + messageLog.add(ctx to msg) + } + + // Wait for new handle to be initialized + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5_000) { + while (newHandle.pointer == 0L) { + delay(100) + } + } + } + + // Verify old data is gone + val accountsAfter = mutableListOf<ChatAccount>() + gnunetChat.iterateAccounts(newHandle) { acc -> + accountsAfter += acc + } + + // Give time for iteration to complete + delay(500) + + // The created account should be gone (only default mock accounts remain) + assertTrue("Created account should not exist after reset", + !accountsAfter.any { it.name == accountName }) + + // Profile should be back to default + val defaultProfile = gnunetChat.getProfileName(newHandle) + assertTrue("Profile should be reset to default", profileName != defaultProfile) + assertEquals("Profile should be default 'GNUnet'", "GNUnet", defaultProfile) + } } diff --git a/GNUnetMessenger/app/src/main/aidl/org/gnunet/gnunetmessenger/ipc/IGnunetChat.aidl b/GNUnetMessenger/app/src/main/aidl/org/gnunet/gnunetmessenger/ipc/IGnunetChat.aidl @@ -18,6 +18,7 @@ interface IGnunetChat { // Core chat operations int getApiVersion(); long startChat(String messengerApp, IChatCallback cb); + void reset(); void iterateAccounts(long handle, IAccountCallback cb); int createAccount(long handle, String name); void connect(long handle, in ChatAccountDto account); diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt @@ -39,6 +39,7 @@ import org.gnunet.gnunetmessenger.model.MessengerApp interface GnunetChat { suspend fun awaitReady(handle: ChatHandle) fun startChat(messengerApp: MessengerApp, callback: (ChatContext, ChatMessage) -> Unit): ChatHandle + suspend fun reset() fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit) suspend fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue suspend fun connect(handle: ChatHandle, account: ChatAccount) @@ -85,4 +86,4 @@ interface GnunetChat { fun getContactAttributes(contact: ChatContact, callback: (String, String) -> Unit) fun shareAttributes (handle: ChatHandle, contact: ChatContact, key: String) fun unshareAttributes (handle: ChatHandle, contact: ChatContact, key: String) -} -\ No newline at end of file +} diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt @@ -247,8 +247,8 @@ class GnunetChatBoundService( val ch = ChatHandle(0L) val remote = remoteRef.get() - if (remote != null && lastHandle == 0L) { - // Wir sind gebunden, aber noch kein Server-Handle -> jetzt starten + if (remote != null) { + // Wir sind gebunden -> Session starten (auch wenn lastHandle == 0L nach reset) ioScope.launch { runCatching { remote.startChat("messengerApp", binderCallback) } .onSuccess { h -> @@ -261,7 +261,7 @@ class GnunetChatBoundService( Log.e(TAG, "startChat failed", t) } } - } else if (remote == null) { + } else { // Noch nicht gebunden: Bind anstoßen; onServiceConnected ruft startChat bind() @@ -274,14 +274,35 @@ class GnunetChatBoundService( Log.d(TAG, "late handle propagation -> ${ch.pointer}") } } - } else if (remote != null && lastHandle != 0L) { - // Bereits eine Session vorhanden (Single-Session-Design) -> denselben Handle verwenden - ch.pointer = lastHandle } return ch } + override suspend fun reset() = withContext(Dispatchers.IO) { + val remote = try { + getOrBindRemote() + } catch (t: Throwable) { + Log.e(TAG, "reset: failed to bind remote", t) + throw t + } + + try { + remote.reset() + Log.i(TAG, "reset: successfully reset remote service") + + // Reset local state + lastHandle = 0L + handleReady.clear() + synchronized(pendingAfterHandle) { + pendingAfterHandle.clear() + } + } catch (e: RemoteException) { + Log.e(TAG, "reset: remote call failed", e) + throw e + } + } + override fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit) { val bridge = object : IAccountCallback.Stub() { override fun onAccount(accountDto: ChatAccountDto) { diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt @@ -375,4 +375,14 @@ class GnunetChatMock : GnunetChat { override fun unshareAttributes(handle: ChatHandle, contact: ChatContact, key: String) { println("unshare ${key} for contact ${contact.name}") } + + override suspend fun reset() { + println("reset mock service - clearing all state") + uuidCounter = 0 + lastDestroyedUri = null + lastSetUserPointer = null + lastSetGroupPointer = null + lastSetMessageForGroupContact = null + messageCallback = { _, _ -> } + } }