Skip to content

Commit 5362b35

Browse files
committed
[ECO-5482] Added unit tests for nonce, stringByteSize and ObjectsAsyncScope
1 parent b02cfd7 commit 5362b35

3 files changed

Lines changed: 301 additions & 1 deletion

File tree

live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ internal class DefaultLiveObjects(internal val channelName: String, internal val
101101
private suspend fun createMapAsync(entries: MutableMap<String, LiveMapValue>): LiveMap {
102102
adapter.throwIfInvalidWriteApiConfiguration(channelName)
103103

104+
if (entries.keys.any { it.isEmpty() }) {
105+
throw objectError("Map keys should not be empty")
106+
}
107+
104108
// Create initial value operation
105109
val initialMapValue = DefaultLiveMap.initialValue(entries)
106110

live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ internal class ObjectsAsyncScope(channelName: String) {
9898
* Generates a random nonce string for object creation.
9999
*/
100100
internal fun generateNonce(): String {
101-
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
101+
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" // avoid calculation using range
102102
return (1..16).map { chars.random() }.joinToString("")
103103
}
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
package io.ably.lib.objects.unit
2+
3+
import io.ably.lib.objects.*
4+
import io.ably.lib.objects.assertWaiter
5+
import io.ably.lib.types.AblyException
6+
import io.ably.lib.types.Callback
7+
import io.ably.lib.types.ErrorInfo
8+
import kotlinx.coroutines.*
9+
import kotlinx.coroutines.test.*
10+
import org.junit.Test
11+
import org.junit.Assert.*
12+
import java.util.concurrent.CancellationException
13+
14+
class UtilsTest {
15+
16+
@Test
17+
fun testGenerateNonce() {
18+
// Test basic functionality
19+
val nonce1 = generateNonce()
20+
val nonce2 = generateNonce()
21+
22+
assertEquals(16, nonce1.length)
23+
assertEquals(16, nonce2.length)
24+
assertNotEquals(nonce1, nonce2) // Should be random
25+
26+
// Test character set
27+
val validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
28+
val nonce = generateNonce()
29+
nonce.forEach { char ->
30+
assertTrue("Nonce should only contain valid characters", validChars.contains(char))
31+
}
32+
}
33+
34+
@Test
35+
fun testStringByteSize() {
36+
// Test ASCII strings
37+
assertEquals(5, "Hello".byteSize)
38+
assertEquals(0, "".byteSize)
39+
assertEquals(1, "A".byteSize)
40+
41+
// Test non-ASCII strings
42+
assertEquals(3, "".byteSize) // Chinese character
43+
assertEquals(4, "😊".byteSize) // Emoji
44+
assertEquals(6, "你好".byteSize) // Two Chinese characters
45+
}
46+
47+
@Test
48+
fun testErrorCreationFunctions() {
49+
// Test clientError
50+
val clientEx = clientError("Bad request")
51+
assertEquals("Bad request", clientEx.errorInfo.message)
52+
assertEquals(ErrorCode.BadRequest.code, clientEx.errorInfo.code)
53+
assertEquals(HttpStatusCode.BadRequest.code, clientEx.errorInfo.statusCode)
54+
55+
// Test serverError
56+
val serverEx = serverError("Internal error")
57+
assertEquals("Internal error", serverEx.errorInfo.message)
58+
assertEquals(ErrorCode.InternalError.code, serverEx.errorInfo.code)
59+
assertEquals(HttpStatusCode.InternalServerError.code, serverEx.errorInfo.statusCode)
60+
61+
// Test objectError
62+
val objectEx = objectError("Invalid object")
63+
assertEquals("Invalid object", objectEx.errorInfo.message)
64+
assertEquals(ErrorCode.InvalidObject.code, objectEx.errorInfo.code)
65+
assertEquals(HttpStatusCode.InternalServerError.code, objectEx.errorInfo.statusCode)
66+
67+
// Test objectError with cause
68+
val cause = RuntimeException("Original error")
69+
val objectExWithCause = objectError("Invalid object", cause)
70+
assertEquals("Invalid object", objectExWithCause.errorInfo.message)
71+
assertEquals(cause, objectExWithCause.cause)
72+
}
73+
74+
@Test
75+
fun testAblyExceptionCreation() {
76+
// Test with error message and codes
77+
val ex = ablyException("Test error", ErrorCode.BadRequest, HttpStatusCode.BadRequest)
78+
assertEquals("Test error", ex.errorInfo.message)
79+
assertEquals(ErrorCode.BadRequest.code, ex.errorInfo.code)
80+
assertEquals(HttpStatusCode.BadRequest.code, ex.errorInfo.statusCode)
81+
82+
// Test with ErrorInfo
83+
val errorInfo = ErrorInfo("Custom error", 400, 40000)
84+
val ex2 = ablyException(errorInfo)
85+
assertEquals("Custom error", ex2.errorInfo.message)
86+
assertEquals(400, ex2.errorInfo.statusCode)
87+
assertEquals(40000, ex2.errorInfo.code)
88+
89+
// Test with cause
90+
val cause = RuntimeException("Cause")
91+
val ex3 = ablyException(errorInfo, cause)
92+
assertEquals(cause, ex3.cause)
93+
}
94+
95+
@Test
96+
fun testObjectsAsyncScopeLaunchWithCallback() = runTest {
97+
val asyncScope = ObjectsAsyncScope("test-channel")
98+
var callbackExecuted = false
99+
var resultReceived: String? = null
100+
101+
val callback = object : Callback<String> {
102+
override fun onSuccess(result: String) {
103+
callbackExecuted = true
104+
resultReceived = result
105+
}
106+
107+
override fun onError(errorInfo: ErrorInfo?) {
108+
fail("Should not call onError for successful execution")
109+
}
110+
}
111+
112+
asyncScope.launchWithCallback(callback) {
113+
delay(10) // Simulate async work
114+
"test result"
115+
}
116+
117+
// Wait for callback to be executed
118+
assertWaiter { callbackExecuted }
119+
120+
assertTrue("Callback should be executed", callbackExecuted)
121+
assertEquals("test result", resultReceived)
122+
}
123+
124+
@Test
125+
fun testObjectsAsyncScopeLaunchWithCallbackError() = runTest {
126+
val asyncScope = ObjectsAsyncScope("test-channel")
127+
var errorReceived: ErrorInfo? = null
128+
129+
val callback = object : Callback<String> {
130+
override fun onSuccess(result: String) {
131+
fail("Should not call onSuccess for error case")
132+
}
133+
134+
override fun onError(errorInfo: ErrorInfo?) {
135+
errorReceived = errorInfo
136+
}
137+
}
138+
139+
asyncScope.launchWithCallback(callback) {
140+
delay(10)
141+
throw AblyException.fromErrorInfo(ErrorInfo("Test error", 400, 40000))
142+
}
143+
144+
// Wait for error to be received
145+
assertWaiter { errorReceived != null }
146+
147+
assertNotNull("Error should be received", errorReceived)
148+
assertEquals("Test error", errorReceived?.message)
149+
assertEquals(400, errorReceived?.statusCode)
150+
}
151+
152+
@Test
153+
fun testObjectsAsyncScopeLaunchWithVoidCallback() = runTest {
154+
val asyncScope = ObjectsAsyncScope("test-channel")
155+
var callbackExecuted = false
156+
157+
val callback = object : Callback<Void> {
158+
override fun onSuccess(result: Void?) {
159+
callbackExecuted = true
160+
}
161+
162+
override fun onError(errorInfo: ErrorInfo?) {
163+
fail("Should not call onError for successful execution")
164+
}
165+
}
166+
167+
asyncScope.launchWithVoidCallback(callback) {
168+
delay(10) // Simulate async work
169+
}
170+
171+
// Wait for callback to be executed
172+
assertWaiter { callbackExecuted }
173+
174+
assertTrue("Callback should be executed", callbackExecuted)
175+
}
176+
177+
@Test
178+
fun testObjectsAsyncScopeLaunchWithVoidCallbackError() = runTest {
179+
val asyncScope = ObjectsAsyncScope("test-channel")
180+
var errorReceived: ErrorInfo? = null
181+
182+
val callback = object : Callback<Void> {
183+
override fun onSuccess(result: Void?) {
184+
fail("Should not call onSuccess for error case")
185+
}
186+
187+
override fun onError(errorInfo: ErrorInfo?) {
188+
errorReceived = errorInfo
189+
}
190+
}
191+
192+
asyncScope.launchWithVoidCallback(callback) {
193+
delay(10)
194+
throw AblyException.fromErrorInfo(ErrorInfo("Test error", 500, 50000))
195+
}
196+
197+
// Wait for error to be received
198+
assertWaiter { errorReceived != null }
199+
200+
assertNotNull("Error should be received", errorReceived)
201+
assertEquals("Test error", errorReceived?.message)
202+
assertEquals(500, errorReceived?.statusCode)
203+
}
204+
205+
@Test
206+
fun testObjectsAsyncScopeCallbackExceptionHandling() = runTest {
207+
val asyncScope = ObjectsAsyncScope("test-channel")
208+
var callback1Called = false
209+
var callback2Called = false
210+
211+
val callback1 = object : Callback<String> {
212+
override fun onSuccess(result: String) {
213+
callback1Called = true
214+
throw RuntimeException("Callback exception")
215+
}
216+
217+
override fun onError(errorInfo: ErrorInfo?) {
218+
fail("Should not call onError when onSuccess throws")
219+
}
220+
}
221+
222+
asyncScope.launchWithCallback(callback1) { "test result" }
223+
// Wait for callback to be called
224+
assertWaiter { callback1Called }
225+
226+
val callback2 = object : Callback<String> {
227+
override fun onSuccess(result: String) {
228+
callback2Called = true
229+
}
230+
231+
override fun onError(errorInfo: ErrorInfo?) {
232+
fail("Should not call onError when onSuccess throws")
233+
}
234+
}
235+
236+
asyncScope.launchWithCallback(callback2) { "test result" }
237+
// Callback 2 should be called even if callback 1 throws an exception
238+
assertWaiter { callback2Called }
239+
}
240+
241+
@Test
242+
fun testObjectsAsyncScopeCancel() = runTest {
243+
val asyncScope = ObjectsAsyncScope("test-channel")
244+
var errorReceived = false
245+
246+
val callback = object : Callback<String> {
247+
override fun onSuccess(result: String) {
248+
fail("Should not call onSuccess")
249+
}
250+
251+
override fun onError(errorInfo: ErrorInfo?) {
252+
errorReceived = true
253+
}
254+
}
255+
256+
asyncScope.launchWithCallback(callback) {
257+
delay(100) // Long delay
258+
"test result"
259+
}
260+
261+
// Cancel immediately
262+
asyncScope.cancel(CancellationException("Test cancellation"))
263+
264+
// Wait a bit to ensure cancellation takes effect
265+
assertWaiter { errorReceived }
266+
}
267+
268+
@Test
269+
fun testObjectsAsyncScopeNonAblyException() = runTest {
270+
val asyncScope = ObjectsAsyncScope("test-channel")
271+
var errorReceived = false
272+
var error: ErrorInfo? = null
273+
274+
val callback = object : Callback<String> {
275+
override fun onSuccess(result: String) {
276+
fail("Should not call onSuccess for error case")
277+
}
278+
279+
override fun onError(errorInfo: ErrorInfo?) {
280+
errorReceived = true
281+
error = errorInfo
282+
}
283+
}
284+
285+
asyncScope.launchWithCallback(callback) {
286+
delay(10)
287+
throw RuntimeException("Non-Ably exception")
288+
}
289+
290+
// Wait for error to be received
291+
assertWaiter { errorReceived }
292+
293+
// Non-Ably exceptions should result in null errorInfo
294+
assertNull("Non-Ably exceptions should result in null errorInfo", error)
295+
}
296+
}

0 commit comments

Comments
 (0)