Merge branch 'feat/ui-testing' into release/1.0.8-rc3
This commit is contained in:
commit
bcd72e17d8
342 changed files with 1058 additions and 1358 deletions
|
|
@ -1,9 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Xunit;
|
||||
using Yavsc.Extensions;
|
||||
|
||||
namespace Yavsc.Org.Tests;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer8.EntityFramework.Entities;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Yavsc.Models;
|
||||
using Yavsc.Tests.Shared;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Xunit;
|
||||
using Yavsc.Tests.Shared;
|
||||
|
||||
namespace Yavsc.Org.Tests.Controllers;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
using IdentityServer8.EntityFramework.DbContexts;
|
||||
using IdentityServer8.EntityFramework.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Yavsc.Org.Tests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Regression sentinel: two <see cref="TestWebApplicationFactory"/>
|
||||
/// instances must not see each other's clients.
|
||||
///
|
||||
/// EF Core's <c>UseInMemoryDatabase(name)</c> returns the same
|
||||
/// backing store to every <c>DbContext</c> that asks for it under
|
||||
/// the same name, in the same process. Before the per-fixture GUID
|
||||
/// fix, both <see cref="TestWebApplicationFactory"/> and
|
||||
/// <see cref="WebServerFixture"/> used the bare <c>"InMemory"</c>
|
||||
/// connection string, so every fixture shared one store and tests
|
||||
/// were silently order-dependent.
|
||||
///
|
||||
/// We assert against <see cref="ConfigurationDbContext"/> directly
|
||||
/// rather than via <c>IClientStore</c>: the validating wrapper around
|
||||
/// <c>IClientStore</c> raises events through <c>IEventService</c>,
|
||||
/// which is not registered in the test host and crashes with a
|
||||
/// <c>NullReferenceException</c> before it can return a result. Going
|
||||
/// straight to the DbContext is the same code path the production
|
||||
/// code uses, so it is the right surface to assert against.
|
||||
/// </summary>
|
||||
public class TestWebApplicationFactoryIsolationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Second_factory_does_not_see_clients_seeded_into_first()
|
||||
{
|
||||
var marker = $"marker-A-{Guid.NewGuid():N}";
|
||||
|
||||
// First factory: seed a distinctive client.
|
||||
using (var first = new TestWebApplicationFactory())
|
||||
{
|
||||
await using var scope = first.Services.CreateAsyncScope();
|
||||
var configDb = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
|
||||
var firstCs = scope.ServiceProvider.GetRequiredService<IConfiguration>()
|
||||
.GetConnectionString("YavscConnection");
|
||||
Assert.StartsWith("InMemory-", firstCs);
|
||||
configDb.Clients.Add(new Client { ClientId = marker, ClientName = "marker-A" });
|
||||
await configDb.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
// Sanity: the first factory can see its own seed.
|
||||
var seenByFirst = await configDb.Clients
|
||||
.AsNoTracking()
|
||||
.AnyAsync(c => c.ClientId == marker, TestContext.Current.CancellationToken);
|
||||
Assert.True(seenByFirst);
|
||||
}
|
||||
|
||||
// Second factory: must start from a clean slate. If the
|
||||
// in-memory store leaked from the first factory, this
|
||||
// assertion fails.
|
||||
using var second = new TestWebApplicationFactory();
|
||||
await using var secondScope = second.Services.CreateAsyncScope();
|
||||
var secondCs = secondScope.ServiceProvider.GetRequiredService<IConfiguration>()
|
||||
.GetConnectionString("YavscConnection");
|
||||
Assert.StartsWith("InMemory-", secondCs);
|
||||
var secondDb = secondScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
|
||||
var seenBySecond = await secondDb.Clients
|
||||
.AsNoTracking()
|
||||
.AnyAsync(c => c.ClientId == marker, TestContext.Current.CancellationToken);
|
||||
Assert.False(seenBySecond);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Yavsc.Models;
|
||||
using Yavsc.Models.Billing;
|
||||
using Yavsc.Server.Helpers;
|
||||
using Yavsc.Server.Models.FileSystem;
|
||||
|
|
@ -97,9 +91,13 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
|||
public async Task ReceiveEstimateSignatureAsync_rejects_null_payload()
|
||||
{
|
||||
var user = MakeUser("bob");
|
||||
// Capture TestContext.Current.CancellationToken outside the
|
||||
// lambda so xUnit1051 sees a real CancellationToken argument
|
||||
// (the lambda body runs on a different stack frame).
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
||||
user, 1L, SignatureType.Pro, payload: null!));
|
||||
user, 1L, SignatureType.Pro, payload: null!, token: ct));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -107,9 +105,10 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
|||
{
|
||||
var user = MakeUser("bob");
|
||||
var payload = new SignaturePadPayload { Strokes = new[] { 1, 100, 100 } };
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
|
||||
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
||||
user, 0L, SignatureType.Pro, payload));
|
||||
user, 0L, SignatureType.Pro, payload, token: ct));
|
||||
}
|
||||
|
||||
// --- helpers ----------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Yavsc.Org.Tests.NonRegression;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
using Yavsc;
|
||||
using Yavsc.Abstract.Workflow;
|
||||
using Yavsc.Helpers;
|
||||
using Yavsc.Models;
|
||||
using Yavsc.Models.Billing;
|
||||
using Yavsc.Models.Haircut;
|
||||
using Yavsc.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Yavsc
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using Xunit;
|
||||
using Yavsc.Abstract;
|
||||
using Yavsc.Abstract.Identity;
|
||||
|
||||
namespace Yavsc.Org.Tests.NonRegression;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Yavsc.Org.Tests.Smoke;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Yavsc.Org.Tests.Smoke;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Yavsc.Org.Tests.Smoke;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
using System.IO;
|
||||
using Xunit;
|
||||
using Xunit.v3;
|
||||
|
||||
namespace Yavsc.Org.Tests;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Yavsc.Tests.Shared;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,54 @@ namespace Yavsc.Org.Tests;
|
|||
/// <see cref="TestUserMiddleware"/> so that <c>User.GetUserId()</c>
|
||||
/// in user code sees a logged-in identity derived from the same
|
||||
/// header.
|
||||
///
|
||||
/// Each instance gets its own in-memory database, identified by a
|
||||
/// GUID generated in the constructor. The connection string
|
||||
/// (<c>ConnectionStrings:YavscConnection</c>) is set as an
|
||||
/// environment variable (<c>ConnectionStrings__YavscConnection</c>)
|
||||
/// in the constructor and unset in <see cref="Dispose"/>, so the
|
||||
/// production <c>AddIdentityDBAndStores</c> registers <c>DbContext</c>
|
||||
/// instances against this fixture's own store. Without this, the
|
||||
/// <c>"InMemory"</c> connection string from
|
||||
/// <c>appsettings-org.Testing.json</c> would route every
|
||||
/// <see cref="TestWebApplicationFactory"/> instance — and any
|
||||
/// <see cref="WebServerFixture"/> running in the same process — to
|
||||
/// the same backing store, leaking state between fixtures.
|
||||
///
|
||||
/// Env vars are used (rather than <c>ConfigureAppConfiguration</c> or
|
||||
/// <c>UseSetting</c>) because <c>WebApplicationFactory</c> applies
|
||||
/// those too late: <c>Program.Main</c> has already captured the
|
||||
/// connection string in <c>AddIdentityDBAndStores</c> by the time
|
||||
/// the test host's overrides take effect. Env vars are the last
|
||||
/// provider added in <c>AddConfiguration</c> (see
|
||||
/// <c>Yavsc.Server/Helpers/ConfigHelpers.cs</c>), so they win.
|
||||
/// </summary>
|
||||
public class TestWebApplicationFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
private readonly string _fixtureId = Guid.NewGuid().ToString("N");
|
||||
|
||||
// ASP.NET Core's environment-variable configuration provider uses
|
||||
// the key ConnectionStrings__YavscConnection (double underscore
|
||||
// for the section separator). Set it before the host starts so
|
||||
// the per-fixture connection string wins over
|
||||
// appsettings-org.Testing.json. We do NOT touch the appsettings
|
||||
// file; env vars take precedence in the configuration pipeline
|
||||
// (see AddConfiguration in Yavsc.Server/Helpers/ConfigHelpers.cs,
|
||||
// which adds AddEnvironmentVariables last).
|
||||
private static readonly object _envLock = new();
|
||||
private bool _envSet;
|
||||
|
||||
public TestWebApplicationFactory()
|
||||
{
|
||||
lock (_envLock)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(
|
||||
"ConnectionStrings__YavscConnection",
|
||||
InMemoryDatabaseName.For(_fixtureId));
|
||||
_envSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
// UseEnvironment("Testing") puts the host in a dedicated
|
||||
|
|
@ -50,4 +95,18 @@ public class TestWebApplicationFactory : WebApplicationFactory<Program>
|
|||
services.AddTransient<IStartupFilter, TestUserStartupFilter>();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _envSet)
|
||||
{
|
||||
lock (_envLock)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(
|
||||
"ConnectionStrings__YavscConnection", null);
|
||||
_envSet = false;
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Microsoft.Extensions.Logging;
|
|||
using Microsoft.Extensions.Options;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Yavsc;
|
||||
using Yavsc.Extensions;
|
||||
using Yavsc.Interfaces;
|
||||
using Yavsc.Models;
|
||||
|
|
@ -43,6 +42,17 @@ public sealed class WebServerFixture : WebHostFixture
|
|||
{
|
||||
private static readonly int _httpsPort = GetAvailableLoopbackPort();
|
||||
|
||||
// One in-memory database name for the whole process: WebHostFixture
|
||||
// is a per-process singleton (see _app, _isInitialized, _sharedServices
|
||||
// in the base class), so every WebServerFixture instance shares the
|
||||
// same backing store. That is intentional — the "Yavsc Server" test
|
||||
// collection groups tests that should see the same seeded state, and
|
||||
// re-initialising the store per fixture would just regress the
|
||||
// order-dependence we are trying to eliminate. The GUID still matters
|
||||
// because TestWebApplicationFactory and WebServerFixture must not
|
||||
// collide in the in-memory store; see InMemoryDatabaseName.
|
||||
private static readonly string _fixtureId = Guid.NewGuid().ToString("N");
|
||||
|
||||
protected override int HttpsPort => _httpsPort;
|
||||
|
||||
private static IConfiguration? _sharedConfiguration;
|
||||
|
|
@ -81,7 +91,7 @@ public sealed class WebServerFixture : WebHostFixture
|
|||
// that plus the in-memory overrides below.
|
||||
builder.AddConfiguration(null).AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
[$"ConnectionStrings:{Yavsc.Constants.YavscConnectionStringName}"] = "InMemory",
|
||||
[$"ConnectionStrings:{Yavsc.Constants.YavscConnectionStringName}"] = InMemoryDatabaseName.For(_fixtureId),
|
||||
// SMTP test config: UserName non-null so MailSender
|
||||
// exercises the Authenticate branch — the
|
||||
// RecordingSmtpClient captures it.
|
||||
|
|
|
|||
|
|
@ -89,4 +89,4 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="GitVersion.MsBuild" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue