1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-17 03:12:56 +00:00

CryptoExchange V12 (#281)

* Result types:
  * (Web)CallResult types are replaced by HttpResult, WebSocketResult and QueryResult with the same logic
  * Updated result types to record type
  * Result creation can be done with (Http/WebSocket/Query)Result.Ok(..) and .Fail(..)
  * Removed implicit result type conversion to bool, `if (result)` no longer works, instead use `if (result.Success)`
  * Replaced CallResult.SuccessResult with CallResult.Ok()
  * Fixed result object nullability hinting, for example Data might be null if Success isn't checked for true

* Parameters & serialization:
  * Added support for `enabled` and `disabled` strings to bool converter
  * Removed ParameterCollection type, has been replaced by Parameters type
  * Removed ArraySerialization, OrderParameters and ParameterOrderComparer properties from RestApiClient, moved to ParameterSerializationsSettings
  * Updated RestRequestConfiguration in AuthenticationProvider.ProcessRequest to contain the full RequestDefinition instead of copied fields	

* Clients:
  * Updated Api client constructor logging parameter from ILogger to ILoggerFactory? 
  * Added Api client constructor exchange name parameter
  * Added ToString overrides on base API types
  * Added Exchange property on BaseApiClient
  * Added ApiCredentials property on IRestApiClient and ISocketApiClient interfaces
  * Updated ILogger source from client name to topic specific client name
  * Removed logging from client creation
  * Fixed BaseRestClient SetApiCredentials not marked as virtual

* Rest:
  * Added BaseAddress to RequestDefinition object
  * Updated RestApiClient AuthenticationProvider logic from private to protected and virtual
  * Removed RestApiClient.SendAsync baseAddress parameter removed
  * Removed RestApiClient.SendAsync without type parameter

* WebSocket:
  * Updated MessageRouting definition into CreateForEvent for subscriptions and CreateForQuery for queries
  * Improved Query type safety with CeateForQuery which allows second parameter for specifying the result type
  * Renamed MessageRouter.CreateWithoutHandler to CreateVoid
  * Updated SocketApiClient.GetSocketConnection to check connection uri instead of Tag for finding compatible connections
  * Removed unused UnhandledMessageExpected property SocketApiClient
  * Fixed issue in SocketApiClient.GetSocketConnection causing requests to always wait the full max 10 seconds when there was a reconnecting socket
	
* Shared APIs:
  * Updated Option definitions to always require the exchange name as first parameter
  * Added missing dedicated option types
  * Added Discover method on ISharedClient interface, returning info on supported capabilities and operations
  * Added SharedRequest GetParamValue helper method accepting multiple parameter names
  * Added ResetStaticExchangeParameters method on ExchangeParameters
  * Added Status property to SharedWithdrawal model
  * Added TradingModes property to SharedBalance model
  * Updated ExchangeSymbolCache to support multiple environments and additional key separation
  * Updated Shared ExchangeParameters parameter names to be case insensitive
  * Updated code comments
  * Replaced ExchangeResult with ExchangeCallResult type
  * Removed AsExchangeResult/ExchangeWebResult
  * Removed TradingMode from the response model, only maintained on models where it makes sense
  * Removed IListenKey support, listen keys now rely on internal management with TokenManager

* Rate limiting:
  * Fixed websocket connection attempts counting towards rate limit even when server could not be reached
  * Removed host from rate limit methods, now part of the already provided RequestDefinition
  * Added amount parameter to RateLimit Reset method to allow partially resetting the limit

* Added TokenManager implementation for automatic listenkey/token management
* Added UserClientProvider base class
* Added async streaming on UserDataTracker items with StreamUpdatesAsync
* Added cancellation token support to UserDataTracker starting
* Added Unit type for non-result types
* Added ServerError constructor taking ErrorType and message to make it easier to create
* Added SupportedEnvironments property to PlatformInfo
* Updated SymbolOrderBook DoResyncAsync to return CallResult instead of CallResult<bool> which was redundant
* Various small performance improvements
This commit is contained in:
Jan Korf
2026-06-29 10:38:09 +02:00
committed by GitHub
parent afb84a1bf0
commit e823114623
285 changed files with 7029 additions and 3658 deletions
+4 -120
View File
@@ -14,157 +14,41 @@ namespace CryptoExchange.Net.UnitTests
[Test]
public void TestBasicErrorCallResult()
{
var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown));
var result = CallResult.Fail(new ServerError("TestError", ErrorInfo.Unknown));
ClassicAssert.AreSame(result.Error!.ErrorCode, "TestError");
ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success);
}
[Test]
public void TestBasicSuccessCallResult()
{
var result = new CallResult(null);
var result = CallResult.Ok();
ClassicAssert.IsNull(result.Error);
Assert.That(result);
Assert.That(result.Success);
}
[Test]
public void TestCallResultError()
{
var result = new CallResult<object>(new ServerError("TestError", ErrorInfo.Unknown));
var result = CallResult.Fail<object>(new ServerError("TestError", ErrorInfo.Unknown));
ClassicAssert.AreSame(result.Error!.ErrorCode, "TestError");
ClassicAssert.IsNull(result.Data);
ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success);
}
[Test]
public void TestCallResultSuccess()
{
var result = new CallResult<object>(new object());
var result = CallResult.Ok<object>(new object());
ClassicAssert.IsNull(result.Error);
ClassicAssert.IsNotNull(result.Data);
Assert.That(result);
Assert.That(result.Success);
}
[Test]
public void TestCallResultSuccessAs()
{
var result = new CallResult<TestObjectResult>(new TestObjectResult());
var asResult = result.As<TestObject2>(result.Data.InnerData);
ClassicAssert.IsNull(asResult.Error);
ClassicAssert.IsNotNull(asResult.Data);
Assert.That(asResult.Data is not null);
Assert.That(asResult);
Assert.That(asResult.Success);
}
[Test]
public void TestCallResultErrorAs()
{
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
var asResult = result.As<TestObject2>(default);
ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError");
ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success);
}
[Test]
public void TestCallResultErrorAsError()
{
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError2");
ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success);
}
[Test]
public void TestWebCallResultErrorAsError()
{
var result = new WebCallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error!.ErrorCode, "TestError2");
ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success);
}
[Test]
public void TestWebCallResultSuccessAsError()
{
var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new HttpResponseMessage().Headers,
TimeSpan.FromSeconds(1),
null,
"{}",
1,
"https://test.com/api",
null,
HttpMethod.Get,
new HttpRequestMessage().Headers,
ResultDataSource.Server,
new TestObjectResult(),
null);
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
ClassicAssert.IsNotNull(asResult.Error);
Assert.That(asResult.Error!.ErrorCode == "TestError2");
Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK);
Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1));
Assert.That(asResult.RequestUrl == "https://test.com/api");
Assert.That(asResult.RequestMethod == HttpMethod.Get);
ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success);
}
[Test]
public void TestWebCallResultSuccessAsSuccess()
{
var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new HttpResponseMessage().Headers,
TimeSpan.FromSeconds(1),
null,
"{}",
1,
"https://test.com/api",
null,
HttpMethod.Get,
new HttpRequestMessage().Headers,
ResultDataSource.Server,
new TestObjectResult(),
null);
var asResult = result.As<TestObject2>(result.Data.InnerData);
ClassicAssert.IsNull(asResult.Error);
Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK);
Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1));
Assert.That(asResult.RequestUrl == "https://test.com/api");
Assert.That(asResult.RequestMethod == HttpMethod.Get);
ClassicAssert.IsNotNull(asResult.Data);
Assert.That(asResult);
Assert.That(asResult.Success);
}
}
public class TestObjectResult
@@ -134,7 +134,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests
client.ApiClient1.SetParameterPosition(httpMethod, pos);
client.ApiClient1.SetNextResponse("{}", System.Net.HttpStatusCode.OK);
var result = await client.ApiClient1.GetResponseAsync<TestObject>(httpMethod, new ParameterCollection
var result = await client.ApiClient1.GetResponseAsync<TestObject>(httpMethod, new Parameters(new ParameterSerializationSettings())
{
{ "TestParam1", "Value1" },
{ "TestParam2", 2 },
@@ -110,7 +110,7 @@ namespace CryptoExchange.Net.UnitTests.ClientTests
var result = await client.ApiClient1.SubscribeToUpdatesAsync<TestObject>(x => {}, false, default);
// act
await client.UnsubscribeAsync(result.Data);
await client.UnsubscribeAsync(result.Data!);
// assert
Assert.That(socket.Connected == false);
@@ -37,8 +37,8 @@ namespace CryptoExchange.Net.UnitTests
var symbols = CreateTestSymbols();
// act
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
var hasCached = ExchangeSymbolCache.HasCached(topicId);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
var hasCached = ExchangeSymbolCache.HasCached(topicId, "Env", null);
// assert
Assert.That(hasCached, Is.True);
@@ -52,14 +52,14 @@ namespace CryptoExchange.Net.UnitTests
var symbols = CreateTestSymbols();
// act
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// assert
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "BTCUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "ETHUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "BTCEUR"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "ETHBTC"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "XRPUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "BTCUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "ETHUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "BTCEUR"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "ETHBTC"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "XRPUSDT"), Is.True);
}
[Test]
@@ -78,13 +78,13 @@ namespace CryptoExchange.Net.UnitTests
};
// act
ExchangeSymbolCache.UpdateSymbolInfo(topicId, initialSymbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, updatedSymbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, initialSymbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, updatedSymbols);
// assert - should still have only the initial symbol since less than 60 minutes passed
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "BTCUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "BTCUSDT"), Is.True);
// The second update should not have been applied
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "ETHUSDT"), Is.False);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "ETHUSDT"), Is.False);
}
[Test]
@@ -95,8 +95,8 @@ namespace CryptoExchange.Net.UnitTests
var symbols = Array.Empty<SharedSpotSymbol>();
// act
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
var hasCached = ExchangeSymbolCache.HasCached(topicId);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
var hasCached = ExchangeSymbolCache.HasCached(topicId, "Env", null);
// assert
Assert.That(hasCached, Is.False);
@@ -109,7 +109,7 @@ namespace CryptoExchange.Net.UnitTests
var nonExistentTopic = "NonExistent_" + Guid.NewGuid();
// act
var result = ExchangeSymbolCache.HasCached(nonExistentTopic);
var result = ExchangeSymbolCache.HasCached(nonExistentTopic, "Env", null);
// assert
Assert.That(result, Is.False);
@@ -121,10 +121,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeWithData";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.HasCached(topicId);
var result = ExchangeSymbolCache.HasCached(topicId, "Env", null);
// assert
Assert.That(result, Is.True);
@@ -135,10 +135,10 @@ namespace CryptoExchange.Net.UnitTests
{
// arrange
var topicId = "ExchangeNoData";
ExchangeSymbolCache.UpdateSymbolInfo(topicId, Array.Empty<SharedSpotSymbol>());
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, Array.Empty<SharedSpotSymbol>());
// act
var result = ExchangeSymbolCache.HasCached(topicId);
var result = ExchangeSymbolCache.HasCached(topicId, "Env", null);
// assert
Assert.That(result, Is.False);
@@ -150,10 +150,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeSupports";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "BTCUSDT");
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "BTCUSDT");
// assert
Assert.That(result, Is.True);
@@ -165,10 +165,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeNoSupport";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "LINKUSDT");
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, "LINKUSDT");
// assert
Assert.That(result, Is.False);
@@ -181,7 +181,7 @@ namespace CryptoExchange.Net.UnitTests
var nonExistentTopic = "NonExistent_" + Guid.NewGuid();
// act
var result = ExchangeSymbolCache.SupportsSymbol(nonExistentTopic, "BTCUSDT");
var result = ExchangeSymbolCache.SupportsSymbol(nonExistentTopic, "Env", null, "BTCUSDT");
// assert
Assert.That(result, Is.False);
@@ -193,11 +193,11 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeSharedSymbol";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
var sharedSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
// act
var result = ExchangeSymbolCache.SupportsSymbol(topicId, sharedSymbol);
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, sharedSymbol);
// assert
Assert.That(result, Is.True);
@@ -209,11 +209,11 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeNoSharedSymbol";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
var sharedSymbol = new SharedSymbol(TradingMode.Spot, "LINK", "USDT");
// act
var result = ExchangeSymbolCache.SupportsSymbol(topicId, sharedSymbol);
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, sharedSymbol);
// assert
Assert.That(result, Is.False);
@@ -225,11 +225,11 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeDifferentMode";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
var sharedSymbol = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDT");
// act
var result = ExchangeSymbolCache.SupportsSymbol(topicId, sharedSymbol);
var result = ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, sharedSymbol);
// assert
Assert.That(result, Is.False);
@@ -243,7 +243,7 @@ namespace CryptoExchange.Net.UnitTests
var sharedSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
// act
var result = ExchangeSymbolCache.SupportsSymbol(nonExistentTopic, sharedSymbol);
var result = ExchangeSymbolCache.SupportsSymbol(nonExistentTopic, "Env", null, sharedSymbol);
// assert
Assert.That(result, Is.False);
@@ -255,10 +255,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeBaseAsset";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "BTC");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Env", null, "BTC");
// assert
Assert.That(result, Is.Not.Null);
@@ -273,10 +273,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeCaseInsensitive";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "btc");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Env", null, "btc");
// assert
Assert.That(result, Is.Not.Null);
@@ -289,10 +289,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeNoBaseAsset";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "LINK");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Env", null, "LINK");
// assert
Assert.That(result, Is.Not.Null);
@@ -306,7 +306,7 @@ namespace CryptoExchange.Net.UnitTests
var nonExistentTopic = "NonExistent_" + Guid.NewGuid();
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(nonExistentTopic, "BTC");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(nonExistentTopic, "Env", null, "BTC");
// assert
Assert.That(result, Is.Not.Null);
@@ -319,10 +319,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeParse";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, "BTCUSDT");
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Env", null, "BTCUSDT");
// assert
Assert.That(result, Is.Not.Null);
@@ -338,10 +338,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeNoParse";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, "LINKUSDT");
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Env", null, "LINKUSDT");
// assert
Assert.That(result, Is.Null);
@@ -353,10 +353,10 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var topicId = "ExchangeNullSymbol";
var symbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, null);
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Env", null, null);
// assert
Assert.That(result, Is.Null);
@@ -369,7 +369,7 @@ namespace CryptoExchange.Net.UnitTests
var nonExistentTopic = "NonExistent_" + Guid.NewGuid();
// act
var result = ExchangeSymbolCache.ParseSymbol(nonExistentTopic, "BTCUSDT");
var result = ExchangeSymbolCache.ParseSymbol(nonExistentTopic, "Env", null, "BTCUSDT");
// assert
Assert.That(result, Is.Null);
@@ -391,14 +391,14 @@ namespace CryptoExchange.Net.UnitTests
};
// act
ExchangeSymbolCache.UpdateSymbolInfo(topic1, symbols1);
ExchangeSymbolCache.UpdateSymbolInfo(topic2, symbols2);
ExchangeSymbolCache.UpdateSymbolInfo(topic1, "Env", null, symbols1);
ExchangeSymbolCache.UpdateSymbolInfo(topic2, "Env", null, symbols2);
// assert
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic1, "BTCUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic1, "ETHUSDT"), Is.False);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic2, "ETHUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic2, "BTCUSDT"), Is.False);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic1, "Env", null, "BTCUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic1, "Env", null, "ETHUSDT"), Is.False);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic2, "Env", null, "ETHUSDT"), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topic2, "Env", null, "BTCUSDT"), Is.False);
}
[Test]
@@ -411,14 +411,14 @@ namespace CryptoExchange.Net.UnitTests
var allSymbols = spotSymbols.Concat(futuresSymbols).ToArray();
// act
ExchangeSymbolCache.UpdateSymbolInfo(topicId, allSymbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, allSymbols);
// assert
var spotSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var futuresSymbol = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDT");
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, spotSymbol), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, futuresSymbol), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, spotSymbol), Is.True);
Assert.That(ExchangeSymbolCache.SupportsSymbol(topicId, "Env", null, futuresSymbol), Is.True);
}
[Test]
@@ -429,10 +429,10 @@ namespace CryptoExchange.Net.UnitTests
var spotSymbols = CreateTestSymbols();
var futuresSymbols = CreateFuturesSymbols();
var allSymbols = spotSymbols.Concat(futuresSymbols).ToArray();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, allSymbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, allSymbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "BTC");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Env", null, "BTC");
// assert
Assert.That(result.Length, Is.GreaterThanOrEqualTo(2));
@@ -451,15 +451,119 @@ namespace CryptoExchange.Net.UnitTests
new SharedSpotSymbol("ETH", "BTC", "ETHBTC", true, TradingMode.Spot),
new SharedSpotSymbol("ETH", "EUR", "ETHEUR", true, TradingMode.Spot)
};
ExchangeSymbolCache.UpdateSymbolInfo(topicId, symbols);
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Env", null, symbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "ETH");
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Env", null, "ETH");
// assert
Assert.That(result.Length, Is.EqualTo(3));
Assert.That(result.All(x => x.BaseAsset == "ETH"), Is.True);
}
[Test]
public void GetSymbolsForBaseAsset_WithDifferentEnvironments_Should_ReturnNone()
{
// arrange
var topicId = "Topic1";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", null, spotSymbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Test", null, "BTC");
// assert
Assert.That(result.Length, Is.EqualTo(0));
}
[Test]
public void GetSymbolsForBaseAsset_WithDifferentKey_Should_ReturnNone()
{
// arrange
var topicId = "Topic2";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", "1", spotSymbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Live", "2", "BTC");
// assert
Assert.That(result.Length, Is.EqualTo(0));
}
[Test]
public void GetSymbolsForBaseAsset_WithSetKey_Should_ReturnNone()
{
// arrange
var topicId = "Topic3";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", null, spotSymbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Live", "2", "BTC");
// assert
Assert.That(result.Length, Is.EqualTo(0));
}
[Test]
public void GetSymbolsForBaseAsset_WithNotSetKey_Should_ReturnNone()
{
// arrange
var topicId = "Topic4";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", "2", spotSymbols);
// act
var result = ExchangeSymbolCache.GetSymbolsForBaseAsset(topicId, "Live", null, "BTC");
// assert
Assert.That(result.Length, Is.EqualTo(2));
}
[Test]
public void ParseSymbol_WithDifferentKey_Should_ReturnNull()
{
// arrange
var topicId = "Topic5";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", "1", spotSymbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Live", "2", "BTCUSDT");
// assert
Assert.That(result, Is.Null);
}
[Test]
public void ParseSymbol_WithSetKey_Should_ReturnNull()
{
// arrange
var topicId = "Topic6";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", null, spotSymbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Live", "2", "BTCUSDT");
// assert
Assert.That(result, Is.Null);
}
[Test]
public void ParseSymbol_WithNotSetKey_Should_ReturnNull()
{
// arrange
var topicId = "Topic7";
var spotSymbols = CreateTestSymbols();
ExchangeSymbolCache.UpdateSymbolInfo(topicId, "Live", "1", spotSymbols);
// act
var result = ExchangeSymbolCache.ParseSymbol(topicId, "Live", null, "BTCUSDT");
// assert
Assert.That(result, Is.Not.Null);
}
}
}
@@ -11,15 +11,15 @@ namespace CryptoExchange.Net.UnitTests.Implementations
{
public TestQuery(TestSocketMessage request, bool authenticated) : base(request, authenticated, 1)
{
MessageRouter = MessageRouter.CreateWithoutTopicFilter<TestSocketMessage>(request.Id.ToString(), HandleMessage);
MessageRouter = MessageRouter.CreateForQuery<TestSocketMessage>(request.Id.ToString(), HandleMessage);
}
private CallResult? HandleMessage(SocketConnection connection, DateTime time, string? arg3, TestSocketMessage message)
private CallResult<TestSocketMessage>? HandleMessage(SocketConnection connection, DateTime time, string? arg3, TestSocketMessage message)
{
if (message.Data != "OK")
return new CallResult(new ServerError(ErrorInfo.Unknown with { Message = message.Data }));
return CallResult.Fail<TestSocketMessage>(new ServerError(ErrorInfo.Unknown with { Message = message.Data }));
return CallResult.SuccessResult;
return CallResult.Ok(message);
}
}
}
@@ -20,8 +20,8 @@ namespace CryptoExchange.Net.UnitTests.Implementations
{
protected override IRestMessageHandler MessageHandler { get; } = new TestRestMessageHandler();
public TestRestApiClient(ILogger logger, HttpClient? httpClient, TestRestOptions options)
: base(logger, httpClient, options.Environment.RestClientAddress, options, options.ExchangeOptions)
public TestRestApiClient(ILoggerFactory? loggerFactory, HttpClient? httpClient, TestRestOptions options)
: base(loggerFactory, "Test", httpClient, options.Environment.RestClientAddress, options, options.ExchangeOptions)
{
}
@@ -48,14 +48,14 @@ namespace CryptoExchange.Net.UnitTests.Implementations
RequestFactory = factory;
}
internal async Task<WebCallResult<T>> GetResponseAsync<T>(HttpMethod? httpMethod = null, ParameterCollection? collection = null, RateLimitGate? rateLimitGate = null)
internal async Task<HttpResult<T>> GetResponseAsync<T>(HttpMethod? httpMethod = null, Parameters? collection = null, RateLimitGate? rateLimitGate = null)
{
var definition = new RequestDefinition("/path", httpMethod ?? HttpMethod.Get)
var definition = new RequestDefinition(BaseAddress, "/path", httpMethod ?? HttpMethod.Get)
{
Weight = rateLimitGate == null ? 0 : 1,
RateLimitGate = rateLimitGate
};
return await SendAsync<T>(BaseAddress, definition, collection ?? new ParameterCollection(), default);
return await SendAsync<T>(definition, collection ?? new Parameters(new ParameterSerializationSettings()), default);
}
internal void SetParameterPosition(HttpMethod httpMethod, HttpMethodParameterPosition pos)
@@ -20,8 +20,8 @@ namespace CryptoExchange.Net.UnitTests.Implementations
{
Initialize(options.Value);
ApiClient1 = AddApiClient(new TestRestApiClient(_logger, httpClient, options.Value));
ApiClient2 = AddApiClient(new TestRestApiClient(_logger, httpClient, options.Value));
ApiClient1 = AddApiClient(new TestRestApiClient(loggerFactory, httpClient, options.Value));
ApiClient2 = AddApiClient(new TestRestApiClient(loggerFactory, httpClient, options.Value));
}
}
}
@@ -1,4 +1,5 @@
using CryptoExchange.Net.UnitTests.ConverterTests;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.UnitTests.ConverterTests;
using CryptoExchange.Net.UnitTests.Implementations;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -11,6 +12,7 @@ namespace CryptoExchange.Net.UnitTests
[JsonSerializable(typeof(IDictionary<string, string>))]
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(IDictionary<string, object>))]
[JsonSerializable(typeof(Parameters))]
[JsonSerializable(typeof(TestObject))]
[JsonSerializable(typeof(TestSocketMessage))]
@@ -17,13 +17,13 @@ namespace CryptoExchange.Net.UnitTests.Implementations
{
internal class TestSocketApiClient : SocketApiClient<TestEnvironment, TestAuthenticationProvider, TestCredentials>
{
public TestSocketApiClient(ILogger logger, TestSocketOptions options)
: base(logger, options.Environment.SocketClientAddress, options, options.ExchangeOptions)
public TestSocketApiClient(ILoggerFactory? loggerFactory, TestSocketOptions options)
: base(loggerFactory, "Test", options.Environment.SocketClientAddress, options, options.ExchangeOptions)
{
}
public TestSocketApiClient(ILogger logger, HttpClient httpClient, string baseAddress, TestSocketOptions options, SocketApiOptions apiOptions)
: base(logger, baseAddress, options, apiOptions)
public TestSocketApiClient(ILoggerFactory? loggerFactory, HttpClient httpClient, string baseAddress, TestSocketOptions options, SocketApiOptions apiOptions)
: base(loggerFactory, "Test", baseAddress, options, apiOptions)
{
}
@@ -36,7 +36,7 @@ namespace CryptoExchange.Net.UnitTests.Implementations
protected override TestAuthenticationProvider CreateAuthenticationProvider(TestCredentials credentials) =>
new TestAuthenticationProvider(credentials);
public async Task<CallResult<UpdateSubscription>> SubscribeToUpdatesAsync<T>(Action<DataEvent<T>> handler, bool subQuery, CancellationToken ct)
public async Task<WebSocketResult<UpdateSubscription>> SubscribeToUpdatesAsync<T>(Action<DataEvent<T>> handler, bool subQuery, CancellationToken ct)
{
return await base.SubscribeAsync(new TestSubscription<T>(_logger, handler, subQuery, false), ct);
}
@@ -19,8 +19,8 @@ namespace CryptoExchange.Net.UnitTests.Implementations
{
Initialize(options.Value);
ApiClient1 = AddApiClient(new TestSocketApiClient(_logger, options.Value));
ApiClient2 = AddApiClient(new TestSocketApiClient(_logger, options.Value));
ApiClient1 = AddApiClient(new TestSocketApiClient(loggerFactory, options.Value));
ApiClient2 = AddApiClient(new TestSocketApiClient(loggerFactory, options.Value));
}
}
}
@@ -21,7 +21,7 @@ namespace CryptoExchange.Net.UnitTests.Implementations
_handler = handler;
_subQuery = subQuery;
MessageRouter = MessageRouter.CreateWithoutTopicFilter<T>("test", HandleUpdate);
MessageRouter = MessageRouter.CreateForEvent<T>("test", HandleUpdate);
}
protected override Query? GetSubQuery(SocketConnection connection)
@@ -44,7 +44,7 @@ namespace CryptoExchange.Net.UnitTests.Implementations
private CallResult? HandleUpdate(SocketConnection connection, DateTime time, string? originalData, T data)
{
_handler(new DataEvent<T>("Test", data, time, originalData));
return CallResult.SuccessResult;
return CallResult.Ok();
}
}
}
@@ -12,319 +12,248 @@ namespace CryptoExchange.Net.UnitTests
[Test]
public void AddingBasicValue_SetValueCorrectly()
{
var parameters = new ParameterCollection();
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", "value");
Assert.That(parameters["test"], Is.EqualTo("value"));
}
[Test]
public void AddingBasicNullValue_ThrowsException()
{
var parameters = new ParameterCollection();
Assert.Throws<ArgumentNullException>(() => parameters.Add("test", null!));
}
[Test]
public void AddingOptionalBasicValue_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptional("test", "value");
Assert.That(parameters["test"], Is.EqualTo("value"));
}
[Test]
public void AddingOptionalBasicNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptional("test", null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingDecimalValueAsString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddString("test", 0.1m);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", 0.1m, DecimalSerialization.String);
Assert.That(parameters["test"], Is.EqualTo("0.1"));
}
[Test]
public void AddingOptionalDecimalValueAsString_SetValueCorrectly()
public void AddingDecimalValueAsString2_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", 0.1m);
var parameters = new Parameters(new ParameterSerializationSettings()
{
Decimal = DecimalSerialization.String
});
parameters.Add("test", 0.1m);
Assert.That(parameters["test"], Is.EqualTo("0.1"));
}
[Test]
public void AddingOptionalDecimalNullValueAsString_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", (decimal?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingIntValueAsString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddString("test", 1);
Assert.That(parameters["test"], Is.EqualTo("1"));
}
[Test]
public void AddingOptionalIntValueAsString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", 1);
Assert.That(parameters["test"], Is.EqualTo("1"));
}
[Test]
public void AddingOptionalIntNullValueAsString_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", (int?)null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", (int?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingLongValueAsString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddString("test", 1L);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", 1L, IntegerSerialization.String);
Assert.That(parameters["test"], Is.EqualTo("1"));
}
[Test]
public void AddingOptionalLongValueAsString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", 1L);
var parameters = new Parameters(new ParameterSerializationSettings()
{
Integer = IntegerSerialization.String
});
parameters.Add("test", 1L);
Assert.That(parameters["test"], Is.EqualTo("1"));
}
[Test]
public void AddingOptionalLongNullValueAsString_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalString("test", (long?)null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", (long?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingMillisecondTimestamp_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddMilliseconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), DateTimeSerialization.MillisecondsNumber);
Assert.That(parameters["test"], Is.EqualTo(1735689600000));
}
[Test]
public void AddingOptionalMillisecondTimestamp_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalMilliseconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings()
{
DateTimes = DateTimeSerialization.MillisecondsNumber
});
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
Assert.That(parameters["test"], Is.EqualTo(1735689600000));
}
[Test]
public void AddingOptionalMillisecondNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalMilliseconds("test", null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", (DateTime?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingMillisecondTimestampString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddMillisecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), DateTimeSerialization.MillisecondsString);
Assert.That(parameters["test"], Is.EqualTo("1735689600000"));
}
[Test]
public void AddingOptionalMillisecondTimestampString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalMillisecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings()
{
DateTimes = DateTimeSerialization.MillisecondsString
});
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
Assert.That(parameters["test"], Is.EqualTo("1735689600000"));
}
[Test]
public void AddingOptionalMillisecondStringNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalMillisecondsString("test", null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingSecondTimestamp_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddSeconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), DateTimeSerialization.SecondsNumber);
Assert.That(parameters["test"], Is.EqualTo(1735689600));
}
[Test]
public void AddingOptionalSecondTimestamp_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalSeconds("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings()
{
DateTimes = DateTimeSerialization.SecondsNumber
});
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
Assert.That(parameters["test"], Is.EqualTo(1735689600));
}
[Test]
public void AddingSecondNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalSeconds("test", null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingSecondTimestampString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddSecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), DateTimeSerialization.SecondsString);
Assert.That(parameters["test"], Is.EqualTo("1735689600"));
}
[Test]
public void AddingOptionalSecondTimestampString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalSecondsString("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var parameters = new Parameters(new ParameterSerializationSettings()
{
DateTimes = DateTimeSerialization.SecondsString
});
parameters.Add("test", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
Assert.That(parameters["test"], Is.EqualTo("1735689600"));
}
[Test]
public void AddingSecondStringNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalSecondsString("test", null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingEnum_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddEnum("test", TestEnum.Two);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", TestEnum.Two);
Assert.That(parameters["test"], Is.EqualTo("2"));
}
[Test]
public void AddingOptionalEnum_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalEnum("test", (TestEnum?)TestEnum.Two);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", (TestEnum?)TestEnum.Two);
Assert.That(parameters["test"], Is.EqualTo("2"));
}
[Test]
public void AddingOptionalEnumNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalEnum("test", (TestEnum?)null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", (TestEnum?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingEnumAsInt_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddEnumAsInt("test", TestEnum.Two);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", TestEnum.Two, EnumSerialization.Number);
Assert.That(parameters["test"], Is.EqualTo(2));
}
[Test]
public void AddingOptionalEnumAsInt_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalEnumAsInt("test", (TestEnum?)TestEnum.Two);
var parameters = new Parameters(new ParameterSerializationSettings()
{
Enum = EnumSerialization.Number
});
parameters.Add("test", TestEnum.Two);
Assert.That(parameters["test"], Is.EqualTo(2));
}
[Test]
public void AddingOptionalEnumAsIntNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalEnumAsInt("test", (TestEnum?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingCommaSeparated_SetValueCorrectly()
{
var parameters = new ParameterCollection();
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.AddCommaSeparated("test", ["1", "2"]);
Assert.That(parameters["test"], Is.EqualTo("1,2"));
}
[Test]
public void AddingOptionalCommaSeparated_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalCommaSeparated("test", ["1", "2"]);
Assert.That(parameters["test"], Is.EqualTo("1,2"));
}
[Test]
public void AddingOptionalCommaSeparatedNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalCommaSeparated("test", (string[]?)null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.AddCommaSeparated("test", (string[]?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingCommaSeparatedEnum_SetValueCorrectly()
{
var parameters = new ParameterCollection();
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.AddCommaSeparated("test", [TestEnum.Two, TestEnum.One]);
Assert.That(parameters["test"], Is.EqualTo("2,1"));
}
[Test]
public void AddingOptionalCommaSeparatedEnum_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalCommaSeparated("test", [TestEnum.Two, TestEnum.One]);
Assert.That(parameters["test"], Is.EqualTo("2,1"));
}
[Test]
public void AddingOptionalCommaSeparatedEnumNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalCommaSeparated("test", (TestEnum[]?)null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
[Test]
public void AddingBoolString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddBoolString("test", true);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", true, BoolSerialization.String);
Assert.That(parameters["test"], Is.EqualTo("true"));
}
[Test]
public void AddingOptionalBoolString_SetValueCorrectly()
{
var parameters = new ParameterCollection();
parameters.AddOptionalBoolString("test", true);
var parameters = new Parameters(new ParameterSerializationSettings()
{
Bool = BoolSerialization.String
});
parameters.Add("test", true);
Assert.That(parameters["test"], Is.EqualTo("true"));
}
[Test]
public void AddingOptionalBoolStringNullValue_DoesntSetValue()
{
var parameters = new ParameterCollection();
parameters.AddOptionalBoolString("test", null);
var parameters = new Parameters(new ParameterSerializationSettings());
parameters.Add("test", null);
Assert.That(parameters.ContainsKey("test"), Is.False);
}
}
+42 -42
View File
@@ -29,16 +29,16 @@ namespace CryptoExchange.Net.UnitTests
var triggered = false;
rateLimiter.RateLimitTriggered += (x) => { triggered = true; };
var requestDefinition = new RequestDefinition("/sapi/v1/system/status", HttpMethod.Get);
var requestDefinition = new RequestDefinition("https://test.com", "/sapi/v1/system/status", HttpMethod.Get);
for (var i = 0; i < requests + 1; i++)
{
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(i == requests ? triggered : !triggered);
}
triggered = false;
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(!triggered);
}
@@ -52,13 +52,13 @@ namespace CryptoExchange.Net.UnitTests
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new PathStartFilter("/sapi/"), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get);
var requestDefinition = new RequestDefinition("https://test.com", endpoint, HttpMethod.Get);
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
for (var i = 0; i < 2; i++)
{
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
bool expected = i == 1 ? expectLimiting ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null;
Assert.That(expected);
}
@@ -73,15 +73,15 @@ namespace CryptoExchange.Net.UnitTests
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new PathStartFilter("/sapi/"), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get);
var requestDefinition1 = new RequestDefinition("https://test.com", endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition("https://test.com", endpoint2, HttpMethod.Get);
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(evnt == null);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(expectLimiting ? evnt != null : evnt == null);
}
@@ -96,16 +96,16 @@ namespace CryptoExchange.Net.UnitTests
bool triggered = false;
rateLimiter.RateLimitTriggered += (x) => { triggered = true; };
var requestDefinition = new RequestDefinition("/sapi/test", HttpMethod.Get);
var requestDefinition = new RequestDefinition("https://test.com", "/sapi/test", HttpMethod.Get);
for (var i = 0; i < requests + 1; i++)
{
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(i == requests ? triggered : !triggered);
}
triggered = false;
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(!triggered);
}
@@ -117,13 +117,13 @@ namespace CryptoExchange.Net.UnitTests
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new ExactPathFilter("/sapi/test"), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get);
var requestDefinition = new RequestDefinition("https://test.com", endpoint, HttpMethod.Get);
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
for (var i = 0; i < 2; i++)
{
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null;
Assert.That(expected);
}
@@ -137,13 +137,13 @@ namespace CryptoExchange.Net.UnitTests
{
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new ExactPathsFilter(new[] { "/sapi/test", "/sapi/test2" }), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition = new RequestDefinition(endpoint, HttpMethod.Get);
var requestDefinition = new RequestDefinition("https://test.com", endpoint, HttpMethod.Get);
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
for (var i = 0; i < 2; i++)
{
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default);
bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null;
Assert.That(expected);
}
@@ -160,15 +160,15 @@ namespace CryptoExchange.Net.UnitTests
{
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerApiKey, new AuthenticatedEndpointFilter(true), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Sliding));
var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get) { Authenticated = key1 != null };
var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = key2 != null };
var requestDefinition1 = new RequestDefinition("https://test.com", endpoint1, HttpMethod.Get) { Authenticated = key1 != null };
var requestDefinition2 = new RequestDefinition("https://test.com", endpoint2, HttpMethod.Get) { Authenticated = key2 != null };
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, key1, 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(evnt == null);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, key2, 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(expectLimited ? evnt != null : evnt == null);
}
@@ -179,15 +179,15 @@ namespace CryptoExchange.Net.UnitTests
{
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, Array.Empty<IGuardFilter>(), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = true };
var requestDefinition1 = new RequestDefinition("https://test.com", endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition("https://test.com", endpoint2, HttpMethod.Get) { Authenticated = true };
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(evnt == null);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, null, 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(expectLimited ? evnt != null : evnt == null);
}
@@ -199,15 +199,15 @@ namespace CryptoExchange.Net.UnitTests
{
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new HostFilter("https://test.com"), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = true };
var requestDefinition1 = new RequestDefinition(host1, endpoint1, HttpMethod.Get);
var requestDefinition2 = new RequestDefinition(host2, endpoint2, HttpMethod.Get) { Authenticated = true };
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(evnt == null);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(expectLimited ? evnt != null : evnt == null);
}
@@ -222,9 +222,9 @@ namespace CryptoExchange.Net.UnitTests
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host1, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(evnt == null);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host2, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, default);
Assert.That(expectLimited ? evnt != null : evnt == null);
}
@@ -238,8 +238,8 @@ namespace CryptoExchange.Net.UnitTests
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2));
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
Assert.That(result2.Error, Is.TypeOf<CancellationRequestedError>());
}
@@ -250,16 +250,16 @@ namespace CryptoExchange.Net.UnitTests
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new LimitItemTypeFilter(RateLimitItemType.Request), 1, TimeSpan.FromSeconds(10), RateLimitWindowType.Fixed));
var definition = new RequestDefinition("1", HttpMethod.Get) { ConnectionId = 1 };
var definition = new RequestDefinition("https://test.com", "1", HttpMethod.Get) { ConnectionId = 1 };
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2));
// act
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, "https://test.com", null, 1, RateLimitingBehaviour.Fail, null, ct.Token);
await rateLimiter.ResetAsync(RateLimitItemType.Request, definition, "https://test.com", null, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, "https://test.com", null, 1, RateLimitingBehaviour.Fail, null, ct.Token);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, ct.Token);
await rateLimiter.ResetAsync(RateLimitItemType.Request, definition, null, null, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, ct.Token);
// assert
Assert.That(evnt, Is.Null);
@@ -272,17 +272,17 @@ namespace CryptoExchange.Net.UnitTests
var rateLimiter = new RateLimitGate("Test");
rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new LimitItemTypeFilter(RateLimitItemType.Request), 1, TimeSpan.FromSeconds(10), RateLimitWindowType.Fixed));
var definition1 = new RequestDefinition("1", HttpMethod.Get) { ConnectionId = 1 };
var definition2 = new RequestDefinition("2", HttpMethod.Get) { ConnectionId = 2 };
var definition1 = new RequestDefinition("https://test.com", "1", HttpMethod.Get) { ConnectionId = 1 };
var definition2 = new RequestDefinition("https://test.com", "2", HttpMethod.Get) { ConnectionId = 2 };
RateLimitEvent? evnt = null;
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
// act
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition1, "https://test.com", null, 1, RateLimitingBehaviour.Fail, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, "https://test.com", null, 1, RateLimitingBehaviour.Fail, null, default);
await rateLimiter.ResetAsync(RateLimitItemType.Request, definition1, "https://test.com", null, null, default);
var result3 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, "https://test.com", null, 1, RateLimitingBehaviour.Fail, null, default);
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition1, null, 1, RateLimitingBehaviour.Fail, null, default);
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, default);
await rateLimiter.ResetAsync(RateLimitItemType.Request, definition1, null, null, null, default);
var result3 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, default);
// assert
Assert.That(evnt, Is.Not.Null);
@@ -16,9 +16,9 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var routes = new MessageRoute[]
{
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null),
MessageRoute<string>.CreateWithTopicFilter("type1", "topic1", (_, _, _, _) => null),
MessageRoute<int>.CreateWithTopicFilter("type2", "topic2", (_, _, _, _) => null)
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null),
MessageRoute.CreateForEvent<string>("type1", "topic1", (_, _, _, _) => null),
MessageRoute.CreateForEvent<int>("type2", "topic2", (_, _, _, _) => null)
};
var router = new QueryRouter(routes);
@@ -46,10 +46,10 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
var collection = new QueryRouteCollection(typeof(string));
// act
collection.AddRoute(null, MessageRoute<string>.CreateWithoutTopicFilter("type", (_, _, _, _) => null));
collection.AddRoute(null, MessageRoute.CreateForEvent<string>("type", (_, _, _, _) => null));
var beforeMultipleReaders = collection.MultipleReaders;
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) => null, true));
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) => null, true));
var afterMultipleReaders = collection.MultipleReaders;
// assert
@@ -63,12 +63,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute(null, MessageRoute<string>.CreateWithoutTopicFilter("type", (_, _, _, _) =>
collection.AddRoute(null, MessageRoute.CreateForEvent<string>("type", (_, _, _, _) =>
{
calls.Add("no-topic");
return null;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("topic");
return null;
@@ -89,7 +89,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute("other-topic", MessageRoute<string>.CreateWithTopicFilter("type", "other-topic", (_, _, _, _) => null));
collection.AddRoute("other-topic", MessageRoute.CreateForEvent<string>("type", "other-topic", (_, _, _, _) => null));
collection.Build();
// act
@@ -106,12 +106,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute(null, MessageRoute<string>.CreateWithoutTopicFilter("type", (_, _, _, _) =>
collection.AddRoute(null, MessageRoute.CreateForEvent<string>("type", (_, _, _, _) =>
{
calls.Add("no-topic");
return null;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("topic");
return null;
@@ -132,17 +132,17 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var calls = new List<string>();
var expectedResult = CallResult.SuccessResult;
var expectedResult = CallResult.Ok();
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("first");
return expectedResult;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("second");
return new CallResult(null);
return CallResult.Ok();
}));
collection.Build();
@@ -160,17 +160,17 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var calls = new List<string>();
var expectedResult = CallResult.SuccessResult;
var expectedResult = CallResult.Ok();
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("first");
return expectedResult;
}, true));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("second");
return new CallResult(null);
return CallResult.Ok();
}));
collection.Build();
@@ -188,22 +188,22 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var calls = new List<string>();
var expectedResult = CallResult.SuccessResult;
var expectedResult = CallResult.Ok();
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("first");
return null;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("second");
return expectedResult;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("third");
return new CallResult(null);
return CallResult.Ok();
}));
collection.Build();
@@ -221,7 +221,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var collection = new QueryRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) => null));
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) => null));
collection.Build();
// act
@@ -17,13 +17,13 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
var processor1 = new TestMessageProcessor(
1,
MessageRouter.Create(
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null),
MessageRoute<string>.CreateWithTopicFilter("type1", "topic1", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null),
MessageRoute.CreateForEvent<string>("type1", "topic1", (_, _, _, _) => null)));
var processor2 = new TestMessageProcessor(
2,
MessageRouter.Create(
MessageRoute<int>.CreateWithTopicFilter("type2", "topic2", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<int>("type2", "topic2", (_, _, _, _) => null)));
var table = new RoutingTable();
@@ -57,12 +57,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
var processor1 = new TestMessageProcessor(
1,
MessageRouter.Create(
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null)));
var processor2 = new TestMessageProcessor(
2,
MessageRouter.Create(
MessageRoute<string>.CreateWithTopicFilter("type1", "topic1", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<string>("type1", "topic1", (_, _, _, _) => null)));
var table = new RoutingTable();
@@ -85,12 +85,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
var initialProcessor = new TestMessageProcessor(
1,
MessageRouter.Create(
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null)));
var replacementProcessor = new TestMessageProcessor(
2,
MessageRouter.Create(
MessageRoute<int>.CreateWithoutTopicFilter("type2", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<int>("type2", (_, _, _, _) => null)));
var table = new RoutingTable();
table.Update(new IMessageProcessor[] { initialProcessor });
@@ -116,7 +116,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
var processor = new TestMessageProcessor(
1,
MessageRouter.Create(
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null)));
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null)));
var table = new RoutingTable();
table.Update(new IMessageProcessor[] { processor });
@@ -15,9 +15,9 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var routes = new MessageRoute[]
{
MessageRoute<string>.CreateWithoutTopicFilter("type1", (_, _, _, _) => null),
MessageRoute<string>.CreateWithTopicFilter("type1", "topic1", (_, _, _, _) => null),
MessageRoute<int>.CreateWithTopicFilter("type2", "topic2", (_, _, _, _) => null)
MessageRoute.CreateForEvent<string>("type1", (_, _, _, _) => null),
MessageRoute.CreateForEvent<string>("type1", "topic1", (_, _, _, _) => null),
MessageRoute.CreateForEvent<int>("type2", "topic2", (_, _, _, _) => null)
};
var router = new SubscriptionRouter(routes);
@@ -44,12 +44,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new SubscriptionRouteCollection(typeof(string));
collection.AddRoute(null, MessageRoute<string>.CreateWithoutTopicFilter("type", (_, _, _, _) =>
collection.AddRoute(null, MessageRoute.CreateForEvent<string>("type", (_, _, _, _) =>
{
calls.Add("no-topic");
return null;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("topic");
return null;
@@ -61,7 +61,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// assert
Assert.That(handled, Is.True);
Assert.That(result, Is.SameAs(CallResult.SuccessResult));
Assert.That(result, Is.SameAs(CallResult.Ok()));
Assert.That(calls, Is.EqualTo(new[] { "no-topic" }));
}
@@ -70,7 +70,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
// arrange
var collection = new SubscriptionRouteCollection(typeof(string));
collection.AddRoute("other-topic", MessageRoute<string>.CreateWithTopicFilter("type", "other-topic", (_, _, _, _) => null));
collection.AddRoute("other-topic", MessageRoute.CreateForEvent<string>("type", "other-topic", (_, _, _, _) => null));
collection.Build();
// act
@@ -78,7 +78,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// assert
Assert.That(handled, Is.False);
Assert.That(result, Is.SameAs(CallResult.SuccessResult));
Assert.That(result, Is.SameAs(CallResult.Ok()));
}
[Test]
@@ -87,12 +87,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new SubscriptionRouteCollection(typeof(string));
collection.AddRoute(null, MessageRoute<string>.CreateWithoutTopicFilter("type", (_, _, _, _) =>
collection.AddRoute(null, MessageRoute.CreateForEvent<string>("type", (_, _, _, _) =>
{
calls.Add("no-topic");
return null;
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("topic");
return null;
@@ -104,7 +104,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// assert
Assert.That(handled, Is.True);
Assert.That(result, Is.SameAs(CallResult.SuccessResult));
Assert.That(result, Is.SameAs(CallResult.Ok()));
Assert.That(calls, Is.EqualTo(new[] { "no-topic", "topic" }));
}
@@ -114,12 +114,12 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new SubscriptionRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("first");
return CallResult.SuccessResult;
return CallResult.Ok();
}));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("second");
return null;
@@ -131,7 +131,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// assert
Assert.That(handled, Is.True);
Assert.That(result, Is.SameAs(CallResult.SuccessResult));
Assert.That(result, Is.SameAs(CallResult.Ok()));
Assert.That(calls, Is.EqualTo(new[] { "first", "second" }));
}
@@ -141,7 +141,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// arrange
var calls = new List<string>();
var collection = new SubscriptionRouteCollection(typeof(string));
collection.AddRoute("topic", MessageRoute<string>.CreateWithTopicFilter("type", "topic", (_, _, _, _) =>
collection.AddRoute("topic", MessageRoute.CreateForEvent<string>("type", "topic", (_, _, _, _) =>
{
calls.Add("topic");
return null;
@@ -153,7 +153,7 @@ namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
// assert
Assert.That(handled, Is.False);
Assert.That(result, Is.SameAs(CallResult.SuccessResult));
Assert.That(result, Is.SameAs(CallResult.Ok()));
Assert.That(calls, Is.Empty);
}
}
@@ -25,7 +25,7 @@ namespace CryptoExchange.Net.UnitTests
}
protected override Task<CallResult<bool>> DoResyncAsync(CancellationToken ct)
protected override Task<CallResult> DoResyncAsync(CancellationToken ct)
{
throw new NotImplementedException();
}
@@ -0,0 +1,347 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.Sockets.Default;
using CryptoExchange.Net.TokenManagement;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using System;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
public class TokenManagementTests
{
private static readonly TimeSpan TestMaintenanceInterval = TimeSpan.FromMilliseconds(5);
[Test]
public async Task AcquireWithoutApiKeyReturnsCredentialsError()
{
var starts = 0;
var manager = CreateManager(
(_, _) =>
{
starts++;
return Task.FromResult(CallResult.Ok("token"));
});
var result = await manager.AcquireAsync(new TokenScope("Test", "Test", "Test", ""));
Assert.That(result.Success, Is.False);
Assert.That(result.Error, Is.TypeOf<NoApiCredentialsError>());
Assert.That(starts, Is.EqualTo(0));
}
[Test]
public async Task StartTokenFailureIsReturned()
{
var error = new ServerError(ErrorType.Unknown, "start failed");
var manager = CreateManager((_, _) => Task.FromResult(CallResult.Fail<string>(error)));
var result = await manager.AcquireAsync(CreateScope());
Assert.That(result.Success, Is.False);
Assert.That(result.Error, Is.SameAs(error));
}
[Test]
public async Task ActiveTokenIsSharedWhileLeasedAndStoppedAfterLastRelease()
{
var starts = 0;
var stops = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
stopToken: (_, _) =>
{
stops++;
return Task.FromResult(CallResult.Ok());
});
var scope = CreateScope();
var first = await manager.AcquireAsync(scope);
var second = await manager.AcquireAsync(scope);
AssertSuccess(first);
AssertSuccess(second);
Assert.That(second.Data!.Token.Token, Is.EqualTo(first.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(1));
await first.Data!.ReleaseAsync();
Assert.That(stops, Is.EqualTo(0));
await second.Data!.ReleaseAsync();
Assert.That(stops, Is.EqualTo(1));
}
[Test]
public async Task ActiveTokenStartsNewTokenAfterLeaseRelease()
{
var starts = 0;
var stops = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
stopToken: (_, _) =>
{
stops++;
return Task.FromResult(CallResult.Ok());
});
var scope = CreateScope();
var first = await manager.AcquireAsync(scope);
AssertSuccess(first);
await first.Data!.ReleaseAsync();
var second = await manager.AcquireAsync(scope);
AssertSuccess(second);
Assert.That(second.Data!.Token.Token, Is.Not.EqualTo(first.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(2));
Assert.That(stops, Is.EqualTo(1));
await second.Data!.ReleaseAsync();
}
[Test]
public async Task ReleasingLeaseTwiceOnlyStopsActiveTokenOnce()
{
var stops = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token")),
stopToken: (_, _) =>
{
stops++;
return Task.FromResult(CallResult.Ok());
});
var leaseResult = await manager.AcquireAsync(CreateScope());
AssertSuccess(leaseResult);
await leaseResult.Data!.ReleaseAsync();
await leaseResult.Data!.ReleaseAsync();
Assert.That(stops, Is.EqualTo(1));
}
[Test]
public async Task CachedTokenIsReusedAfterLeaseRelease()
{
var starts = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
managementType: TokenManagementType.Cached);
var scope = CreateScope();
var first = await manager.AcquireAsync(scope);
AssertSuccess(first);
await first.Data!.ReleaseAsync();
var second = await manager.AcquireAsync(scope);
AssertSuccess(second);
Assert.That(second.Data!.Token.Token, Is.EqualTo(first.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(1));
await second.Data!.ReleaseAsync();
}
[Test]
public async Task CachedTokensAreScopedIndependently()
{
var starts = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
managementType: TokenManagementType.Cached);
var firstScope = CreateScope(additionalIdentifier: "one");
var secondScope = CreateScope(additionalIdentifier: "two");
var first = await manager.AcquireAsync(firstScope);
var second = await manager.AcquireAsync(secondScope);
AssertSuccess(first);
AssertSuccess(second);
await first.Data!.ReleaseAsync();
await second.Data!.ReleaseAsync();
var firstAgain = await manager.AcquireAsync(firstScope);
AssertSuccess(firstAgain);
Assert.That(firstAgain.Data!.Token.Token, Is.EqualTo(first.Data!.Token.Token));
Assert.That(second.Data!.Token.Token, Is.Not.EqualTo(first.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(2));
await firstAgain.Data!.ReleaseAsync();
}
[Test]
public async Task ExpiredCachedTokenIsNotReused()
{
var starts = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
timeValid: TimeSpan.FromMilliseconds(20),
managementType: TokenManagementType.Cached);
var scope = CreateScope();
var first = await manager.AcquireAsync(scope);
AssertSuccess(first);
await first.Data!.ReleaseAsync();
await Task.Delay(50);
var second = await manager.AcquireAsync(scope);
AssertSuccess(second);
Assert.That(first.Data!.Token.Status, Is.EqualTo(TokenStatus.Expired));
Assert.That(second.Data!.Token.Token, Is.Not.EqualTo(first.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(2));
await second.Data!.ReleaseAsync();
}
[Test]
public async Task CachedTokenDoesNotRunKeepAliveLoop()
{
var keepAlives = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token")),
refreshInterval: TimeSpan.FromMilliseconds(1),
keepAliveToken: (_, _) =>
{
keepAlives++;
return Task.FromResult(CallResult.Ok());
},
managementType: TokenManagementType.Cached);
var leaseResult = await manager.AcquireAsync(CreateScope());
AssertSuccess(leaseResult);
await Task.Delay(50);
Assert.That(keepAlives, Is.EqualTo(0));
await leaseResult.Data!.ReleaseAsync();
}
[Test]
public async Task ActiveTokenKeepAliveRefreshesValidity()
{
var keepAlives = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token")),
refreshInterval: TimeSpan.FromMilliseconds(1),
timeValid: TimeSpan.FromSeconds(1),
keepAliveToken: (_, _) =>
{
keepAlives++;
return Task.FromResult(CallResult.Ok());
});
var leaseResult = await manager.AcquireAsync(CreateScope());
AssertSuccess(leaseResult);
var originalValidUntil = leaseResult.Data!.Token.ValidUntil;
await WaitUntilAsync(() => keepAlives > 0);
Assert.That(leaseResult.Data!.Token.ValidUntil, Is.GreaterThan(originalValidUntil));
await leaseResult.Data!.ReleaseAsync();
}
[Test]
public async Task ActiveTokenKeepAliveFailureExpiresTokenWhenValidityPassed()
{
var starts = 0;
var expired = false;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
refreshInterval: TimeSpan.FromMilliseconds(1),
timeValid: TimeSpan.FromMilliseconds(25),
keepAliveToken: (_, _) => Task.FromResult(CallResult.Fail(new ServerError(ErrorType.Unknown, "keep alive failed"))));
var leaseResult = await manager.AcquireAsync(CreateScope());
AssertSuccess(leaseResult);
leaseResult.Data!.Token.Expired += _ => expired = true;
await WaitUntilAsync(() => expired);
Assert.That(leaseResult.Data!.Token.Status, Is.EqualTo(TokenStatus.Expired));
var nextLease = await manager.AcquireAsync(CreateScope());
AssertSuccess(nextLease);
Assert.That(nextLease.Data!.Token.Token, Is.Not.EqualTo(leaseResult.Data!.Token.Token));
Assert.That(starts, Is.EqualTo(2));
await leaseResult.Data!.ReleaseAsync();
await nextLease.Data!.ReleaseAsync();
}
[Test]
public async Task AcquireAndReplaceReleasesPreviousSubscriptionLease()
{
var starts = 0;
var stops = 0;
var manager = CreateManager(
(_, _) => Task.FromResult(CallResult.Ok("token-" + ++starts)),
stopToken: (_, _) =>
{
stops++;
return Task.FromResult(CallResult.Ok());
});
var subscription = new TestSubscription();
var first = await manager.AcquireAndReplaceAsync(subscription, CreateScope(additionalIdentifier: "one"));
AssertSuccess(first);
var second = await manager.AcquireAndReplaceAsync(subscription, CreateScope(additionalIdentifier: "two"));
AssertSuccess(second);
Assert.That(subscription.TokenLease, Is.SameAs(second.Data));
Assert.That(second.Data!.Token.Token, Is.Not.EqualTo(first.Data!.Token.Token));
Assert.That(stops, Is.EqualTo(1));
await subscription.TokenLease!.ReleaseAsync();
}
private static TokenManager CreateManager(
Func<TokenScope, System.Threading.CancellationToken, Task<CallResult<string>>> startToken,
TimeSpan? refreshInterval = null,
TimeSpan? timeValid = null,
Func<TokenInfo, System.Threading.CancellationToken, Task<CallResult>>? keepAliveToken = null,
Func<TokenInfo, System.Threading.CancellationToken, Task<CallResult>>? stopToken = null,
TokenManagementType managementType = TokenManagementType.Active)
{
return new TokenManager(
Guid.NewGuid().ToString(),
null,
refreshInterval ?? TimeSpan.FromMinutes(1),
timeValid ?? TimeSpan.FromMinutes(1),
startToken,
keepAliveToken,
stopToken,
managementType,
TestMaintenanceInterval);
}
private static TokenScope CreateScope(string apiKey = "apiKey", string? additionalIdentifier = null)
=> new TokenScope("Test", "Test", "Test", apiKey, additionalIdentifier);
private static void AssertSuccess(CallResult<TokenLease> result)
{
Assert.That(result.Success, Is.True, result.Error?.ToString());
Assert.That(result.Data, Is.Not.Null);
}
private static async Task WaitUntilAsync(Func<bool> condition)
{
var timeout = DateTime.UtcNow.AddSeconds(2);
while (!condition())
{
if (DateTime.UtcNow > timeout)
Assert.Fail("Condition was not met within the timeout");
await Task.Delay(10);
}
}
private sealed class TestSubscription : Subscription
{
public TestSubscription() : base(NullLogger.Instance, true)
{
}
protected override Query? GetSubQuery(SocketConnection connection) => null;
protected override Query? GetUnsubQuery(SocketConnection connection) => null;
}
}
}