diff --git a/android/src/main/java/io/ably/lib/push/ActivationStateMachine.java b/android/src/main/java/io/ably/lib/push/ActivationStateMachine.java index 9c397f716..ca6a03392 100644 --- a/android/src/main/java/io/ably/lib/push/ActivationStateMachine.java +++ b/android/src/main/java/io/ably/lib/push/ActivationStateMachine.java @@ -20,6 +20,7 @@ import io.ably.lib.types.*; import io.ably.lib.util.IntentUtils; import io.ably.lib.util.Log; +import io.ably.lib.util.ParamsUtils; import io.ably.lib.util.Serialisation; public class ActivationStateMachine { @@ -135,10 +136,7 @@ public ActivationStateMachine.State transition(final ActivationStateMachine.Even ably.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if(ably.options.pushFullWait) { - params = Param.push(null, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, ably.options); /* this is authenticated using the Ably library credentials, plus the deviceSecret in the request body */ http.post("/push/deviceRegistrations", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), params, body, new Serialisation.HttpResponseHandler(), true, callback); } @@ -425,11 +423,7 @@ private void updateRegistration() { ably.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (ably.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } - + Param[] params = ParamsUtils.enrichParams(null, ably.options); http.patch("/push/deviceRegistrations/" + device.id, ably.push.pushRequestHeaders(true), params, body, null, false, callback); } }).async(new Callback() { @@ -473,11 +467,7 @@ private void validateRegistration() { ably.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (ably.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } - + Param[] params = ParamsUtils.enrichParams(null, ably.options); final HttpCore.RequestBody body = HttpUtils.requestBodyFromGson(device.toJsonObject(), ably.options.useBinaryProtocol); http.put("/push/deviceRegistrations/" + device.id, ably.push.pushRequestHeaders(true), params, body, new Serialisation.HttpResponseHandler(), true, callback); } @@ -521,10 +511,7 @@ private void deregister() { ably.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = new Param[0]; - if (ably.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(new Param[0], ably.options); http.del("/push/deviceRegistrations/" + device.id, ably.push.pushRequestHeaders(true), params, null, true, callback); } }).async(new Callback() { diff --git a/android/src/main/java/io/ably/lib/push/PushChannel.java b/android/src/main/java/io/ably/lib/push/PushChannel.java index 5765a7a83..ce655d2ce 100644 --- a/android/src/main/java/io/ably/lib/push/PushChannel.java +++ b/android/src/main/java/io/ably/lib/push/PushChannel.java @@ -1,6 +1,5 @@ package io.ably.lib.push; -import android.content.Context; import com.google.gson.JsonObject; import io.ably.lib.http.*; import io.ably.lib.realtime.CompletionListener; @@ -8,6 +7,7 @@ import io.ably.lib.rest.Channel; import io.ably.lib.rest.DeviceDetails; import io.ably.lib.types.*; +import io.ably.lib.util.ParamsUtils; public class PushChannel { protected final Channel channel; @@ -64,10 +64,7 @@ protected Http.Request postSubscription(JsonObject bodyJson) { return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.post("/push/channelSubscriptions", rest.push.pushRequestHeaders(true), params, body, null, true, callback); } }); @@ -109,10 +106,7 @@ protected Http.Request unsubscribeDeviceImpl() { } protected Http.Request delSubscription(Param[] params) { - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } - final Param[] finalParams = params; + final Param[] finalParams = ParamsUtils.enrichParams(params, rest.options); return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { diff --git a/lib/src/main/java/io/ably/lib/http/HttpScheduler.java b/lib/src/main/java/io/ably/lib/http/HttpScheduler.java index 54046b995..9c07d7146 100644 --- a/lib/src/main/java/io/ably/lib/http/HttpScheduler.java +++ b/lib/src/main/java/io/ably/lib/http/HttpScheduler.java @@ -189,6 +189,12 @@ private AblyRequestWithFallback( this.path = path; this.requireAblyAuth = requireAblyAuth; } + + private String extendMessage(String msg) { + return Param.getFirst(params, "request_id") == null ? + msg : String.format("%s request_id=%s", msg, Param.getFirst(params, "request_id")); + } + @Override public void run() { String candidateHost = httpCore.hosts.getPreferredHost(); @@ -202,17 +208,20 @@ public void run() { break; } catch (AblyException.HostFailedException e) { if(--retryCountRemaining < 0) { + e.errorInfo.message = extendMessage(e.errorInfo.message); setError(e.errorInfo); break; } - Log.d(TAG, "Connection failed to host `" + candidateHost + "`. Searching for new host..."); + Log.d(TAG, extendMessage("Connection failed to host `" + candidateHost + "`. Searching for new host...")); candidateHost = httpCore.hosts.getFallback(candidateHost); if (candidateHost == null) { + e.errorInfo.message = extendMessage(e.errorInfo.message); setError(e.errorInfo); break; } - Log.d(TAG, "Switched to `" + candidateHost + "`."); + Log.d(TAG, extendMessage("Switched to `" + candidateHost + "`.")); } catch(AblyException e) { + e.errorInfo.message = extendMessage(e.errorInfo.message); setError(e.errorInfo); break; } finally { diff --git a/lib/src/main/java/io/ably/lib/push/PushBase.java b/lib/src/main/java/io/ably/lib/push/PushBase.java index c735df2c5..5541925a8 100644 --- a/lib/src/main/java/io/ably/lib/push/PushBase.java +++ b/lib/src/main/java/io/ably/lib/push/PushBase.java @@ -16,6 +16,7 @@ import io.ably.lib.types.PaginatedResult; import io.ably.lib.types.Param; import io.ably.lib.util.Log; +import io.ably.lib.util.ParamsUtils; import io.ably.lib.util.Serialisation; import io.ably.lib.util.StringUtils; @@ -71,11 +72,7 @@ public void execute(HttpScheduler http, Callback callback) throws AblyExce bodyJson.add(entry.getKey(), entry.getValue()); } HttpCore.RequestBody body = HttpUtils.requestBodyFromGson(bodyJson, rest.options.useBinaryProtocol); - - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.post("/push/publish", HttpUtils.defaultAcceptHeaders(rest.options.useBinaryProtocol), params, body, null, true, callback); } @@ -101,11 +98,8 @@ protected Http.Request saveImpl(final DeviceDetails device) { final HttpCore.RequestBody body = HttpUtils.requestBodyFromGson(device.toJsonObject(), rest.options.useBinaryProtocol); return rest.http.request(new Http.Execute() { @Override - public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + public void execute(HttpScheduler http, Callback callback) { + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.put("/push/deviceRegistrations/" + device.id, rest.push.pushRequestHeaders(device.id), params, body, DeviceDetails.httpResponseHandler, true, callback); } }); @@ -124,10 +118,7 @@ protected Http.Request getImpl(final String deviceId) { return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.get("/push/deviceRegistrations/" + deviceId, rest.push.pushRequestHeaders(deviceId), params, DeviceDetails.httpResponseHandler, true, callback); } }); @@ -167,10 +158,7 @@ protected Http.Request removeImpl(final String deviceId) { return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.del("/push/deviceRegistrations/" + deviceId, rest.push.pushRequestHeaders(deviceId), params, null, true, callback); } }); @@ -186,10 +174,7 @@ public void removeWhereAsync(Param[] params, CompletionListener listener) { protected Http.Request removeWhereImpl(Param[] params) { Log.v(TAG, "removeWhereImpl(): params=" + Arrays.toString(params)); - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } - final Param[] finalParams = params; + final Param[] finalParams = ParamsUtils.enrichParams(params, rest.options); return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { @@ -222,10 +207,7 @@ protected Http.Request saveImpl(final ChannelSubscription s return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - Param[] params = null; - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + Param[] params = ParamsUtils.enrichParams(null, rest.options); http.post("/push/channelSubscriptions", rest.push.pushRequestHeaders(subscription.deviceId), params, body, ChannelSubscription.httpResponseHandler, true, callback); } }); @@ -279,11 +261,8 @@ public void removeWhereAsync(Param[] params, CompletionListener listener) { protected Http.Request removeWhereImpl(Param[] params) { Log.v(TAG, "removeWhereImpl(): params=" + Arrays.toString(params)); String deviceId = HttpUtils.getParam(params, "deviceId"); - if (rest.options.pushFullWait) { - params = Param.push(params, "fullWait", "true"); - } + final Param[] finalParams = ParamsUtils.enrichParams(params, rest.options); final Param[] finalHeaders = rest.push.pushRequestHeaders(deviceId); - final Param[] finalParams = params; return rest.http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { diff --git a/lib/src/main/java/io/ably/lib/rest/AblyBase.java b/lib/src/main/java/io/ably/lib/rest/AblyBase.java index 72c4d0392..f9f3a9456 100644 --- a/lib/src/main/java/io/ably/lib/rest/AblyBase.java +++ b/lib/src/main/java/io/ably/lib/rest/AblyBase.java @@ -154,10 +154,11 @@ public void timeAsync(Callback callback) { } private Http.Request timeImpl() { + final Param[] params = this.options.addRequestIds ? Param.array(Crypto.generateRandomRequestId()) : null; // RSC7c return http.request(new Http.Execute() { @Override public void execute(HttpScheduler http, Callback callback) throws AblyException { - http.get("/time", HttpUtils.defaultAcceptHeaders(false), null, new HttpCore.ResponseHandler() { + http.get("/time", HttpUtils.defaultAcceptHeaders(false), params, new HttpCore.ResponseHandler() { @Override public Long handleResponse(HttpCore.Response response, ErrorInfo error) throws AblyException { if(error != null) { @@ -251,7 +252,7 @@ public void publishBatchAsync(Message.Batch[] pubSpecs, ChannelOptions channelOp publishBatchImpl(pubSpecs, channelOptions, params).async(callback); } - private Http.Request publishBatchImpl(final Message.Batch[] pubSpecs, ChannelOptions channelOptions, final Param[] params) throws AblyException { + private Http.Request publishBatchImpl(final Message.Batch[] pubSpecs, ChannelOptions channelOptions, final Param[] initialParams) throws AblyException { boolean hasClientSuppliedId = false; for(Message.Batch spec : pubSpecs) { for(Message message : spec.messages) { @@ -264,7 +265,7 @@ private Http.Request publishBatchImpl(final Message.Batch[] p } if(!hasClientSuppliedId && options.idempotentRestPublishing) { /* RSL1k1: populate the message id with a library-generated id */ - String messageId = Crypto.getRandomMessageId(); + String messageId = Crypto.getRandomId(); for (int i = 0; i < spec.messages.length; i++) { spec.messages[i].id = messageId + ':' + i; } @@ -274,6 +275,7 @@ private Http.Request publishBatchImpl(final Message.Batch[] p @Override public void execute(HttpScheduler http, final Callback callback) throws AblyException { HttpCore.RequestBody requestBody = options.useBinaryProtocol ? MessageSerializer.asMsgpackRequest(pubSpecs) : MessageSerializer.asJSONRequest(pubSpecs); + final Param[] params = options.addRequestIds ? Param.set(initialParams, Crypto.generateRandomRequestId()) : initialParams ; // RSC7c http.post("/messages", HttpUtils.defaultAcceptHeaders(options.useBinaryProtocol), params, requestBody, new HttpCore.ResponseHandler() { @Override public PublishResponse[] handleResponse(HttpCore.Response response, ErrorInfo error) throws AblyException { diff --git a/lib/src/main/java/io/ably/lib/rest/ChannelBase.java b/lib/src/main/java/io/ably/lib/rest/ChannelBase.java index 9de0289f9..859f4415b 100644 --- a/lib/src/main/java/io/ably/lib/rest/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/rest/ChannelBase.java @@ -103,15 +103,16 @@ public void execute(HttpScheduler http, final Callback callback) throws Ab } if(!hasClientSuppliedId && ably.options.idempotentRestPublishing) { /* RSL1k1: populate the message id with a library-generated id */ - String messageId = Crypto.getRandomMessageId(); + String messageId = Crypto.getRandomId(); for (int i = 0; i < messages.length; i++) { messages[i].id = messageId + ':' + i; } } HttpCore.RequestBody requestBody = ably.options.useBinaryProtocol ? MessageSerializer.asMsgpackRequest(messages) : MessageSerializer.asJsonRequest(messages); + final Param[] params = ably.options.addRequestIds ? Param.array(Crypto.generateRandomRequestId()) : null; // RSC7c - http.post(basePath + "/messages", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), null, requestBody, null, true, callback); + http.post(basePath + "/messages", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), params, requestBody, null, true, callback); } }); } @@ -139,8 +140,9 @@ public void historyAsync(Param[] params, Callback> historyImpl(params).async(callback); } - private BasePaginatedQuery.ResultRequest historyImpl(Param[] params) { + private BasePaginatedQuery.ResultRequest historyImpl(Param[] initialParams) { HttpCore.BodyHandler bodyHandler = MessageSerializer.getMessageResponseHandler(options); + final Param[] params = ably.options.addRequestIds ? Param.set(initialParams, Crypto.generateRandomRequestId()) : initialParams; // RSC7c return (new BasePaginatedQuery(ably.http, basePath + "/messages", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), params, bodyHandler)).get(); } @@ -169,8 +171,9 @@ public void getAsync(Param[] params, Callback getImpl(Param[] params) { + private BasePaginatedQuery.ResultRequest getImpl(Param[] initialParams) { HttpCore.BodyHandler bodyHandler = PresenceSerializer.getPresenceResponseHandler(options); + final Param[] params = ably.options.addRequestIds ? Param.set(initialParams, Crypto.generateRandomRequestId()) : initialParams; // RSC7c return (new BasePaginatedQuery(ably.http, basePath + "/presence", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), params, bodyHandler)).get(); } @@ -195,8 +198,9 @@ public void historyAsync(Param[] params, Callback historyImpl(Param[] params) { + private BasePaginatedQuery.ResultRequest historyImpl(Param[] initialParams) { HttpCore.BodyHandler bodyHandler = PresenceSerializer.getPresenceResponseHandler(options); + final Param[] params = ably.options.addRequestIds ? Param.set(initialParams, Crypto.generateRandomRequestId()) : initialParams; // RSC7c return (new BasePaginatedQuery(ably.http, basePath + "/presence/history", HttpUtils.defaultAcceptHeaders(ably.options.useBinaryProtocol), params, bodyHandler)).get(); } diff --git a/lib/src/main/java/io/ably/lib/types/ClientOptions.java b/lib/src/main/java/io/ably/lib/types/ClientOptions.java index 8444964b3..d7a548465 100644 --- a/lib/src/main/java/io/ably/lib/types/ClientOptions.java +++ b/lib/src/main/java/io/ably/lib/types/ClientOptions.java @@ -195,4 +195,10 @@ public ClientOptions(String key) throws AblyException { * before responding. */ public boolean pushFullWait = false; + + /** + If enabled, every REST request to Ably includes a `request_id` query string parameter. This request ID + remain the same if a request is retried to a fallback host. + */ + public boolean addRequestIds = false; } diff --git a/lib/src/main/java/io/ably/lib/types/Param.java b/lib/src/main/java/io/ably/lib/types/Param.java index aca9ffc85..8a2e85365 100644 --- a/lib/src/main/java/io/ably/lib/types/Param.java +++ b/lib/src/main/java/io/ably/lib/types/Param.java @@ -10,6 +10,10 @@ public class Param { public String key; public String value; + public static Param[] array(final Param val) { + return new Param[] { val }; + } + public static Param[] push(Param[] params, Param val) { if (params == null) { return new Param[] { val }; diff --git a/lib/src/main/java/io/ably/lib/util/Crypto.java b/lib/src/main/java/io/ably/lib/util/Crypto.java index 56ea62fb9..1680f0d29 100644 --- a/lib/src/main/java/io/ably/lib/util/Crypto.java +++ b/lib/src/main/java/io/ably/lib/util/Crypto.java @@ -16,6 +16,7 @@ import io.ably.lib.types.AblyException; import io.ably.lib.types.ChannelOptions; import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.Param; /** * Utility classes and interfaces for message payload encryption. @@ -318,12 +319,22 @@ private static int getPaddedLength(int plaintextLength) { }; } - public static String getRandomMessageId() { + public static String getRandomId() { byte[] entropy = new byte[9]; secureRandom.nextBytes(entropy); return Base64Coder.encodeToString(entropy); } + /** + * Returns a "request_id" query param, based on a sequence of 9 random bytes + * which have been base64 encoded. + * + * Spec: RSC7c + */ + public static Param generateRandomRequestId() { + return new Param("request_id", Crypto.getRandomId()); + } + /** * Determine whether or not 256-bit AES is supported. (If this determines that * it is not supported, install the JCE unlimited strength JCE extensions). diff --git a/lib/src/main/java/io/ably/lib/util/ParamsUtils.java b/lib/src/main/java/io/ably/lib/util/ParamsUtils.java new file mode 100644 index 000000000..a89a74ecf --- /dev/null +++ b/lib/src/main/java/io/ably/lib/util/ParamsUtils.java @@ -0,0 +1,25 @@ +package io.ably.lib.util; + +import io.ably.lib.types.ClientOptions; +import io.ably.lib.types.Param; + +public class ParamsUtils { + + /** + * Produce either new or extend provided array of parameters based on values in Client options + * + * @param params Array of already set parameters + * @param options Client options + * @return Array of parameters extended of parameters based on values in client options + */ + public static Param[] enrichParams(Param[] params, ClientOptions options) { + if (options.pushFullWait) { + params = Param.push(params, "fullWait", "true"); + } + if (options.addRequestIds) { // RSC7c + params = Param.set(params, Crypto.generateRandomRequestId()); + } + + return params; + } +} diff --git a/lib/src/test/java/io/ably/lib/test/rest/RestClientTest.java b/lib/src/test/java/io/ably/lib/test/rest/RestClientTest.java new file mode 100644 index 000000000..34c9624bc --- /dev/null +++ b/lib/src/test/java/io/ably/lib/test/rest/RestClientTest.java @@ -0,0 +1,110 @@ +package io.ably.lib.test.rest; + +import io.ably.lib.debug.DebugOptions; +import io.ably.lib.rest.AblyRest; +import io.ably.lib.test.common.Helpers; +import io.ably.lib.test.common.ParameterizedTest; +import io.ably.lib.types.AblyException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class RestClientTest extends ParameterizedTest { + + @Rule + public Timeout testTimeout = Timeout.seconds(30); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Include `request_id` if addRequestIds in client options is enabled + * Spec: RSC7c + */ + @Test + public void request_contains_request_id() throws AblyException { + DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); + fillInOptions(opts); + Helpers.RawHttpTracker httpListener = new Helpers.RawHttpTracker(); + + opts.httpListener = httpListener; + /* disable addRequestIds */ + opts.addRequestIds = false; + AblyRest ablyA = new AblyRest(opts); + + ablyA.channels.get("test").publish("foo", "bar"); + /* verify client_id is not a part of url query */ + assertNull("Verify clientId is not present in query", httpListener.getFirstRequest().url.getQuery()); + + /* enable addRequestIds */ + opts.addRequestIds = true; + AblyRest ablyB = new AblyRest(opts); + + ablyB.channels.get("test").publish("foo", "bar"); + /* verify client_id is a part of url query */ + assertTrue("Verify clientId is present in query", httpListener.getLastRequest().url.getQuery().contains("request_id")); + } + + /** + * Include `request_id` in ErrorInfo if addRequestIds in client options is enabled + * Spec: RSC7c + */ + @Test + public void error_info_contains_request_id() throws AblyException { + DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); + fillInOptions(opts); + + Helpers.RawHttpTracker httpListener = new Helpers.RawHttpTracker(); + opts.httpListener = httpListener; + opts.addRequestIds = true; + opts.environment = null; + opts.restHost = ""; + opts.fallbackHosts = new String[]{"ably.com"}; + AblyRest ably = new AblyRest(opts); + + try{ + ably.channels.get("test").publish("foo", "bar"); + } catch (AblyException e) { + assertTrue(e.errorInfo.message.contains("request_id")); + } + + /* verify client_id is a part of url query */ + assertTrue("Verify clientId is present in query", httpListener.getFirstRequest().url.getQuery().contains("request_id")); + } + + /** + * `clientId` remain the same if a request is retried to a fallback host + * Spec: RSC7c + */ + @Test + public void request_id_remain_same_retried_fallbacks() throws AblyException { + DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); + fillInOptions(opts); + + Helpers.RawHttpTracker httpListener = new Helpers.RawHttpTracker(); + opts.httpListener = httpListener; + opts.addRequestIds = true; + opts.environment = null; + opts.restHost = "invalid-host1.com"; + opts.fallbackHosts = new String[]{"invalid-host2.com", "invalid-host3.com"}; + AblyRest ably = new AblyRest(opts); + + try{ + ably.channels.get("test").publish("foo", "bar"); + } catch (AblyException e) { } + + /* verify client_id is a part of url query */ + assertTrue("Verify clientId is present in query", httpListener.getFirstRequest().url.getQuery().contains("request_id")); + String query = httpListener.getFirstRequest().url.getQuery(); + /* verify request was retried 3 times */ + assertEquals(3, httpListener.values().size()); + for (Helpers.RawHttpRequest rawHttpRequest : httpListener.values()) { + assertTrue("Verify clientId remain the same if a request is retried to a fallback host", rawHttpRequest.url.getQuery().contains(query)); + } + } +} diff --git a/lib/src/test/java/io/ably/lib/util/CryptoTest.java b/lib/src/test/java/io/ably/lib/util/CryptoTest.java index f89541be3..e4b0cb53e 100644 --- a/lib/src/test/java/io/ably/lib/util/CryptoTest.java +++ b/lib/src/test/java/io/ably/lib/util/CryptoTest.java @@ -202,7 +202,7 @@ private static byte[] msgPacked(final String name, final byte[] data, final Stri @Test public void getRandomId() { - String randomId = Crypto.getRandomMessageId(); + String randomId = Crypto.getRandomId(); assertEquals(12, randomId.length()); } } diff --git a/lib/src/test/java/io/ably/lib/util/ParamsUtilsTest.java b/lib/src/test/java/io/ably/lib/util/ParamsUtilsTest.java new file mode 100644 index 000000000..04df947c2 --- /dev/null +++ b/lib/src/test/java/io/ably/lib/util/ParamsUtilsTest.java @@ -0,0 +1,53 @@ +package io.ably.lib.util; + +import io.ably.lib.types.AblyException; +import io.ably.lib.types.ClientOptions; +import io.ably.lib.types.Param; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ParamsUtilsTest { + + @Test + public void enrichParams_creates_params_if_original_are_null() throws AblyException { + ClientOptions opts = new ClientOptions("secret_key"); + opts.pushFullWait = true; + opts.addRequestIds = true; + + Param[] newParams = ParamsUtils.enrichParams(null, opts); + + assertEquals(2, newParams.length); + assertTrue(Param.containsKey(newParams, "fullWait")); + assertTrue(Param.containsKey(newParams, "request_id")); + } + + @Test + public void enrichParams_add_params_to_existing_ones() throws AblyException { + ClientOptions opts = new ClientOptions("secret_key"); + opts.pushFullWait = true; + opts.addRequestIds = true; + + Param[] originParams = Param.array(new Param("propertyName", "value")); + Param[] newParams = ParamsUtils.enrichParams(originParams, opts); + + assertEquals(3, newParams.length); + assertTrue(Param.containsKey(newParams, "propertyName")); + assertTrue(Param.containsKey(newParams, "fullWait")); + assertTrue(Param.containsKey(newParams, "request_id")); + } + + @Test + public void enrichParams_produce_only_requested_params() throws AblyException { + ClientOptions opts = new ClientOptions("secret_key"); + opts.addRequestIds = true; + + Param[] originParams = Param.array(new Param("propertyName", "value")); + Param[] newParams = ParamsUtils.enrichParams(originParams, opts); + + assertEquals(2,newParams.length); + assertTrue(Param.containsKey(newParams, "propertyName")); + assertTrue(Param.containsKey(newParams, "request_id")); + } +}