Skip to content

Commit

Permalink
feat: V3 only dms (#307)
Browse files Browse the repository at this point in the history
* bump version

* get on the latest bindings

* add the defaults

* bump version

* Fix kt lint error that was blocking build

* allow block number to be optional

* bump version again

* write a test for it

* probably just want the bytes that were passed directly

* bump

* bump the lib as well

* add real smart contract wallet test

* add the binary files

* need to fix the signature issue

* make the signing key optional

* get on the latest version

* new binaries

* a few more tweaks to the sign functions

* maybe getting closer

* Fix failing SCW test

* Fix anvil command

* dump the bindings again

* update the client to create and a seperate to build

* get the tests cleaned up

* remove the read me

* dumpt he v

* make optional

* get all the tests working

* fix the linter

* rename

* pull the pieces for just dms

* get all the tests passing

* add tests to make sure dms are not leaking

* fix up the linter

* push notification methods

* find conversation by topic

* add ability to filter consent

* improve conversation sort performance

* remove ability to set and get metadata on dms

* remove metadata test

* feat: get v3 only dms working all over

---------

Co-authored-by: koleok <[email protected]>
Co-authored-by: Nicholas Molnar <[email protected]>
  • Loading branch information
3 people authored Oct 23, 2024
1 parent 9c7c3ad commit bbdac09
Show file tree
Hide file tree
Showing 9 changed files with 1,596 additions and 408 deletions.
359 changes: 359 additions & 0 deletions library/src/androidTest/java/org/xmtp/android/library/DmTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
package org.xmtp.android.library

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import app.cash.turbine.test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.codecs.ContentTypeReaction
import org.xmtp.android.library.codecs.Reaction
import org.xmtp.android.library.codecs.ReactionAction
import org.xmtp.android.library.codecs.ReactionCodec
import org.xmtp.android.library.codecs.ReactionSchema
import org.xmtp.android.library.messages.DecryptedMessage
import org.xmtp.android.library.messages.MessageDeliveryStatus
import org.xmtp.android.library.messages.PrivateKey
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.walletAddress
import java.security.SecureRandom

@RunWith(AndroidJUnit4::class)
class DmTest {
private lateinit var alixWallet: PrivateKeyBuilder
private lateinit var boWallet: PrivateKeyBuilder
private lateinit var caroWallet: PrivateKeyBuilder
private lateinit var alix: PrivateKey
private lateinit var alixClient: Client
private lateinit var bo: PrivateKey
private lateinit var boClient: Client
private lateinit var caro: PrivateKey
private lateinit var caroClient: Client

@Before
fun setUp() {
val key = SecureRandom().generateSeed(32)
val context = InstrumentationRegistry.getInstrumentation().targetContext
alixWallet = PrivateKeyBuilder()
alix = alixWallet.getPrivateKey()
alixClient = runBlocking {
Client().createV3(
account = alixWallet,
options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableV3 = true,
appContext = context,
dbEncryptionKey = key
)
)
}
boWallet = PrivateKeyBuilder()
bo = boWallet.getPrivateKey()
boClient = runBlocking {
Client().createV3(
account = boWallet,
options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableV3 = true,
appContext = context,
dbEncryptionKey = key
)
)
}

caroWallet = PrivateKeyBuilder()
caro = caroWallet.getPrivateKey()
caroClient = runBlocking {
Client().createV3(
account = caroWallet,
options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableV3 = true,
appContext = context,
dbEncryptionKey = key
)
)
}
}

@Test
fun testCanCreateADm() {
runBlocking {
val convo1 = boClient.conversations.findOrCreateDm(alix.walletAddress)
alixClient.conversations.syncConversations()
val sameConvo1 = alixClient.conversations.findOrCreateDm(bo.walletAddress)
assertEquals(convo1.id, sameConvo1.id)
}
}

@Test
fun testCanListDmMembers() {
val dm = runBlocking {
boClient.conversations.findOrCreateDm(
alix.walletAddress,
)
}
assertEquals(
runBlocking { dm.members().map { it.inboxId }.sorted() },
listOf(
alixClient.inboxId,
boClient.inboxId
).sorted()
)

assertEquals(
runBlocking {
Conversation.Dm(dm).members().map { it.inboxId }.sorted()
},
listOf(
alixClient.inboxId,
boClient.inboxId
).sorted()
)

assertEquals(
runBlocking
{ dm.peerInboxId() },
alixClient.inboxId,
)
}

@Test
fun testCannotCreateDmWithMemberNotOnV3() {
val chuxAccount = PrivateKeyBuilder()
val chux: PrivateKey = chuxAccount.getPrivateKey()
runBlocking { Client().create(account = chuxAccount) }

assertThrows("Recipient not on network", XMTPException::class.java) {
runBlocking { boClient.conversations.findOrCreateDm(chux.walletAddress) }
}
}

@Test
fun testCannotStartDmWithSelf() {
assertThrows("Recipient is sender", XMTPException::class.java) {
runBlocking { boClient.conversations.findOrCreateDm(bo.walletAddress) }
}
}

@Test
fun testDmStartsWithAllowedState() {
runBlocking {
val dm = boClient.conversations.findOrCreateDm(alix.walletAddress)
dm.send("howdy")
dm.send("gm")
dm.sync()
assert(boClient.contacts.isGroupAllowed(dm.id))
assertEquals(boClient.contacts.consentList.groupState(dm.id), ConsentState.ALLOWED)
assertEquals(dm.consentState(), ConsentState.ALLOWED)
}
}

@Test
fun testCanSendMessageToDm() {
val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) }
runBlocking { dm.send("howdy") }
val messageId = runBlocking { dm.send("gm") }
runBlocking { dm.sync() }
assertEquals(dm.messages().first().body, "gm")
assertEquals(dm.messages().first().id, messageId)
assertEquals(dm.messages().first().deliveryStatus, MessageDeliveryStatus.PUBLISHED)
assertEquals(dm.messages().size, 3)

runBlocking { alixClient.conversations.syncConversations() }
val sameDm = runBlocking { alixClient.conversations.listDms().last() }
runBlocking { sameDm.sync() }
assertEquals(sameDm.messages().size, 2)
assertEquals(sameDm.messages().first().body, "gm")
}

@Test
fun testCanListDmMessages() {
val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) }
runBlocking {
dm.send("howdy")
dm.send("gm")
}

assertEquals(dm.messages().size, 3)
assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3)
runBlocking { dm.sync() }
assertEquals(dm.messages().size, 3)
assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.UNPUBLISHED).size, 0)
assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3)

runBlocking { alixClient.conversations.syncConversations() }
val sameDm = runBlocking { alixClient.conversations.listDms().last() }
runBlocking { sameDm.sync() }
assertEquals(sameDm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 2)
}

@Test
fun testCanSendContentTypesToDm() {
Client.register(codec = ReactionCodec())

val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) }
runBlocking { dm.send("gm") }
runBlocking { dm.sync() }
val messageToReact = dm.messages()[0]

val reaction = Reaction(
reference = messageToReact.id,
action = ReactionAction.Added,
content = "U+1F603",
schema = ReactionSchema.Unicode
)

runBlocking {
dm.send(
content = reaction,
options = SendOptions(contentType = ContentTypeReaction)
)
}
runBlocking { dm.sync() }

val messages = dm.messages()
assertEquals(messages.size, 3)
val content: Reaction? = messages.first().content()
assertEquals("U+1F603", content?.content)
assertEquals(messageToReact.id, content?.reference)
assertEquals(ReactionAction.Added, content?.action)
assertEquals(ReactionSchema.Unicode, content?.schema)
}

@Test
fun testCanStreamDmMessages() = kotlinx.coroutines.test.runTest {
val group = boClient.conversations.findOrCreateDm(alix.walletAddress.lowercase())
alixClient.conversations.syncConversations()
val alixDm = alixClient.findDm(bo.walletAddress)
group.streamMessages().test {
alixDm?.send("hi")
assertEquals("hi", awaitItem().body)
alixDm?.send("hi again")
assertEquals("hi again", awaitItem().body)
}
}

@Test
fun testCanStreamAllMessages() {
val boDm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) }
runBlocking { alixClient.conversations.syncConversations() }

val allMessages = mutableListOf<DecodedMessage>()

val job = CoroutineScope(Dispatchers.IO).launch {
try {
alixClient.conversations.streamAllConversationMessages().collect { message ->
allMessages.add(message)
}
} catch (e: Exception) {
}
}
Thread.sleep(2500)

for (i in 0 until 2) {
runBlocking { boDm.send(text = "Message $i") }
Thread.sleep(100)
}
assertEquals(2, allMessages.size)

val caroDm =
runBlocking { caroClient.conversations.findOrCreateDm(alixClient.address) }
Thread.sleep(2500)

for (i in 0 until 2) {
runBlocking { caroDm.send(text = "Message $i") }
Thread.sleep(100)
}

assertEquals(4, allMessages.size)

job.cancel()
}

@Test
fun testCanStreamDecryptedDmMessages() = kotlinx.coroutines.test.runTest {
val dm = boClient.conversations.findOrCreateDm(alix.walletAddress)
alixClient.conversations.syncConversations()
val alixDm = alixClient.findDm(bo.walletAddress)
dm.streamDecryptedMessages().test {
alixDm?.send("hi")
assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8())
alixDm?.send("hi again")
assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8())
}
}

@Test
fun testCanStreamAllDecryptedDmMessages() {
val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) }
runBlocking { alixClient.conversations.syncConversations() }

val allMessages = mutableListOf<DecryptedMessage>()

val job = CoroutineScope(Dispatchers.IO).launch {
try {
alixClient.conversations.streamAllConversationDecryptedMessages().collect { message ->
allMessages.add(message)
}
} catch (e: Exception) {
}
}
Thread.sleep(2500)

for (i in 0 until 2) {
runBlocking { dm.send(text = "Message $i") }
Thread.sleep(100)
}
assertEquals(2, allMessages.size)

val caroDm =
runBlocking { caroClient.conversations.findOrCreateDm(alixClient.address) }
Thread.sleep(2500)

for (i in 0 until 2) {
runBlocking { caroDm.send(text = "Message $i") }
Thread.sleep(100)
}

assertEquals(4, allMessages.size)

job.cancel()
}

@Test
fun testCanStreamConversations() = kotlinx.coroutines.test.runTest {
boClient.conversations.streamConversations().test {
val dm =
alixClient.conversations.findOrCreateDm(bo.walletAddress)
assertEquals(dm.id, awaitItem().id)
val dm2 =
caroClient.conversations.findOrCreateDm(bo.walletAddress)
assertEquals(dm2.id, awaitItem().id)
}
}

@Test
fun testDmConsent() {
runBlocking {
val dm =
boClient.conversations.findOrCreateDm(alix.walletAddress)
assert(boClient.contacts.isGroupAllowed(dm.id))
assertEquals(dm.consentState(), ConsentState.ALLOWED)

boClient.contacts.denyGroups(listOf(dm.id))
assert(boClient.contacts.isGroupDenied(dm.id))
assertEquals(dm.consentState(), ConsentState.DENIED)

dm.updateConsentState(ConsentState.ALLOWED)
assert(boClient.contacts.isGroupAllowed(dm.id))
assertEquals(dm.consentState(), ConsentState.ALLOWED)
}
}
}
Loading

0 comments on commit bbdac09

Please sign in to comment.