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.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
{
///
/// Testing helpers
///
public class TestHelpers
{
[ExcludeFromCodeCoverage]
internal static bool AreEqual(T? self, T? to, params string[] ignore) where T : class
{
if (self != null && to != null)
{
var type = self.GetType();
var ignoreList = new List(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 client, string address) where T : BaseSocketClient
{
var socket = new TestSocket(address);
foreach (var apiClient in client.ApiClients.OfType())
{
apiClient.SocketFactory = new TestWebsocketFactory(socket);
}
return socket;
}
internal static void ConfigureRestClient(T client, string data, HttpStatusCode code) where T : BaseRestClient
{
foreach (var apiClient in client.ApiClients.OfType())
{
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;
}
}
///
/// Check a signature matches the expected signature
///
///
///
///
///
///
///
///
///
///
///
///
///
public static void CheckSignature(
RestApiClient client,
AuthenticationProvider authProvider,
HttpMethod method,
string path,
Func?, IDictionary?, IDictionary?, string> getSignature,
string expectedSignature,
Dictionary? parameters = null,
DateTime? time = null,
bool disableOrdering = false,
bool compareCase = true,
string host = "https://test.test-api.com")
{
parameters ??= new Dictionary
{
{ "test", 123 },
{ "test2", "abc" }
};
if (disableOrdering)
client.OrderParameters = false;
var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : null;
var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : null;
var headers = new Dictionary();
authProvider.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
authProvider.AuthenticateRequest(
client,
new Uri(host.AppendPath(path)),
method,
ref uriParams,
ref bodyParams,
ref 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}");
}
///
/// Scan the TClient rest client type for missing interface methods
///
///
///
public static void CheckForMissingRestInterfaces()
{
CheckForMissingInterfaces(typeof(TClient), typeof(Task));
}
///
/// Scan the TClient socket client type for missing interface methods
///
///
///
public static void CheckForMissingSocketInterfaces()
{
CheckForMissingInterfaces(typeof(TClient), typeof(Task>));
}
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) && !t.Name.EndsWith("Shared"));
foreach (var clientInterface in clientInterfaces)
{
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
foreach (var implementation in implementations)
{
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()) ?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
methods++;
}
Debug.WriteLine($"{clientInterface.Name} {methods} methods validated");
}
}
}
}
}