XuqmGroup-AndroidSDK/sample-app/src/androidTest/java/com/xuqm/sdk/sample/CrossDeviceTest.kt

218 行
9.0 KiB
Kotlin

package com.xuqm.sdk.sample
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.xuqm.sdk.XuqmSDK
import com.xuqm.sdk.im.ImSDK
import com.xuqm.sdk.im.model.ImConnectionState
import com.xuqm.sdk.sample.config.SampleEnvironmentConfig
import com.xuqm.sdk.sample.data.api.DEMO_APP_ID
import com.xuqm.sdk.sample.data.api.DemoApiFactory
import com.xuqm.sdk.sample.data.api.LoginRequest
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import org.junit.After
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
// ─────────────────────────────────────────────────────────────────────────────
// TC-03 / TC-04 双设备协同自动化测试
//
// 用法:
// 发送方 (emulator-5554):
// adb -s emulator-5554 shell am instrument -w -r
// -e class com.xuqm.sdk.sample.CrossDeviceSenderTest
// com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner
//
// 接收方 (emulator-5556) — 与发送方并行或在发送方完成后执行:
// adb -s emulator-5556 shell am instrument -w -r
// -e class com.xuqm.sdk.sample.CrossDeviceReceiverTest
// com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner
// ─────────────────────────────────────────────────────────────────────────────
private const val CROSS_MSG_PREFIX = "CROSS_DEVICE_AUTO"
private const val GROUP_NAME_PREFIX = "TC04_AUTO_GROUP"
private const val BASE_URL = "https://dev.xuqinmin.com/"
private const val CONNECT_TIMEOUT_MS = 20_000L
private const val POLL_INTERVAL_MS = 2_000L
private const val POLL_TOTAL_MS = 40_000L
private fun initSdkOnce(ctx: Context) {
XuqmSDK.initialize(ctx, DEMO_APP_ID)
XuqmSDK.logout()
Thread.sleep(1_500)
XuqmSDK.logout()
SampleEnvironmentConfig.useExternal()
Thread.sleep(500)
}
private suspend fun loginAndConnect(userId: String, ctx: Context): String {
val api = DemoApiFactory.create(BASE_URL) { null }
val res = api.login(LoginRequest(DEMO_APP_ID, userId, "123456"))
val token = requireNotNull(res.data?.imToken) { "Login failed for $userId: ${res.message}" }
XuqmSDK.login(userId, token)
withTimeoutOrNull(CONNECT_TIMEOUT_MS) {
ImSDK.connectionState.first { it is ImConnectionState.Connected }
} ?: error("WebSocket 未在 ${CONNECT_TIMEOUT_MS}ms 内连接")
return token
}
// ─────────────────────────────────────────────────────────────────────────────
// 发送方: user_a on emulator-5554
// ─────────────────────────────────────────────────────────────────────────────
@RunWith(AndroidJUnit4::class)
class CrossDeviceSenderTest {
companion object {
lateinit var appCtx: Context
@BeforeClass @JvmStatic
fun init() {
appCtx = InstrumentationRegistry.getInstrumentation().targetContext
initSdkOnce(appCtx)
}
}
@Before
fun setUp() { runBlocking { loginAndConnect("user_a", appCtx) } }
@After
fun tearDown() { XuqmSDK.logout(); Thread.sleep(500) }
/**
* TC-03 发送方: user_a user_b 发送带有唯一标识的单聊消息
*/
@Test
fun tc03_sender_sendSingleChatMessage() = runBlocking {
val tag = "${CROSS_MSG_PREFIX}_SINGLE_${System.currentTimeMillis()}"
val msg = ImSDK.sendTextMessage("user_b", "SINGLE", tag)
assertTrue("消息发送不应失败", msg.status != "FAILED")
delay(1_500)
// 验证消息出现在 user_a 侧的历史中
val history = ImSDK.fetchHistory("user_b")
val found = history.any { it.content.contains(CROSS_MSG_PREFIX) }
assertTrue("发送的消息应出现在 user_a 的单聊历史中", found)
}
/**
* TC-04 发送方: user_a 创建包含 user_b 的群组并发送群消息
*/
@Test
fun tc04_sender_createGroupAndSendMessage() = runBlocking {
val groupName = "${GROUP_NAME_PREFIX}_${System.currentTimeMillis() / 60_000}" // 分钟级唯一
val group = ImSDK.createGroup(groupName, listOf("user_b"))
assertNotNull("群组创建应成功", group)
val groupId = group!!.id
delay(1_000)
// 订阅群组并发送消息
ImSDK.subscribeGroup(groupId)
delay(500)
val tag = "${CROSS_MSG_PREFIX}_GROUP_${System.currentTimeMillis()}"
val msg = ImSDK.sendTextMessage(groupId, "GROUP", tag)
assertTrue("群消息发送不应失败", msg.status != "FAILED")
delay(1_500)
// 验证群历史
val history = ImSDK.fetchGroupHistory(groupId)
val found = history.any { it.content.contains(CROSS_MSG_PREFIX) }
assertTrue("发送的群消息应出现在历史中", found)
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 接收方: user_b on emulator-5556
// ─────────────────────────────────────────────────────────────────────────────
@RunWith(AndroidJUnit4::class)
class CrossDeviceReceiverTest {
companion object {
lateinit var appCtx: Context
@BeforeClass @JvmStatic
fun init() {
appCtx = InstrumentationRegistry.getInstrumentation().targetContext
initSdkOnce(appCtx)
}
}
@Before
fun setUp() { runBlocking { loginAndConnect("user_b", appCtx) } }
@After
fun tearDown() { XuqmSDK.logout(); Thread.sleep(500) }
/**
* TC-03 接收方: user_b 通过轮询 fetchHistory 验证收到 user_a 发送的单聊消息
* 最长等待 POLL_TOTAL_MS POLL_INTERVAL_MS 检查一次
*/
@Test
fun tc03_receiver_verifySingleChatMessage() = runBlocking {
val cutoff = System.currentTimeMillis() - 60_000L // 最近 60 秒内
var found = false
val deadline = System.currentTimeMillis() + POLL_TOTAL_MS
while (System.currentTimeMillis() < deadline && !found) {
val history = ImSDK.fetchHistory("user_a")
found = history.any { it.content.contains(CROSS_MSG_PREFIX) && it.createdAt >= cutoff }
if (!found) delay(POLL_INTERVAL_MS)
}
assertTrue(
"user_b 应在 ${POLL_TOTAL_MS / 1000}s 内通过 fetchHistory 收到 user_a 的跨设备消息",
found,
)
// 标记已读
ImSDK.markRead("user_a", "SINGLE")
delay(500)
val convs = ImSDK.listConversations()
val conv = convs.find { it.targetId == "user_a" && it.chatType == "SINGLE" }
assertTrue("标记已读后 unreadCount 应为 0", conv?.unreadCount == 0)
}
/**
* TC-04 接收方: user_b 查找 user_a 创建的最新群组验证可读取群消息
* 不施加时间截止群名含分钟级时间戳最新群只含本轮测试消息
*/
@Test
fun tc04_receiver_verifyGroupMessage() = runBlocking {
var found = false
var groupFound = false
val deadline = System.currentTimeMillis() + POLL_TOTAL_MS
while (System.currentTimeMillis() < deadline && !found) {
val groups = ImSDK.listGroups()
val targetGroup = groups.filter { it.name.startsWith(GROUP_NAME_PREFIX) }
.maxByOrNull { it.createdAt }
if (targetGroup != null) {
groupFound = true
ImSDK.subscribeGroup(targetGroup.id)
delay(800)
val history = ImSDK.fetchGroupHistory(targetGroup.id)
found = history.any { it.content.contains(CROSS_MSG_PREFIX) }
}
if (!found) delay(POLL_INTERVAL_MS)
}
assertTrue("user_b 应能看到 user_a 创建的 $GROUP_NAME_PREFIX 群组", groupFound)
assertTrue(
"user_b 应在 ${POLL_TOTAL_MS / 1000}s 内通过 fetchGroupHistory 收到 user_a 的群消息",
found,
)
// 验证群出现在会话列表中
val convs = ImSDK.listConversations()
assertTrue("群聊会话应出现在会话列表中", convs.any { it.chatType == "GROUP" })
}
}