mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 16:06:15 +00:00
* Added support for specifying seperate uri and body parameters * Added support for different message and handling generic types on socket queries * Split DataEvent.Topic into StreamId and Symbol properties * Added support for negative time values parsing * Added some helper methods for converting DataEvent to CallResult * Added support for GZip/Deflate automatic decompressing in the default HttpClient * Updated some testing methods
192 lines
7.8 KiB
C#
192 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Sockets;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using CryptoExchange.Net.Authentication;
|
|
using CryptoExchange.Net.Clients;
|
|
using CryptoExchange.Net.Objects;
|
|
using CryptoExchange.Net.Objects.Sockets;
|
|
using CryptoExchange.Net.Testing.Implementations;
|
|
|
|
namespace CryptoExchange.Net.Testing
|
|
{
|
|
/// <summary>
|
|
/// Testing helpers
|
|
/// </summary>
|
|
public class TestHelpers
|
|
{
|
|
[ExcludeFromCodeCoverage]
|
|
internal static bool AreEqual<T>(T? self, T? to, params string[] ignore) where T : class
|
|
{
|
|
if (self != null && to != null)
|
|
{
|
|
var type = self.GetType();
|
|
var ignoreList = new List<string>(ignore);
|
|
foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (ignoreList.Contains(pi.Name))
|
|
continue;
|
|
|
|
var selfValue = type.GetProperty(pi.Name)!.GetValue(self, null);
|
|
var toValue = type.GetProperty(pi.Name)!.GetValue(to, null);
|
|
|
|
if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("System.Private.CoreLib.dll"))
|
|
{
|
|
// Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
|
|
if (AreEqual(selfValue, toValue, ignore))
|
|
continue;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return self == to;
|
|
}
|
|
|
|
internal static TestSocket ConfigureSocketClient<T>(T client) where T : BaseSocketClient
|
|
{
|
|
var socket = new TestSocket();
|
|
foreach (var apiClient in client.ApiClients.OfType<SocketApiClient>())
|
|
{
|
|
apiClient.SocketFactory = new TestWebsocketFactory(socket);
|
|
}
|
|
return socket;
|
|
}
|
|
|
|
internal static void ConfigureRestClient<T>(T client, string data, HttpStatusCode code) where T : BaseRestClient
|
|
{
|
|
foreach (var apiClient in client.ApiClients.OfType<RestApiClient>())
|
|
{
|
|
var expectedBytes = Encoding.UTF8.GetBytes(data);
|
|
var responseStream = new MemoryStream();
|
|
responseStream.Write(expectedBytes, 0, expectedBytes.Length);
|
|
responseStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
var response = new TestResponse(code, responseStream);
|
|
var request = new TestRequest(response);
|
|
|
|
var factory = new TestRequestFactory(request);
|
|
apiClient.RequestFactory = factory;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check a signature matches the expected signature
|
|
/// </summary>
|
|
/// <param name="client"></param>
|
|
/// <param name="authProvider"></param>
|
|
/// <param name="method"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="getSignature"></param>
|
|
/// <param name="expectedSignature"></param>
|
|
/// <param name="parameters"></param>
|
|
/// <param name="time"></param>
|
|
/// <param name="disableOrdering"></param>
|
|
/// <param name="compareCase"></param>
|
|
/// <param name="host"></param>
|
|
/// <exception cref="Exception"></exception>
|
|
public static void CheckSignature(
|
|
RestApiClient client,
|
|
AuthenticationProvider authProvider,
|
|
HttpMethod method,
|
|
string path,
|
|
Func<IDictionary<string, object>?, IDictionary<string, object>?, IDictionary<string, string>?, string> getSignature,
|
|
string expectedSignature,
|
|
Dictionary<string, object>? parameters = null,
|
|
DateTime? time = null,
|
|
bool disableOrdering = false,
|
|
bool compareCase = true,
|
|
string host = "https://test.test-api.com")
|
|
{
|
|
parameters ??= new Dictionary<string, object>
|
|
{
|
|
{ "test", 123 },
|
|
{ "test2", "abc" }
|
|
};
|
|
|
|
if (disableOrdering)
|
|
client.OrderParameters = false;
|
|
|
|
var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
|
var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
|
|
|
var headers = new Dictionary<string, string>();
|
|
|
|
authProvider.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
|
|
authProvider.AuthenticateRequest(
|
|
client,
|
|
new Uri(host.AppendPath(path)),
|
|
method,
|
|
uriParams,
|
|
bodyParams,
|
|
headers,
|
|
true,
|
|
client.ArraySerialization,
|
|
client.ParameterPositions[method],
|
|
client.RequestBodyFormat
|
|
);
|
|
|
|
var signature = getSignature(uriParams, bodyParams, headers);
|
|
|
|
if (!string.Equals(signature, expectedSignature, compareCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
|
|
throw new Exception($"Signatures do not match. Expected: {expectedSignature}, Actual: {signature}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scan the TClient rest client type for missing interface methods
|
|
/// </summary>
|
|
/// <typeparam name="TClient"></typeparam>
|
|
/// <exception cref="Exception"></exception>
|
|
public static void CheckForMissingRestInterfaces<TClient>()
|
|
{
|
|
CheckForMissingInterfaces(typeof(TClient), typeof(Task));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scan the TClient socket client type for missing interface methods
|
|
/// </summary>
|
|
/// <typeparam name="TClient"></typeparam>
|
|
/// <exception cref="Exception"></exception>
|
|
public static void CheckForMissingSocketInterfaces<TClient>()
|
|
{
|
|
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>));
|
|
}
|
|
|
|
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes)
|
|
{
|
|
var assembly = Assembly.GetAssembly(clientType);
|
|
var interfaceType = clientType.GetInterface("I" + clientType.Name);
|
|
var clientInterfaces = assembly.GetTypes().Where(t => t.Name.StartsWith("I" + clientType.Name));
|
|
|
|
foreach (var clientInterface in clientInterfaces)
|
|
{
|
|
var implementation = assembly.GetTypes().Single(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
|
|
int methods = 0;
|
|
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
|
|
{
|
|
var interfaceMethod = clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray());
|
|
if (interfaceMethod == null)
|
|
throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
|
|
methods++;
|
|
}
|
|
|
|
Debug.WriteLine($"{clientInterface.Name} {methods} methods validated");
|
|
}
|
|
}
|
|
}
|
|
}
|