From 3365837338d7e8fcef9e238f6e9c15e50710cc53 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 13 Nov 2022 15:31:28 +0100 Subject: [PATCH] Updated tests --- .../BaseClientTests.cs | 8 +- CryptoExchange.Net.UnitTests/OptionsTests.cs | 2 +- .../RestClientTests.cs | 20 +-- .../SocketClientTests.cs | 81 +++++++--- .../TestImplementations/TestBaseClient.cs | 23 ++- .../TestImplementations/TestRestClient.cs | 64 +++++--- .../TestImplementations/TestSocketClient.cs | 50 +++--- CryptoExchange.Net/Clients/BaseApiClient.cs | 5 +- CryptoExchange.Net/Clients/RestApiClient.cs | 2 +- CryptoExchange.Net/Clients/SocketApiClient.cs | 2 +- CryptoExchange.Net/Objects/Options.cs | 39 ++++- .../OrderBook/SymbolOrderBook.cs | 10 +- .../Sockets/SocketConnection.cs | 148 +++++++++--------- 13 files changed, 288 insertions(+), 166 deletions(-) diff --git a/CryptoExchange.Net.UnitTests/BaseClientTests.cs b/CryptoExchange.Net.UnitTests/BaseClientTests.cs index 7c6dd88..4e838ae 100644 --- a/CryptoExchange.Net.UnitTests/BaseClientTests.cs +++ b/CryptoExchange.Net.UnitTests/BaseClientTests.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.UnitTests { // arrange var logger = new TestStringLogger(); - var client = new TestBaseClient(new BaseRestClientOptions() + var client = new TestBaseClient(new TestOptions() { LogWriters = new List { logger } }); @@ -56,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests { // arrange var logger = new TestStringLogger(); - var options = new BaseRestClientOptions() + var options = new TestOptions() { LogWriters = new List { logger } }; @@ -78,7 +78,7 @@ namespace CryptoExchange.Net.UnitTests var client = new TestBaseClient(); // act - var result = client.Deserialize("{\"testProperty\": 123}"); + var result = client.SubClient.Deserialize("{\"testProperty\": 123}"); // assert Assert.IsTrue(result.Success); @@ -91,7 +91,7 @@ namespace CryptoExchange.Net.UnitTests var client = new TestBaseClient(); // act - var result = client.Deserialize("{\"testProperty\": 123"); + var result = client.SubClient.Deserialize("{\"testProperty\": 123"); // assert Assert.IsFalse(result.Success); diff --git a/CryptoExchange.Net.UnitTests/OptionsTests.cs b/CryptoExchange.Net.UnitTests/OptionsTests.cs index 7294c1c..1dd0195 100644 --- a/CryptoExchange.Net.UnitTests/OptionsTests.cs +++ b/CryptoExchange.Net.UnitTests/OptionsTests.cs @@ -248,7 +248,7 @@ namespace CryptoExchange.Net.UnitTests } } - public class TestClientOptions: BaseRestClientOptions + public class TestClientOptions: ClientOptions { /// /// Default options for the futures client diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index ca876c3..0ad2480 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests client.SetResponse(JsonConvert.SerializeObject(expected), out _); // act - var result = client.Request().Result; + var result = client.Api1.Request().Result; // assert Assert.IsTrue(result.Success); @@ -43,7 +43,7 @@ namespace CryptoExchange.Net.UnitTests client.SetResponse("{\"property\": 123", out _); // act - var result = client.Request().Result; + var result = client.Api1.Request().Result; // assert Assert.IsFalse(result.Success); @@ -58,7 +58,7 @@ namespace CryptoExchange.Net.UnitTests client.SetErrorWithoutResponse(System.Net.HttpStatusCode.BadRequest, "Invalid request"); // act - var result = await client.Request(); + var result = await client.Api1.Request(); // assert Assert.IsFalse(result.Success); @@ -73,7 +73,7 @@ namespace CryptoExchange.Net.UnitTests client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest); // act - var result = await client.Request(); + var result = await client.Api1.Request(); // assert Assert.IsFalse(result.Success); @@ -91,7 +91,7 @@ namespace CryptoExchange.Net.UnitTests client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest); // act - var result = await client.Request(); + var result = await client.Api2.Request(); // assert Assert.IsFalse(result.Success); @@ -112,9 +112,9 @@ namespace CryptoExchange.Net.UnitTests { BaseAddress = "http://test.address.com", RateLimiters = new List { new RateLimiter() }, - RateLimitingBehaviour = RateLimitingBehaviour.Fail - }, - RequestTimeout = TimeSpan.FromMinutes(1) + RateLimitingBehaviour = RateLimitingBehaviour.Fail, + RequestTimeout = TimeSpan.FromMinutes(1) + } }); @@ -122,7 +122,7 @@ namespace CryptoExchange.Net.UnitTests Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.BaseAddress == "http://test.address.com"); Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimiters.Count == 1); Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimitingBehaviour == RateLimitingBehaviour.Fail); - Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1)); + Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RequestTimeout == TimeSpan.FromMinutes(1)); } [TestCase("GET", HttpMethodParameterPosition.InUri)] // No need to test InBody for GET since thats not valid @@ -148,7 +148,7 @@ namespace CryptoExchange.Net.UnitTests client.SetResponse("{}", out var request); - await client.RequestWithParams(new HttpMethod(method), new Dictionary + await client.Api1.RequestWithParams(new HttpMethod(method), new Dictionary { { "TestParam1", "Value1" }, { "TestParam2", 2 }, diff --git a/CryptoExchange.Net.UnitTests/SocketClientTests.cs b/CryptoExchange.Net.UnitTests/SocketClientTests.cs index 44b9a5d..a09ed77 100644 --- a/CryptoExchange.Net.UnitTests/SocketClientTests.cs +++ b/CryptoExchange.Net.UnitTests/SocketClientTests.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using CryptoExchange.Net.Logging; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Sockets; using CryptoExchange.Net.UnitTests.TestImplementations; @@ -19,17 +20,17 @@ namespace CryptoExchange.Net.UnitTests //act var client = new TestSocketClient(new TestOptions() { - SubOptions = new RestApiClientOptions + SubOptions = new SocketApiClientOptions { - BaseAddress = "http://test.address.com" - }, - ReconnectInterval = TimeSpan.FromSeconds(6) + BaseAddress = "http://test.address.com", + ReconnectInterval = TimeSpan.FromSeconds(6) + } }); //assert Assert.IsTrue(client.SubClient.Options.BaseAddress == "http://test.address.com"); - Assert.IsTrue(client.ClientOptions.ReconnectInterval.TotalSeconds == 6); + Assert.IsTrue(client.SubClient.Options.ReconnectInterval.TotalSeconds == 6); } [TestCase(true)] @@ -42,7 +43,7 @@ namespace CryptoExchange.Net.UnitTests socket.CanConnect = canConnect; //act - var connectResult = client.ConnectSocketSub(new SocketConnection(client, null, socket, null)); + var connectResult = client.SubClient.ConnectSocketSub(new SocketConnection(new Log(""), client.SubClient, socket, null)); //assert Assert.IsTrue(connectResult.Success == canConnect); @@ -52,12 +53,18 @@ namespace CryptoExchange.Net.UnitTests public void SocketMessages_Should_BeProcessedInDataHandlers() { // arrange - var client = new TestSocketClient(new TestOptions() { ReconnectInterval = TimeSpan.Zero, LogLevel = LogLevel.Debug }); + var client = new TestSocketClient(new TestOptions() { + SubOptions = new SocketApiClientOptions + { + ReconnectInterval = TimeSpan.Zero, + }, + LogLevel = LogLevel.Debug + }); var socket = client.CreateSocket(); socket.ShouldReconnect = true; socket.CanConnect = true; socket.DisconnectTime = DateTime.UtcNow; - var sub = new SocketConnection(client, null, socket, null); + var sub = new SocketConnection(new Log(""), client.SubClient, socket, null); var rstEvent = new ManualResetEvent(false); JToken result = null; sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) => @@ -65,7 +72,7 @@ namespace CryptoExchange.Net.UnitTests result = messageEvent.JsonData; rstEvent.Set(); })); - client.ConnectSocketSub(sub); + client.SubClient.ConnectSocketSub(sub); // act socket.InvokeMessage("{\"property\": 123}"); @@ -80,12 +87,19 @@ namespace CryptoExchange.Net.UnitTests public void SocketMessages_Should_ContainOriginalDataIfEnabled(bool enabled) { // arrange - var client = new TestSocketClient(new TestOptions() { ReconnectInterval = TimeSpan.Zero, LogLevel = LogLevel.Debug, OutputOriginalData = enabled }); + var client = new TestSocketClient(new TestOptions() { + SubOptions = new SocketApiClientOptions + { + ReconnectInterval = TimeSpan.Zero, + OutputOriginalData = enabled + }, + LogLevel = LogLevel.Debug, + }); var socket = client.CreateSocket(); socket.ShouldReconnect = true; socket.CanConnect = true; socket.DisconnectTime = DateTime.UtcNow; - var sub = new SocketConnection(client, null, socket, null); + var sub = new SocketConnection(new Log(""), client.SubClient, socket, null); var rstEvent = new ManualResetEvent(false); string original = null; sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) => @@ -93,7 +107,7 @@ namespace CryptoExchange.Net.UnitTests original = messageEvent.OriginalData; rstEvent.Set(); })); - client.ConnectSocketSub(sub); + client.SubClient.ConnectSocketSub(sub); // act socket.InvokeMessage("{\"property\": 123}"); @@ -107,11 +121,18 @@ namespace CryptoExchange.Net.UnitTests public void UnsubscribingStream_Should_CloseTheSocket() { // arrange - var client = new TestSocketClient(new TestOptions() { ReconnectInterval = TimeSpan.Zero, LogLevel = LogLevel.Debug }); + var client = new TestSocketClient(new TestOptions() + { + SubOptions = new SocketApiClientOptions + { + ReconnectInterval = TimeSpan.Zero, + }, + LogLevel = LogLevel.Debug + }); var socket = client.CreateSocket(); socket.CanConnect = true; - var sub = new SocketConnection(client, null, socket, null); - client.ConnectSocketSub(sub); + var sub = new SocketConnection(new Log(""), client.SubClient, socket, null); + client.SubClient.ConnectSocketSub(sub); var us = SocketSubscription.CreateForIdentifier(10, "Test", true, false, (e) => { }); var ups = new UpdateSubscription(sub, us); sub.AddSubscription(us); @@ -127,15 +148,22 @@ namespace CryptoExchange.Net.UnitTests public void UnsubscribingAll_Should_CloseAllSockets() { // arrange - var client = new TestSocketClient(new TestOptions() { ReconnectInterval = TimeSpan.Zero, LogLevel = LogLevel.Debug }); + var client = new TestSocketClient(new TestOptions() + { + SubOptions = new SocketApiClientOptions + { + ReconnectInterval = TimeSpan.Zero, + }, + LogLevel = LogLevel.Debug + }); var socket1 = client.CreateSocket(); var socket2 = client.CreateSocket(); socket1.CanConnect = true; socket2.CanConnect = true; - var sub1 = new SocketConnection(client, null, socket1, null); - var sub2 = new SocketConnection(client, null, socket2, null); - client.ConnectSocketSub(sub1); - client.ConnectSocketSub(sub2); + var sub1 = new SocketConnection(new Log(""), client.SubClient, socket1, null); + var sub2 = new SocketConnection(new Log(""), client.SubClient, socket2, null); + client.SubClient.ConnectSocketSub(sub1); + client.SubClient.ConnectSocketSub(sub2); // act client.UnsubscribeAllAsync().Wait(); @@ -149,13 +177,20 @@ namespace CryptoExchange.Net.UnitTests public void FailingToConnectSocket_Should_ReturnError() { // arrange - var client = new TestSocketClient(new TestOptions() { ReconnectInterval = TimeSpan.Zero, LogLevel = LogLevel.Debug }); + var client = new TestSocketClient(new TestOptions() + { + SubOptions = new SocketApiClientOptions + { + ReconnectInterval = TimeSpan.Zero, + }, + LogLevel = LogLevel.Debug + }); var socket = client.CreateSocket(); socket.CanConnect = false; - var sub = new SocketConnection(client, null, socket, null); + var sub1 = new SocketConnection(new Log(""), client.SubClient, socket, null); // act - var connectResult = client.ConnectSocketSub(sub); + var connectResult = client.SubClient.ConnectSocketSub(sub1); // assert Assert.IsFalse(connectResult.Success); diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs index dc580f1..0b26388 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs @@ -1,19 +1,25 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Threading.Tasks; using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Logging; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.UnitTests.TestImplementations; using Microsoft.Extensions.Logging; namespace CryptoExchange.Net.UnitTests { public class TestBaseClient: BaseClient { - public TestBaseClient(): base("Test", new BaseClientOptions()) + public TestSubClient SubClient { get; } + + public TestBaseClient(): base("Test", new TestOptions()) { + SubClient = AddApiClient(new TestSubClient(new TestOptions(), new RestApiClientOptions())); } - public TestBaseClient(BaseRestClientOptions exchangeOptions) : base("Test", exchangeOptions) + public TestBaseClient(ClientOptions exchangeOptions) : base("Test", exchangeOptions) { } @@ -21,11 +27,20 @@ namespace CryptoExchange.Net.UnitTests { log.Write(verbosity, data); } + } - public CallResult Deserialize(string data) + public class TestSubClient : RestApiClient + { + public TestSubClient(ClientOptions options, RestApiClientOptions apiOptions) : base(new Log(""), options, apiOptions) { - return Deserialize(data, null, null); } + + public CallResult Deserialize(string data) => Deserialize(data, null, null); + + public override TimeSpan GetTimeOffset() => throw new NotImplementedException(); + public override TimeSyncInfo GetTimeSyncInfo() => throw new NotImplementedException(); + protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => throw new NotImplementedException(); + protected override Task> GetServerTimestampAsync() => throw new NotImplementedException(); } public class TestAuthProvider : AuthenticationProvider diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs index 2a6800c..3dbf67b 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; using System.Collections.Generic; +using CryptoExchange.Net.Logging; namespace CryptoExchange.Net.UnitTests.TestImplementations { @@ -28,7 +29,6 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations { Api1 = new TestRestApi1Client(exchangeOptions); Api2 = new TestRestApi2Client(exchangeOptions); - RequestFactory = new Mock().Object; } public void SetResponse(string responseData, out IRequest requestObj) @@ -50,7 +50,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations request.Setup(c => c.AddHeader(It.IsAny(), It.IsAny())).Callback((key, val) => headers.Add(key, new List { val })); request.Setup(c => c.GetHeaders()).Returns(() => headers); - var factory = Mock.Get(RequestFactory); + var factory = Mock.Get(Api1.RequestFactory); factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((method, uri, id) => { @@ -58,6 +58,15 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations request.Setup(a => a.Method).Returns(method); }) .Returns(request.Object); + + factory = Mock.Get(Api2.RequestFactory); + factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((method, uri, id) => + { + request.Setup(a => a.Uri).Returns(uri); + request.Setup(a => a.Method).Returns(method); + }) + .Returns(request.Object); requestObj = request.Object; } @@ -71,7 +80,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations request.Setup(c => c.GetHeaders()).Returns(new Dictionary>()); request.Setup(c => c.GetResponseAsync(It.IsAny())).Throws(we); - var factory = Mock.Get(RequestFactory); + var factory = Mock.Get(Api1.RequestFactory); + factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(request.Object); + + + factory = Mock.Get(Api2.RequestFactory); factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(request.Object); } @@ -94,27 +108,33 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations request.Setup(c => c.AddHeader(It.IsAny(), It.IsAny())).Callback((key, val) => headers.Add(key, new List { val })); request.Setup(c => c.GetHeaders()).Returns(headers); - var factory = Mock.Get(RequestFactory); + var factory = Mock.Get(Api1.RequestFactory); factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((method, uri, id) => request.Setup(a => a.Uri).Returns(uri)) .Returns(request.Object); - } - public async Task> Request(CancellationToken ct = default) where T:class - { - return await SendRequestAsync(Api1, new Uri("http://www.test.com"), HttpMethod.Get, ct); - } - - public async Task> RequestWithParams(HttpMethod method, Dictionary parameters, Dictionary headers) where T : class - { - return await SendRequestAsync(Api1, new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers); + factory = Mock.Get(Api2.RequestFactory); + factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((method, uri, id) => request.Setup(a => a.Uri).Returns(uri)) + .Returns(request.Object); } } public class TestRestApi1Client : RestApiClient { - public TestRestApi1Client(TestClientOptions options): base(options, options.Api1Options) + public TestRestApi1Client(TestClientOptions options): base(new Log(""), options, options.Api1Options) { + RequestFactory = new Mock().Object; + } + + public async Task> Request(CancellationToken ct = default) where T : class + { + return await SendRequestAsync(new Uri("http://www.test.com"), HttpMethod.Get, ct); + } + + public async Task> RequestWithParams(HttpMethod method, Dictionary parameters, Dictionary headers) where T : class + { + return await SendRequestAsync(new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers); } public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position) @@ -143,9 +163,19 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public class TestRestApi2Client : RestApiClient { - public TestRestApi2Client(TestClientOptions options) : base(options, options.Api2Options) + public TestRestApi2Client(TestClientOptions options) : base(new Log(""), options, options.Api2Options) { + RequestFactory = new Mock().Object; + } + public async Task> Request(CancellationToken ct = default) where T : class + { + return await SendRequestAsync(new Uri("http://www.test.com"), HttpMethod.Get, ct); + } + + protected override Error ParseErrorResponse(JToken error) + { + return new ServerError((int)error["errorCode"], (string)error["errorMessage"]); } public override TimeSpan GetTimeOffset() @@ -186,9 +216,5 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public ParseErrorTestRestClient() { } public ParseErrorTestRestClient(TestClientOptions exchangeOptions) : base(exchangeOptions) { } - protected override Error ParseErrorResponse(JToken error) - { - return new ServerError((int)error["errorCode"], (string)error["errorMessage"]); - } } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs index eb99274..0ef2927 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs @@ -20,16 +20,39 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public TestSocketClient(TestOptions exchangeOptions) : base("test", exchangeOptions) { - SubClient = new TestSubSocketClient(exchangeOptions, exchangeOptions.SubOptions); - SocketFactory = new Mock().Object; - Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(new TestSocket()); + SubClient = AddApiClient(new TestSubSocketClient(exchangeOptions, exchangeOptions.SubOptions)); + SubClient.SocketFactory = new Mock().Object; + Mock.Get(SubClient.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(new TestSocket()); } public TestSocket CreateSocket() { - Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(new TestSocket()); - return (TestSocket)CreateSocket("https://localhost:123/"); + Mock.Get(SubClient.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(new TestSocket()); + return (TestSocket)SubClient.CreateSocketInternal("https://localhost:123/"); } + + } + + public class TestOptions: ClientOptions + { + public SocketApiClientOptions SubOptions { get; set; } = new SocketApiClientOptions(); + } + + public class TestSubSocketClient : SocketApiClient + { + + public TestSubSocketClient(ClientOptions options, SocketApiClientOptions apiOptions): base(new Log(""), options, apiOptions) + { + + } + + internal IWebsocket CreateSocketInternal(string address) + { + return CreateSocket(address); + } + + protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) + => new TestAuthProvider(credentials); public CallResult ConnectSocketSub(SocketConnection sub) { @@ -67,21 +90,4 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations throw new NotImplementedException(); } } - - public class TestOptions: BaseSocketClientOptions - { - public ApiClientOptions SubOptions { get; set; } = new ApiClientOptions(); - } - - public class TestSubSocketClient : SocketApiClient - { - - public TestSubSocketClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions) - { - - } - - protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) - => new TestAuthProvider(credentials); - } } diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index 57e6f17..5407929 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -113,12 +113,13 @@ namespace CryptoExchange.Net /// ctor /// /// Logger + /// Client options /// Api client options - protected BaseApiClient(Log log, ApiClientOptions apiOptions) + protected BaseApiClient(Log log, ClientOptions clientOptions, ApiClientOptions apiOptions) { Options = apiOptions; _log = log; - _apiCredentials = apiOptions.ApiCredentials?.Copy(); + _apiCredentials = apiOptions.ApiCredentials?.Copy() ?? clientOptions.ApiCredentials?.Copy(); BaseAddress = apiOptions.BaseAddress; } diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 12f921f..0c4cfbf 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -70,7 +70,7 @@ namespace CryptoExchange.Net /// Logger /// The base client options /// The Api client options - public RestApiClient(Log log, ClientOptions options, RestApiClientOptions apiOptions): base(log, apiOptions) + public RestApiClient(Log log, ClientOptions options, RestApiClientOptions apiOptions): base(log, options, apiOptions) { var rateLimiters = new List(); foreach (var rateLimiter in apiOptions.RateLimiters) diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs index 522c5c1..383bb19 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -117,7 +117,7 @@ namespace CryptoExchange.Net /// log /// Client options /// The Api client options - public SocketApiClient(Log log, ClientOptions options, SocketApiClientOptions apiOptions): base(log, apiOptions) + public SocketApiClient(Log log, ClientOptions options, SocketApiClientOptions apiOptions): base(log, options, apiOptions) { ClientOptions = options; } diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index b00f3d1..533478d 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -9,7 +9,10 @@ using Microsoft.Extensions.Logging; namespace CryptoExchange.Net.Objects { - public class ClientOptions + /// + /// Client options + /// + public abstract class ClientOptions { internal event Action? OnLoggingChanged; @@ -46,16 +49,44 @@ namespace CryptoExchange.Net.Objects /// public ApiProxy? Proxy { get; set; } + /// + /// The api credentials used for signing requests to this API. + /// + public ApiCredentials? ApiCredentials { get; set; } + + /// + /// ctor + /// + public ClientOptions() + { + } + + /// + /// ctor + /// + /// Copy values for the provided options + public ClientOptions(ClientOptions? clientOptions) + { + if (clientOptions == null) + return; + + LogLevel = clientOptions.LogLevel; + LogWriters = clientOptions.LogWriters.ToList(); + Proxy = clientOptions.Proxy; + ApiCredentials = clientOptions.ApiCredentials?.Copy(); + } + /// /// ctor /// /// Copy values for the provided options /// Copy values for the provided options - public ClientOptions(ClientOptions baseOptions, ClientOptions? newValues) + internal ClientOptions(ClientOptions baseOptions, ClientOptions? newValues) { Proxy = newValues?.Proxy ?? baseOptions.Proxy; LogLevel = baseOptions.LogLevel; LogWriters = baseOptions.LogWriters.ToList(); + ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy(); } /// @@ -112,7 +143,7 @@ namespace CryptoExchange.Net.Objects { BaseAddress = newValues?.BaseAddress ?? baseOptions.BaseAddress; ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy(); - OutputOriginalData = baseOptions.OutputOriginalData; + OutputOriginalData = newValues?.OutputOriginalData ?? baseOptions.OutputOriginalData; } /// @@ -281,7 +312,7 @@ namespace CryptoExchange.Net.Objects /// /// Base for order book options /// - public class OrderBookOptions : ApiClientOptions + public class OrderBookOptions : ClientOptions { /// /// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages. diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs index 0c9dda7..d6be9e7 100644 --- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs +++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs @@ -40,7 +40,7 @@ namespace CryptoExchange.Net.OrderBook set { } } } - private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry(); + private static readonly ISymbolOrderBookEntry _emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry(); /// @@ -167,7 +167,7 @@ namespace CryptoExchange.Net.OrderBook get { lock (_bookLock) - return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; + return bids.FirstOrDefault().Value ?? _emptySymbolOrderBookEntry; } } @@ -177,7 +177,7 @@ namespace CryptoExchange.Net.OrderBook get { lock (_bookLock) - return asks.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; + return asks.FirstOrDefault().Value ?? _emptySymbolOrderBookEntry; } } @@ -581,7 +581,9 @@ namespace CryptoExchange.Net.OrderBook var (bestBid, bestAsk) = BestOffers; if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity || bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity) + { OnBestOffersChanged?.Invoke((bestBid, bestAsk)); + } } private void Reset() @@ -752,7 +754,9 @@ namespace CryptoExchange.Net.OrderBook _ = _subscription!.ReconnectAsync(); } else + { await ResyncAsync().ConfigureAwait(false); + } }); } diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs index a33fd09..ab4050e 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -53,8 +53,8 @@ namespace CryptoExchange.Net.Sockets /// public int SubscriptionCount { - get { lock (subscriptionLock) - return subscriptions.Count(h => h.UserSubscription); } + get { lock (_subscriptionLock) + return _subscriptions.Count(h => h.UserSubscription); } } /// @@ -64,8 +64,8 @@ namespace CryptoExchange.Net.Sockets { get { - lock (subscriptionLock) - return subscriptions.Where(h => h.UserSubscription).ToArray(); + lock (_subscriptionLock) + return _subscriptions.Where(h => h.UserSubscription).ToArray(); } } @@ -114,14 +114,14 @@ namespace CryptoExchange.Net.Sockets /// public bool PausedActivity { - get => pausedActivity; + get => _pausedActivity; set { - if (pausedActivity != value) + if (_pausedActivity != value) { - pausedActivity = value; - log.Write(LogLevel.Information, $"Socket {SocketId} Paused activity: " + value); - if(pausedActivity) _ = Task.Run(() => ActivityPaused?.Invoke()); + _pausedActivity = value; + _log.Write(LogLevel.Information, $"Socket {SocketId} Paused activity: " + value); + if(_pausedActivity) _ = Task.Run(() => ActivityPaused?.Invoke()); else _ = Task.Run(() => ActivityUnpaused?.Invoke()); } } @@ -140,17 +140,17 @@ namespace CryptoExchange.Net.Sockets var oldStatus = _status; _status = value; - log.Write(LogLevel.Debug, $"Socket {SocketId} status changed from {oldStatus} to {_status}"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} status changed from {oldStatus} to {_status}"); } } - private bool pausedActivity; - private readonly List subscriptions; - private readonly object subscriptionLock = new(); + private bool _pausedActivity; + private readonly List _subscriptions; + private readonly object _subscriptionLock = new(); - private readonly Log log; + private readonly Log _log; - private readonly List pendingRequests; + private readonly List _pendingRequests; private SocketStatus _status; @@ -168,12 +168,12 @@ namespace CryptoExchange.Net.Sockets /// public SocketConnection(Log log, SocketApiClient apiClient, IWebsocket socket, string tag) { - this.log = log; + this._log = log; ApiClient = apiClient; Tag = tag; - pendingRequests = new List(); - subscriptions = new List(); + _pendingRequests = new List(); + _subscriptions = new List(); _socket = socket; _socket.OnMessage += HandleMessage; @@ -201,9 +201,9 @@ namespace CryptoExchange.Net.Sockets { Status = SocketStatus.Closed; Authenticated = false; - lock(subscriptionLock) + lock(_subscriptionLock) { - foreach (var sub in subscriptions) + foreach (var sub in _subscriptions) sub.Confirmed = false; } Task.Run(() => ConnectionClosed?.Invoke()); @@ -217,9 +217,9 @@ namespace CryptoExchange.Net.Sockets Status = SocketStatus.Reconnecting; DisconnectTime = DateTime.UtcNow; Authenticated = false; - lock (subscriptionLock) + lock (_subscriptionLock) { - foreach (var sub in subscriptions) + foreach (var sub in _subscriptions) sub.Confirmed = false; } @@ -232,7 +232,7 @@ namespace CryptoExchange.Net.Sockets /// protected virtual async Task GetReconnectionUrlAsync() { - return await ApiClient.GetReconnectUriAsync(ApiClient, this).ConfigureAwait(false); + return await ApiClient.GetReconnectUriAsync(this).ConfigureAwait(false); } /// @@ -241,18 +241,20 @@ namespace CryptoExchange.Net.Sockets protected virtual async void HandleReconnected() { Status = SocketStatus.Resubscribing; - lock (pendingRequests) + lock (_pendingRequests) { - foreach (var pendingRequest in pendingRequests.ToList()) + foreach (var pendingRequest in _pendingRequests.ToList()) { pendingRequest.Fail(); - pendingRequests.Remove(pendingRequest); + _pendingRequests.Remove(pendingRequest); } } var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false); if (!reconnectSuccessful) + { await _socket.ReconnectAsync().ConfigureAwait(false); + } else { Status = SocketStatus.Connected; @@ -271,9 +273,9 @@ namespace CryptoExchange.Net.Sockets protected virtual void HandleError(Exception e) { if (e is WebSocketException wse) - log.Write(LogLevel.Warning, $"Socket {SocketId} error: Websocket error code {wse.WebSocketErrorCode}, details: " + e.ToLogString()); + _log.Write(LogLevel.Warning, $"Socket {SocketId} error: Websocket error code {wse.WebSocketErrorCode}, details: " + e.ToLogString()); else - log.Write(LogLevel.Warning, $"Socket {SocketId} error: " + e.ToLogString()); + _log.Write(LogLevel.Warning, $"Socket {SocketId} error: " + e.ToLogString()); } /// @@ -283,14 +285,14 @@ namespace CryptoExchange.Net.Sockets protected virtual void HandleMessage(string data) { var timestamp = DateTime.UtcNow; - log.Write(LogLevel.Trace, $"Socket {SocketId} received data: " + data); + _log.Write(LogLevel.Trace, $"Socket {SocketId} received data: " + data); if (string.IsNullOrEmpty(data)) return; - var tokenData = data.ToJToken(log); + var tokenData = data.ToJToken(_log); if (tokenData == null) { data = $"\"{data}\""; - tokenData = data.ToJToken(log); + tokenData = data.ToJToken(_log); if (tokenData == null) return; } @@ -299,10 +301,10 @@ namespace CryptoExchange.Net.Sockets // Remove any timed out requests PendingRequest[] requests; - lock (pendingRequests) + lock (_pendingRequests) { - pendingRequests.RemoveAll(r => r.Completed); - requests = pendingRequests.ToArray(); + _pendingRequests.RemoveAll(r => r.Completed); + requests = _pendingRequests.ToArray(); } // Check if this message is an answer on any pending requests @@ -310,8 +312,8 @@ namespace CryptoExchange.Net.Sockets { if (pendingRequest.CheckData(tokenData)) { - lock (pendingRequests) - pendingRequests.Remove(pendingRequest); + lock (_pendingRequests) + _pendingRequests.Remove(pendingRequest); if (!ApiClient.ContinueOnQueryResponse) return; @@ -327,16 +329,18 @@ namespace CryptoExchange.Net.Sockets if (!handled && !handledResponse) { if (!ApiClient.UnhandledMessageExpected) - log.Write(LogLevel.Warning, $"Socket {SocketId} Message not handled: " + tokenData); + _log.Write(LogLevel.Warning, $"Socket {SocketId} Message not handled: " + tokenData); UnhandledMessage?.Invoke(tokenData); } var total = DateTime.UtcNow - timestamp; if (userProcessTime.TotalMilliseconds > 500) - log.Write(LogLevel.Debug, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processing slow ({(int)total.TotalMilliseconds}ms, {(int)userProcessTime.TotalMilliseconds}ms user code), consider offloading data handling to another thread. " + + { + _log.Write(LogLevel.Debug, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processing slow ({(int)total.TotalMilliseconds}ms, {(int)userProcessTime.TotalMilliseconds}ms user code), consider offloading data handling to another thread. " + "Data from this socket may arrive late or not at all if message processing is continuously slow."); + } - log.Write(LogLevel.Trace, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)"); + _log.Write(LogLevel.Trace, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)"); } /// @@ -369,9 +373,9 @@ namespace CryptoExchange.Net.Sockets if (ApiClient.socketConnections.ContainsKey(SocketId)) ApiClient.socketConnections.TryRemove(SocketId, out _); - lock (subscriptionLock) + lock (_subscriptionLock) { - foreach (var subscription in subscriptions) + foreach (var subscription in _subscriptions) { if (subscription.CancellationTokenRegistration.HasValue) subscription.CancellationTokenRegistration.Value.Dispose(); @@ -389,9 +393,9 @@ namespace CryptoExchange.Net.Sockets /// public async Task CloseAsync(SocketSubscription subscription) { - lock (subscriptionLock) + lock (_subscriptionLock) { - if (!subscriptions.Contains(subscription)) + if (!_subscriptions.Contains(subscription)) return; subscription.Closed = true; @@ -400,7 +404,7 @@ namespace CryptoExchange.Net.Sockets if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed) return; - log.Write(LogLevel.Debug, $"Socket {SocketId} closing subscription {subscription.Id}"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} closing subscription {subscription.Id}"); if (subscription.CancellationTokenRegistration.HasValue) subscription.CancellationTokenRegistration.Value.Dispose(); @@ -408,27 +412,27 @@ namespace CryptoExchange.Net.Sockets await ApiClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false); bool shouldCloseConnection; - lock (subscriptionLock) + lock (_subscriptionLock) { if (Status == SocketStatus.Closing) { - log.Write(LogLevel.Debug, $"Socket {SocketId} already closing"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} already closing"); return; } - shouldCloseConnection = subscriptions.All(r => !r.UserSubscription || r.Closed); + shouldCloseConnection = _subscriptions.All(r => !r.UserSubscription || r.Closed); if (shouldCloseConnection) Status = SocketStatus.Closing; } if (shouldCloseConnection) { - log.Write(LogLevel.Debug, $"Socket {SocketId} closing as there are no more subscriptions"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} closing as there are no more subscriptions"); await CloseAsync().ConfigureAwait(false); } - lock (subscriptionLock) - subscriptions.Remove(subscription); + lock (_subscriptionLock) + _subscriptions.Remove(subscription); } /// @@ -446,14 +450,14 @@ namespace CryptoExchange.Net.Sockets /// public bool AddSubscription(SocketSubscription subscription) { - lock (subscriptionLock) + lock (_subscriptionLock) { if (Status != SocketStatus.None && Status != SocketStatus.Connected) return false; - subscriptions.Add(subscription); + _subscriptions.Add(subscription); if(subscription.UserSubscription) - log.Write(LogLevel.Debug, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {subscriptions.Count(s => s.UserSubscription)}"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {_subscriptions.Count(s => s.UserSubscription)}"); return true; } } @@ -464,8 +468,8 @@ namespace CryptoExchange.Net.Sockets /// public SocketSubscription? GetSubscription(int id) { - lock (subscriptionLock) - return subscriptions.SingleOrDefault(s => s.Id == id); + lock (_subscriptionLock) + return _subscriptions.SingleOrDefault(s => s.Id == id); } /// @@ -475,8 +479,8 @@ namespace CryptoExchange.Net.Sockets /// public SocketSubscription? GetSubscriptionByRequest(Func predicate) { - lock(subscriptionLock) - return subscriptions.SingleOrDefault(s => predicate(s.Request)); + lock(_subscriptionLock) + return _subscriptions.SingleOrDefault(s => predicate(s.Request)); } /// @@ -494,8 +498,8 @@ namespace CryptoExchange.Net.Sockets // Loop the subscriptions to check if any of them signal us that the message is for them List subscriptionsCopy; - lock (subscriptionLock) - subscriptionsCopy = subscriptions.ToList(); + lock (_subscriptionLock) + subscriptionsCopy = _subscriptions.ToList(); foreach (var subscription in subscriptionsCopy) { @@ -529,7 +533,7 @@ namespace CryptoExchange.Net.Sockets } catch (Exception ex) { - log.Write(LogLevel.Error, $"Socket {SocketId} Exception during message processing\r\nException: {ex.ToLogString()}\r\nData: {messageEvent.JsonData}"); + _log.Write(LogLevel.Error, $"Socket {SocketId} Exception during message processing\r\nException: {ex.ToLogString()}\r\nData: {messageEvent.JsonData}"); currentSubscription?.InvokeExceptionHandler(ex); return (false, TimeSpan.Zero, null); } @@ -546,9 +550,9 @@ namespace CryptoExchange.Net.Sockets public virtual Task SendAndWaitAsync(T obj, TimeSpan timeout, Func handler) { var pending = new PendingRequest(handler, timeout); - lock (pendingRequests) + lock (_pendingRequests) { - pendingRequests.Add(pending); + _pendingRequests.Add(pending); } var sendOk = Send(obj); if(!sendOk) @@ -577,7 +581,7 @@ namespace CryptoExchange.Net.Sockets /// The data to send public virtual bool Send(string data) { - log.Write(LogLevel.Trace, $"Socket {SocketId} sending data: {data}"); + _log.Write(LogLevel.Trace, $"Socket {SocketId} sending data: {data}"); try { _socket.Send(data); @@ -595,36 +599,36 @@ namespace CryptoExchange.Net.Sockets return new CallResult(new WebError("Socket not connected")); bool anySubscriptions = false; - lock (subscriptionLock) - anySubscriptions = subscriptions.Any(s => s.UserSubscription); + lock (_subscriptionLock) + anySubscriptions = _subscriptions.Any(s => s.UserSubscription); if (!anySubscriptions) { // No need to resubscribe anything - log.Write(LogLevel.Debug, $"Socket {SocketId} Nothing to resubscribe, closing connection"); + _log.Write(LogLevel.Debug, $"Socket {SocketId} Nothing to resubscribe, closing connection"); _ = _socket.CloseAsync(); return new CallResult(true); } - if (subscriptions.Any(s => s.Authenticated)) + if (_subscriptions.Any(s => s.Authenticated)) { // If we reconnected a authenticated connection we need to re-authenticate var authResult = await ApiClient.AuthenticateSocketAsync(this).ConfigureAwait(false); if (!authResult) { - log.Write(LogLevel.Warning, $"Socket {SocketId} authentication failed on reconnected socket. Disconnecting and reconnecting."); + _log.Write(LogLevel.Warning, $"Socket {SocketId} authentication failed on reconnected socket. Disconnecting and reconnecting."); return authResult; } Authenticated = true; - log.Write(LogLevel.Debug, $"Socket {SocketId} authentication succeeded on reconnected socket."); + _log.Write(LogLevel.Debug, $"Socket {SocketId} authentication succeeded on reconnected socket."); } // Get a list of all subscriptions on the socket List subscriptionList = new List(); - lock (subscriptionLock) + lock (_subscriptionLock) { - foreach (var subscription in subscriptions) + foreach (var subscription in _subscriptions) { if (subscription.Request != null) subscriptionList.Add(subscription); @@ -654,7 +658,7 @@ namespace CryptoExchange.Net.Sockets if (!_socket.IsOpen) return new CallResult(new WebError("Socket not connected")); - log.Write(LogLevel.Debug, $"Socket {SocketId} all subscription successfully resubscribed on reconnected socket."); + _log.Write(LogLevel.Debug, $"Socket {SocketId} all subscription successfully resubscribed on reconnected socket."); return new CallResult(true); }