1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-07 16:06:15 +00:00

Updated tests

This commit is contained in:
JKorf 2022-11-13 15:31:28 +01:00
parent 66ac2972d6
commit 3365837338
13 changed files with 288 additions and 166 deletions

View File

@ -16,7 +16,7 @@ namespace CryptoExchange.Net.UnitTests
{ {
// arrange // arrange
var logger = new TestStringLogger(); var logger = new TestStringLogger();
var client = new TestBaseClient(new BaseRestClientOptions() var client = new TestBaseClient(new TestOptions()
{ {
LogWriters = new List<ILogger> { logger } LogWriters = new List<ILogger> { logger }
}); });
@ -56,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests
{ {
// arrange // arrange
var logger = new TestStringLogger(); var logger = new TestStringLogger();
var options = new BaseRestClientOptions() var options = new TestOptions()
{ {
LogWriters = new List<ILogger> { logger } LogWriters = new List<ILogger> { logger }
}; };
@ -78,7 +78,7 @@ namespace CryptoExchange.Net.UnitTests
var client = new TestBaseClient(); var client = new TestBaseClient();
// act // act
var result = client.Deserialize<object>("{\"testProperty\": 123}"); var result = client.SubClient.Deserialize<object>("{\"testProperty\": 123}");
// assert // assert
Assert.IsTrue(result.Success); Assert.IsTrue(result.Success);
@ -91,7 +91,7 @@ namespace CryptoExchange.Net.UnitTests
var client = new TestBaseClient(); var client = new TestBaseClient();
// act // act
var result = client.Deserialize<object>("{\"testProperty\": 123"); var result = client.SubClient.Deserialize<object>("{\"testProperty\": 123");
// assert // assert
Assert.IsFalse(result.Success); Assert.IsFalse(result.Success);

View File

@ -248,7 +248,7 @@ namespace CryptoExchange.Net.UnitTests
} }
} }
public class TestClientOptions: BaseRestClientOptions public class TestClientOptions: ClientOptions
{ {
/// <summary> /// <summary>
/// Default options for the futures client /// Default options for the futures client

View File

@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests
client.SetResponse(JsonConvert.SerializeObject(expected), out _); client.SetResponse(JsonConvert.SerializeObject(expected), out _);
// act // act
var result = client.Request<TestObject>().Result; var result = client.Api1.Request<TestObject>().Result;
// assert // assert
Assert.IsTrue(result.Success); Assert.IsTrue(result.Success);
@ -43,7 +43,7 @@ namespace CryptoExchange.Net.UnitTests
client.SetResponse("{\"property\": 123", out _); client.SetResponse("{\"property\": 123", out _);
// act // act
var result = client.Request<TestObject>().Result; var result = client.Api1.Request<TestObject>().Result;
// assert // assert
Assert.IsFalse(result.Success); Assert.IsFalse(result.Success);
@ -58,7 +58,7 @@ namespace CryptoExchange.Net.UnitTests
client.SetErrorWithoutResponse(System.Net.HttpStatusCode.BadRequest, "Invalid request"); client.SetErrorWithoutResponse(System.Net.HttpStatusCode.BadRequest, "Invalid request");
// act // act
var result = await client.Request<TestObject>(); var result = await client.Api1.Request<TestObject>();
// assert // assert
Assert.IsFalse(result.Success); Assert.IsFalse(result.Success);
@ -73,7 +73,7 @@ namespace CryptoExchange.Net.UnitTests
client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest); client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest);
// act // act
var result = await client.Request<TestObject>(); var result = await client.Api1.Request<TestObject>();
// assert // assert
Assert.IsFalse(result.Success); Assert.IsFalse(result.Success);
@ -91,7 +91,7 @@ namespace CryptoExchange.Net.UnitTests
client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest); client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest);
// act // act
var result = await client.Request<TestObject>(); var result = await client.Api2.Request<TestObject>();
// assert // assert
Assert.IsFalse(result.Success); Assert.IsFalse(result.Success);
@ -112,9 +112,9 @@ namespace CryptoExchange.Net.UnitTests
{ {
BaseAddress = "http://test.address.com", BaseAddress = "http://test.address.com",
RateLimiters = new List<IRateLimiter> { new RateLimiter() }, RateLimiters = new List<IRateLimiter> { new RateLimiter() },
RateLimitingBehaviour = RateLimitingBehaviour.Fail RateLimitingBehaviour = RateLimitingBehaviour.Fail,
}, RequestTimeout = TimeSpan.FromMinutes(1)
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.BaseAddress == "http://test.address.com");
Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimiters.Count == 1); Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimiters.Count == 1);
Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimitingBehaviour == RateLimitingBehaviour.Fail); 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 [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); client.SetResponse("{}", out var request);
await client.RequestWithParams<TestObject>(new HttpMethod(method), new Dictionary<string, object> await client.Api1.RequestWithParams<TestObject>(new HttpMethod(method), new Dictionary<string, object>
{ {
{ "TestParam1", "Value1" }, { "TestParam1", "Value1" },
{ "TestParam2", 2 }, { "TestParam2", 2 },

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
using CryptoExchange.Net.Logging;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Sockets; using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.UnitTests.TestImplementations; using CryptoExchange.Net.UnitTests.TestImplementations;
@ -19,17 +20,17 @@ namespace CryptoExchange.Net.UnitTests
//act //act
var client = new TestSocketClient(new TestOptions() var client = new TestSocketClient(new TestOptions()
{ {
SubOptions = new RestApiClientOptions SubOptions = new SocketApiClientOptions
{ {
BaseAddress = "http://test.address.com" BaseAddress = "http://test.address.com",
}, ReconnectInterval = TimeSpan.FromSeconds(6)
ReconnectInterval = TimeSpan.FromSeconds(6) }
}); });
//assert //assert
Assert.IsTrue(client.SubClient.Options.BaseAddress == "http://test.address.com"); 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)] [TestCase(true)]
@ -42,7 +43,7 @@ namespace CryptoExchange.Net.UnitTests
socket.CanConnect = canConnect; socket.CanConnect = canConnect;
//act //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
Assert.IsTrue(connectResult.Success == canConnect); Assert.IsTrue(connectResult.Success == canConnect);
@ -52,12 +53,18 @@ namespace CryptoExchange.Net.UnitTests
public void SocketMessages_Should_BeProcessedInDataHandlers() public void SocketMessages_Should_BeProcessedInDataHandlers()
{ {
// arrange // 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(); var socket = client.CreateSocket();
socket.ShouldReconnect = true; socket.ShouldReconnect = true;
socket.CanConnect = true; socket.CanConnect = true;
socket.DisconnectTime = DateTime.UtcNow; 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); var rstEvent = new ManualResetEvent(false);
JToken result = null; JToken result = null;
sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) => sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) =>
@ -65,7 +72,7 @@ namespace CryptoExchange.Net.UnitTests
result = messageEvent.JsonData; result = messageEvent.JsonData;
rstEvent.Set(); rstEvent.Set();
})); }));
client.ConnectSocketSub(sub); client.SubClient.ConnectSocketSub(sub);
// act // act
socket.InvokeMessage("{\"property\": 123}"); socket.InvokeMessage("{\"property\": 123}");
@ -80,12 +87,19 @@ namespace CryptoExchange.Net.UnitTests
public void SocketMessages_Should_ContainOriginalDataIfEnabled(bool enabled) public void SocketMessages_Should_ContainOriginalDataIfEnabled(bool enabled)
{ {
// arrange // 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(); var socket = client.CreateSocket();
socket.ShouldReconnect = true; socket.ShouldReconnect = true;
socket.CanConnect = true; socket.CanConnect = true;
socket.DisconnectTime = DateTime.UtcNow; 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); var rstEvent = new ManualResetEvent(false);
string original = null; string original = null;
sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) => sub.AddSubscription(SocketSubscription.CreateForIdentifier(10, "TestHandler", true, false, (messageEvent) =>
@ -93,7 +107,7 @@ namespace CryptoExchange.Net.UnitTests
original = messageEvent.OriginalData; original = messageEvent.OriginalData;
rstEvent.Set(); rstEvent.Set();
})); }));
client.ConnectSocketSub(sub); client.SubClient.ConnectSocketSub(sub);
// act // act
socket.InvokeMessage("{\"property\": 123}"); socket.InvokeMessage("{\"property\": 123}");
@ -107,11 +121,18 @@ namespace CryptoExchange.Net.UnitTests
public void UnsubscribingStream_Should_CloseTheSocket() public void UnsubscribingStream_Should_CloseTheSocket()
{ {
// arrange // 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(); var socket = client.CreateSocket();
socket.CanConnect = true; socket.CanConnect = true;
var sub = new SocketConnection(client, null, socket, null); var sub = new SocketConnection(new Log(""), client.SubClient, socket, null);
client.ConnectSocketSub(sub); client.SubClient.ConnectSocketSub(sub);
var us = SocketSubscription.CreateForIdentifier(10, "Test", true, false, (e) => { }); var us = SocketSubscription.CreateForIdentifier(10, "Test", true, false, (e) => { });
var ups = new UpdateSubscription(sub, us); var ups = new UpdateSubscription(sub, us);
sub.AddSubscription(us); sub.AddSubscription(us);
@ -127,15 +148,22 @@ namespace CryptoExchange.Net.UnitTests
public void UnsubscribingAll_Should_CloseAllSockets() public void UnsubscribingAll_Should_CloseAllSockets()
{ {
// arrange // 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 socket1 = client.CreateSocket();
var socket2 = client.CreateSocket(); var socket2 = client.CreateSocket();
socket1.CanConnect = true; socket1.CanConnect = true;
socket2.CanConnect = true; socket2.CanConnect = true;
var sub1 = new SocketConnection(client, null, socket1, null); var sub1 = new SocketConnection(new Log(""), client.SubClient, socket1, null);
var sub2 = new SocketConnection(client, null, socket2, null); var sub2 = new SocketConnection(new Log(""), client.SubClient, socket2, null);
client.ConnectSocketSub(sub1); client.SubClient.ConnectSocketSub(sub1);
client.ConnectSocketSub(sub2); client.SubClient.ConnectSocketSub(sub2);
// act // act
client.UnsubscribeAllAsync().Wait(); client.UnsubscribeAllAsync().Wait();
@ -149,13 +177,20 @@ namespace CryptoExchange.Net.UnitTests
public void FailingToConnectSocket_Should_ReturnError() public void FailingToConnectSocket_Should_ReturnError()
{ {
// arrange // 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(); var socket = client.CreateSocket();
socket.CanConnect = false; socket.CanConnect = false;
var sub = new SocketConnection(client, null, socket, null); var sub1 = new SocketConnection(new Log(""), client.SubClient, socket, null);
// act // act
var connectResult = client.ConnectSocketSub(sub); var connectResult = client.SubClient.ConnectSocketSub(sub1);
// assert // assert
Assert.IsFalse(connectResult.Success); Assert.IsFalse(connectResult.Success);

View File

@ -1,19 +1,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Logging;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.UnitTests.TestImplementations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.UnitTests namespace CryptoExchange.Net.UnitTests
{ {
public class TestBaseClient: BaseClient 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); log.Write(verbosity, data);
} }
}
public CallResult<T> Deserialize<T>(string data) public class TestSubClient : RestApiClient
{
public TestSubClient(ClientOptions options, RestApiClientOptions apiOptions) : base(new Log(""), options, apiOptions)
{ {
return Deserialize<T>(data, null, null);
} }
public CallResult<T> Deserialize<T>(string data) => Deserialize<T>(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<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
} }
public class TestAuthProvider : AuthenticationProvider public class TestAuthProvider : AuthenticationProvider

View File

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using System.Collections.Generic; using System.Collections.Generic;
using CryptoExchange.Net.Logging;
namespace CryptoExchange.Net.UnitTests.TestImplementations namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
@ -28,7 +29,6 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
Api1 = new TestRestApi1Client(exchangeOptions); Api1 = new TestRestApi1Client(exchangeOptions);
Api2 = new TestRestApi2Client(exchangeOptions); Api2 = new TestRestApi2Client(exchangeOptions);
RequestFactory = new Mock<IRequestFactory>().Object;
} }
public void SetResponse(string responseData, out IRequest requestObj) public void SetResponse(string responseData, out IRequest requestObj)
@ -50,7 +50,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val })); request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val }));
request.Setup(c => c.GetHeaders()).Returns(() => headers); 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<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) => .Callback<HttpMethod, Uri, int>((method, uri, id) =>
{ {
@ -58,6 +58,15 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(a => a.Method).Returns(method); request.Setup(a => a.Method).Returns(method);
}) })
.Returns(request.Object); .Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
{
request.Setup(a => a.Uri).Returns(uri);
request.Setup(a => a.Method).Returns(method);
})
.Returns(request.Object);
requestObj = request.Object; requestObj = request.Object;
} }
@ -71,7 +80,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetHeaders()).Returns(new Dictionary<string, IEnumerable<string>>()); request.Setup(c => c.GetHeaders()).Returns(new Dictionary<string, IEnumerable<string>>());
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we); request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
var factory = Mock.Get(RequestFactory); var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object); .Returns(request.Object);
} }
@ -94,27 +108,33 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val })); request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val }));
request.Setup(c => c.GetHeaders()).Returns(headers); 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<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri)) .Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
.Returns(request.Object); .Returns(request.Object);
}
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T:class factory = Mock.Get(Api2.RequestFactory);
{ factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
return await SendRequestAsync<T>(Api1, new Uri("http://www.test.com"), HttpMethod.Get, ct); .Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
} .Returns(request.Object);
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
{
return await SendRequestAsync<T>(Api1, new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers);
} }
} }
public class TestRestApi1Client : RestApiClient 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<IRequestFactory>().Object;
}
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T : class
{
return await SendRequestAsync<T>(new Uri("http://www.test.com"), HttpMethod.Get, ct);
}
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
{
return await SendRequestAsync<T>(new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers);
} }
public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position) public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position)
@ -143,9 +163,19 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
public class TestRestApi2Client : RestApiClient 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<IRequestFactory>().Object;
}
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T : class
{
return await SendRequestAsync<T>(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() public override TimeSpan GetTimeOffset()
@ -186,9 +216,5 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
public ParseErrorTestRestClient() { } public ParseErrorTestRestClient() { }
public ParseErrorTestRestClient(TestClientOptions exchangeOptions) : base(exchangeOptions) { } public ParseErrorTestRestClient(TestClientOptions exchangeOptions) : base(exchangeOptions) { }
protected override Error ParseErrorResponse(JToken error)
{
return new ServerError((int)error["errorCode"], (string)error["errorMessage"]);
}
} }
} }

View File

@ -20,16 +20,39 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
public TestSocketClient(TestOptions exchangeOptions) : base("test", exchangeOptions) public TestSocketClient(TestOptions exchangeOptions) : base("test", exchangeOptions)
{ {
SubClient = new TestSubSocketClient(exchangeOptions, exchangeOptions.SubOptions); SubClient = AddApiClient(new TestSubSocketClient(exchangeOptions, exchangeOptions.SubOptions));
SocketFactory = new Mock<IWebsocketFactory>().Object; SubClient.SocketFactory = new Mock<IWebsocketFactory>().Object;
Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<WebSocketParameters>())).Returns(new TestSocket()); Mock.Get(SubClient.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<WebSocketParameters>())).Returns(new TestSocket());
} }
public TestSocket CreateSocket() public TestSocket CreateSocket()
{ {
Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<WebSocketParameters>())).Returns(new TestSocket()); Mock.Get(SubClient.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<WebSocketParameters>())).Returns(new TestSocket());
return (TestSocket)CreateSocket("https://localhost:123/"); 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<bool> ConnectSocketSub(SocketConnection sub) public CallResult<bool> ConnectSocketSub(SocketConnection sub)
{ {
@ -67,21 +90,4 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
throw new NotImplementedException(); 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);
}
} }

View File

@ -113,12 +113,13 @@ namespace CryptoExchange.Net
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="log">Logger</param> /// <param name="log">Logger</param>
/// <param name="clientOptions">Client options</param>
/// <param name="apiOptions">Api client options</param> /// <param name="apiOptions">Api client options</param>
protected BaseApiClient(Log log, ApiClientOptions apiOptions) protected BaseApiClient(Log log, ClientOptions clientOptions, ApiClientOptions apiOptions)
{ {
Options = apiOptions; Options = apiOptions;
_log = log; _log = log;
_apiCredentials = apiOptions.ApiCredentials?.Copy(); _apiCredentials = apiOptions.ApiCredentials?.Copy() ?? clientOptions.ApiCredentials?.Copy();
BaseAddress = apiOptions.BaseAddress; BaseAddress = apiOptions.BaseAddress;
} }

View File

@ -70,7 +70,7 @@ namespace CryptoExchange.Net
/// <param name="log">Logger</param> /// <param name="log">Logger</param>
/// <param name="options">The base client options</param> /// <param name="options">The base client options</param>
/// <param name="apiOptions">The Api client options</param> /// <param name="apiOptions">The Api client options</param>
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<IRateLimiter>(); var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in apiOptions.RateLimiters) foreach (var rateLimiter in apiOptions.RateLimiters)

View File

@ -117,7 +117,7 @@ namespace CryptoExchange.Net
/// <param name="log">log</param> /// <param name="log">log</param>
/// <param name="options">Client options</param> /// <param name="options">Client options</param>
/// <param name="apiOptions">The Api client options</param> /// <param name="apiOptions">The Api client options</param>
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; ClientOptions = options;
} }

View File

@ -9,7 +9,10 @@ using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
public class ClientOptions /// <summary>
/// Client options
/// </summary>
public abstract class ClientOptions
{ {
internal event Action? OnLoggingChanged; internal event Action? OnLoggingChanged;
@ -46,16 +49,44 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public ApiProxy? Proxy { get; set; } public ApiProxy? Proxy { get; set; }
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// ctor
/// </summary>
public ClientOptions()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="clientOptions">Copy values for the provided options</param>
public ClientOptions(ClientOptions? clientOptions)
{
if (clientOptions == null)
return;
LogLevel = clientOptions.LogLevel;
LogWriters = clientOptions.LogWriters.ToList();
Proxy = clientOptions.Proxy;
ApiCredentials = clientOptions.ApiCredentials?.Copy();
}
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="baseOptions">Copy values for the provided options</param> /// <param name="baseOptions">Copy values for the provided options</param>
/// <param name="newValues">Copy values for the provided options</param> /// <param name="newValues">Copy values for the provided options</param>
public ClientOptions(ClientOptions baseOptions, ClientOptions? newValues) internal ClientOptions(ClientOptions baseOptions, ClientOptions? newValues)
{ {
Proxy = newValues?.Proxy ?? baseOptions.Proxy; Proxy = newValues?.Proxy ?? baseOptions.Proxy;
LogLevel = baseOptions.LogLevel; LogLevel = baseOptions.LogLevel;
LogWriters = baseOptions.LogWriters.ToList(); LogWriters = baseOptions.LogWriters.ToList();
ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -112,7 +143,7 @@ namespace CryptoExchange.Net.Objects
{ {
BaseAddress = newValues?.BaseAddress ?? baseOptions.BaseAddress; BaseAddress = newValues?.BaseAddress ?? baseOptions.BaseAddress;
ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy(); ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy();
OutputOriginalData = baseOptions.OutputOriginalData; OutputOriginalData = newValues?.OutputOriginalData ?? baseOptions.OutputOriginalData;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -281,7 +312,7 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// Base for order book options /// Base for order book options
/// </summary> /// </summary>
public class OrderBookOptions : ApiClientOptions public class OrderBookOptions : ClientOptions
{ {
/// <summary> /// <summary>
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages. /// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.

View File

@ -40,7 +40,7 @@ namespace CryptoExchange.Net.OrderBook
set { } } set { } }
} }
private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry(); private static readonly ISymbolOrderBookEntry _emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry();
/// <summary> /// <summary>
@ -167,7 +167,7 @@ namespace CryptoExchange.Net.OrderBook
get get
{ {
lock (_bookLock) lock (_bookLock)
return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; return bids.FirstOrDefault().Value ?? _emptySymbolOrderBookEntry;
} }
} }
@ -177,7 +177,7 @@ namespace CryptoExchange.Net.OrderBook
get get
{ {
lock (_bookLock) lock (_bookLock)
return asks.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; return asks.FirstOrDefault().Value ?? _emptySymbolOrderBookEntry;
} }
} }
@ -581,7 +581,9 @@ namespace CryptoExchange.Net.OrderBook
var (bestBid, bestAsk) = BestOffers; var (bestBid, bestAsk) = BestOffers;
if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity || if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity ||
bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity) bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity)
{
OnBestOffersChanged?.Invoke((bestBid, bestAsk)); OnBestOffersChanged?.Invoke((bestBid, bestAsk));
}
} }
private void Reset() private void Reset()
@ -752,7 +754,9 @@ namespace CryptoExchange.Net.OrderBook
_ = _subscription!.ReconnectAsync(); _ = _subscription!.ReconnectAsync();
} }
else else
{
await ResyncAsync().ConfigureAwait(false); await ResyncAsync().ConfigureAwait(false);
}
}); });
} }

View File

@ -53,8 +53,8 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public int SubscriptionCount public int SubscriptionCount
{ {
get { lock (subscriptionLock) get { lock (_subscriptionLock)
return subscriptions.Count(h => h.UserSubscription); } return _subscriptions.Count(h => h.UserSubscription); }
} }
/// <summary> /// <summary>
@ -64,8 +64,8 @@ namespace CryptoExchange.Net.Sockets
{ {
get get
{ {
lock (subscriptionLock) lock (_subscriptionLock)
return subscriptions.Where(h => h.UserSubscription).ToArray(); return _subscriptions.Where(h => h.UserSubscription).ToArray();
} }
} }
@ -114,14 +114,14 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool PausedActivity public bool PausedActivity
{ {
get => pausedActivity; get => _pausedActivity;
set set
{ {
if (pausedActivity != value) if (_pausedActivity != value)
{ {
pausedActivity = value; _pausedActivity = value;
log.Write(LogLevel.Information, $"Socket {SocketId} Paused activity: " + value); _log.Write(LogLevel.Information, $"Socket {SocketId} Paused activity: " + value);
if(pausedActivity) _ = Task.Run(() => ActivityPaused?.Invoke()); if(_pausedActivity) _ = Task.Run(() => ActivityPaused?.Invoke());
else _ = Task.Run(() => ActivityUnpaused?.Invoke()); else _ = Task.Run(() => ActivityUnpaused?.Invoke());
} }
} }
@ -140,17 +140,17 @@ namespace CryptoExchange.Net.Sockets
var oldStatus = _status; var oldStatus = _status;
_status = value; _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 bool _pausedActivity;
private readonly List<SocketSubscription> subscriptions; private readonly List<SocketSubscription> _subscriptions;
private readonly object subscriptionLock = new(); private readonly object _subscriptionLock = new();
private readonly Log log; private readonly Log _log;
private readonly List<PendingRequest> pendingRequests; private readonly List<PendingRequest> _pendingRequests;
private SocketStatus _status; private SocketStatus _status;
@ -168,12 +168,12 @@ namespace CryptoExchange.Net.Sockets
/// <param name="tag"></param> /// <param name="tag"></param>
public SocketConnection(Log log, SocketApiClient apiClient, IWebsocket socket, string tag) public SocketConnection(Log log, SocketApiClient apiClient, IWebsocket socket, string tag)
{ {
this.log = log; this._log = log;
ApiClient = apiClient; ApiClient = apiClient;
Tag = tag; Tag = tag;
pendingRequests = new List<PendingRequest>(); _pendingRequests = new List<PendingRequest>();
subscriptions = new List<SocketSubscription>(); _subscriptions = new List<SocketSubscription>();
_socket = socket; _socket = socket;
_socket.OnMessage += HandleMessage; _socket.OnMessage += HandleMessage;
@ -201,9 +201,9 @@ namespace CryptoExchange.Net.Sockets
{ {
Status = SocketStatus.Closed; Status = SocketStatus.Closed;
Authenticated = false; Authenticated = false;
lock(subscriptionLock) lock(_subscriptionLock)
{ {
foreach (var sub in subscriptions) foreach (var sub in _subscriptions)
sub.Confirmed = false; sub.Confirmed = false;
} }
Task.Run(() => ConnectionClosed?.Invoke()); Task.Run(() => ConnectionClosed?.Invoke());
@ -217,9 +217,9 @@ namespace CryptoExchange.Net.Sockets
Status = SocketStatus.Reconnecting; Status = SocketStatus.Reconnecting;
DisconnectTime = DateTime.UtcNow; DisconnectTime = DateTime.UtcNow;
Authenticated = false; Authenticated = false;
lock (subscriptionLock) lock (_subscriptionLock)
{ {
foreach (var sub in subscriptions) foreach (var sub in _subscriptions)
sub.Confirmed = false; sub.Confirmed = false;
} }
@ -232,7 +232,7 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
protected virtual async Task<Uri?> GetReconnectionUrlAsync() protected virtual async Task<Uri?> GetReconnectionUrlAsync()
{ {
return await ApiClient.GetReconnectUriAsync(ApiClient, this).ConfigureAwait(false); return await ApiClient.GetReconnectUriAsync(this).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -241,18 +241,20 @@ namespace CryptoExchange.Net.Sockets
protected virtual async void HandleReconnected() protected virtual async void HandleReconnected()
{ {
Status = SocketStatus.Resubscribing; Status = SocketStatus.Resubscribing;
lock (pendingRequests) lock (_pendingRequests)
{ {
foreach (var pendingRequest in pendingRequests.ToList()) foreach (var pendingRequest in _pendingRequests.ToList())
{ {
pendingRequest.Fail(); pendingRequest.Fail();
pendingRequests.Remove(pendingRequest); _pendingRequests.Remove(pendingRequest);
} }
} }
var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false); var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false);
if (!reconnectSuccessful) if (!reconnectSuccessful)
{
await _socket.ReconnectAsync().ConfigureAwait(false); await _socket.ReconnectAsync().ConfigureAwait(false);
}
else else
{ {
Status = SocketStatus.Connected; Status = SocketStatus.Connected;
@ -271,9 +273,9 @@ namespace CryptoExchange.Net.Sockets
protected virtual void HandleError(Exception e) protected virtual void HandleError(Exception e)
{ {
if (e is WebSocketException wse) 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 else
log.Write(LogLevel.Warning, $"Socket {SocketId} error: " + e.ToLogString()); _log.Write(LogLevel.Warning, $"Socket {SocketId} error: " + e.ToLogString());
} }
/// <summary> /// <summary>
@ -283,14 +285,14 @@ namespace CryptoExchange.Net.Sockets
protected virtual void HandleMessage(string data) protected virtual void HandleMessage(string data)
{ {
var timestamp = DateTime.UtcNow; 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; if (string.IsNullOrEmpty(data)) return;
var tokenData = data.ToJToken(log); var tokenData = data.ToJToken(_log);
if (tokenData == null) if (tokenData == null)
{ {
data = $"\"{data}\""; data = $"\"{data}\"";
tokenData = data.ToJToken(log); tokenData = data.ToJToken(_log);
if (tokenData == null) if (tokenData == null)
return; return;
} }
@ -299,10 +301,10 @@ namespace CryptoExchange.Net.Sockets
// Remove any timed out requests // Remove any timed out requests
PendingRequest[] requests; PendingRequest[] requests;
lock (pendingRequests) lock (_pendingRequests)
{ {
pendingRequests.RemoveAll(r => r.Completed); _pendingRequests.RemoveAll(r => r.Completed);
requests = pendingRequests.ToArray(); requests = _pendingRequests.ToArray();
} }
// Check if this message is an answer on any pending requests // Check if this message is an answer on any pending requests
@ -310,8 +312,8 @@ namespace CryptoExchange.Net.Sockets
{ {
if (pendingRequest.CheckData(tokenData)) if (pendingRequest.CheckData(tokenData))
{ {
lock (pendingRequests) lock (_pendingRequests)
pendingRequests.Remove(pendingRequest); _pendingRequests.Remove(pendingRequest);
if (!ApiClient.ContinueOnQueryResponse) if (!ApiClient.ContinueOnQueryResponse)
return; return;
@ -327,16 +329,18 @@ namespace CryptoExchange.Net.Sockets
if (!handled && !handledResponse) if (!handled && !handledResponse)
{ {
if (!ApiClient.UnhandledMessageExpected) 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); UnhandledMessage?.Invoke(tokenData);
} }
var total = DateTime.UtcNow - timestamp; var total = DateTime.UtcNow - timestamp;
if (userProcessTime.TotalMilliseconds > 500) 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."); "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)");
} }
/// <summary> /// <summary>
@ -369,9 +373,9 @@ namespace CryptoExchange.Net.Sockets
if (ApiClient.socketConnections.ContainsKey(SocketId)) if (ApiClient.socketConnections.ContainsKey(SocketId))
ApiClient.socketConnections.TryRemove(SocketId, out _); ApiClient.socketConnections.TryRemove(SocketId, out _);
lock (subscriptionLock) lock (_subscriptionLock)
{ {
foreach (var subscription in subscriptions) foreach (var subscription in _subscriptions)
{ {
if (subscription.CancellationTokenRegistration.HasValue) if (subscription.CancellationTokenRegistration.HasValue)
subscription.CancellationTokenRegistration.Value.Dispose(); subscription.CancellationTokenRegistration.Value.Dispose();
@ -389,9 +393,9 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public async Task CloseAsync(SocketSubscription subscription) public async Task CloseAsync(SocketSubscription subscription)
{ {
lock (subscriptionLock) lock (_subscriptionLock)
{ {
if (!subscriptions.Contains(subscription)) if (!_subscriptions.Contains(subscription))
return; return;
subscription.Closed = true; subscription.Closed = true;
@ -400,7 +404,7 @@ namespace CryptoExchange.Net.Sockets
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed) if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
return; 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) if (subscription.CancellationTokenRegistration.HasValue)
subscription.CancellationTokenRegistration.Value.Dispose(); subscription.CancellationTokenRegistration.Value.Dispose();
@ -408,27 +412,27 @@ namespace CryptoExchange.Net.Sockets
await ApiClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false); await ApiClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false);
bool shouldCloseConnection; bool shouldCloseConnection;
lock (subscriptionLock) lock (_subscriptionLock)
{ {
if (Status == SocketStatus.Closing) if (Status == SocketStatus.Closing)
{ {
log.Write(LogLevel.Debug, $"Socket {SocketId} already closing"); _log.Write(LogLevel.Debug, $"Socket {SocketId} already closing");
return; return;
} }
shouldCloseConnection = subscriptions.All(r => !r.UserSubscription || r.Closed); shouldCloseConnection = _subscriptions.All(r => !r.UserSubscription || r.Closed);
if (shouldCloseConnection) if (shouldCloseConnection)
Status = SocketStatus.Closing; Status = SocketStatus.Closing;
} }
if (shouldCloseConnection) 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); await CloseAsync().ConfigureAwait(false);
} }
lock (subscriptionLock) lock (_subscriptionLock)
subscriptions.Remove(subscription); _subscriptions.Remove(subscription);
} }
/// <summary> /// <summary>
@ -446,14 +450,14 @@ namespace CryptoExchange.Net.Sockets
/// <param name="subscription"></param> /// <param name="subscription"></param>
public bool AddSubscription(SocketSubscription subscription) public bool AddSubscription(SocketSubscription subscription)
{ {
lock (subscriptionLock) lock (_subscriptionLock)
{ {
if (Status != SocketStatus.None && Status != SocketStatus.Connected) if (Status != SocketStatus.None && Status != SocketStatus.Connected)
return false; return false;
subscriptions.Add(subscription); _subscriptions.Add(subscription);
if(subscription.UserSubscription) 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; return true;
} }
} }
@ -464,8 +468,8 @@ namespace CryptoExchange.Net.Sockets
/// <param name="id"></param> /// <param name="id"></param>
public SocketSubscription? GetSubscription(int id) public SocketSubscription? GetSubscription(int id)
{ {
lock (subscriptionLock) lock (_subscriptionLock)
return subscriptions.SingleOrDefault(s => s.Id == id); return _subscriptions.SingleOrDefault(s => s.Id == id);
} }
/// <summary> /// <summary>
@ -475,8 +479,8 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public SocketSubscription? GetSubscriptionByRequest(Func<object?, bool> predicate) public SocketSubscription? GetSubscriptionByRequest(Func<object?, bool> predicate)
{ {
lock(subscriptionLock) lock(_subscriptionLock)
return subscriptions.SingleOrDefault(s => predicate(s.Request)); return _subscriptions.SingleOrDefault(s => predicate(s.Request));
} }
/// <summary> /// <summary>
@ -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 // Loop the subscriptions to check if any of them signal us that the message is for them
List<SocketSubscription> subscriptionsCopy; List<SocketSubscription> subscriptionsCopy;
lock (subscriptionLock) lock (_subscriptionLock)
subscriptionsCopy = subscriptions.ToList(); subscriptionsCopy = _subscriptions.ToList();
foreach (var subscription in subscriptionsCopy) foreach (var subscription in subscriptionsCopy)
{ {
@ -529,7 +533,7 @@ namespace CryptoExchange.Net.Sockets
} }
catch (Exception ex) 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); currentSubscription?.InvokeExceptionHandler(ex);
return (false, TimeSpan.Zero, null); return (false, TimeSpan.Zero, null);
} }
@ -546,9 +550,9 @@ namespace CryptoExchange.Net.Sockets
public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler) public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
{ {
var pending = new PendingRequest(handler, timeout); var pending = new PendingRequest(handler, timeout);
lock (pendingRequests) lock (_pendingRequests)
{ {
pendingRequests.Add(pending); _pendingRequests.Add(pending);
} }
var sendOk = Send(obj); var sendOk = Send(obj);
if(!sendOk) if(!sendOk)
@ -577,7 +581,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="data">The data to send</param> /// <param name="data">The data to send</param>
public virtual bool Send(string data) 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 try
{ {
_socket.Send(data); _socket.Send(data);
@ -595,36 +599,36 @@ namespace CryptoExchange.Net.Sockets
return new CallResult<bool>(new WebError("Socket not connected")); return new CallResult<bool>(new WebError("Socket not connected"));
bool anySubscriptions = false; bool anySubscriptions = false;
lock (subscriptionLock) lock (_subscriptionLock)
anySubscriptions = subscriptions.Any(s => s.UserSubscription); anySubscriptions = _subscriptions.Any(s => s.UserSubscription);
if (!anySubscriptions) if (!anySubscriptions)
{ {
// No need to resubscribe anything // 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(); _ = _socket.CloseAsync();
return new CallResult<bool>(true); return new CallResult<bool>(true);
} }
if (subscriptions.Any(s => s.Authenticated)) if (_subscriptions.Any(s => s.Authenticated))
{ {
// If we reconnected a authenticated connection we need to re-authenticate // If we reconnected a authenticated connection we need to re-authenticate
var authResult = await ApiClient.AuthenticateSocketAsync(this).ConfigureAwait(false); var authResult = await ApiClient.AuthenticateSocketAsync(this).ConfigureAwait(false);
if (!authResult) 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; return authResult;
} }
Authenticated = true; 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 // Get a list of all subscriptions on the socket
List<SocketSubscription> subscriptionList = new List<SocketSubscription>(); List<SocketSubscription> subscriptionList = new List<SocketSubscription>();
lock (subscriptionLock) lock (_subscriptionLock)
{ {
foreach (var subscription in subscriptions) foreach (var subscription in _subscriptions)
{ {
if (subscription.Request != null) if (subscription.Request != null)
subscriptionList.Add(subscription); subscriptionList.Add(subscription);
@ -654,7 +658,7 @@ namespace CryptoExchange.Net.Sockets
if (!_socket.IsOpen) if (!_socket.IsOpen)
return new CallResult<bool>(new WebError("Socket not connected")); return new CallResult<bool>(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<bool>(true); return new CallResult<bool>(true);
} }