Beispiel #1
0
        public IEnumerator RealTimeWithAuthUrl_WhenTokenExpired_ShouldRetry_WhenRetryFails_ShouldSetError([ValueSource(nameof(_protocols))] Protocol protocol)
        {
            return(UniTask.ToCoroutine(async() =>
            {
                var authRestClient = await AblySandbox.GetRestClient(protocol);
                var token = await authRestClient.Auth.RequestTokenAsync(new TokenParams
                {
                    Ttl = TimeSpan.FromMilliseconds(1000)
                });

                // this realtime client will have a key for the sandbox, thus a means to renew
                var realtimeClient = await AblySandbox.GetRealtimeClient(protocol, (options, _) =>
                {
                    options.TokenDetails = token;
                    options.AuthUrl = new Uri(_errorUrl);
                    options.AutoConnect = false;
                });

                var awaiter = new TaskCompletionAwaiter(5000);
                realtimeClient.Connection.Once(ConnectionEvent.Disconnected, state =>
                {
                    state.Reason.Code.Should().Be(ErrorCodes.ClientAuthProviderRequestFailed);
                    awaiter.SetCompleted();
                });

                await Task.Delay(2000);
                realtimeClient.Connect();

                var result = await awaiter.Task;
                result.Should().BeTrue();
            }));
        }
Beispiel #2
0
        public async Task WhenWebSocketClientIsNull_SendShouldSetDisconnectedState(Protocol protocol)
        {
            var client = await GetRealtimeClient(protocol);

            await WaitForState(client); //wait for connection

            client.Connection.State.Should().Be(ConnectionState.Connected);

            var transportWrapper = client.ConnectionManager.Transport as TestTransportWrapper;

            transportWrapper.Should().NotBe(null);
            var wsTransport = transportWrapper._wrappedTransport as MsWebSocketTransport;

            wsTransport.Should().NotBe(null);
            wsTransport._socket.ClientWebSocket = null;

            var tca = new TaskCompletionAwaiter();

            client.Connection.On(s =>
            {
                if (s.Current == ConnectionState.Disconnected)
                {
                    tca.SetCompleted();
                }
            });

            await client.Channels.Get("test").PublishAsync("event", "data");

            await tca.Task;

            // should auto connect again
            await WaitForState(client);
        }
Beispiel #3
0
        public async Task WhenFakeDisconnectedMessageContainsTokenError_ForcesClientToReauthenticate(Protocol protocol)
        {
            var reconnectAwaiter = new TaskCompletionAwaiter();
            var client           = await GetRealtimeClient(protocol, (options, settings) =>
            {
                options.UseTokenAuth = true;
            });

            await client.WaitForState(ConnectionState.Connected);

            var initialToken = client.RestClient.AblyAuth.CurrentToken;

            client.Connection.Once(ConnectionEvent.Disconnected, state2 =>
            {
                client.Connection.Once(ConnectionEvent.Connected, state3 =>
                {
                    reconnectAwaiter.SetCompleted();
                });
            });

            client.FakeProtocolMessageReceived(new ProtocolMessage(ProtocolMessage.MessageAction.Disconnected)
            {
                Error = new ErrorInfo("testing RTN22a", ErrorCodes.TokenError)
            });
            var didReconnect = await reconnectAwaiter.Task;

            didReconnect.Should().BeTrue();
            client.RestClient.AblyAuth.CurrentToken.Should().NotBe(initialToken);
            client.Close();
        }
Beispiel #4
0
            public async Task SubscribeDevice_ShouldUsePushDeviceAuthentication()
            {
                const string deviceIdentityToken = "identityToken";
                var          taskAwaiter         = new TaskCompletionAwaiter();

                async Task <AblyResponse> RequestHandler(AblyRequest request)
                {
                    request.Headers.Should().ContainKey(Defaults.DeviceIdentityTokenHeader).WhoseValue.Should()
                    .Be(deviceIdentityToken);

                    taskAwaiter.SetCompleted();
                    return(new AblyResponse()
                    {
                        TextResponse = JsonConvert.SerializeObject(new PushChannelSubscription())
                    });
                }

                var client = GetRestClient(RequestHandler, mobileDevice: new FakeMobileDevice());

                client.Device = new LocalDevice()
                {
                    Id = "id",
                    DeviceIdentityToken = deviceIdentityToken
                };

                var pushChannel = client.Channels.Get("test").Push;

                await pushChannel.SubscribeDevice();

                (await taskAwaiter).Should().BeTrue("Didn't validate function");
            }
Beispiel #5
0
        public async Task Auth_WithRealtimeClient_WhenAuthFails_ShouldTransitionToOrRemainInTheCorrectState(Protocol protocol)
        {
            async Task TestConnectingBecomesDisconnected(string context, Action <ClientOptions, TestEnvironmentSettings> optionsAction)
            {
                TaskCompletionAwaiter tca = new TaskCompletionAwaiter(5000);
                var realtimeClient        = await GetRealtimeClient(protocol, optionsAction);

                realtimeClient.Connection.On(ConnectionEvent.Disconnected, change =>
                {
                    change.Previous.Should().Be(ConnectionState.Connecting);
                    change.Reason.Code.Should().Be(ErrorCodes.ClientAuthProviderRequestFailed);
                    tca.SetCompleted();
                });

                realtimeClient.Connection.Connect();
                await realtimeClient.ProcessCommands();

                (await tca.Task).Should().BeTrue(context);
            }

            // authUrl fails
            void AuthUrlOptions(ClientOptions options, TestEnvironmentSettings settings)
            {
                options.AutoConnect            = false;
                options.AuthUrl                = new Uri(ErrorUrl);
                options.RealtimeRequestTimeout = TimeSpan.FromSeconds(2);
                options.HttpRequestTimeout     = TimeSpan.FromSeconds(2);
            }
Beispiel #6
0
 public Action <T> Wrap(Action <T> callback)
 {
     return(csc =>
     {
         callback(csc);
         _tca.SetCompleted();
     });
 }
Beispiel #7
0
        public async Task WhenConnectedMessageReceived_ShouldEmitUpdate(Protocol protocol)
        {
            var updateAwaiter = new TaskCompletionAwaiter(5000);
            var client        = await GetRealtimeClient(protocol, (options, settings) =>
            {
                options.UseTokenAuth = true;
                options.AutoConnect  = true;
            });

            await client.WaitForState(ConnectionState.Connected);

            client.Connection.ConnectionStateTtl.Should().NotBe(TimeSpan.MaxValue);

            var key = client.Connection.Key;

            client.Connection.Once(state =>
            {
                // RTN4h - can emit UPDATE event
                if (state.Event == ConnectionEvent.Update)
                {
                    // should have both previous and current attributes set to CONNECTED
                    state.Current.Should().Be(ConnectionState.Connected);
                    state.Previous.Should().Be(ConnectionState.Connected);
                    state.Reason.Message = "fake-error";
                    updateAwaiter.SetCompleted();
                }
                else
                {
                    throw new Exception($"'{state.Event}' was handled. Only an 'Update' event should have occured");
                }
            });

            client.FakeProtocolMessageReceived(new ProtocolMessage(ProtocolMessage.MessageAction.Connected)
            {
                ConnectionDetails = new ConnectionDetails
                {
                    ConnectionKey      = "key",
                    ClientId           = "RTN21",
                    ConnectionStateTtl = TimeSpan.MaxValue
                },
                Error = new ErrorInfo("fake-error"),
            });

            var didUpdate = await updateAwaiter.Task;

            didUpdate.Should().BeTrue();

            // RTN21 - new connection details over write old values
            client.Connection.Key.Should().NotBe(key);
            client.ClientId.Should().Be("RTN21");
            client.Connection.ConnectionStateTtl.Should().Be(TimeSpan.MaxValue);
        }
            public async Task WhenSendingMessage_AckCallbackCalled_ForMultipleMessages()
            {
                // Arrange
                var client = await GetConnectedClient();

                var callbacks = new List <ValueTuple <bool, ErrorInfo> >();

                var message1 = new ProtocolMessage(ProtocolMessage.MessageAction.Message, "Test");
                var message2 = new ProtocolMessage(ProtocolMessage.MessageAction.Message, "Test");
                var message3 = new ProtocolMessage(ProtocolMessage.MessageAction.Message, "Test");

                var awaiter = new TaskCompletionAwaiter();

                Action <bool, ErrorInfo> GetCallback(int forCount) =>
                (ack, err) =>
                {
                    if (callbacks.Count == forCount)
                    {
                        callbacks.Add((ack, err));
                    }

                    if (callbacks.Count == 3)
                    {
                        awaiter.SetCompleted();
                    }
                };

                var ackMessage = new ProtocolMessage(ProtocolMessage.MessageAction.Ack)
                {
                    MsgSerial = 0, Count = 3
                };

                // Act
                client.Workflow.QueueAck(message1, GetCallback(0));
                client.Workflow.QueueAck(message2, GetCallback(1));
                client.Workflow.QueueAck(message3, GetCallback(2));
                client.ExecuteCommand(ProcessMessageCommand.Create(ackMessage));

                await client.ProcessCommands();

                await awaiter.Task;

                // Assert
                callbacks.Count.Should().Be(3);
                Assert.True(callbacks.TrueForAll(c => c.Item1));         // Ack
                Assert.True(callbacks.TrueForAll(c => c.Item2 == null)); // No error
            }
        public async Task WithConnectedClient_AuthorizeObtainsNewTokenAndUpgradesConnection_AndShouldEmitUpdate(Protocol protocol)
        {
            const string validClientId1  = "RTC8";
            const string invalidClientId = "RTC8-incompatible-clientId";

            // For a realtime client, Auth#authorize instructs the library to obtain
            // a token using the provided tokenParams and authOptions and upgrade
            // the current connection to use that token
            var client = await GetRealtimeClient(protocol, (opts, _) =>
            {
                opts.ClientId     = validClientId1;
                opts.UseTokenAuth = true;
            });

            var awaiter = new TaskCompletionAwaiter();

            client.Connection.On(ConnectionEvent.Update, args => { awaiter.SetCompleted(); });
            await client.WaitForState(ConnectionState.Connected);

            var tokenDetails = await client.Auth.AuthorizeAsync(new TokenParams { ClientId = validClientId1 });

            tokenDetails.ClientId.Should().Be(validClientId1);
            client.Connection.State.Should().Be(ConnectionState.Connected);
            client.RestClient.AblyAuth.CurrentToken.Should().Be(tokenDetails);
            var didUpdate = await awaiter.Task;

            client.Connection.State.Should().Be(ConnectionState.Connected);
            didUpdate.Should().BeTrue(
                "the AUTH message should trigger a CONNECTED response from the server that causes an UPDATE to be emitted.");

            client.Connection.On(args =>
            {
                if (args.Current != ConnectionState.Failed)
                {
                    Assert.True(false, $"unexpected state '{args.Current}'");
                }
            });

            // AuthorizeAsync will not return until either a CONNECTED or ERROR response
            // (or timeout) is seen from Ably, so we do not need to use WaitForState() here
            await Assert.ThrowsAsync <AblyException>(() => client.Auth.AuthorizeAsync(new TokenParams {
                ClientId = invalidClientId
            }));

            client.Connection.State.Should().Be(ConnectionState.Failed);
        }
Beispiel #10
0
            public async Task ListSubscriptions_ShouldCallApiWithCorrectParameters()
            {
                const string channelName         = "testChannel";
                const string clientId            = "clientId";
                const string deviceid            = "deviceId";
                const string deviceIdentityToken = "token";

                var taskAwaiter = new TaskCompletionAwaiter();

                async Task <AblyResponse> RequestHandler(AblyRequest request)
                {
                    request.Url.Should().Be("/push/channelSubscriptions");
                    request.Method.Should().Be(HttpMethod.Get);
                    var queryParams = request.QueryParameters;

                    queryParams.Should().ContainKey("clientId").WhoseValue.Should().Be(clientId);
                    queryParams.Should().ContainKey("channel").WhoseValue.Should().Be(channelName);
                    queryParams.Should().ContainKey("deviceId").WhoseValue.Should().Be(deviceid);
                    queryParams.Should().ContainKey("concatFilters").WhoseValue.Should().Be("true");

                    taskAwaiter.SetCompleted();
                    return(new AblyResponse {
                        TextResponse = JsonConvert.SerializeObject(new List <PushChannelSubscription>())
                    });
                }

                var client = GetRestClient(RequestHandler, mobileDevice: new FakeMobileDevice());

                client.Device = new LocalDevice()
                {
                    DeviceIdentityToken = deviceIdentityToken,
                    ClientId            = clientId,
                    Id = deviceid
                };

                var pushChannel = client.Channels.Get(channelName).Push;

                var subscriptions = await pushChannel.ListSubscriptions();

                subscriptions.Should().NotBeNull();

                (await taskAwaiter).Should().BeTrue("Didn't validate function");
            }
Beispiel #11
0
            public async Task UnsubscribeDevice_ShouldSendADeleteRequestTo_PushChannelSubscriptions_WithCorrectParameters_And_AuthHeader()
            {
                const string channelName         = "testChannel";
                const string deviceId            = "deviceId";
                const string deviceIdentityToken = "token";

                var taskAwaiter = new TaskCompletionAwaiter();

                async Task <AblyResponse> RequestHandler(AblyRequest request)
                {
                    // RSH7c2, check the correct request is made
                    request.Url.Should().Be("/push/channelSubscriptions");
                    request.Method.Should().Be(HttpMethod.Delete);
                    var queryParams = request.QueryParameters;

                    queryParams.Should().ContainKey("deviceId").WhoseValue.Should().Be(deviceId);
                    queryParams.Should().ContainKey("channel").WhoseValue.Should().Be(channelName);
                    queryParams.Should().NotContainKey("clientId");

                    // Check the auth header RSH7c3
                    request.Headers.Should().ContainKey(Defaults.DeviceIdentityTokenHeader).WhoseValue.Should()
                    .Be(deviceIdentityToken);

                    taskAwaiter.SetCompleted();
                    return(new AblyResponse()
                    {
                        TextResponse = JsonConvert.SerializeObject(new PushChannelSubscription())
                    });
                }

                var client = GetRestClient(RequestHandler, mobileDevice: new FakeMobileDevice());

                client.Device = new LocalDevice()
                {
                    Id = deviceId,
                    DeviceIdentityToken = deviceIdentityToken
                };
                var pushChannel = client.Channels.Get(channelName).Push;

                await pushChannel.UnsubscribeDevice();

                (await taskAwaiter).Should().BeTrue("Didn't validate function");
            }
Beispiel #12
0
        /// <summary>
        /// This method yields the current thread and waits until the whole command queue is processed.
        /// </summary>
        /// <returns></returns>
        public static async Task ProcessCommands(this IRealtimeClient client)
        {
            var realtime    = (AblyRealtime)client;
            var taskAwaiter = new TaskCompletionAwaiter();

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            _ = Task.Run(async() =>
            {
                while (true)
                {
                    await Task.Delay(50);

                    if (realtime.Workflow.IsProcessingCommands() == false)
                    {
                        taskAwaiter.SetCompleted();
                    }
                }
            });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

            await taskAwaiter.Task;
        }
            public async Task ShouldSuccessfullyPublishAPayload(Protocol protocol)
            {
                // Arrange
                var client = await GetRealtimeClient(protocol);

                var channelName = "pushenabled:test".AddRandomSuffix();

                var channel = client.Channels.Get(channelName);
                await channel.AttachAsync();

                var pushPayload   = JObject.FromObject(new { notification = new { title = "test", body = "message body" }, data = new { foo = "bar" }, });
                var pushRecipient = JObject.FromObject(new
                {
                    transportType = "ablyChannel",
                    channel       = channelName,
                    ablyKey       = client.Options.Key,
                    ablyUrl       = "https://" + client.Options.FullRestHost(),
                });

                var awaiter = new TaskCompletionAwaiter();

                channel.Subscribe(message =>
                {
                    // Assert
                    message.Name.Should().Be("__ably_push__");
                    var payload = JObject.Parse((string)message.Data);
                    payload["data"].Should().BeEquivalentTo(pushPayload["data"]);
                    ((string)payload["notification"]["title"]).Should().Be("test");
                    ((string)payload["notification"]["body"]).Should().Be("message body");

                    awaiter.SetCompleted();
                });

                // Act
                await client.Push.Admin.PublishAsync(pushRecipient, pushPayload);

                // Wait for 10 seconds for awaiter.SetCompleted() and make sure it was called.
                (await awaiter.Task).Should().BeTrue();
            }
Beispiel #14
0
        public async Task WhenDisconnectedMessageContainsTokenError_ForcesClientToReauthenticate(Protocol protocol)
        {
            var authClient = await GetRestClient(protocol);

            var reconnectAwaiter = new TaskCompletionAwaiter(60000);
            var client           = await GetRealtimeClient(protocol, (options, settings) =>
            {
                options.AuthCallback = tokenParams =>
                {
                    var results = authClient.AblyAuth.RequestToken(new TokenParams {
                        ClientId = "RTN22a", Ttl = TimeSpan.FromSeconds(35)
                    });
                    return(Task.FromResult <object>(results));
                };
                options.ClientId = "RTN22a";
            });

            await client.WaitForState(ConnectionState.Connected);

            var initialToken = client.RestClient.AblyAuth.CurrentToken;

            client.Connection.Once(ConnectionEvent.Disconnected, state2 =>
            {
                client.Connection.Once(ConnectionEvent.Connected, state3 =>
                {
                    reconnectAwaiter.SetCompleted();
                });
            });

            client.FakeProtocolMessageReceived(new ProtocolMessage(ProtocolMessage.MessageAction.Disconnected)
            {
                Error = new ErrorInfo("testing RTN22a", ErrorCodes.TokenError)
            });
            var didReconnect = await reconnectAwaiter.Task;

            didReconnect.Should().BeTrue();
            client.RestClient.AblyAuth.CurrentToken.Should().NotBe(initialToken);
            client.Close();
        }
Beispiel #15
0
        public async Task RealTimeWithAuthCallback_WhenTokenExpired_ShouldRetry_WhenRetryFails_ShouldSetError(Protocol protocol)
        {
            // create a short lived token
            var authRestClient = await GetRestClient(protocol);

            var token = await authRestClient.Auth.RequestTokenAsync(new TokenParams
            {
                Ttl = TimeSpan.FromMilliseconds(1000),
            });

            bool didRetry       = false;
            var  realtimeClient = await GetRealtimeClient(protocol, (options, _) =>
            {
                options.TokenDetails = token;
                options.AuthCallback = tokenParams =>
                {
                    didRetry = true;
                    throw new Exception("AuthCallback failed");
                };
                options.AutoConnect = false;
            });

            var awaiter = new TaskCompletionAwaiter(5000);

            realtimeClient.Connection.Once(ConnectionEvent.Disconnected, state =>
            {
                state.Reason.Code.Should().Be(ErrorCodes.ClientAuthProviderRequestFailed);
                awaiter.SetCompleted();
            });

            await Task.Delay(2000);

            realtimeClient.Connect();

            var result = await awaiter.Task;

            result.Should().BeTrue();
            didRetry.Should().BeTrue();
        }
Beispiel #16
0
        public IEnumerator RealtimeWithAuthError_WhenTokenExpired_ShouldRetry_WhenRetryFails_ShouldSetError([ValueSource(nameof(_protocols))] Protocol protocol)
        {
            return(UniTask.ToCoroutine(async() =>
            {
                var helper = new RSA4Helper(this);

                var restClient = await AblySandbox.GetRestClient(protocol);
                var token = await restClient.Auth.AuthorizeAsync(new TokenParams
                {
                    Ttl = TimeSpan.FromMilliseconds(1000),
                });

                // this realtime client will have a key for the sandbox, thus a means to renew
                var realtimeClient = await AblySandbox.GetRealtimeClient(protocol, (options, _) =>
                {
                    options.TokenDetails = token;
                    options.AutoConnect = false;
                });

                realtimeClient.RestClient.ExecuteHttpRequest = helper.AblyResponseWith500Status;

                var awaiter = new TaskCompletionAwaiter(5000);

                realtimeClient.Connection.Once(ConnectionEvent.Disconnected, state =>
                {
                    state.Reason.Code.Should().Be(ErrorCodes.ClientAuthProviderRequestFailed);
                    awaiter.SetCompleted();
                });

                await Task.Delay(2000);
                realtimeClient.Connect();

                var result = await awaiter.Task;
                result.Should().BeTrue();
                helper.Requests.Count.Should().Be(1);
                helper.Requests[0].Url.EndsWith("requestToken").Should().BeTrue();
            }));
        }
Beispiel #17
0
            public async Task SubscribeChannel_ShouldSendARequestTo_PushChannelSubscriptions_WithCorrectParameters()
            {
                const string channelName = "testChannel";
                const string clientId    = "client101";

                var taskAwaiter = new TaskCompletionAwaiter();

                async Task <AblyResponse> RequestHandler(AblyRequest request)
                {
                    request.Url.Should().Be("/push/channelSubscriptions");
                    var postData = (PushChannelSubscription)request.PostData;

                    postData.Should().NotBeNull();
                    postData.Channel.Should().Be(channelName);
                    postData.ClientId.Should().Be(clientId);
                    postData.DeviceId.Should().BeNullOrEmpty();

                    taskAwaiter.SetCompleted();
                    return(new AblyResponse()
                    {
                        TextResponse = JsonConvert.SerializeObject(new PushChannelSubscription())
                    });
                }

                var client = GetRestClient(RequestHandler, mobileDevice: new FakeMobileDevice());

                client.Device = new LocalDevice()
                {
                    ClientId            = clientId,
                    DeviceIdentityToken = "identityToken"
                };
                var pushChannel = client.Channels.Get(channelName).Push;

                await pushChannel.SubscribeClient();

                (await taskAwaiter).Should().BeTrue("Didn't validate function");
            }
Beispiel #18
0
        public IEnumerator Auth_WithRealtimeClient_WhenAuthFails_ShouldTransitionToOrRemainInTheCorrectState([ValueSource(nameof(_protocols))] Protocol protocol)
        {
            return(UniTask.ToCoroutine(async() =>
            {
                async Task TestConnectingBecomesDisconnected(string context, Action <ClientOptions, TestEnvironmentSettings> optionsAction)
                {
                    TaskCompletionAwaiter tca = new TaskCompletionAwaiter(5000);
                    var realtimeClient = await AblySandbox.GetRealtimeClient(protocol, optionsAction);
                    realtimeClient.Connection.On(ConnectionEvent.Disconnected, change =>
                    {
                        change.Previous.Should().Be(ConnectionState.Connecting);
                        change.Reason.Code.Should().Be(ErrorCodes.ClientAuthProviderRequestFailed);
                        tca.SetCompleted();
                    });

                    realtimeClient.Connection.Connect();
                    await realtimeClient.ProcessCommands();

                    (await tca.Task).Should().BeTrue(context);
                }

                // authUrl fails
                void AuthUrlOptions(ClientOptions options, TestEnvironmentSettings settings)
                {
                    options.AutoConnect = false;
                    options.AuthUrl = new Uri(_errorUrl);
                    options.RealtimeRequestTimeout = TimeSpan.FromSeconds(2);
                    options.HttpRequestTimeout = TimeSpan.FromSeconds(2);
                }

                // authCallback fails
                static void AuthCallbackOptions(ClientOptions options, TestEnvironmentSettings settings)
                {
                    options.AutoConnect = false;
                    options.AuthCallback = (tokenParams) => throw new Exception("AuthCallback force error");
                }
            public async Task WhenAClientAttachedToPresenceChannel_ShouldEmitPresentForEachMember(Protocol protocol)
            {
                var channelName = "presence".AddRandomSuffix();

                var clientA = await GetRealtimeClient(protocol);

                await clientA.WaitForState(ConnectionState.Connected);

                clientA.Connection.State.ShouldBeEquivalentTo(ConnectionState.Connected);

                var channelA = clientA.Channels.Get(channelName);

                channelA.Attach();
                await channelA.WaitForState(ChannelState.Attached);

                channelA.State.ShouldBeEquivalentTo(ChannelState.Attached);

                //  enters 250 members on a single connection A
                for (int i = 0; i < ExpectedEnterCount; i++)
                {
                    var clientId = GetClientId(i);
                    await channelA.Presence.EnterClientAsync(clientId, null);
                }

                var clientB = await GetRealtimeClient(protocol);

                await clientB.WaitForState(ConnectionState.Connected);

                clientB.Connection.State.ShouldBeEquivalentTo(ConnectionState.Connected);

                var channelB = clientB.Channels.Get(channelName);

                channelB.Attach();
                await channelB.WaitForState(ChannelState.Attached);

                channelB.State.ShouldBeEquivalentTo(ChannelState.Attached);

                // checks for PRESENT events to be emitted on another connection for each member
                List <PresenceMessage> presenceMessages = new List <PresenceMessage>();
                var awaiter = new TaskCompletionAwaiter(timeoutMs: 200000);

                channelB.Presence.Subscribe(x =>
                {
                    presenceMessages.Add(x);
                    if (presenceMessages.Count == ExpectedEnterCount)
                    {
                        awaiter.SetCompleted();
                    }
                });
                var received250MessagesBeforeTimeout = await awaiter.Task;

                received250MessagesBeforeTimeout.ShouldBeEquivalentTo(true);

                // all 250 members should be present in a Presence#get request
                var messages = await channelB.Presence.GetAsync(new GetOptions { WaitForSync = true });

                var messageList = messages as IList <PresenceMessage> ?? messages.ToList();

                messageList.Count().ShouldBeEquivalentTo(ExpectedEnterCount);
                foreach (var m in messageList)
                {
                    presenceMessages.Select(x => x.ClientId == m.ClientId).Any().Should().BeTrue();
                }

                clientA.Close();
                clientB.Close();
            }
        public async Task WithConnectedClient_WhenDowngradingCapabilities_ChannelShouldBecomeFailed(Protocol protocol)
        {
            var clientId         = "RTC8a1-downgrade".AddRandomSuffix();
            var channelName      = "RTC8a1-downgrade-channel".AddRandomSuffix();
            var wrongChannelName = "wrong".AddRandomSuffix();

            var(realtime, channel) = await SetupRealtimeClient();

            channel.On(statechange => Output.WriteLine($"Changed state: {statechange.Previous} to {statechange.Current}. Error: {statechange.Error}"));
            realtime.Connection.Once(ConnectionEvent.Disconnected, change => throw new Exception("Should not require a disconnect"));

            var result = await channel.PublishAsync("test", "should-not-fail");

            result.IsSuccess.Should().BeTrue();

            ChannelStateChange stateChange = null;

            var failedAwaiter = new TaskCompletionAwaiter(2000);

            channel.Once(ChannelEvent.Failed, state =>
            {
                stateChange = state;
                failedAwaiter.SetCompleted();
            });
            await DowngradeCapability(realtime);

            await channel.WaitForState(ChannelState.Failed, TimeSpan.FromSeconds(6));

            await failedAwaiter.Task;

            stateChange.Should().NotBeNull("channel should have failed");
            stateChange.Error.Code.Should().Be(ErrorCodes.OperationNotPermittedWithCapability);
            stateChange.Error.Message.Should().Contain("Channel denied access");

            async Task DowngradeCapability(AblyRealtime rt)
            {
                var capability = new Capability();

                capability.AddResource(wrongChannelName).AllowSubscribe();

                var newToken = await rt.Auth.AuthorizeAsync(new TokenParams
                {
                    Capability = capability,
                    ClientId   = clientId,
                });

                newToken.Should().NotBeNull();
            }

            async Task <(AblyRealtime, IRealtimeChannel)> SetupRealtimeClient()
            {
                var capability = new Capability();

                capability.AddResource(channelName).AllowAll();

                var restClient = await GetRestClient(protocol);

                var tokenDetails = await restClient.Auth.RequestTokenAsync(new TokenParams
                {
                    ClientId   = clientId,
                    Capability = capability
                });

                var rt = await GetRealtimeClient(protocol, (opts, _) => { opts.Token = tokenDetails.Token; });

                await rt.WaitForState(ConnectionState.Connected);

                var ch = rt.Channels.Get(channelName);
                await ch.AttachAsync();

                return(rt, ch);
            }
        }
        public async Task WithConnectedClient_WhenUpgradingCapabilities_ConnectionShouldNotBeImpaired(Protocol protocol)
        {
            var clientId   = "RTC8a1".AddRandomSuffix();
            var capability = new Capability();

            capability.AddResource("foo").AllowPublish();

            var restClient = await GetRestClient(protocol, options => { options.UseTokenAuth = true; });

            var tokenDetails = await restClient.Auth.RequestTokenAsync(new TokenParams
            {
                ClientId   = clientId,
                Capability = capability,
            });

            var realtime = await GetRealtimeClient(protocol, (opts, _) =>
            {
                opts.Token = tokenDetails.Token;
            });

            await realtime.WaitForState();

            // upgrade of capabilities without any loss of continuity or connectivity
            realtime.Connection.Once(ConnectionEvent.Disconnected, change => throw new Exception("should not disconnect"));

            var fooChannel = realtime.Channels.Get("foo");
            var barChannel = realtime.Channels.Get("bar");

            var fooSuccessAWaiter = new TaskCompletionAwaiter(5000);

            fooChannel.Publish("test", "should-not-fail", (b, info) =>
            {
                // foo should succeed
                b.Should().BeTrue();
                info.Should().BeNull();
                fooSuccessAWaiter.SetCompleted();
            });
            fooChannel.Attach();
            Assert.True(await fooSuccessAWaiter.Task);

            var barFailAwaiter = new TaskCompletionAwaiter(5000);

            barChannel.Publish("test", "should-fail", (b, info) =>
            {
                // bar should fail
                b.Should().BeFalse();
                info.Code.Should().Be(ErrorCodes.OperationNotPermittedWithCapability);
                barFailAwaiter.SetCompleted();
            });
            barChannel.Attach();
            Assert.True(await barFailAwaiter.Task);

            // upgrade bar
            capability = new Capability();
            capability.AddResource("bar").AllowPublish();
            await realtime.Auth.AuthorizeAsync(new TokenParams
            {
                Capability = capability,
                ClientId   = clientId,
            });

            realtime.Connection.State.Should().Be(ConnectionState.Connected);
            var barSuccessAwaiter = new TaskCompletionAwaiter(5000);

            barChannel.Attach((b2, info2) =>
            {
                b2.Should().BeTrue();
                barChannel.Publish("test", "should-succeed", (b, info) =>
                {
                    b.Should().BeTrue();
                    info.Should().BeNull();
                    barSuccessAwaiter.SetCompleted();
                });
            });

            Assert.True(await barSuccessAwaiter.Task);
        }