Repository: ably/ably-python (python)

Issue:
Pass `clientId` as query string param when opening a new connection
When opening a new connection, `clientId` from `ClientOptions` should be passed as a query string param, unless `clientId` is `null`.

Relevant spec point: [`RTN2d`](https://sdk.ably.com/builds/ably/specification/main/features/#RTN2d)



Pull request description:
Pass clientId as query string param when opening a new connection
Resolves #449 

Patch:
diff --git a/ably/rest/auth.py b/ably/rest/auth.py
index 5eec990..0e7c747 100644
--- a/ably/rest/auth.py
+++ b/ably/rest/auth.py
@@ -76,13 +76,17 @@ class Auth:
                              "auth_callback, auth_url, key, token or a TokenDetail")
 
     async def get_auth_transport_param(self):
+        auth_credentials = {}
+        if self.auth_options.client_id:
+            auth_credentials["client_id"] = self.auth_options.client_id
         if self.__auth_mechanism == Auth.Method.BASIC:
             key_name = self.__auth_options.key_name
             key_secret = self.__auth_options.key_secret
-            return {"key": f"{key_name}:{key_secret}"}
+            auth_credentials["key"] = f"{key_name}:{key_secret}"
         elif self.__auth_mechanism == Auth.Method.TOKEN:
             token_details = await self._ensure_valid_auth_credentials()
-            return {"accessToken": token_details.token}
+            auth_credentials["accessToken"] = token_details.token
+        return auth_credentials
 
     async def __authorize_when_necessary(self, token_params=None, auth_options=None, force=False):
         token_details = await self._ensure_valid_auth_credentials(token_params, auth_options, force)


Test patch:
diff --git a/test/ably/realtime/realtimeconnection_test.py b/test/ably/realtime/realtimeconnection_test.py
index 93ba9bd..2017f1c 100644
--- a/test/ably/realtime/realtimeconnection_test.py
+++ b/test/ably/realtime/realtimeconnection_test.py
@@ -323,3 +323,29 @@ class TestRealtimeConnection(BaseAsyncTestCase):
         await ably.connection.once_async(ConnectionState.CONNECTED)
         assert ably.connection.connection_manager.transport.host == fallback_host
         await ably.close()
+
+    #  RTN2d
+    async def test_connection_null_client_id_query_params(self):
+        rest = await TestApp.get_ably_rest()
+
+        token_details = await rest.auth.request_token()
+
+        realtime = await TestApp.get_ably_realtime(token_details=token_details)
+
+        await realtime.connection.once_async(ConnectionState.CONNECTED)
+        assert realtime.connection.connection_manager.transport.params.get("client_id") is None
+        assert realtime.auth.client_id is None
+
+        await realtime.close()
+        await rest.close()
+
+    async def test_connection_client_id_query_params(self):
+        client_id = 'test_client_id'
+
+        ably = await TestApp.get_ably_realtime(client_id=client_id)
+
+        await ably.connection.once_async(ConnectionState.CONNECTED)
+        assert ably.connection.connection_manager.transport.params["client_id"] == client_id
+        assert ably.auth.client_id == client_id
+
+        await ably.close()