commonMain.com.pubnub.test.BaseIntegrationTest.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pubnub-kotlin-test Show documentation
Show all versions of pubnub-kotlin-test Show documentation
PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter second!
The newest version!
package com.pubnub.test
import com.pubnub.api.PubNub
import com.pubnub.api.UserId
import com.pubnub.api.enums.PNLogVerbosity
import com.pubnub.api.enums.PNStatusCategory
import com.pubnub.api.models.consumer.PNStatus
import com.pubnub.api.models.consumer.pubsub.PNEvent
import com.pubnub.api.models.consumer.pubsub.PNMessageResult
import com.pubnub.api.v2.PNConfiguration
import com.pubnub.api.v2.createPNConfiguration
import com.pubnub.api.v2.subscriptions.EmptyOptions
import com.pubnub.api.v2.subscriptions.SubscriptionOptions
import com.pubnub.kmp.PNFuture
import com.pubnub.kmp.createEventListener
import com.pubnub.kmp.createPubNub
import com.pubnub.kmp.createStatusListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.assertTrue
abstract class BaseIntegrationTest {
lateinit var config: PNConfiguration
lateinit var configPam: PNConfiguration
lateinit var pubnub: PubNub
lateinit var pubnubPam: PubNub
@BeforeTest
open fun before() {
config = createPNConfiguration(UserId(randomString()), Keys.subKey, Keys.pubKey, logVerbosity = PNLogVerbosity.BODY)
pubnub = createPubNub(config)
configPam = createPNConfiguration(
UserId(randomString()),
Keys.pamSubKey,
Keys.pamPubKey,
Keys.pamSecKey,
logVerbosity = PNLogVerbosity.BODY
)
pubnubPam = createPubNub(configPam)
}
@AfterTest
open fun after() {
pubnub.unsubscribeAll()
pubnub.destroy()
pubnubPam.unsubscribeAll()
pubnubPam.destroy()
}
}
suspend fun PNFuture.await(): T {
val t = suspendCancellableCoroutine { cont ->
async { result ->
result.onSuccess {
cont.resume(it)
}.onFailure {
cont.resumeWithException(it)
}
}
}
withContext(Dispatchers.Default) {
delay(100)
}
return t
}
class PubNubTest(
private val pubNub: PubNub,
private val withPresenceOverride: Boolean,
backgroundScope: CoroutineScope,
private val checkAllEvents: Boolean = true,
) {
private val messageQueue = Channel(10)
private val statusQueue = Channel(10)
private val statusVerificationListener = createStatusListener(pubNub) { _, status ->
backgroundScope.launch {
statusQueue.send(status)
}
}
private val eventVerificationListener = createEventListener(
pubNub,
onMessage = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
onSignal = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
onFile = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
onPresence = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
onObjects = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
onMessageAction = { _, event ->
backgroundScope.launch {
messageQueue.send(event)
}
},
)
init {
pubNub.addListener(eventVerificationListener)
pubNub.addListener(statusVerificationListener)
}
suspend fun com.pubnub.api.v2.entities.Channel.awaitSubscribe(options: SubscriptionOptions = EmptyOptions) = suspendCancellableCoroutine { cont ->
val subscription = subscription(options)
val statusListener = createStatusListener(pubNub) { _, pnStatus ->
if ((pnStatus.category == PNStatusCategory.PNConnectedCategory || pnStatus.category == PNStatusCategory.PNSubscriptionChanged) &&
pnStatus.affectedChannels.contains(name)
) {
cont.resume(subscription)
}
if (pnStatus.category == PNStatusCategory.PNUnexpectedDisconnectCategory || pnStatus.category == PNStatusCategory.PNConnectionError) {
cont.resumeWithException(pnStatus.exception ?: RuntimeException(pnStatus.category.toString()))
}
}
pubNub.addListener(statusListener)
cont.invokeOnCancellation {
pubNub.removeListener(statusListener)
}
subscription.subscribe()
}
suspend fun PubNub.awaitSubscribe(
channels: Collection = setOf(),
channelGroups: Collection = setOf(),
withPresence: Boolean = false,
customSubscriptionBlock: () -> Unit = {
subscribe(channels.toList(), channelGroups.toList(), withPresence)
}
) = suspendCancellableCoroutine { cont ->
val statusListener = createStatusListener(pubNub) { _, pnStatus ->
if ((pnStatus.category == PNStatusCategory.PNConnectedCategory || pnStatus.category == PNStatusCategory.PNSubscriptionChanged) &&
pnStatus.affectedChannels.containsAll(channels) && pnStatus.affectedChannelGroups.containsAll(
channelGroups
)
) {
cont.resume(Unit)
}
if (pnStatus.category == PNStatusCategory.PNUnexpectedDisconnectCategory || pnStatus.category == PNStatusCategory.PNConnectionError) {
cont.resumeWithException(pnStatus.exception ?: RuntimeException(pnStatus.category.toString()))
}
}
pubNub.addListener(statusListener)
cont.invokeOnCancellation {
pubNub.removeListener(statusListener)
}
customSubscriptionBlock()
}
suspend fun PubNub.awaitUnsubscribe(
channels: Collection = setOf(),
channelGroups: Collection = setOf(),
customUnsubscribeBlock: () -> Unit = {
unsubscribe(channels.toList(), channelGroups.toList())
}
) = suspendCancellableCoroutine { cont ->
val statusListener = createStatusListener(pubNub) { _, pnStatus ->
if (cont.isCompleted) {
return@createStatusListener
}
if (pnStatus.category == PNStatusCategory.PNDisconnectedCategory || pnStatus.category == PNStatusCategory.PNSubscriptionChanged &&
pnStatus.affectedChannels.containsAll(channels) && pnStatus.affectedChannelGroups.containsAll(
channelGroups
)
) {
cont.resume(Unit)
}
if (pnStatus.category == PNStatusCategory.PNUnexpectedDisconnectCategory || pnStatus.category == PNStatusCategory.PNConnectionError) {
cont.resumeWithException(pnStatus.exception ?: RuntimeException(pnStatus.category.toString()))
}
}
pubNub.addListener(statusListener)
cont.invokeOnCancellation {
pubNub.removeListener(statusListener)
}
customUnsubscribeBlock()
}
// fun unsubscribeAll() {
// pubNub.unsubscribeAll()
// val status = statusQueue.take()
// Assert.assertTrue(status.category == PNStatusCategory.PNDisconnectedCategory)
// }
suspend fun nextStatus(): PNStatus = statusQueue.receive()
// suspend fun nextStatus(timeout: Duration): PNStatus? {
// return try {
// FutureTask {
// statusQueue.take()
// }.get(timeout.inWholeMilliseconds, TimeUnit.MILLISECONDS)
// } catch (e: TimeoutException) {
// null
// }
// }
suspend fun nextEvent(): T {
return messageQueue.receive() as T
}
suspend fun nextMessage() = nextEvent()
suspend fun skip(n: Int = 1) {
for (i in 0 until n) {
nextEvent()
}
}
fun close() {
pubNub.unsubscribeAll()
pubNub.destroy()
val remainingMessages = buildList {
messageQueue.tryReceive().getOrNull()?.apply { add(this) } ?: return@buildList
}
if (checkAllEvents) {
assertTrue(
"There were ${remainingMessages.size} unverified events in the test: ${remainingMessages.joinToString(", ")}"
) { remainingMessages.isEmpty() }
}
}
}
suspend fun PubNub.test(
backgroundScope: CoroutineScope,
withPresence: Boolean = false,
checkAllEvents: Boolean = true,
action: suspend PubNubTest.() -> Unit,
) {
val pubNubTest = PubNubTest(this, withPresence, backgroundScope, checkAllEvents)
try {
with(pubNubTest) {
action()
}
} finally {
pubNubTest.close()
}
}
fun randomString() = (0..6).map { "abcdefghijklmnopqrstuvw".random() }.joinToString("")