diff --git a/CryptoExchange.Net.UnitTests/ExchangeClientTests.cs b/CryptoExchange.Net.UnitTests/ExchangeClientTests.cs deleted file mode 100644 index 9c4dc2b..0000000 --- a/CryptoExchange.Net.UnitTests/ExchangeClientTests.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using CryptoExchange.Net.Authentication; -using CryptoExchange.Net.Interfaces; -using CryptoExchange.Net.Logging; -using CryptoExchange.Net.Objects; -using CryptoExchange.Net.RateLimiter; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; - -namespace CryptoExchange.Net.UnitTests -{ - [TestFixture()] - public class ExchangeClientTests - { - //[TestCase(null, null)] - //[TestCase("", "")] - //[TestCase("test", null)] - //[TestCase("test", "")] - //[TestCase(null, "test")] - //[TestCase("", "test")] - //public void SettingEmptyValuesForAPICredentials_Should_ThrowException(string key, string secret) - //{ - // // arrange - // var client = PrepareClient(""); - - // // act - // // assert - // Assert.Throws(typeof(ArgumentException), () => client.SetApiCredentails(key, secret)); - //} - - //[TestCase()] - //public void SettingLogOutput_Should_RedirectLogOutput() - //{ - // // arrange - // var stringBuilder = new StringBuilder(); - // var client = PrepareClient("{}", true, LogVerbosity.Debug, new StringWriter(stringBuilder)); - - // // act - // client.TestCall(); - - // // assert - // Assert.IsFalse(string.IsNullOrEmpty(stringBuilder.ToString())); - //} - - //[TestCase()] - //public void ObjectDeserializationFail_Should_GiveFailedResult() - //{ - // // arrange - // var errorMessage = "TestErrorMessage"; - // var client = PrepareClient(JsonConvert.SerializeObject(errorMessage)); - - // // act - // var result = client.TestCall(); - - // // assert - // Assert.IsFalse(result.Success); - // Assert.AreNotEqual(0, result.Error.Code); - // Assert.IsTrue(result.Error.Message.Contains(errorMessage)); - //} - - //[TestCase()] - //public void InvalidJson_Should_GiveFailedResult() - //{ - // // arrange - // var errorMessage = "TestErrorMessage"; - // var client = PrepareClient(JsonConvert.SerializeObject(errorMessage)); - - // // act - // var result = client.TestCall(); - - // // assert - // Assert.IsFalse(result.Success); - // Assert.AreNotEqual(0, result.Error.Code); - // Assert.IsTrue(result.Error.Message.Contains(errorMessage)); - //} - - //[TestCase()] - //public void WhenUsingRateLimiterTotalRequests_Should_BeDelayed() - //{ - // // arrange - // var client = PrepareClient(JsonConvert.SerializeObject(new TestObject())); - // client.AddRateLimiter(new RateLimiterTotal(1, TimeSpan.FromSeconds(5))); - - // // act - // var sw = Stopwatch.StartNew(); - // client.TestCall(); - // client.TestCall(); - // client.TestCall(); - // sw.Stop(); - - // // assert - // Assert.IsTrue(sw.ElapsedMilliseconds > 9000); - //} - - //[TestCase()] - //public void WhenUsingRateLimiterPerEndpointRequests_Should_BeDelayed() - //{ - // // arrange - // var client = PrepareClient(JsonConvert.SerializeObject(new TestObject())); - // client.AddRateLimiter(new RateLimiterTotal(1, TimeSpan.FromSeconds(5))); - - // // act - // var sw = Stopwatch.StartNew(); - // client.TestCall(); - // client.TestCall(); - // client.TestCall(); - // sw.Stop(); - - // // assert - // Assert.IsTrue(sw.ElapsedMilliseconds > 9000); - //} - - //[TestCase()] - //public void WhenRemovingRateLimiterRequest_Should_NoLongerBeDelayed() - //{ - // // arrange - // var client = PrepareClient(JsonConvert.SerializeObject(new TestObject())); - // client.AddRateLimiter(new RateLimiterTotal(1, TimeSpan.FromSeconds(5))); - // client.RemoveRateLimiters(); - - // // act - // var sw = Stopwatch.StartNew(); - // client.TestCall(); - // client.TestCall(); - // client.TestCall(); - // sw.Stop(); - - // // assert - // Assert.IsTrue(sw.ElapsedMilliseconds < 5000); - //} - - //[TestCase()] - //public void ReceivingErrorStatusCode_Should_NotSuccess() - //{ - // // arrange - // var client = PrepareExceptionClient(JsonConvert.SerializeObject(new TestObject()), "InvalidStatusCodeResponse", 203); - - // // act - // var result = client.TestCall(); - - // // assert - // Assert.IsFalse(result.Success); - // Assert.IsNotNull(result.Error); - // Assert.IsTrue(result.Error.Message.Contains("InvalidStatusCodeResponse")); - //} - - //private TestRestClient PrepareClient(string responseData, bool withOptions = true, LogVerbosity verbosity = LogVerbosity.Warning, TextWriter tw = null) - //{ - // var expectedBytes = Encoding.UTF8.GetBytes(responseData); - // var responseStream = new MemoryStream(); - // responseStream.Write(expectedBytes, 0, expectedBytes.Length); - // responseStream.Seek(0, SeekOrigin.Begin); - - // var response = new Mock(); - // response.Setup(c => c.GetResponseStream()).Returns(responseStream); - - // var request = new Mock(); - // request.Setup(c => c.Headers).Returns(new WebHeaderCollection()); - // request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); - // request.Setup(c => c.GetResponse()).Returns(Task.FromResult(response.Object)); - - // var factory = new Mock(); - // factory.Setup(c => c.Create(It.IsAny())) - // .Returns(request.Object); - // TestRestClient client; - // if (withOptions) - // { - // var options = new ClientOptions() - // { - // ApiCredentials = new ApiCredentials("Test", "Test2"), - // LogVerbosity = verbosity - // }; - // if (tw != null) - // options.LogWriters = new List() { tw }; - - // client = new TestRestClient(options); - // } - // else - // { - // client = new TestRestClient(); - // } - // client.RequestFactory = factory.Object; - // return client; - //} - - //private TestRestClient PrepareExceptionClient(string responseData, string exceptionMessage, int statusCode, bool credentials = true) - //{ - // var expectedBytes = Encoding.UTF8.GetBytes(responseData); - // var responseStream = new MemoryStream(); - // responseStream.Write(expectedBytes, 0, expectedBytes.Length); - // responseStream.Seek(0, SeekOrigin.Begin); - - // var we = new WebException(); - // var r = new HttpWebResponse(); - // var re = new HttpResponseMessage(); - - // typeof(HttpResponseMessage).GetField("_statusCode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(re, (HttpStatusCode)statusCode); - // typeof(HttpWebResponse).GetField("_httpResponseMessage", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(r, re); - // typeof(WebException).GetField("_message", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(we, exceptionMessage); - // typeof(WebException).GetField("_response", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(we, r); - - // var response = new Mock(); - // response.Setup(c => c.GetResponseStream()).Throws(we); - - // var request = new Mock(); - // request.Setup(c => c.Headers).Returns(new WebHeaderCollection()); - // request.Setup(c => c.GetResponse()).Returns(Task.FromResult(response.Object)); - - // var factory = new Mock(); - // factory.Setup(c => c.Create(It.IsAny())) - // .Returns(request.Object); - - // TestRestClient client = credentials ? new TestRestClient(new ClientOptions() { ApiCredentials = new ApiCredentials("Test", "Test2") }) : new TestRestClient(); - // client.RequestFactory = factory.Object; - // return client; - //} - } -} diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index bf67ba8..b04f59f 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -6,8 +6,12 @@ using Newtonsoft.Json; using NUnit.Framework; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.RateLimiter; namespace CryptoExchange.Net.UnitTests { @@ -94,5 +98,72 @@ namespace CryptoExchange.Net.UnitTests Assert.IsTrue(result.Error.Code == 123); Assert.IsTrue(result.Error.Message == "Invalid request"); } + + [TestCase] + public void SettingOptions_Should_ResultInOptionsSet() + { + // arrange + // act + var client = new TestRestClient(new ClientOptions() + { + BaseAddress = "http://test.address.com", + RateLimiters = new List{new RateLimiterTotal(1, TimeSpan.FromSeconds(1))}, + RateLimitingBehaviour = RateLimitingBehaviour.Fail + }); + + + // assert + Assert.IsTrue(client.BaseAddress == "http://test.address.com"); + Assert.IsTrue(client.RateLimiters.Count() == 1); + Assert.IsTrue(client.RateLimitBehaviour == RateLimitingBehaviour.Fail); + } + + [TestCase] + public void SettingRateLimitingBehaviourToFail_Should_FailLimitedRequests() + { + // arrange + var client = new TestRestClient(new ClientOptions() + { + RateLimiters = new List { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, + RateLimitingBehaviour = RateLimitingBehaviour.Fail + }); + client.SetResponse("{\"property\": 123}"); + + + // act + var result1 = client.Request().Result; + client.SetResponse("{\"property\": 123}"); + var result2 = client.Request().Result; + + + // assert + Assert.IsTrue(result1.Success); + Assert.IsFalse(result2.Success); + } + + [TestCase] + public void SettingRateLimitingBehaviourToWait_Should_DelayLimitedRequests() + { + // arrange + var client = new TestRestClient(new ClientOptions() + { + RateLimiters = new List { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, + RateLimitingBehaviour = RateLimitingBehaviour.Wait + }); + client.SetResponse("{\"property\": 123}"); + + + // act + var sw = Stopwatch.StartNew(); + var result1 = client.Request().Result; + client.SetResponse("{\"property\": 123}"); // reset response stream + var result2 = client.Request().Result; + sw.Stop(); + + // assert + Assert.IsTrue(result1.Success); + Assert.IsTrue(result2.Success); + Assert.IsTrue(sw.ElapsedMilliseconds > 900, $"Actual: {sw.ElapsedMilliseconds}"); + } } } diff --git a/CryptoExchange.Net.UnitTests/SocketClientTests.cs b/CryptoExchange.Net.UnitTests/SocketClientTests.cs new file mode 100644 index 0000000..0ca9f7c --- /dev/null +++ b/CryptoExchange.Net.UnitTests/SocketClientTests.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.UnitTests.TestImplementations; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + public class SocketClientTests + { + [TestCase] + public void SettingOptions_Should_ResultInOptionsSet() + { + // arrange + // act + var client = new TestSocketClient(new SocketClientOptions() + { + BaseAddress = "http://test.address.com", + ReconnectInterval = TimeSpan.FromSeconds(6) + }); + + + // assert + Assert.IsTrue(client.BaseAddress == "http://test.address.com"); + Assert.IsTrue(client.ReconnectInterval.TotalSeconds == 6); + } + + [TestCase(true)] + [TestCase(false)] + public void ConnectSocket_Should_ReturnConnectionResult(bool canConnect) + { + // arrange + var client = new TestSocketClient(); + var socket = client.CreateSocket(); + socket.CanConnect = canConnect; + + // act + var connectResult = client.ConnectSocketSub(new SocketSubscription(socket)); + + // assert + Assert.IsTrue(connectResult.Success == canConnect); + } + + [TestCase] + public void SocketMessages_Should_BeProcessedInDataHandlers() + { + // arrange + var client = new TestSocketClient(new SocketClientOptions() { ReconnectInterval = TimeSpan.Zero, LogVerbosity = LogVerbosity.Debug }); + var socket = client.CreateSocket(); + socket.ShouldReconnect = true; + socket.CanConnect = true; + socket.DisconnectTime = DateTime.UtcNow; + var sub = new SocketSubscription(socket); + var rstEvent = new ManualResetEvent(false); + JToken result = null; + sub.MessageHandlers.Add("TestHandler", (subs, data) => + { + result = data; + rstEvent.Set(); + return true; + + }); + client.ConnectSocketSub(sub); + + // act + socket.InvokeMessage("{\"property\": 123}"); + rstEvent.WaitOne(1000); + + // assert + Assert.IsTrue((int)result["property"] == 123); + } + + [TestCase] + public void SocketMessages_Should_NotBeProcessedInSubsequentHandlersIfHandlerReturnsTrue() + { + // arrange + var client = new TestSocketClient(new SocketClientOptions() { ReconnectInterval = TimeSpan.Zero, LogVerbosity = LogVerbosity.Debug }); + var socket = client.CreateSocket(); + socket.ShouldReconnect = true; + socket.CanConnect = true; + socket.DisconnectTime = DateTime.UtcNow; + var sub = new SocketSubscription(socket); + var rstEvent1 = new ManualResetEvent(false); + var rstEvent2 = new ManualResetEvent(false); + JToken result1 = null; + JToken result2 = null; + sub.MessageHandlers.Add("TestHandler", (subs, data) => + { + result1 = data; + rstEvent1.Set(); + return true; + }); + sub.MessageHandlers.Add("TestHandlerNotHit", (subs, data) => + { + result2 = data; + rstEvent2.Set(); + return true; + }); + client.ConnectSocketSub(sub); + + // act + socket.InvokeMessage("{\"property\": 123}"); + rstEvent1.WaitOne(100); + rstEvent2.WaitOne(100); + + // assert + Assert.IsTrue((int)result1["property"] == 123); + Assert.IsTrue(result2 == null); + } + + [TestCase] + public void SocketMessages_Should_BeProcessedInSubsequentHandlersIfHandlerReturnsFalse() + { + // arrange + var client = new TestSocketClient(new SocketClientOptions() { ReconnectInterval = TimeSpan.Zero, LogVerbosity = LogVerbosity.Debug }); + var socket = client.CreateSocket(); + socket.ShouldReconnect = true; + socket.CanConnect = true; + socket.DisconnectTime = DateTime.UtcNow; + var sub = new SocketSubscription(socket); + var rstEvent = new ManualResetEvent(false); + JToken result = null; + sub.MessageHandlers.Add("TestHandlerNotProcessing", (subs, data) => + { + return false; + }); + sub.MessageHandlers.Add("TestHandler", (subs, data) => + { + result = data; + rstEvent.Set(); + return true; + }); + client.ConnectSocketSub(sub); + + // act + socket.InvokeMessage("{\"property\": 123}"); + rstEvent.WaitOne(100); + + // assert + Assert.IsTrue((int)result["property"] == 123); + } + + + [TestCase] + public void DisconnectedSocket_Should_Reconnect() + { + // arrange + bool reconnected = false; + var client = new TestSocketClient(new SocketClientOptions(){ReconnectInterval = TimeSpan.Zero ,LogVerbosity = LogVerbosity.Debug}); + var socket = client.CreateSocket(); + socket.ShouldReconnect = true; + socket.CanConnect = true; + socket.DisconnectTime = DateTime.UtcNow; + var sub = new SocketSubscription(socket); + client.ConnectSocketSub(sub); + var rstEvent = new ManualResetEvent(false); + client.OnReconnect += () => + { + reconnected = true; + rstEvent.Set(); + }; + + // act + socket.InvokeClose(); + rstEvent.WaitOne(1000); + + // assert + Assert.IsTrue(reconnected); + } + } +} diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs index d34c972..21c398e 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs @@ -22,9 +22,10 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public TestRestClient(ClientOptions exchangeOptions) : base(exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials)) { + RequestFactory = new Mock().Object; } - public void SetResponse(string responseData) + public void SetResponse(string responseData, Stream requestStream = null) { var expectedBytes = Encoding.UTF8.GetBytes(responseData); var responseStream = new MemoryStream(); @@ -37,6 +38,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations var request = new Mock(); request.Setup(c => c.Headers).Returns(new WebHeaderCollection()); request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); + request.Setup(c => c.GetRequestStream()).Returns(Task.FromResult(requestStream)); request.Setup(c => c.GetResponse()).Returns(Task.FromResult(response.Object)); var factory = Mock.Get(RequestFactory); @@ -84,9 +86,9 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations .Returns(request.Object); } - public async Task> Request() where T:class + public async Task> Request(string method = "GET") where T:class { - return await ExecuteRequest(new Uri("http://www.test.com")); + return await ExecuteRequest(new Uri("http://www.test.com"), method); } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs new file mode 100644 index 0000000..c911453 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; +using CryptoExchange.Net.Interfaces; +using WebSocket4Net; + +namespace CryptoExchange.Net.UnitTests.TestImplementations +{ + public class TestSocket: IWebsocket + { + public bool CanConnect { get; set; } + public bool Connected { get; set; } + + public event Action OnClose; + public event Action OnMessage; + public event Action OnError; + public event Action OnOpen; + + public int Id { get; } + public bool ShouldReconnect { get; set; } + public Func DataInterpreter { get; set; } + public DateTime? DisconnectTime { get; set; } + public string Url { get; } + public WebSocketState SocketState { get; } + public bool IsClosed => !Connected; + public bool IsOpen => Connected; + public bool PingConnection { get; set; } + public TimeSpan PingInterval { get; set; } + public SslProtocols SSLProtocols { get; set; } + + public Task Connect() + { + Connected = CanConnect; + return Task.FromResult(CanConnect); + } + + public void Send(string data) + { + if(!Connected) + throw new Exception("Socket not connected"); + } + + public Task Close() + { + Connected = false; + return Task.FromResult(0); + } + + public void SetProxy(string host, int port) + { + throw new NotImplementedException(); + } + public void Dispose() + { + } + + public void InvokeClose() + { + Connected = false; + OnClose?.Invoke(); + } + + public void InvokeOpen() + { + OnOpen?.Invoke(); + } + + public void InvokeMessage(string data) + { + OnMessage?.Invoke(data); + } + } +} diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs new file mode 100644 index 0000000..45ce0e3 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs @@ -0,0 +1,40 @@ +using System; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Sockets; +using Moq; + +namespace CryptoExchange.Net.UnitTests.TestImplementations +{ + public class TestSocketClient: SocketClient + { + public Action OnReconnect { get; set; } + + public TestSocketClient() : this(new SocketClientOptions()) + { + } + + public TestSocketClient(SocketClientOptions exchangeOptions) : base(exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials)) + { + SocketFactory = new Mock().Object; + Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(new TestSocket()); + } + + public TestSocket CreateSocket() + { + return (TestSocket)CreateSocket(BaseAddress); + } + + public CallResult ConnectSocketSub(SocketSubscription sub) + { + return ConnectSocket(sub).Result; + } + + protected override bool SocketReconnect(SocketSubscription subscription, TimeSpan disconnectedTime) + { + OnReconnect?.Invoke(); + return true; + } + } +} diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs index b504582..46cf768 100644 --- a/CryptoExchange.Net/BaseClient.cs +++ b/CryptoExchange.Net/BaseClient.cs @@ -14,7 +14,7 @@ namespace CryptoExchange.Net { public abstract class BaseClient { - protected string baseAddress; + public string BaseAddress; protected Log log; protected ApiProxy apiProxy; protected AuthenticationProvider authProvider; @@ -46,7 +46,7 @@ namespace CryptoExchange.Net log.UpdateWriters(exchangeOptions.LogWriters); log.Level = exchangeOptions.LogVerbosity; - baseAddress = exchangeOptions.BaseAddress; + BaseAddress = exchangeOptions.BaseAddress; apiProxy = exchangeOptions.Proxy; if (apiProxy != null) log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}"); diff --git a/CryptoExchange.Net/Interfaces/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 33154e9..98517f1 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -10,7 +10,7 @@ namespace CryptoExchange.Net.Interfaces Uri Uri { get; } WebHeaderCollection Headers { get; set; } string Method { get; set; } - int Timeout { get; set; } + TimeSpan Timeout { get; set; } void SetProxy(string host, int port); string ContentType { get; set; } diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 564fc77..c35f92e 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -44,10 +44,10 @@ namespace CryptoExchange.Net.Requests set => request.Method = value; } - public int Timeout + public TimeSpan Timeout { - get => request.Timeout; - set => request.Timeout = value; + get => TimeSpan.FromMilliseconds(request.Timeout); + set => request.Timeout = (int)Math.Round(value.TotalMilliseconds); } public Uri Uri => request.RequestUri; diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 9393e91..dd3f7d4 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -13,7 +13,6 @@ using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Logging; using CryptoExchange.Net.Objects; -using CryptoExchange.Net.RateLimiter; using CryptoExchange.Net.Requests; using Newtonsoft.Json; @@ -25,14 +24,14 @@ namespace CryptoExchange.Net /// The factory for creating requests. Used for unit testing /// public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); - - protected RateLimitingBehaviour rateLimitBehaviour; - protected int requestTimeout; + protected PostParameters postParametersPosition = PostParameters.InBody; protected RequestBodyFormat requestBodyFormat = RequestBodyFormat.Json; - private List rateLimiters; + protected TimeSpan RequestTimeout { get; private set; } + public RateLimitingBehaviour RateLimitBehaviour { get; private set; } + public IEnumerable RateLimiters { get; private set; } protected RestClient(ClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) { @@ -45,11 +44,12 @@ namespace CryptoExchange.Net /// Options protected void Configure(ClientOptions exchangeOptions) { - requestTimeout = (int)Math.Round(exchangeOptions.RequestTimeout.TotalMilliseconds, 0); - rateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; - rateLimiters = new List(); + RequestTimeout = exchangeOptions.RequestTimeout; + RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; + var rateLimiters = new List(); foreach (var rateLimiter in exchangeOptions.RateLimiters) rateLimiters.Add(rateLimiter); + RateLimiters = rateLimiters; } /// @@ -58,7 +58,9 @@ namespace CryptoExchange.Net /// The limiter to add public void AddRateLimiter(IRateLimiter limiter) { + var rateLimiters = RateLimiters.ToList(); rateLimiters.Add(limiter); + RateLimiters = rateLimiters; } /// @@ -66,7 +68,7 @@ namespace CryptoExchange.Net /// public void RemoveRateLimiters() { - rateLimiters.Clear(); + RateLimiters = new List(); } /// @@ -82,7 +84,7 @@ namespace CryptoExchange.Net public virtual async Task> PingAsync() { var ping = new Ping(); - var uri = new Uri(baseAddress); + var uri = new Uri(BaseAddress); PingReply reply; try { @@ -130,9 +132,9 @@ namespace CryptoExchange.Net request.SetProxy(apiProxy.Host, apiProxy.Port); } - foreach (var limiter in rateLimiters) + foreach (var limiter in RateLimiters) { - var limitResult = limiter.LimitRequest(uri.AbsolutePath, rateLimitBehaviour); + var limitResult = limiter.LimitRequest(uri.AbsolutePath, RateLimitBehaviour); if (!limitResult.Success) { log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} failed because of rate limit"); @@ -248,7 +250,7 @@ namespace CryptoExchange.Net var returnedData = ""; try { - request.Timeout = requestTimeout; + request.Timeout = RequestTimeout; var response = await request.GetResponse().ConfigureAwait(false); using (var reader = new StreamReader(response.GetResponseStream())) { diff --git a/CryptoExchange.Net/SocketClient.cs b/CryptoExchange.Net/SocketClient.cs index d642e49..f41ad2f 100644 --- a/CryptoExchange.Net/SocketClient.cs +++ b/CryptoExchange.Net/SocketClient.cs @@ -20,11 +20,11 @@ namespace CryptoExchange.Net /// /// The factory for creating sockets. Used for unit testing /// - public virtual IWebsocketFactory SocketFactory { get; set; } = new WebsocketFactory(); + public IWebsocketFactory SocketFactory { get; set; } = new WebsocketFactory(); protected List sockets = new List(); - protected TimeSpan reconnectInterval; + public TimeSpan ReconnectInterval { get; private set; } protected Func dataInterpreter; protected const string DataHandlerName = "DataHandler"; @@ -48,7 +48,7 @@ namespace CryptoExchange.Net /// Options protected void Configure(SocketClientOptions exchangeOptions) { - reconnectInterval = exchangeOptions.ReconnectInterval; + ReconnectInterval = exchangeOptions.ReconnectInterval; } /// @@ -168,7 +168,7 @@ namespace CryptoExchange.Net log.Write(LogVerbosity.Info, $"Socket {socket.Id} Connection lost, will try to reconnect"); Task.Run(() => { - Thread.Sleep(reconnectInterval); + Thread.Sleep(ReconnectInterval); if (!socket.Connect().Result) { log.Write(LogVerbosity.Debug, $"Socket {socket.Id} failed to reconnect"); diff --git a/CryptoExchange.Net/Sockets/UpdateSubscription.cs b/CryptoExchange.Net/Sockets/UpdateSubscription.cs index 24fa6f7..16be6ed 100644 --- a/CryptoExchange.Net/Sockets/UpdateSubscription.cs +++ b/CryptoExchange.Net/Sockets/UpdateSubscription.cs @@ -8,7 +8,7 @@ namespace CryptoExchange.Net.Sockets private readonly SocketSubscription subscription; /// - /// Event when the connection is lost + /// Event when the connection is lost. The socket will automatically reconnect when possible. /// public event Action ConnectionLost {