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"); } } } } }