End-to-end integration tests
DELETE
WalletWasabi.Tests/RegressionTests/WabiSabi/IntegrationTest.cs
-using System.Net.Http;
-using Xunit;
-
-namespace WalletWasabi.Tests.RegressionTests.WabiSabi
-{
- public abstract class IntegrationTest : IClassFixture<WabiSabiApiApplicationFactory>
- {
- protected readonly WabiSabiApiApplicationFactory Factory;
- protected readonly HttpClient HttpClient;
-
- public IntegrationTest(WabiSabiApiApplicationFactory factory)
- {
- Factory = factory;
- HttpClient = Factory.CreateClient();
- }
- }
-}

ADD
WalletWasabi.Tests/RegressionTests/WabiSabi/Startup.cs
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using WalletWasabi.WabiSabi.Models.Serialization;
+
+namespace WalletWasabi.Tests.RegressionTests.WabiSabi
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ app.UseStaticFiles();
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ var backendAssembly = typeof(WalletWasabi.Backend.Controllers.WabiSabiController).Assembly;
+ services.AddMvc()
+ .AddApplicationPart(backendAssembly)
+ .AddControllersAsServices()
+ .AddNewtonsoftJson(x => x.SerializerSettings.Converters = JsonSerializationOptions.Default.Settings.Converters);
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Tests/RegressionTests/WabiSabi/WabiSabiApiApplicationFactory.cs
WalletWasabi.Tests/RegressionTests/WabiSabi/WabiSabiApiApplicationFactory.cs
@@ -3,10 +3,13 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using NBitcoin;
using NBitcoin.RPC;
using WalletWasabi.BitcoinCore.Rpc;
@@ -19,14 +22,19 @@ using WalletWasabi.WabiSabi.Backend.PostRequests;
using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client;
using WalletWasabi.WabiSabi.Crypto;
+using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
namespace WalletWasabi.Tests.RegressionTests.WabiSabi
{
- public class WabiSabiApiApplicationFactory : WebApplicationFactory<WalletWasabi.Backend.Startup>
+ public class WabiSabiApiApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
- public WabiSabiApiApplicationFactory()
- : base()
+ protected override IHostBuilder CreateHostBuilder()
{
+ var builder = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(x =>
+ {
+ x.UseStartup<TStartup>().UseTestServer();
+ });
+ return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
@@ -34,11 +42,17 @@ namespace WalletWasabi.Tests.RegressionTests.WabiSabi
// will be called after the `ConfigureServices` from the Startup
builder.ConfigureTestServices(services =>
{
- services.AddSingleton<IRPCClient>(GetMockRpc());
- services.AddTransient<IArenaRequestHandler, ArenaRequestHandler>();
- services.AddTransient<Prison>();
- services.AddTransient<WabiSabiConfig>();
- services.AddSingleton<Arena>(serviceProvider =>
+ services.AddHostedService<BackgroundServiceStarter<Arena>>();
+ services.AddSingleton<Arena>();
+ services.AddSingleton<ArenaRequestHandler>();
+ services.AddScoped<Network>(_ => Network.Main);
+ services.AddScoped<IRPCClient>(_ => GetMockRpc());
+ services.AddScoped<Prison>();
+ services.AddScoped<WabiSabiConfig>();
+ services.AddScoped(typeof(TimeSpan), _ => TimeSpan.FromSeconds(2));
+
+ /*
+ serviceProvider =>
{
var rpc = serviceProvider.GetRequiredService<IRPCClient>();
var prison = serviceProvider.GetRequiredService<Prison>();
@@ -47,33 +61,34 @@ namespace WalletWasabi.Tests.RegressionTests.WabiSabi
arena.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult();
return arena;
+ });*/
});
+ builder.ConfigureServices(services =>
+ {
});
}
- public ArenaClient CreateArenaClient(HttpClient? httpClient = null)
+ public async Task<ArenaClient> CreateArenaClientAsync(HttpClient? httpClient = null)
{
- httpClient ??= CreateClient();
- var httpArenaRequestHandlerProxy = new WabiSabiHttpApiClient(new HttpClientWrapper(httpClient));
- var round = GetCurrentRound();
+ var wabiSabiHttpApiClient = CreateWabiSabiHttpApiClient(httpClient);
+ var rounds = await wabiSabiHttpApiClient.GetStatusAsync(CancellationToken.None);
+ var round = rounds.First(x => x.CoinjoinState is ConstructionState);
var arenaClient = new ArenaClient(
round.AmountCredentialIssuerParameters,
round.VsizeCredentialIssuerParameters,
new CredentialPool(),
new CredentialPool(),
- httpArenaRequestHandlerProxy,
+ wabiSabiHttpApiClient,
new InsecureRandom());
return arenaClient;
}
- public Round GetCurrentRound()
- {
- var arena = Services.GetRequiredService<Arena>();
- var round = arena.Rounds.First().Value;
- return round;
- }
+ public WabiSabiHttpApiClient CreateWabiSabiHttpApiClient(HttpClient? httpClient = null) =>
+ new(new HttpClientWrapper(httpClient ?? CreateClient()));
- private MockRpcClient GetMockRpc()
+ // Creates and configure an fake RPC client used to simulate the
+ // interaction with our bitcoin full node RPC server.
+ private static MockRpcClient GetMockRpc()
{
var mockRpc = new MockRpcClient();
mockRpc.OnGetMempoolInfoAsync = () => Task.FromResult(
@@ -95,4 +110,24 @@ namespace WalletWasabi.Tests.RegressionTests.WabiSabi
return mockRpc;
}
}
+
+ public class BackgroundServiceStarter<T> : IHostedService where T : IHostedService
+ {
+ readonly T backgroundService;
+
+ public BackgroundServiceStarter(T backgroundService)
+ {
+ this.backgroundService = backgroundService;
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ return backgroundService.StartAsync(cancellationToken);
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return backgroundService.StopAsync(cancellationToken);
+ }
+ }
}

MODIFY
WalletWasabi.Tests/RegressionTests/WabiSabi/WabiSabiHttpApiIntegrationTests.cs
WalletWasabi.Tests/RegressionTests/WabiSabi/WabiSabiHttpApiIntegrationTests.cs
+using System;
+using System.Linq;
+using System.Net.Http;
using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
+using NBitcoin.RPC;
+using WalletWasabi.BitcoinCore.Rpc;
+using WalletWasabi.Tests.Helpers;
+using WalletWasabi.Tests.UnitTests;
using WalletWasabi.WabiSabi.Backend.Models;
+using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
using Xunit;
namespace WalletWasabi.Tests.RegressionTests.WabiSabi
{
- public class WabiSabiHttpApiIntegrationTests : IntegrationTest
+ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicationFactory<Startup>>
{
- private readonly WabiSabiApiApplicationFactory _apiApplicationFactory;
+ private readonly WabiSabiApiApplicationFactory<Startup> _apiApplicationFactory;
- public WabiSabiHttpApiIntegrationTests(WabiSabiApiApplicationFactory apiApplicationFactory)
- : base(apiApplicationFactory)
+ public WabiSabiHttpApiIntegrationTests(WabiSabiApiApplicationFactory<Startup> apiApplicationFactory)
{
_apiApplicationFactory = apiApplicationFactory;
}
[Fact]
- public async void RegisterSpentOrInNonExistentCoin()
+ public async Task RegisterSpentOrInNonExistentCoinAsync()
{
- var round = _apiApplicationFactory.GetCurrentRound();
+ var httpClient = _apiApplicationFactory.CreateClient();
+ var apiClient = await _apiApplicationFactory.CreateArenaClientAsync();
+ var rounds = await apiClient.GetStatusAsync(CancellationToken.None);
+ var round = rounds.First(x => x.CoinjoinState is ConstructionState);
// If an output is not in the utxo dataset then it is not unspent, this
// means that the output is spent or simply doesn't even exist.
var nonExistingOutPoint = new OutPoint();
using var signingKey = new Key();
- var arenaClient = _apiApplicationFactory.CreateArenaClient();
- var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>( async () =>
- await arenaClient.RegisterInputAsync(Money.Coins(1), nonExistingOutPoint, signingKey, round.Id, CancellationToken.None));
+ var ex = await Assert.ThrowsAsync<HttpRequestException>( async () =>
+ await apiClient.RegisterInputAsync(Money.Coins(1), nonExistingOutPoint, signingKey, round.Id, CancellationToken.None));
- Assert.Equal(WabiSabiProtocolErrorCode.InputSpent, ex.ErrorCode);
+ var wex = Assert.IsType<WabiSabiProtocolException>(ex.InnerException);
+ Assert.Equal(WabiSabiProtocolErrorCode.InputSpent, wex.ErrorCode);
+ }
+
+ [Fact]
+ public async Task RegisterCoinAsync()
+ {
+ using var signingKey = new Key();
+ var coinToRegister = new Coin(
+ BitcoinFactory.CreateOutPoint(),
+ new TxOut(Money.Coins(1), signingKey.PubKey.WitHash.ScriptPubKey));
+
+ var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
+ {
+ builder.ConfigureServices(services =>
+ {
+ var rpc = GetMockRpc();
+ rpc.OnGetTxOutAsync = (_, _, _) => new ()
+ {
+ Confirmations = 101,
+ IsCoinBase = false,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = coinToRegister.TxOut
+ };
+ services.AddScoped<IRPCClient>(s => rpc);
+ });
+ }).CreateClient();
+
+ var apiClient = await _apiApplicationFactory.CreateArenaClientAsync(httpClient);
+ var rounds = await apiClient.GetStatusAsync(CancellationToken.None);
+ var round = rounds.First(x => x.CoinjoinState is ConstructionState);
+
+ uint256 aliceId = await apiClient.RegisterInputAsync(Money.Coins(1), coinToRegister.Outpoint, signingKey, round.Id, CancellationToken.None);
+
+ Assert.NotEqual(uint256.Zero, aliceId);
+ }
+
+ // Creates and configure an fake RPC client used to simulate the
+ // interaction with our bitcoin full node RPC server.
+ private static MockRpcClient GetMockRpc()
+ {
+ var mockRpc = new MockRpcClient();
+ mockRpc.OnGetMempoolInfoAsync = () => Task.FromResult(
+ new MemPoolInfo
+ {
+ MemPoolMinFee = 0.00001000, // 1 s/b (default value)
+ Histogram = Array.Empty<FeeRateGroup>()
+ });
+
+ mockRpc.OnEstimateSmartFeeAsync = (target, mode) => Task.FromResult(
+ new EstimateSmartFeeResponse()
+ {
+ Blocks = target,
+ FeeRate = new FeeRate(Money.Satoshis(5000))
+ });
+
+ mockRpc.OnGetTxOutAsync = (_, _, _) => null;
+
+ return mockRpc;
}
}
}

End-to-end integration tests