diff --git a/CryptoExchange.Net.UnitTests/AsyncResetEventTests.cs b/CryptoExchange.Net.UnitTests/AsyncResetEventTests.cs index 8fb13bc..672cfb3 100644 --- a/CryptoExchange.Net.UnitTests/AsyncResetEventTests.cs +++ b/CryptoExchange.Net.UnitTests/AsyncResetEventTests.cs @@ -96,7 +96,7 @@ namespace CryptoExchange.Net.UnitTests waiters.Add(evnt.WaitAsync()); } - List results = null; + List? results = null; var resultsWaiter = Task.Run(async () => { await Task.WhenAll(waiters); @@ -112,7 +112,7 @@ namespace CryptoExchange.Net.UnitTests await resultsWaiter; - Assert.That(10 == results.Count(r => r)); + Assert.That(10 == results?.Count(r => r)); } [Test] diff --git a/CryptoExchange.Net.UnitTests/BodySerializationTests.cs b/CryptoExchange.Net.UnitTests/BodySerializationTests.cs new file mode 100644 index 0000000..97947a1 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/BodySerializationTests.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using CryptoExchange.Net; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Converters.SystemTextJson; + +namespace CryptoExchange.Net.UnitTests +{ + internal class BodySerializationTests + { + [Test] + public void ToFormData_SerializesBasicValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", "1" }, + { "b", 2 }, + { "c", true } + }; + + var parameterString = parameters.ToFormData(); + + Assert.That(parameterString, Is.EqualTo("a=1&b=2&c=True")); + } + + [Test] + public void JsonSerializer_SerializesBasicValuesCorrectly() + { + var serializer = new SystemTextJsonMessageSerializer(SerializerOptions.WithConverters(new TestSerializerContext())); + var parameters = new Dictionary() + { + { "a", "1" }, + { "b", 2 }, + { "c", true } + }; + + var parameterString = serializer.Serialize(parameters); + Assert.That(parameterString, Is.EqualTo("{\"a\":\"1\",\"b\":2,\"c\":true}")); + } + } +} diff --git a/CryptoExchange.Net.UnitTests/CallResultTests.cs b/CryptoExchange.Net.UnitTests/CallResultTests.cs index a0daea0..27860bb 100644 --- a/CryptoExchange.Net.UnitTests/CallResultTests.cs +++ b/CryptoExchange.Net.UnitTests/CallResultTests.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.UnitTests { var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown)); - ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); + ClassicAssert.AreSame(result.Error!.ErrorCode, "TestError"); ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result.Success); } @@ -36,7 +36,7 @@ namespace CryptoExchange.Net.UnitTests { var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown)); - ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); + ClassicAssert.AreSame(result.Error!.ErrorCode, "TestError"); ClassicAssert.IsNull(result.Data); ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result.Success); @@ -73,7 +73,7 @@ namespace CryptoExchange.Net.UnitTests var asResult = result.As(default); ClassicAssert.IsNotNull(asResult.Error); - ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError"); + ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError"); ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult.Success); @@ -86,7 +86,7 @@ namespace CryptoExchange.Net.UnitTests var asResult = result.AsError(new ServerError("TestError2", ErrorInfo.Unknown)); ClassicAssert.IsNotNull(asResult.Error); - ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); + ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError2"); ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult.Success); @@ -99,7 +99,7 @@ namespace CryptoExchange.Net.UnitTests var asResult = result.AsError(new ServerError("TestError2", ErrorInfo.Unknown)); ClassicAssert.IsNotNull(asResult.Error); - ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); + ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError2"); ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult.Success); @@ -126,7 +126,7 @@ namespace CryptoExchange.Net.UnitTests var asResult = result.AsError(new ServerError("TestError2", ErrorInfo.Unknown)); ClassicAssert.IsNotNull(asResult.Error); - Assert.That(asResult.Error.ErrorCode == "TestError2"); + Assert.That(asResult.Error!.ErrorCode == "TestError2"); Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1)); Assert.That(asResult.RequestUrl == "https://test.com/api"); diff --git a/CryptoExchange.Net.UnitTests/ClientTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/ClientTests/RestClientTests.cs index ef12157..effa519 100644 --- a/CryptoExchange.Net.UnitTests/ClientTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/ClientTests/RestClientTests.cs @@ -97,7 +97,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests ClassicAssert.IsFalse(result.Success); Assert.That(result.Error != null); Assert.That(result.Error is DeserializeError); - Assert.That(result.Error.Message.Contains(response)); + Assert.That(result.Error!.Message!.Contains(response)); } [TestCase] @@ -114,7 +114,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests ClassicAssert.IsFalse(result.Success); Assert.That(result.Error != null); Assert.That(result.Error is ServerError); - Assert.That(result.Error.ErrorCode == "123"); + Assert.That(result.Error!.ErrorCode == "123"); Assert.That(result.Error.Message == "Invalid request"); } @@ -186,12 +186,12 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); - bool expected = i == 1 ? expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; + bool expected = i == 1 ? expectLimiting ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } } @@ -208,7 +208,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get); var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); @@ -251,12 +251,12 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); - bool expected = i == 1 ? expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; + bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } } @@ -271,12 +271,12 @@ namespace CryptoExchange.Net.UnitTests.ClientTests rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new ExactPathsFilter(new[] { "/sapi/test", "/sapi/test2" }), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed)); var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); - bool expected = i == 1 ? expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; + bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } } @@ -295,7 +295,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get) { Authenticated = key1 != null }; var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = key2 != null }; - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, null, default); @@ -314,7 +314,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get); var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = true }; - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); @@ -334,7 +334,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get); var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = true }; - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, null, default); @@ -351,7 +351,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var rateLimiter = new RateLimitGate("Test"); rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new LimitItemTypeFilter(RateLimitItemType.Connection), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed)); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, null, default); @@ -366,7 +366,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests var rateLimiter = new RateLimitGate("Test"); rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new LimitItemTypeFilter(RateLimitItemType.Connection), 1, TimeSpan.FromSeconds(10), RateLimitWindowType.Fixed)); - RateLimitEvent evnt = null; + RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2)); diff --git a/CryptoExchange.Net.UnitTests/ClientTests/SocketClientTests.cs b/CryptoExchange.Net.UnitTests/ClientTests/SocketClientTests.cs index 9a7f2ea..3d26f5a 100644 --- a/CryptoExchange.Net.UnitTests/ClientTests/SocketClientTests.cs +++ b/CryptoExchange.Net.UnitTests/ClientTests/SocketClientTests.cs @@ -4,6 +4,7 @@ using CryptoExchange.Net.UnitTests.Implementations; using CryptoExchange.Net.UnitTests.TestImplementations; using NUnit.Framework; using System; +using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -27,8 +28,24 @@ namespace CryptoExchange.Net.UnitTests Assert.That(1 == client.ApiClient1.ApiOptions.MaxSocketConnections); } + [TestCase(true)] + [TestCase(false)] + public async Task ConnectSocket_Should_ReturnConnectionResult(bool canConnect) + { + //arrange + var client = new TestSocketClient(); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + socket.CanConnect = canConnect; + + //act + var connectResult = await client.ApiClient1.SubscribeToUpdatesAsync(x => { }, false, default); + + //assert + Assert.That(connectResult.Success == canConnect); + } + [TestCase] - public async Task Test() + public async Task SocketMessages_Should_BeProcessedInDataHandlers() { var client = new TestSocketClient(); var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); @@ -43,209 +60,119 @@ namespace CryptoExchange.Net.UnitTests { received = x.Data; resetEvent.Set(); - }, default); + }, false, default); socket.InvokeMessage(strData); - await resetEvent.WaitAsync(TimeSpan.FromSeconds(1)); Assert.That(received != null); } - //[TestCase(true)] - //[TestCase(false)] - //public void ConnectSocket_Should_ReturnConnectionResult(bool canConnect) - //{ - // //arrange - // var client = new TestSocketClient(); - // var socket = client.CreateSocket(); - // socket.CanConnect = canConnect; + [TestCase(false)] + [TestCase(true)] + public async Task SocketMessages_Should_ContainOriginalDataIfEnabled(bool enabled) + { + // arrange + var client = new TestSocketClient(options => + { + options.ReconnectInterval = TimeSpan.Zero; + options.ExchangeOptions.OutputOriginalData = enabled; + }); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" }; + var strData = JsonSerializer.Serialize(expected, new JsonSerializerOptions { TypeInfoResolver = new TestSerializerContext() }); - // //act - // var connectResult = client.SubClient.ConnectSocketSub( - // new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, "")); + string? originalData = null; + var resetEvent = new AsyncResetEvent(false); - // //assert - // Assert.That(connectResult.Success == canConnect); - //} + await client.ApiClient1.SubscribeToUpdatesAsync(x => + { + originalData = x.OriginalData; + resetEvent.Set(); + }, false, default); - //[TestCase] - //public void SocketMessages_Should_BeProcessedInDataHandlers() - //{ - // // arrange - // var client = new TestSocketClient(options => - // { - // options.ReconnectInterval = TimeSpan.Zero; - // }); - // var socket = client.CreateSocket(); - // socket.CanConnect = true; - // var sub = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); - // var rstEvent = new ManualResetEvent(false); - // Dictionary result = null; + socket.InvokeMessage(strData); + await resetEvent.WaitAsync(TimeSpan.FromSeconds(1)); - // client.SubClient.ConnectSocketSub(sub); + // assert + Assert.That(originalData == (enabled ? strData : null)); + } - // var subObj = new TestSubscription>(Mock.Of(), (messageEvent) => - // { - // result = messageEvent.Data; - // rstEvent.Set(); - // }); - // sub.AddSubscription(subObj); + [TestCase()] + public async Task UnsubscribingStream_Should_CloseTheSocket() + { + // arrange + var client = new TestSocketClient(options => + { + options.ReconnectInterval = TimeSpan.Zero; + }); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); - // // act - // socket.InvokeMessage("{\"property\": \"123\", \"action\": \"update\", \"topic\": \"topic\"}"); - // rstEvent.WaitOne(1000); + var result = await client.ApiClient1.SubscribeToUpdatesAsync(x => {}, false, default); - // // assert - // Assert.That(result["property"] == "123"); - //} + // act + await client.UnsubscribeAsync(result.Data); - //[TestCase(false)] - //[TestCase(true)] - //public void SocketMessages_Should_ContainOriginalDataIfEnabled(bool enabled) - //{ - // // arrange - // var client = new TestSocketClient(options => - // { - // options.ReconnectInterval = TimeSpan.Zero; - // options.SubOptions.OutputOriginalData = enabled; - // }); - // var socket = client.CreateSocket(); - // socket.CanConnect = true; - // var sub = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); - // var rstEvent = new ManualResetEvent(false); - // string original = null; + // assert + Assert.That(socket.Connected == false); + } - // client.SubClient.ConnectSocketSub(sub); - // var subObj = new TestSubscription>(Mock.Of(), (messageEvent) => - // { - // original = messageEvent.OriginalData; - // rstEvent.Set(); - // }); - // sub.AddSubscription(subObj); - // var msgToSend = JsonSerializer.Serialize(new { topic = "topic", action = "update", property = "123" }); + [TestCase()] + public async Task UnsubscribingAll_Should_CloseAllSockets() + { + // arrange + var client = new TestSocketClient(options => + { + options.ReconnectInterval = TimeSpan.Zero; + }); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + var result = await client.ApiClient1.SubscribeToUpdatesAsync(x => { }, false, default); - // // act - // socket.InvokeMessage(msgToSend); - // rstEvent.WaitOne(1000); + var socket2 = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + var result2 = await client.ApiClient1.SubscribeToUpdatesAsync(x => { }, false, default); - // // assert - // Assert.That(original == (enabled ? msgToSend : null)); - //} + // act + await client.UnsubscribeAllAsync(); - //[TestCase()] - //public void UnsubscribingStream_Should_CloseTheSocket() - //{ - // // arrange - // var client = new TestSocketClient(options => - // { - // options.ReconnectInterval = TimeSpan.Zero; - // }); - // var socket = client.CreateSocket(); - // socket.CanConnect = true; - // var sub = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); - // client.SubClient.ConnectSocketSub(sub); + // assert + Assert.That(socket.Connected == false); + Assert.That(socket2.Connected == false); + } - // var subscription = new TestSubscription>(Mock.Of(), (messageEvent) => { }); - // var ups = new UpdateSubscription(sub, subscription); - // sub.AddSubscription(subscription); + [TestCase()] + public async Task ErrorResponse_ShouldNot_ConfirmSubscription() + { + // arrange + var client = new TestSocketClient(opt => + { + opt.OutputOriginalData = true; + }); - // // act - // client.UnsubscribeAsync(ups).Wait(); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + var subTask = client.ApiClient1.SubscribeToUpdatesAsync(x => { }, true, default); - // // assert - // Assert.That(socket.Connected == false); - //} + socket.InvokeMessage(JsonSerializer.Serialize(new TestSocketMessage { Id = 1, Data = "ErrorWithSub" })); - //[TestCase()] - //public void UnsubscribingAll_Should_CloseAllSockets() - //{ - // // arrange - // var client = new TestSocketClient(options => { options.ReconnectInterval = TimeSpan.Zero; }); - // var socket1 = client.CreateSocket(); - // var socket2 = client.CreateSocket(); - // socket1.CanConnect = true; - // socket2.CanConnect = true; - // var sub1 = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket1), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); - // var sub2 = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket2), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); - // client.SubClient.ConnectSocketSub(sub1); - // client.SubClient.ConnectSocketSub(sub2); - // var subscription1 = new TestSubscription>(Mock.Of(), (messageEvent) => { }); - // var subscription2 = new TestSubscription>(Mock.Of(), (messageEvent) => { }); + var result = await subTask; - // sub1.AddSubscription(subscription1); - // sub2.AddSubscription(subscription2); - // var ups1 = new UpdateSubscription(sub1, subscription1); - // var ups2 = new UpdateSubscription(sub2, subscription2); + // assert + Assert.That(result.Success == false); + Assert.That(result.Error!.Message!.Contains("ErrorWithSub")); + } - // // act - // client.UnsubscribeAllAsync().Wait(); + [TestCase()] + public async Task SuccessResponse_Should_ConfirmSubscription() + { + var client = new TestSocketClient(); + var socket = TestHelpers.ConfigureSocketClient(client, "wss://localhost"); + var subTask = client.ApiClient1.SubscribeToUpdatesAsync(x => { }, true, default); - // // assert - // Assert.That(socket1.Connected == false); - // Assert.That(socket2.Connected == false); - //} + socket.InvokeMessage(JsonSerializer.Serialize(new TestSocketMessage { Id = 1, Data = "OK" })); - //[TestCase()] - //public void FailingToConnectSocket_Should_ReturnError() - //{ - // // arrange - // var client = new TestSocketClient(options => { options.ReconnectInterval = TimeSpan.Zero; }); - // var socket = client.CreateSocket(); - // socket.CanConnect = false; - // var sub1 = new SocketConnection(new TraceLogger(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, ""); + var result = await subTask; - // // act - // var connectResult = client.SubClient.ConnectSocketSub(sub1); - - // // assert - // ClassicAssert.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(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, "")); - - // // act - // var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => { }, ct: default); - // socket.InvokeMessage(JsonSerializer.Serialize(new { channel, action = "subscribe", status = "error" })); - // await sub; - - // // assert - // ClassicAssert.IsTrue(client.SubClient.TestSubscription.Status != SubscriptionStatus.Subscribed); - //} - - //[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(), new TestWebsocketFactory(socket), new WebSocketParameters(new Uri("https://localhost/"), ReconnectPolicy.Disabled), client.SubClient, "")); - - // // act - // var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => { }, ct: default); - // socket.InvokeMessage(JsonSerializer.Serialize(new { channel, action = "subscribe", status = "confirmed" })); - // await sub; - - // // assert - // Assert.That(client.SubClient.TestSubscription.Status == SubscriptionStatus.Subscribed); - //} + var subscription = client.ApiClient1._socketConnections.Single().Value.Subscriptions.Single(); + Assert.That(subscription.Status == SubscriptionStatus.Subscribed); + } } } diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/ArrayConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/ArrayConverterTests.cs index fa822e8..6069359 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/ArrayConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/ArrayConverterTests.cs @@ -48,18 +48,18 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests var serialized = JsonSerializer.Serialize(data); var deserialized = JsonSerializer.Deserialize(serialized); - Assert.That(deserialized.Prop1, Is.EqualTo(2)); + Assert.That(deserialized!.Prop1, Is.EqualTo(2)); Assert.That(deserialized.Prop2, Is.Null); Assert.That(deserialized.Prop3, Is.EqualTo("123")); Assert.That(deserialized.Prop3Again, Is.EqualTo("123")); Assert.That(deserialized.Prop4, Is.Null); - Assert.That(deserialized.Prop5.Prop21, Is.EqualTo(3)); - Assert.That(deserialized.Prop5.Prop22, Is.EqualTo("456")); - Assert.That(deserialized.Prop6.Prop31, Is.EqualTo(4)); + Assert.That(deserialized.Prop5!.Prop21, Is.EqualTo(3)); + Assert.That(deserialized.Prop5!.Prop22, Is.EqualTo("456")); + Assert.That(deserialized.Prop6!.Prop31, Is.EqualTo(4)); Assert.That(deserialized.Prop6.Prop32, Is.EqualTo("789")); Assert.That(deserialized.Prop7, Is.EqualTo(TestEnum.Two)); - Assert.That(deserialized.TestInternal.Prop1, Is.EqualTo(10)); - Assert.That(deserialized.Prop8.Prop31, Is.EqualTo(5)); + Assert.That(deserialized.TestInternal!.Prop1, Is.EqualTo(10)); + Assert.That(deserialized.Prop8!.Prop31, Is.EqualTo(5)); Assert.That(deserialized.Prop8.Prop32, Is.EqualTo("101")); } } @@ -72,21 +72,21 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests [ArrayProperty(1)] public int? Prop2 { get; set; } [ArrayProperty(2)] - public string Prop3 { get; set; } + public string? Prop3 { get; set; } [ArrayProperty(2)] - public string Prop3Again { get; set; } + public string? Prop3Again { get; set; } [ArrayProperty(3)] - public string Prop4 { get; set; } + public string? Prop4 { get; set; } [ArrayProperty(4)] - public Test2 Prop5 { get; set; } + public Test2? Prop5 { get; set; } [ArrayProperty(5)] - public Test3 Prop6 { get; set; } + public Test3? Prop6 { get; set; } [ArrayProperty(6), JsonConverter(typeof(EnumConverter))] public TestEnum? Prop7 { get; set; } [ArrayProperty(7)] - public Test TestInternal { get; set; } + public Test? TestInternal { get; set; } [ArrayProperty(8), JsonConversion] - public Test3 Prop8 { get; set; } + public Test3? Prop8 { get; set; } } [JsonConverter(typeof(ArrayConverter))] @@ -95,7 +95,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests [ArrayProperty(0)] public int Prop21 { get; set; } [ArrayProperty(1)] - public string Prop22 { get; set; } + public string? Prop22 { get; set; } } public record Test3 @@ -103,7 +103,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests [JsonPropertyName("prop31")] public int Prop31 { get; set; } [JsonPropertyName("prop32")] - public string Prop32 { get; set; } + public string? Prop32 { get; set; } } } diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/BoolConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/BoolConverterTests.cs index e0bc121..b65d1ed 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/BoolConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/BoolConverterTests.cs @@ -22,7 +22,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests { var val = value == null ? "null" : $"\"{value}\""; var output = JsonSerializer.Deserialize($"{{ \"Value\": {val} }}", SerializerOptions.WithConverters(new TestSerializerContext())); - Assert.That(output.Value == expected); + Assert.That(output!.Value == expected); } [TestCase("1", true)] @@ -41,7 +41,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests { var val = value == null ? "null" : $"\"{value}\""; var output = JsonSerializer.Deserialize($"{{ \"Value\": {val} }}", SerializerOptions.WithConverters(new TestSerializerContext())); - Assert.That(output.Value == expected); + Assert.That(output!.Value == expected); } } diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/DateTimeConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/DateTimeConverterTests.cs index 92ed666..4892a05 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/DateTimeConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/DateTimeConverterTests.cs @@ -22,7 +22,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDateTimeConverterString(string input, bool expectNull = false) { var output = JsonSerializer.Deserialize($"{{ \"time\": \"{input}\" }}"); - Assert.That(output.Time == (expectNull ? null : new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); + Assert.That(output!.Time == (expectNull ? null : new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); } [TestCase(1620777600.000)] @@ -30,7 +30,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDateTimeConverterDouble(double input) { var output = JsonSerializer.Deserialize($"{{ \"time\": {input} }}"); - Assert.That(output.Time == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(output!.Time == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); } [TestCase(1620777600)] @@ -41,7 +41,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDateTimeConverterLong(long input, bool expectNull = false) { var output = JsonSerializer.Deserialize($"{{ \"time\": {input} }}"); - Assert.That(output.Time == (expectNull ? null : new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); + Assert.That(output!.Time == (expectNull ? null : new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); } [TestCase(1620777600)] @@ -106,7 +106,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDateTimeConverterNull() { var output = JsonSerializer.Deserialize($"{{ \"time\": null }}"); - Assert.That(output.Time == null); + Assert.That(output!.Time == null); } } diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/DecimalConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/DecimalConverterTests.cs index 4503b01..b7302d7 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/DecimalConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/DecimalConverterTests.cs @@ -23,7 +23,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDecimalConverterString(string value, decimal? expected) { var result = JsonSerializer.Deserialize("{ \"test\": \"" + value + "\"}"); - Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MinValue : expected == 999 ? decimal.MaxValue : expected)); + Assert.That(result!.Test, Is.EqualTo(expected == -999 ? decimal.MinValue : expected == 999 ? decimal.MaxValue : expected)); } [TestCase("1", 1)] @@ -36,7 +36,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests public void TestDecimalConverterNumber(string value, decimal? expected) { var result = JsonSerializer.Deserialize("{ \"test\": " + value + "}"); - Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MaxValue : expected)); + Assert.That(result!.Test, Is.EqualTo(expected == -999 ? decimal.MaxValue : expected)); } } diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/EnumConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/EnumConverterTests.cs index 18a427e..ee9b1d8 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/EnumConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/EnumConverterTests.cs @@ -45,7 +45,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests { var val = value == null ? "null" : $"\"{value}\""; var output = JsonSerializer.Deserialize($"{{ \"Value\": {val} }}", SerializerOptions.WithConverters(new TestSerializerContext())); - Assert.That(output.Value == expected); + Assert.That(output!.Value == expected); } [TestCase("1", TestEnum.One)] @@ -60,7 +60,7 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests { var val = value == null ? "null" : $"\"{value}\""; var output = JsonSerializer.Deserialize($"{{ \"Value\": {val} }}"); - Assert.That(output.Value == expected); + Assert.That(output!.Value == expected); } [Test] diff --git a/CryptoExchange.Net.UnitTests/ConverterTests/SharedModelConversionTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests/SharedModelConversionTests.cs index e910885..d90e33f 100644 --- a/CryptoExchange.Net.UnitTests/ConverterTests/SharedModelConversionTests.cs +++ b/CryptoExchange.Net.UnitTests/ConverterTests/SharedModelConversionTests.cs @@ -20,7 +20,7 @@ namespace CryptoExchange.Net.UnitTests var serialized = JsonSerializer.Serialize(symbol); var restored = JsonSerializer.Deserialize(serialized); - Assert.That(restored.TradingMode, Is.EqualTo(symbol.TradingMode)); + Assert.That(restored!.TradingMode, Is.EqualTo(symbol.TradingMode)); Assert.That(restored.BaseAsset, Is.EqualTo(symbol.BaseAsset)); Assert.That(restored.QuoteAsset, Is.EqualTo(symbol.QuoteAsset)); Assert.That(restored.DeliverTime, Is.EqualTo(symbol.DeliverTime)); @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.UnitTests var serialized = JsonSerializer.Serialize(symbol); var restored = JsonSerializer.Deserialize(serialized); - Assert.That(restored.QuantityInBaseAsset, Is.EqualTo(symbol.QuantityInBaseAsset)); + Assert.That(restored!.QuantityInBaseAsset, Is.EqualTo(symbol.QuantityInBaseAsset)); Assert.That(restored.QuantityInQuoteAsset, Is.EqualTo(symbol.QuantityInQuoteAsset)); Assert.That(restored.QuantityInContracts, Is.EqualTo(symbol.QuantityInContracts)); } diff --git a/CryptoExchange.Net.UnitTests/ExchangeSymbolCacheTests.cs b/CryptoExchange.Net.UnitTests/ExchangeSymbolCacheTests.cs index c11c41b..61dda9e 100644 --- a/CryptoExchange.Net.UnitTests/ExchangeSymbolCacheTests.cs +++ b/CryptoExchange.Net.UnitTests/ExchangeSymbolCacheTests.cs @@ -326,7 +326,7 @@ namespace CryptoExchange.Net.UnitTests // assert Assert.That(result, Is.Not.Null); - Assert.That(result.BaseAsset, Is.EqualTo("BTC")); + Assert.That(result!.BaseAsset, Is.EqualTo("BTC")); Assert.That(result.QuoteAsset, Is.EqualTo("USDT")); Assert.That(result.TradingMode, Is.EqualTo(TradingMode.Spot)); Assert.That(result.SymbolName, Is.EqualTo("BTCUSDT")); diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestQuery.cs b/CryptoExchange.Net.UnitTests/Implementations/TestQuery.cs new file mode 100644 index 0000000..4d762dd --- /dev/null +++ b/CryptoExchange.Net.UnitTests/Implementations/TestQuery.cs @@ -0,0 +1,24 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Errors; +using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.Default; +using System; + +namespace CryptoExchange.Net.UnitTests.Implementations +{ + internal class TestQuery : Query + { + public TestQuery(TestSocketMessage request, bool authenticated) : base(request, authenticated, 1) + { + MessageRouter = MessageRouter.CreateWithoutTopicFilter(request.Id.ToString(), HandleMessage); + } + + private CallResult? HandleMessage(SocketConnection connection, DateTime time, string? arg3, TestSocketMessage message) + { + if (message.Data != "OK") + return new CallResult(new ServerError(ErrorInfo.Unknown with { Message = message.Data })); + + return CallResult.SuccessResult; + } + } +} diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestSerializerContext.cs b/CryptoExchange.Net.UnitTests/Implementations/TestSerializerContext.cs index dd1b403..b68f3c7 100644 --- a/CryptoExchange.Net.UnitTests/Implementations/TestSerializerContext.cs +++ b/CryptoExchange.Net.UnitTests/Implementations/TestSerializerContext.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.UnitTests.ConverterTests; +using CryptoExchange.Net.UnitTests.Implementations; using CryptoExchange.Net.UnitTests.TestImplementations; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -13,6 +14,7 @@ namespace CryptoExchange.Net.UnitTests [JsonSerializable(typeof(IDictionary))] [JsonSerializable(typeof(TestObject))] + [JsonSerializable(typeof(TestSocketMessage))] [JsonSerializable(typeof(Test))] [JsonSerializable(typeof(Test2))] [JsonSerializable(typeof(Test3))] diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestSocketApiClient.cs b/CryptoExchange.Net.UnitTests/Implementations/TestSocketApiClient.cs index 1bdc8d0..6f4a034 100644 --- a/CryptoExchange.Net.UnitTests/Implementations/TestSocketApiClient.cs +++ b/CryptoExchange.Net.UnitTests/Implementations/TestSocketApiClient.cs @@ -36,9 +36,9 @@ namespace CryptoExchange.Net.UnitTests.Implementations protected override TestAuthenticationProvider CreateAuthenticationProvider(TestCredentials credentials) => new TestAuthenticationProvider(credentials); - public async Task> SubscribeToUpdatesAsync(Action> handler, CancellationToken ct) + public async Task> SubscribeToUpdatesAsync(Action> handler, bool subQuery, CancellationToken ct) { - return await base.SubscribeAsync(new TestSubscription(_logger, handler, false), ct); + return await base.SubscribeAsync(new TestSubscription(_logger, handler, subQuery, false), ct); } } } diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessage.cs b/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessage.cs new file mode 100644 index 0000000..5a38b6c --- /dev/null +++ b/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessage.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace CryptoExchange.Net.UnitTests.Implementations +{ + internal record TestSocketMessage + { + [JsonPropertyName("id")] + public int Id { get; set; } + [JsonPropertyName("data")] + public string Data { get; set; } = string.Empty; + } +} diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessageHandler.cs b/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessageHandler.cs index 201c053..9acc48b 100644 --- a/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessageHandler.cs +++ b/CryptoExchange.Net.UnitTests/Implementations/TestSocketMessageHandler.cs @@ -15,8 +15,15 @@ namespace CryptoExchange.Net.UnitTests.Implementations protected override MessageTypeDefinition[] TypeEvaluators { get; } = [ - new MessageTypeDefinition { + new MessageTypeDefinition { ForceIfFound = true, + Fields = [ + new PropertyFieldReference("id") + ], + TypeIdentifierCallback = (doc) => doc.FieldValue("id")! + }, + + new MessageTypeDefinition { Fields = [ ], StaticIdentifier = "test" diff --git a/CryptoExchange.Net.UnitTests/Implementations/TestSubscription.cs b/CryptoExchange.Net.UnitTests/Implementations/TestSubscription.cs index a8cf1c5..6aa3b4b 100644 --- a/CryptoExchange.Net.UnitTests/Implementations/TestSubscription.cs +++ b/CryptoExchange.Net.UnitTests/Implementations/TestSubscription.cs @@ -13,16 +13,31 @@ namespace CryptoExchange.Net.UnitTests.Implementations internal class TestSubscription : Subscription { private readonly Action> _handler; + private bool _subQuery; - public TestSubscription(ILogger logger, Action> handler, bool authenticated) : base(logger, authenticated, true) + public TestSubscription(ILogger logger, Action> handler, bool subQuery, bool authenticated) : base(logger, authenticated, true) { _handler = handler; + _subQuery = subQuery; MessageRouter = MessageRouter.CreateWithoutTopicFilter("test", HandleUpdate); } - protected override Query? GetSubQuery(SocketConnection connection) => null; - protected override Query? GetUnsubQuery(SocketConnection connection) => null; + protected override Query? GetSubQuery(SocketConnection connection) + { + if (!_subQuery) + return null; + + return new TestQuery(new TestSocketMessage { Id = 1, Data = "Sub" }, false); + } + + protected override Query? GetUnsubQuery(SocketConnection connection) + { + if (!_subQuery) + return null; + + return new TestQuery(new TestSocketMessage { Id = 2, Data = "Unsub" }, false); + } private CallResult? HandleUpdate(SocketConnection connection, DateTime time, string? originalData, T data) diff --git a/CryptoExchange.Net.UnitTests/ParameterCollectionTests.cs b/CryptoExchange.Net.UnitTests/ParameterCollectionTests.cs new file mode 100644 index 0000000..9853c6a --- /dev/null +++ b/CryptoExchange.Net.UnitTests/ParameterCollectionTests.cs @@ -0,0 +1,331 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.UnitTests.ConverterTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.UnitTests +{ + internal class ParameterCollectionTests + { + [Test] + public void AddingBasicValue_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.Add("test", "value"); + Assert.That(parameters["test"], Is.EqualTo("value")); + } + + [Test] + public void AddingBasicNullValue_ThrowExecption() + { + var parameters = new ParameterCollection(); + Assert.Throws(() => parameters.Add("test", null!)); + } + + [Test] + public void AddingOptionalBasicValue_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptional("test", "value"); + Assert.That(parameters["test"], Is.EqualTo("value")); + } + + [Test] + public void AddingOptionalBasicNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptional("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingDecimalValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddString("test", 0.1m); + Assert.That(parameters["test"], Is.EqualTo("0.1")); + } + + [Test] + public void AddingOptionalDecimalValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", 0.1m); + Assert.That(parameters["test"], Is.EqualTo("0.1")); + } + + [Test] + public void AddingOptionalDecimalNullValueAsString_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", (decimal?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingIntValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddString("test", 1); + Assert.That(parameters["test"], Is.EqualTo("1")); + } + + [Test] + public void AddingOptionalIntValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", 1); + Assert.That(parameters["test"], Is.EqualTo("1")); + } + + [Test] + public void AddingOptionalIntNullValueAsString_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", (int?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingLongValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddString("test", 1L); + Assert.That(parameters["test"], Is.EqualTo("1")); + } + + [Test] + public void AddingOptionalLongValueAsString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", 1L); + Assert.That(parameters["test"], Is.EqualTo("1")); + } + + [Test] + public void AddingOptionalLongNullValueAsString_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalString("test", (long?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingMillisecondTimestamp_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddMilliseconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo(1735689600000)); + } + + [Test] + public void AddingOptionalMillisecondTimestamp_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalMilliseconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo(1735689600000)); + } + + [Test] + public void AddingOptionalMillisecondNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalMilliseconds("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingMillisecondTimestampString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddMillisecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo("1735689600000")); + } + + [Test] + public void AddingOptionalMillisecondTimestampString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalMillisecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo("1735689600000")); + } + + [Test] + public void AddingOptionalMillisecondStringNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalMillisecondsString("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingSecondTimestamp_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddSeconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo(1735689600)); + } + + [Test] + public void AddingOptionalSecondTimestamp_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalSeconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo(1735689600)); + } + + [Test] + public void AddingSecondNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalSeconds("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingSecondTimestampString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddSecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo("1735689600")); + } + + [Test] + public void AddingOptionalSecondTimestampString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalSecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.That(parameters["test"], Is.EqualTo("1735689600")); + } + + [Test] + public void AddingSecondStringNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalSecondsString("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingEnum_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddEnum("test", TestEnum.Two); + Assert.That(parameters["test"], Is.EqualTo("2")); + } + + [Test] + public void AddingOptionalEnum_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalEnum("test", (TestEnum?)TestEnum.Two); + Assert.That(parameters["test"], Is.EqualTo("2")); + } + + [Test] + public void AddingOptionalEnumNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalEnum("test", (TestEnum?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingEnumAsInt_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddEnumAsInt("test", TestEnum.Two); + Assert.That(parameters["test"], Is.EqualTo(2)); + } + + [Test] + public void AddingOptionalEnumAsInt_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalEnumAsInt("test", (TestEnum?)TestEnum.Two); + Assert.That(parameters["test"], Is.EqualTo(2)); + } + + [Test] + public void AddingOptionalEnumAsIntNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalEnumAsInt("test", (TestEnum?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingCommaSeparated_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddCommaSeparated("test", ["1", "2"]); + Assert.That(parameters["test"], Is.EqualTo("1,2")); + } + + [Test] + public void AddingOptionalCommaSeparated_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalCommaSeparated("test", ["1", "2"]); + Assert.That(parameters["test"], Is.EqualTo("1,2")); + } + + [Test] + public void AddingOptionalCommaSeparatedNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalCommaSeparated("test", (string[]?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingCommaSeparatedEnum_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddCommaSeparated("test", [TestEnum.Two, TestEnum.One]); + Assert.That(parameters["test"], Is.EqualTo("2,1")); + } + + [Test] + public void AddingOptionalCommaSeparatedEnum_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalCommaSeparated("test", [TestEnum.Two, TestEnum.One]); + Assert.That(parameters["test"], Is.EqualTo("2,1")); + } + + [Test] + public void AddingOptionalCommaSeparatedEnumNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalCommaSeparated("test", (TestEnum[]?)null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + + [Test] + public void AddingBoolString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddBoolString("test", true); + Assert.That(parameters["test"], Is.EqualTo("true")); + } + + [Test] + public void AddingOptionalBoolString_SetValueCorrectly() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalBoolString("test", true); + Assert.That(parameters["test"], Is.EqualTo("true")); + } + + [Test] + public void AddingOptionalBoolStringNullValue_DoesntSetValue() + { + var parameters = new ParameterCollection(); + parameters.AddOptionalBoolString("test", null); + Assert.That(parameters.ContainsKey("test"), Is.False); + } + } +} diff --git a/CryptoExchange.Net.UnitTests/UriSerializationTests.cs b/CryptoExchange.Net.UnitTests/UriSerializationTests.cs new file mode 100644 index 0000000..8ff70d4 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/UriSerializationTests.cs @@ -0,0 +1,105 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using CryptoExchange.Net; +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.UnitTests +{ + internal class UriSerializationTests + { + [Test] + public void CreateParamString_SerializesBasicValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", "1" }, + { "b", 2 }, + { "c", true } + }; + + var parameterString = parameters.CreateParamString(false, ArrayParametersSerialization.Array); + + Assert.That(parameterString, Is.EqualTo("a=1&b=2&c=True")); + } + + [Test] + public void CreateParamString_SerializesArrayValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1", "2" } }, + }; + + var parameterString = parameters.CreateParamString(false, ArrayParametersSerialization.Array); + + Assert.That(parameterString, Is.EqualTo("a[]=1&a[]=2")); + } + + [Test] + public void CreateParamStringEncoded_SerializesArrayValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1+2", "2+3" } }, + }; + + var parameterString = parameters.CreateParamString(true, ArrayParametersSerialization.Array); + + Assert.That(parameterString, Is.EqualTo("a[]=1%2B2&a[]=2%2B3")); + } + + [Test] + public void CreateParamString_SerializesJsonArrayValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1", "2" } }, + }; + + var parameterString = parameters.CreateParamString(false, ArrayParametersSerialization.JsonArray); + + Assert.That(parameterString, Is.EqualTo("a=[1,2]")); + } + + [Test] + public void CreateParamStringEncoded_SerializesJsonArrayValuesCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1+2", "2+3" } }, + }; + + var parameterString = parameters.CreateParamString(true, ArrayParametersSerialization.JsonArray); + + Assert.That(parameterString, Is.EqualTo("a=[1%2B2,2%2B3]")); + } + + [Test] + public void CreateParamString_SerializesMultipleValuesArrayCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1", "2" } }, + }; + + var parameterString = parameters.CreateParamString(false, ArrayParametersSerialization.MultipleValues); + + Assert.That(parameterString, Is.EqualTo("a=1&a=2")); + } + + [Test] + public void CreateParamStringEncoded_SerializesMultipleValuesArrayCorrectly() + { + var parameters = new Dictionary() + { + { "a", new [] { "1+2", "2+3" } }, + }; + + var parameterString = parameters.CreateParamString(true, ArrayParametersSerialization.MultipleValues); + + Assert.That(parameterString, Is.EqualTo("a=1%2B2&a=2%2B3")); + } + } +} diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index c8d1693..452dce8 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -107,7 +107,7 @@ namespace CryptoExchange.Net } else { - uriString.Append('['); + uriString.Append($"{parameter.Key}=["); var firstArrayEntry = true; foreach (var entry in (Array)parameter.Value) { diff --git a/CryptoExchange.Net/Objects/ParameterCollection.cs b/CryptoExchange.Net/Objects/ParameterCollection.cs index d49f8fa..723fb6f 100644 --- a/CryptoExchange.Net/Objects/ParameterCollection.cs +++ b/CryptoExchange.Net/Objects/ParameterCollection.cs @@ -278,6 +278,35 @@ namespace CryptoExchange.Net.Objects base.Add(key, string.Join(",", values)); } + /// + /// Add key as comma separated values + /// +#if NET5_0_OR_GREATER + public void AddCommaSeparated<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string key, IEnumerable values) +#else + public void AddCommaSeparated(string key, IEnumerable values) +#endif + where T : struct, Enum + { + base.Add(key, string.Join(",", values.Select(x => EnumConverter.GetString(x)))); + } + + /// + /// Add key as comma separated values if there are values provided + /// +#if NET5_0_OR_GREATER + public void AddOptionalCommaSeparated<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string key, IEnumerable? values) +#else + public void AddOptionalCommaSeparated(string key, IEnumerable? values) +#endif + where T : struct, Enum + { + if (values == null || !values.Any()) + return; + + base.Add(key, string.Join(",", values.Select(x => EnumConverter.GetString(x)))); + } + /// /// Add key as boolean lower case value ///