Skip to content

Commit a659c96

Browse files
committed
Merge branch 'main' of https://github.com/xmtp/xmtp-android into np/group-streaming
2 parents cbfdce1 + c6fe0ea commit a659c96

File tree

15 files changed

+145
-98
lines changed

15 files changed

+145
-98
lines changed

.github/workflows/test.yml

+19-21
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,35 @@ jobs:
2424
uses: gradle/gradle-build-action@v2
2525
- name: Validate Gradle Wrapper
2626
uses: gradle/wrapper-validation-action@v1
27-
- name: Start local test server
28-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml up -d
27+
- name: Start Docker containers
28+
run: dev/up
2929
- name: Gradle Run Unit Tests
3030
run: ./gradlew library:testDebug
31-
- name: Stop local test server
32-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml down
3331
library-integration:
3432
name: Library (Integration Tests)
35-
runs-on: macos-latest
33+
runs-on: ubuntu-latest
3634
steps:
37-
- name: Checkout project sources
38-
uses: actions/checkout@v3
39-
- uses: actions/setup-java@v3
35+
- name: Checkout
36+
uses: actions/checkout@v4
4037
with:
41-
distribution: 'adopt'
42-
java-version: '11'
43-
- name: Setup Gradle
38+
fetch-depth: 0
39+
- name: Configure JDK
40+
uses: actions/setup-java@v4
41+
with:
42+
distribution: 'zulu'
43+
java-version: 11
44+
- name: Enable KVM group perms
45+
run: |
46+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
47+
sudo udevadm control --reload-rules
48+
sudo udevadm trigger --name-match=kvm
49+
- name: Gradle cache
4450
uses: gradle/gradle-build-action@v2
45-
- name: Validate Gradle Wrapper
46-
uses: gradle/wrapper-validation-action@v1
47-
- name: Set up Docker
48-
run: brew install docker docker-compose
49-
- name: Start Colima
50-
run: colima start
51-
- name: Start local test server
52-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml up -d
51+
- name: Start Docker containers
52+
run: dev/up
5353
- name: Gradle Run Integration Tests
5454
uses: reactivecircus/android-emulator-runner@v2
5555
with:
5656
api-level: 29
5757
script: ./gradlew connectedCheck
58-
- name: Stop local test server
59-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml down
6058

dev/local/compose

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
docker-compose -f dev/local/docker-compose.yml -p "xmtp-android" "$@"

dev/local/docker-compose.yml

-8
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@ services:
2222
validation:
2323
image: xmtp/mls-validation-service:latest
2424
platform: linux/amd64
25-
build:
26-
context: ../..
27-
dockerfile: ./dev/validation_service/local.Dockerfile
28-
29-
db:
30-
image: postgres:13
31-
environment:
32-
POSTGRES_PASSWORD: xmtp
3325

3426
mlsdb:
3527
image: postgres:13

dev/local/up

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
4+
5+
"${script_dir}"/compose pull
6+
"${script_dir}"/compose up -d --build

dev/up

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
if [[ "${OSTYPE}" == "darwin"* ]]; then
5+
if ! which buf &>/dev/null; then brew install buf; fi
6+
if ! which shellcheck &>/dev/null; then brew install shellcheck; fi
7+
if ! which markdownlint &>/dev/null; then brew install markdownlint-cli; fi
8+
if ! java -version &>/dev/null; then
9+
brew install java
10+
sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk \
11+
/Library/Java/JavaVirtualMachines/
12+
fi
13+
if ! kotlinc -version &>/dev/null; then brew install kotlin; fi
14+
fi
15+
16+
rustup update
17+
18+
dev/local/up

example/src/main/java/org/xmtp/android/example/conversation/NewGroupBottomSheet.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.lifecycle.repeatOnLifecycle
1414
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
1515
import kotlinx.coroutines.launch
1616
import org.xmtp.android.example.R
17-
import org.xmtp.android.example.databinding.BottomSheetNewConversationBinding
1817
import org.xmtp.android.example.databinding.BottomSheetNewGroupBinding
1918
import java.util.regex.Pattern
2019

@@ -58,7 +57,7 @@ class NewGroupBottomSheet : BottomSheetDialogFragment() {
5857
val input = binding.addressInput1.text.trim()
5958
val matcher = ADDRESS_PATTERN.matcher(input)
6059
if (matcher.matches()) {
61-
addresses.add(input.toString().lowercase())
60+
addresses.add(input.toString())
6261
binding.addressInput2.visibility = VISIBLE
6362
}
6463
}
@@ -68,7 +67,7 @@ class NewGroupBottomSheet : BottomSheetDialogFragment() {
6867
val input = binding.addressInput2.text.trim()
6968
val matcher = ADDRESS_PATTERN.matcher(input)
7069
if (matcher.matches()) {
71-
addresses.add(input.toString().lowercase())
70+
addresses.add(input.toString())
7271
viewModel.createGroup(addresses)
7372
}
7473
}

library/src/androidTest/java/org/xmtp/android/library/GroupMembershipChangeTest.kt

+11-9
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ class GroupMembershipChangeTest {
5656

5757
val group = alixClient.conversations.newGroup(
5858
listOf(
59-
bo.walletAddress.lowercase(),
60-
caro.walletAddress.lowercase()
59+
bo.walletAddress,
60+
caro.walletAddress
6161
)
6262
)
6363
val messages = group.messages()
@@ -76,17 +76,19 @@ class GroupMembershipChangeTest {
7676

7777
val group = alixClient.conversations.newGroup(
7878
listOf(
79-
bo.walletAddress.lowercase(),
80-
caro.walletAddress.lowercase()
79+
bo.walletAddress,
80+
caro.walletAddress
8181
)
8282
)
8383
val messages = group.messages()
8484
assertEquals(messages.size, 1)
8585
assertEquals(group.memberAddresses().size, 3)
86-
group.removeMembers(listOf(caro.walletAddress.lowercase()))
87-
assertEquals(group.messages().size, 2)
86+
group.removeMembers(listOf(caro.walletAddress))
87+
val updatedMessages = group.messages()
88+
assertEquals(updatedMessages.size, 2)
8889
assertEquals(group.memberAddresses().size, 2)
89-
val content: GroupMembershipChanges? = messages.first().content()
90+
val content: GroupMembershipChanges? = updatedMessages.first().content()
91+
9092
assertEquals(
9193
listOf(caro.walletAddress.lowercase()),
9294
content?.membersRemovedList?.map { it.accountAddress.lowercase() }?.sorted()
@@ -98,8 +100,8 @@ class GroupMembershipChangeTest {
98100
fun testIfNotRegisteredReturnsFallback() {
99101
val group = alixClient.conversations.newGroup(
100102
listOf(
101-
bo.walletAddress.lowercase(),
102-
caro.walletAddress.lowercase()
103+
bo.walletAddress,
104+
caro.walletAddress
103105
)
104106
)
105107
val messages = group.messages()

library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt

+42-22
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package org.xmtp.android.library
22

33
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import androidx.test.platform.app.InstrumentationRegistry
5-
import app.cash.turbine.test
6-
import kotlinx.coroutines.ExperimentalCoroutinesApi
75
import kotlinx.coroutines.runBlocking
86
import org.junit.Assert.assertEquals
7+
import org.junit.Assert.assertThrows
98
import org.junit.Before
109
import org.junit.Test
1110
import org.junit.runner.RunWith
@@ -18,7 +17,6 @@ import org.xmtp.android.library.messages.PrivateKey
1817
import org.xmtp.android.library.messages.PrivateKeyBuilder
1918
import org.xmtp.android.library.messages.walletAddress
2019

21-
@OptIn(ExperimentalCoroutinesApi::class)
2220
@RunWith(AndroidJUnit4::class)
2321
class GroupTest {
2422
lateinit var fakeApiClient: FakeApiClient
@@ -59,16 +57,16 @@ class GroupTest {
5957

6058
@Test
6159
fun testCanCreateAGroup() {
62-
val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
60+
val group = boClient.conversations.newGroup(listOf(alix.walletAddress))
6361
assert(group.id.isNotEmpty())
6462
}
6563

6664
@Test
6765
fun testCanListGroupMembers() {
6866
val group = boClient.conversations.newGroup(
6967
listOf(
70-
alix.walletAddress.lowercase(),
71-
caro.walletAddress.lowercase()
68+
alix.walletAddress,
69+
caro.walletAddress
7270
)
7371
)
7472
assertEquals(
@@ -83,8 +81,8 @@ class GroupTest {
8381

8482
@Test
8583
fun testCanAddGroupMembers() {
86-
val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
87-
group.addMembers(listOf(caro.walletAddress.lowercase()))
84+
val group = boClient.conversations.newGroup(listOf(alix.walletAddress))
85+
group.addMembers(listOf(caro.walletAddress))
8886
assertEquals(
8987
group.memberAddresses().sorted(),
9088
listOf(
@@ -99,11 +97,11 @@ class GroupTest {
9997
fun testCanRemoveGroupMembers() {
10098
val group = boClient.conversations.newGroup(
10199
listOf(
102-
alix.walletAddress.lowercase(),
103-
caro.walletAddress.lowercase()
100+
alix.walletAddress,
101+
caro.walletAddress
104102
)
105103
)
106-
group.removeMembers(listOf(caro.walletAddress.lowercase()))
104+
group.removeMembers(listOf(caro.walletAddress))
107105
assertEquals(
108106
group.memberAddresses().sorted(),
109107
listOf(
@@ -115,28 +113,50 @@ class GroupTest {
115113

116114
@Test
117115
fun testCanListGroups() {
118-
boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
119-
boClient.conversations.newGroup(listOf(caro.walletAddress.lowercase()))
116+
boClient.conversations.newGroup(listOf(alix.walletAddress))
117+
boClient.conversations.newGroup(listOf(caro.walletAddress))
120118
val groups = boClient.conversations.listGroups()
121119
assertEquals(groups.size, 2)
122120
}
123121

124122
@Test
125123
fun testCanListGroupsAndConversations() {
126-
boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
127-
boClient.conversations.newGroup(listOf(caro.walletAddress.lowercase()))
128-
try {
129-
boClient.conversations.newConversation(alix.walletAddress)
130-
} catch (e: Exception) {
131-
boClient.conversations.newConversation(alix.walletAddress.lowercase())
132-
}
124+
boClient.conversations.newGroup(listOf(alix.walletAddress))
125+
boClient.conversations.newGroup(listOf(caro.walletAddress))
126+
boClient.conversations.newConversation(alix.walletAddress)
133127
val convos = boClient.conversations.list(includeGroups = true)
134128
assertEquals(convos.size, 3)
135129
}
136130

131+
@Test
132+
fun testCannotSendMessageToGroupMemberNotOnV3() {
133+
var fakeApiClient = FakeApiClient()
134+
val chuxAccount = PrivateKeyBuilder()
135+
val chux: PrivateKey = chuxAccount.getPrivateKey()
136+
val chuxClient: Client = Client().create(account = chuxAccount, apiClient = fakeApiClient)
137+
138+
assertThrows("Recipient not on network", XMTPException::class.java) {
139+
boClient.conversations.newGroup(listOf(chux.walletAddress))
140+
}
141+
}
142+
143+
@Test
144+
fun testCannotStartGroupWithSelf() {
145+
assertThrows("Recipient is sender", XMTPException::class.java) {
146+
boClient.conversations.newGroup(listOf(bo.walletAddress))
147+
}
148+
}
149+
150+
@Test
151+
fun testCannotStartEmptyGroupChat() {
152+
assertThrows("Cannot start an empty group chat.", XMTPException::class.java) {
153+
boClient.conversations.newGroup(listOf())
154+
}
155+
}
156+
137157
@Test
138158
fun testCanSendMessageToGroup() {
139-
val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
159+
val group = boClient.conversations.newGroup(listOf(alix.walletAddress))
140160
group.send("howdy")
141161
group.send("gm")
142162
runBlocking { group.sync() }
@@ -154,7 +174,7 @@ class GroupTest {
154174
fun testCanSendContentTypesToGroup() {
155175
Client.register(codec = ReactionCodec())
156176

157-
val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
177+
val group = boClient.conversations.newGroup(listOf(alix.walletAddress))
158178
group.send("gm")
159179
runBlocking { group.sync() }
160180
val messageToReact = group.messages()[0]

library/src/main/java/org/xmtp/android/library/Client.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ class Client() {
327327
isSecure = false,
328328
db = dbPath,
329329
encryptionKey = retrievedKey.encoded,
330-
accountAddress = accountAddress.lowercase(),
330+
accountAddress = accountAddress,
331331
legacyIdentitySource = legacyIdentitySource,
332332
legacySignedPrivateKeyProto = privateKeyBundleV1.toV2().identityKey.toByteArray()
333333
)
@@ -559,6 +559,13 @@ class Client() {
559559
return runBlocking { query(Topic.contact(peerAddress)).envelopesList.size > 0 }
560560
}
561561

562+
fun canMessage(addresses: List<String>): Boolean {
563+
return runBlocking {
564+
libXMTPClient != null && !libXMTPClient!!.canMessage(addresses.map { it })
565+
.contains(false)
566+
}
567+
}
568+
562569
val privateKeyBundle: PrivateKeyBundle
563570
get() = PrivateKeyBundleBuilder.buildFromV1Key(privateKeyBundleV1)
564571

0 commit comments

Comments
 (0)