Skip to content

Commit 911f57e

Browse files
committed
[ECO-5338[Integration test] Added ably sandbox script
1. Added ktor http dependencies and updated kotlin tests bundle 2. Added IntegrationTest parameterized class to run tests for both msgpack and json 3. Updated LiveObjectTest extending IntegrationTest class
1 parent 0b44a36 commit 911f57e

File tree

11 files changed

+268
-29
lines changed

11 files changed

+268
-29
lines changed

gradle/libs.versions.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ kotlin = "2.1.10"
2323
coroutine = "1.9.0"
2424
mockk = "1.13.13"
2525
turbine = "1.2.0"
26+
ktor = "3.0.1"
2627
jetbrains-annoations = "26.0.2"
2728

2829
[libraries]
@@ -50,12 +51,14 @@ coroutine-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-c
5051
coroutine-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine" }
5152
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
5253
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
54+
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
55+
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
5356
jetbrains = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annoations" }
5457

5558
[bundles]
5659
common = ["msgpack", "vcdiff-core"]
5760
tests = ["junit", "hamcrest-all", "nanohttpd", "nanohttpd-nanolets", "nanohttpd-websocket", "mockito-core", "concurrentunit", "slf4j-simple"]
58-
kotlin-tests = ["junit", "mockk", "coroutine-test", "nanohttpd", "turbine"]
61+
kotlin-tests = ["junit", "mockk", "coroutine-test", "nanohttpd", "turbine", "ktor-client-cio", "ktor-client-core"]
5962
instrumental-android = ["android-test-runner", "android-test-rules", "dexmaker", "dexmaker-dx", "dexmaker-mockito", "android-retrostreams"]
6063

6164
[plugins]

lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class AblyRealtime extends AblyRest {
4848
* <p>
4949
* This field is initialized only if the LiveObjects plugin is present in the classpath.
5050
*/
51-
private final LiveObjectsPlugin liveObjectsPlugin;
51+
public final LiveObjectsPlugin liveObjectsPlugin;
5252

5353
/**
5454
* Constructs a Realtime client object using an Ably API key or token string.
@@ -73,9 +73,7 @@ public AblyRealtime(ClientOptions options) throws AblyException {
7373
final InternalChannels channels = new InternalChannels();
7474
this.channels = channels;
7575

76-
liveObjectsPlugin = tryInitializeLiveObjectsPlugin();
77-
78-
connection = new Connection(this, channels, platformAgentProvider, liveObjectsPlugin);
76+
connection = new Connection(this, channels, platformAgentProvider);
7977

8078
if (!StringUtils.isNullOrEmpty(options.recover)) {
8179
RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover);
@@ -85,6 +83,8 @@ public AblyRealtime(ClientOptions options) throws AblyException {
8583
}
8684
}
8785

86+
liveObjectsPlugin = tryInitializeLiveObjectsPlugin();
87+
8888
if(options.autoConnect) connection.connect();
8989
}
9090

lib/src/main/java/io/ably/lib/realtime/Connection.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.ably.lib.realtime;
22

3-
import io.ably.lib.objects.LiveObjectsPlugin;
43
import io.ably.lib.realtime.ConnectionStateListener.ConnectionStateChange;
54
import io.ably.lib.transport.ConnectionManager;
65
import io.ably.lib.types.AblyException;
@@ -123,10 +122,10 @@ public void close() {
123122
* internal
124123
*****************/
125124

126-
Connection(AblyRealtime ably, ConnectionManager.Channels channels, PlatformAgentProvider platformAgentProvider, LiveObjectsPlugin liveObjectsPlugin) throws AblyException {
125+
Connection(AblyRealtime ably, ConnectionManager.Channels channels, PlatformAgentProvider platformAgentProvider) throws AblyException {
127126
this.ably = ably;
128127
this.state = ConnectionState.initialized;
129-
this.connectionManager = new ConnectionManager(ably, this, channels, platformAgentProvider, liveObjectsPlugin);
128+
this.connectionManager = new ConnectionManager(ably, this, channels, platformAgentProvider);
130129
}
131130

132131
public void onConnectionStateChange(ConnectionStateChange stateChange) {

lib/src/main/java/io/ably/lib/transport/ConnectionManager.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.ably.lib.debug.DebugOptions;
1515
import io.ably.lib.debug.DebugOptions.RawProtocolListener;
1616
import io.ably.lib.http.HttpHelpers;
17-
import io.ably.lib.objects.LiveObjectsPlugin;
1817
import io.ably.lib.plugins.PluginConnectionAdapter;
1918
import io.ably.lib.realtime.AblyRealtime;
2019
import io.ably.lib.realtime.Channel;
@@ -81,13 +80,6 @@ public class ConnectionManager implements ConnectListener, PluginConnectionAdapt
8180
*/
8281
private boolean cleaningUpAfterEnteringTerminalState = false;
8382

84-
/**
85-
* A nullable reference to the LiveObjects plugin.
86-
* <p>
87-
* This field is initialized only if the LiveObjects plugin is present in the classpath.
88-
*/
89-
private final LiveObjectsPlugin liveObjectsPlugin;
90-
9183
/**
9284
* Methods on the channels map owned by the {@link AblyRealtime} instance
9385
* which the {@link ConnectionManager} needs access to.
@@ -773,12 +765,11 @@ public void run() {
773765
* ConnectionManager
774766
***********************/
775767

776-
public ConnectionManager(final AblyRealtime ably, final Connection connection, final Channels channels, final PlatformAgentProvider platformAgentProvider, LiveObjectsPlugin liveObjectsPlugin) throws AblyException {
768+
public ConnectionManager(final AblyRealtime ably, final Connection connection, final Channels channels, final PlatformAgentProvider platformAgentProvider) throws AblyException {
777769
this.ably = ably;
778770
this.connection = connection;
779771
this.channels = channels;
780772
this.platformAgentProvider = platformAgentProvider;
781-
this.liveObjectsPlugin = liveObjectsPlugin;
782773

783774
ClientOptions options = ably.options;
784775
this.hosts = new Hosts(options.realtimeHost, Defaults.HOST_REALTIME, options);
@@ -1232,9 +1223,9 @@ public void onMessage(ITransport transport, ProtocolMessage message) throws Ably
12321223
break;
12331224
case object:
12341225
case object_sync:
1235-
if (liveObjectsPlugin != null) {
1226+
if (ably.liveObjectsPlugin != null) {
12361227
try {
1237-
liveObjectsPlugin.handle(message);
1228+
ably.liveObjectsPlugin.handle(message);
12381229
} catch (Throwable t) {
12391230
Log.e(TAG, "LiveObjectsPlugin threw while handling message", t);
12401231
}

lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void connectionmanager_fallback_none_withoutconnection() throws AblyExcep
137137
Connection connection = Mockito.mock(Connection.class);
138138
final ConnectionManager.Channels channels = Mockito.mock(ConnectionManager.Channels.class);
139139

140-
ConnectionManager connectionManager = new ConnectionManager(ably, connection, channels, new EmptyPlatformAgentProvider(), null) {
140+
ConnectionManager connectionManager = new ConnectionManager(ably, connection, channels, new EmptyPlatformAgentProvider()) {
141141
@Override
142142
protected boolean checkConnectivity() {
143143
return false;

live-objects/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
33
plugins {
44
`java-library`
55
alias(libs.plugins.kotlin.jvm)
6-
alias(libs.plugins.test.retry)
76
}
87

98
repositories {
@@ -41,8 +40,8 @@ tasks.register<Test>("runLiveObjectUnitTests") {
4140
tasks.register<Test>("runLiveObjectIntegrationTests") {
4241
filter {
4342
includeTestsMatching("io.ably.lib.objects.integration.*")
43+
exclude("**/IntegrationTest.class") // Exclude the base integration test class
4444
}
45-
// TODO - check if we need retry mechanism for integration tests in the future
4645
}
4746

4847
kotlin {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.ably.lib.objects
2+
3+
internal enum class ErrorCode(public val code: Int) {
4+
BadRequest(40_000),
5+
InternalError(50_000),
6+
}
7+
8+
internal enum class HttpStatusCode(public val code: Int) {
9+
BadRequest(400),
10+
InternalServerError(500),
11+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.ably.lib.objects
2+
3+
import io.ably.lib.types.AblyException
4+
import io.ably.lib.types.ErrorInfo
5+
6+
internal fun ablyException(
7+
errorMessage: String,
8+
errorCode: ErrorCode,
9+
statusCode: HttpStatusCode = HttpStatusCode.BadRequest,
10+
cause: Throwable? = null,
11+
): AblyException {
12+
val errorInfo = createErrorInfo(errorMessage, errorCode, statusCode)
13+
return createAblyException(errorInfo, cause)
14+
}
15+
16+
internal fun ablyException(
17+
errorInfo: ErrorInfo,
18+
cause: Throwable? = null,
19+
): AblyException = createAblyException(errorInfo, cause)
20+
21+
private fun createErrorInfo(
22+
errorMessage: String,
23+
errorCode: ErrorCode,
24+
statusCode: HttpStatusCode,
25+
) = ErrorInfo(errorMessage, statusCode.code, errorCode.code)
26+
27+
private fun createAblyException(
28+
errorInfo: ErrorInfo,
29+
cause: Throwable?,
30+
) = cause?.let { AblyException.fromErrorInfo(it, errorInfo) }
31+
?: AblyException.fromErrorInfo(errorInfo)
32+
33+
internal fun clientError(errorMessage: String) = ablyException(errorMessage, ErrorCode.BadRequest, HttpStatusCode.BadRequest)
34+
35+
internal fun serverError(errorMessage: String) = ablyException(errorMessage, ErrorCode.InternalError, HttpStatusCode.InternalServerError)
36+
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package io.ably.lib.objects.integration
22

3-
import org.junit.Assert.assertTrue
3+
import io.ably.lib.objects.integration.setup.IntegrationTest
4+
import kotlinx.coroutines.test.runTest
45
import org.junit.Test
6+
import kotlin.test.assertNotNull
57

6-
class LiveObjectTest {
8+
open class LiveObjectTest : IntegrationTest() {
79

810
@Test
9-
fun testLiveObjectCreationIntegrationTest() {
10-
// This is a placeholder for the actual test implementation.
11-
// You would typically create instances of LiveObjects and perform assertions here.
12-
assertTrue(true)
11+
fun testLiveObjectCreationTest() = runTest {
12+
val channel = getRealtimeChannel()
13+
val objects = channel.objects
14+
assertNotNull(objects)
1315
}
1416
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.ably.lib.objects.integration.setup
2+
3+
import io.ably.lib.realtime.AblyRealtime
4+
import io.ably.lib.realtime.Channel
5+
import io.ably.lib.types.ClientOptions
6+
import kotlinx.coroutines.runBlocking
7+
import org.junit.After
8+
import org.junit.AfterClass
9+
import org.junit.BeforeClass
10+
import org.junit.Rule
11+
import org.junit.rules.Timeout
12+
import org.junit.runner.RunWith
13+
import org.junit.runners.Parameterized
14+
15+
@RunWith(Parameterized::class)
16+
open class IntegrationTest {
17+
@Parameterized.Parameter
18+
lateinit var testParams: String
19+
20+
@JvmField
21+
@Rule
22+
val timeout: Timeout = Timeout.seconds(10)
23+
24+
private val realtimeClients = mutableListOf<AblyRealtime>()
25+
26+
private suspend fun getRealtimeClient(clientOptions: ClientOptions): AblyRealtime {
27+
clientOptions.useBinaryProtocol = testParams == "msgpack_protocol"
28+
return sandbox.createRealtimeClient(clientOptions).also {
29+
realtimeClients.add(it)
30+
it.ensureConnected()
31+
}
32+
}
33+
34+
/**
35+
* Retrieves a realtime channel for the specified channel name and client ID
36+
* If a client with the given clientID does not exist, a new client is created using the provided options.
37+
* The channel is attached and ensured to be in the attached state before returning.
38+
*
39+
* @param name Optional name of the channel. If not provided, a random name is generated.
40+
* @param clientId The ID of the client to use or create. Defaults to "client1".
41+
* @return The attached realtime channel.
42+
* @throws Exception If the channel fails to attach or the client fails to connect.
43+
*/
44+
suspend fun getRealtimeChannel(name: String? = null, clientId: String = "client1"): Channel {
45+
val client = realtimeClients.find { it.options.clientId == clientId } ?: getRealtimeClient(
46+
ClientOptions().apply {
47+
this.clientId = clientId
48+
useBinaryProtocol = testParams == "msgpack_protocol"
49+
}
50+
)
51+
val channelName = name ?: "test-channel-${java.util.UUID.randomUUID()}"
52+
return client.channels.get(channelName).apply {
53+
attach()
54+
ensureAttached()
55+
}
56+
}
57+
58+
@After
59+
fun afterEach() {
60+
for (ablyRealtime in realtimeClients) {
61+
for ((channelName, channel) in ablyRealtime.channels.entrySet()) {
62+
channel.off()
63+
ablyRealtime.channels.release(channelName)
64+
}
65+
ablyRealtime.close()
66+
}
67+
realtimeClients.clear()
68+
}
69+
70+
companion object {
71+
private lateinit var sandbox: Sandbox
72+
73+
@JvmStatic
74+
@Parameterized.Parameters(name = "{0}")
75+
fun data(): Iterable<String> {
76+
return listOf("msgpack_protocol", "json_protocol")
77+
}
78+
79+
@JvmStatic
80+
@BeforeClass
81+
@Throws(Exception::class)
82+
fun setUpBeforeClass() {
83+
runBlocking {
84+
sandbox = Sandbox.createInstance()
85+
}
86+
}
87+
88+
@JvmStatic
89+
@AfterClass
90+
@Throws(Exception::class)
91+
fun tearDownAfterClass() {
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)