using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Sdk; namespace BTCPayServer.Tests { public static class TestUtils { public const int LongRunningTestTimeout = 60_000; // 60s #if DEBUG && !SHORT_TIMEOUT public const int TestTimeout = 600_000; #else public const int TestTimeout = 90_000; #endif public static DirectoryInfo TryGetSolutionDirectoryInfo() { var btcPayDirectory = ((OutputPathAttribute)typeof(TestUtils).Assembly.GetCustomAttributes(typeof(OutputPathAttribute), true)[0]).BuiltPath; var directory = new DirectoryInfo(btcPayDirectory); while (directory != null && !directory.GetFiles("*.sln").Any()) { directory = directory.Parent; } return directory; } static TestUtils() { TestDirectory = AppContext.BaseDirectory; } public static readonly string TestDirectory; public static string GetTestDataFullPath(string relativeFilePath) { var directory = new DirectoryInfo(TestDirectory); while (directory != null && !directory.GetFiles("*.csproj").Any()) { directory = directory.Parent; } return Path.Combine(directory.FullName, "TestData", relativeFilePath); } public static DateTimeOffset RoundSeconds(DateTimeOffset dateTimeOffset) => new DateTimeOffset(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Offset); public static T AssertType(this object obj) => Assert.IsType(obj); public static FormFile GetFormFile(string filename, string content) { File.WriteAllText(filename, content); var fileInfo = new FileInfo(filename); FormFile formFile = new FormFile( new FileStream(filename, FileMode.OpenOrCreate), 0, fileInfo.Length, fileInfo.Name, fileInfo.Name) { Headers = new HeaderDictionary() }; formFile.ContentType = "text/plain"; formFile.ContentDisposition = $"form-data; name=\"file\"; filename=\"{fileInfo.Name}\""; return formFile; } public static FormFile GetFormFile(string filename, byte[] content) { File.WriteAllBytes(filename, content); var fileInfo = new FileInfo(filename); FormFile formFile = new FormFile( new FileStream(filename, FileMode.OpenOrCreate), 0, fileInfo.Length, fileInfo.Name, fileInfo.Name) { Headers = new HeaderDictionary() }; formFile.ContentType = "application/octet-stream"; formFile.ContentDisposition = $"form-data; name=\"file\"; filename=\"{fileInfo.Name}\""; return formFile; } public static void Eventually(Action act, int ms = 20_000) { CancellationTokenSource cts = new CancellationTokenSource(ms); while (true) { try { act(); break; } catch (XunitException) when (!cts.Token.IsCancellationRequested) { cts.Token.WaitHandle.WaitOne(500); } } } public static async Task EventuallyAsync(Func act, int delay = 20000) { CancellationTokenSource cts = new CancellationTokenSource(delay); while (true) { try { await act(); break; } catch (XunitException) when (!cts.Token.IsCancellationRequested) { bool timeout =false; try { await Task.Delay(500, cts.Token); } catch { timeout = true; } if (timeout) throw; } } } internal static IHttpClientFactory CreateHttpFactory() { var services = new ServiceCollection(); services.AddHttpClient(); return services.BuildServiceProvider().GetRequiredService(); } } internal sealed class TestHttpMessageHandler(Func handle = null) : HttpMessageHandler { private readonly Dictionary> _responses = new(); public void Register(string url, Func responseFactory) => _responses[url] = responseFactory; public static HttpResponseMessage JsonResponse(string json) { return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(json, Encoding.UTF8, "application/json") }; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (handle is not null) return Task.FromResult(handle(request)); var url = request.RequestUri!.ToString(); if (_responses.TryGetValue(url, out var responseFactory)) return Task.FromResult(responseFactory()); return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); } } }