From 462c857bba47f6354391edb7195c3ee7afd751e0 Mon Sep 17 00:00:00 2001 From: Jonnern <10881387+Jonnern@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:34:14 +0100 Subject: [PATCH] Add tests for subscription confirmation (#191) * Add tests for subscription confirmation --- .../SocketClientTests.cs | 54 +++++++++++++++++-- .../Sockets/TestChannelQuery.cs | 45 ++++++++++++++++ .../TestSubscriptionWithResponseCheck.cs | 38 +++++++++++++ .../TestImplementations/TestSocketClient.cs | 28 +++++++++- 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs create mode 100644 CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs diff --git a/CryptoExchange.Net.UnitTests/SocketClientTests.cs b/CryptoExchange.Net.UnitTests/SocketClientTests.cs index 0092caa0..b4fa97f1 100644 --- a/CryptoExchange.Net.UnitTests/SocketClientTests.cs +++ b/CryptoExchange.Net.UnitTests/SocketClientTests.cs @@ -9,9 +9,8 @@ using CryptoExchange.Net.UnitTests.TestImplementations.Sockets; using Microsoft.Extensions.Logging; using Moq; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using NUnit.Framework; -using NUnit.Framework.Constraints; namespace CryptoExchange.Net.UnitTests { @@ -106,13 +105,14 @@ public async Task SocketMessages_Should_ContainOriginalDataIfEnabled(bool enable original = messageEvent.OriginalData; rstEvent.Set(); })); + var msgToSend = JsonConvert.SerializeObject(new { topic = "topic", property = 123 }); // act - await socket.InvokeMessage("{\"property\": 123}"); + await socket.InvokeMessage(msgToSend); rstEvent.WaitOne(1000); // assert - Assert.IsTrue(original == (enabled ? "{\"property\": 123}" : null)); + Assert.IsTrue(original == (enabled ? msgToSend : null)); } [TestCase()] @@ -183,5 +183,51 @@ public void FailingToConnectSocket_Should_ReturnError() // assert Assert.IsFalse(connectResult.Success); } + + [TestCase()] + public async Task ErrorResponse_ShouldNot_ConfirmSubscription() + { + // arrange + var channel = "trade_btcusd"; + var client = new TestSocketClient(opt => + { + opt.OutputOriginalData = true; + opt.SocketSubscriptionsCombineTarget = 1; + }); + var socket = client.CreateSocket(); + socket.CanConnect = true; + client.SubClient.ConnectSocketSub(new SocketConnection(new TraceLogger(), client.SubClient, socket, "https://test.test")); + + // act + var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => {}, ct: default); + await socket.InvokeMessage(JsonConvert.SerializeObject(new { channel, status = "error" })); + await sub; + + // assert + Assert.IsFalse(client.SubClient.TestSubscription.Confirmed); + } + + [TestCase()] + public async Task SuccessResponse_Should_ConfirmSubscription() + { + // arrange + var channel = "trade_btcusd"; + var client = new TestSocketClient(opt => + { + opt.OutputOriginalData = true; + opt.SocketSubscriptionsCombineTarget = 1; + }); + var socket = client.CreateSocket(); + socket.CanConnect = true; + client.SubClient.ConnectSocketSub(new SocketConnection(new TraceLogger(), client.SubClient, socket, "https://test.test")); + + // act + var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => {}, ct: default); + await socket.InvokeMessage(JsonConvert.SerializeObject(new { channel, status = "confirmed" })); + await sub; + + // assert + Assert.IsTrue(client.SubClient.TestSubscription.Confirmed); + } } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs new file mode 100644 index 00000000..ad618e15 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs @@ -0,0 +1,45 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets +{ + internal class SubResponse + { + [JsonProperty("channel")] + public string Channel { get; set; } = null!; + + [JsonProperty("status")] + public string Status { get; set; } = null!; + } + + internal class UnsubResponse + { + [JsonProperty("status")] + public string Status { get; set; } = null!; + } + + internal class TestChannelQuery : Query + { + public override HashSet ListenerIdentifiers { get; set; } + + public TestChannelQuery(string channel, string request, bool authenticated, int weight = 1) : base(request, authenticated, weight) + { + ListenerIdentifiers = new HashSet { channel }; + } + + public override Task> HandleMessageAsync(SocketConnection connection, DataEvent message) + { + if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase)) + { + return Task.FromResult(new CallResult(new ServerError(message.Data.Status))); + } + + return base.HandleMessageAsync(connection, message); + } + } +} diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs new file mode 100644 index 00000000..0789a97d --- /dev/null +++ b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; +using Microsoft.Extensions.Logging; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets +{ + internal class TestSubscriptionWithResponseCheck : Subscription + { + private readonly Action> _handler; + private readonly string _channel; + + public override HashSet ListenerIdentifiers { get; set; } + + public TestSubscriptionWithResponseCheck(string channel, Action> handler) : base(Mock.Of(), false) + { + ListenerIdentifiers = new HashSet() { channel }; + _handler = handler; + _channel = channel; + } + + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) + { + var data = (T)message.Data; + _handler.Invoke(message.As(data)); + return Task.FromResult(new CallResult(null)); + } + + public override Type GetMessageType(IMessageAccessor message) => typeof(T); + public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1); + public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1); + } +} diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs index eb9a3588..ce5d04dd 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; @@ -7,7 +8,9 @@ using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.MessageParsing; using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; +using CryptoExchange.Net.UnitTests.TestImplementations.Sockets; using Microsoft.Extensions.Logging; using Moq; using Newtonsoft.Json.Linq; @@ -71,8 +74,12 @@ public class TestSocketOptions: SocketExchangeOptions public class TestSubSocketClient : SocketApiClient { + private MessagePath _channelPath = MessagePath.Get().Property("channel"); + private MessagePath _topicPath = MessagePath.Get().Property("topic"); - public TestSubSocketClient(TestSocketOptions options, SocketApiOptions apiOptions): base(new TraceLogger(), options.Environment.TestAddress, options, apiOptions) + public Subscription TestSubscription { get; private set; } = null; + + public TestSubSocketClient(TestSocketOptions options, SocketApiOptions apiOptions) : base(new TraceLogger(), options.Environment.TestAddress, options, apiOptions) { } @@ -90,6 +97,23 @@ public CallResult ConnectSocketSub(SocketConnection sub) return ConnectSocketAsync(sub).Result; } - public override string GetListenerIdentifier(IMessageAccessor messageAccessor) => "topic"; + public override string GetListenerIdentifier(IMessageAccessor message) + { + if (!message.IsJson) + { + return "topic"; + } + + var id = message.GetValue(_channelPath); + id ??= message.GetValue(_topicPath); + + return id; + } + + public Task> SubscribeToSomethingAsync(string channel, Action> onUpdate, CancellationToken ct) + { + TestSubscription = new TestSubscriptionWithResponseCheck(channel, onUpdate); + return SubscribeAsync(TestSubscription, ct); + } } }