tests en vert
Some checks failed
Dotnet build and test / build (pull_request) Failing after 5m36s

This commit is contained in:
Paul Schneider 2026-09-12 17:31:18 +01:00
commit 67715f9d67
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
3 changed files with 40 additions and 65 deletions

View file

@ -357,21 +357,9 @@ public sealed class ApiWebServerFixture : WebHostFixture
public override void Dispose() public override void Dispose()
{ {
try // Keep the shared in-memory SQLite connection alive for the
{ // whole test process. Closing it from one fixture instance can
// drop the schema while other tests are still running.
base.Dispose(); base.Dispose();
} }
finally
{
lock (_sqliteLock)
{
if (_sharedSqliteConnection is not null)
{
_sharedSqliteConnection.Close();
_sharedSqliteConnection.Dispose();
_sharedSqliteConnection = null;
}
}
}
}
} }

View file

@ -51,7 +51,7 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
private string BlogAclUrl() private string BlogAclUrl()
=> $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}"; => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}";
/// <summary>Delete any ACL rows tied to the fixture's seeded /// <summary>Delete any ACL rows tied to the specified
/// <c>(CircleId, BlogPostId)</c> pair. The shared SQLite store /// <c>(CircleId, BlogPostId)</c> pair. The shared SQLite store
/// persists across tests, so tests that POST a successful ACL /// persists across tests, so tests that POST a successful ACL
/// row would otherwise conflict with whichever other test runs /// row would otherwise conflict with whichever other test runs
@ -59,13 +59,13 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
/// execution order. Calling this at the start of each /// execution order. Calling this at the start of each
/// insert-bearing test guarantees a clean slate regardless of /// insert-bearing test guarantees a clean slate regardless of
/// the previous test's outcome.</summary> /// the previous test's outcome.</summary>
private void CleanupAcl() private void CleanupAcl(long circleId, long blogPostId)
{ {
using var scope = _fixture.Services.CreateScope(); using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.CircleAuthorizationToBlogPost db.CircleAuthorizationToBlogPost
.Where(a => a.CircleId == _fixture.CircleId .Where(a => a.CircleId == circleId
&& a.BlogPostId == _fixture.PostId) && a.BlogPostId == blogPostId)
.ExecuteDelete(); .ExecuteDelete();
} }
@ -122,13 +122,16 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
// The prod circle already exists with Name="test", Public=true, // The prod circle already exists with Name="test", Public=true,
// owned by the caller. We seed the same shape pre-POST so the // owned by the caller. We seed the same shape pre-POST so the
// test reproduces the prod scenario end-to-end. // test reproduces the prod scenario end-to-end.
CleanupAcl(); _fixture.SeedUser(_fixture.DefaultUserLogin);
var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test");
var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-target");
CleanupAcl(seededCircleId, seededBlogPostId);
using var http = NewClient(_fixture.DefaultUserLogin); using var http = NewClient(_fixture.DefaultUserLogin);
var payload = new PostAccessControlRulePayload var payload = new PostAccessControlRulePayload
{ {
CircleId = _fixture.CircleId, CircleId = seededCircleId,
BlogPostId = _fixture.PostId BlogPostId = seededBlogPostId
}; };
var response = await http.PostAsJsonAsync( var response = await http.PostAsJsonAsync(
@ -194,13 +197,16 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
[Fact] [Fact]
async Task PostCircleAuthorization_dosent_return_500 () async Task PostCircleAuthorization_dosent_return_500 ()
{ {
CleanupAcl(); _fixture.SeedUser(_fixture.DefaultUserLogin);
var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"));
var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-never-500");
CleanupAcl(seededCircleId, seededBlogPostId);
await PostCircleAuthorization_never_returns_500( await PostCircleAuthorization_never_returns_500(
new PostAccessControlRulePayload new PostAccessControlRulePayload
{ {
BlogPostId = -1, BlogPostId = -1,
CircleId = _fixture.CircleId CircleId = seededCircleId
} }
); );
@ -209,13 +215,16 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
[Fact] [Fact]
async Task PostCircleAuthorization_dosent_return_500_on_success () async Task PostCircleAuthorization_dosent_return_500_on_success ()
{ {
CleanupAcl(); _fixture.SeedUser(_fixture.DefaultUserLogin);
var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"));
var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-never-500-success");
CleanupAcl(seededCircleId, seededBlogPostId);
await PostCircleAuthorization_never_returns_500( await PostCircleAuthorization_never_returns_500(
new PostAccessControlRulePayload new PostAccessControlRulePayload
{ {
BlogPostId = _fixture.PostId, BlogPostId = seededBlogPostId,
CircleId = _fixture.CircleId CircleId = seededCircleId
} }
); );
@ -224,16 +233,17 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
[Fact] [Fact]
public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list() public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list()
{ {
CleanupAcl();
_fixture.SeedUser(_fixture.DefaultUserLogin); _fixture.SeedUser(_fixture.DefaultUserLogin);
_fixture.SeedUser("tester"); _fixture.SeedUser("tester");
_fixture.SeedCircle(_fixture.DefaultUserLogin, "test", var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"),
false, false,
new String[] new String[]
{ {
_fixture.DefaultUserLogin, _fixture.DefaultUserLogin,
"tester" "tester"
}); });
var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-seeded-target");
CleanupAcl(seededCircleId, seededBlogPostId);
using var http = NewClient(_fixture.DefaultUserLogin ); using var http = NewClient(_fixture.DefaultUserLogin );
// Create a minimal BlogPost. The server assigns Id, so we // Create a minimal BlogPost. The server assigns Id, so we
@ -252,8 +262,8 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
{ {
new CircleAuthorizationToBlogPost new CircleAuthorizationToBlogPost
{ {
CircleId = _fixture.CircleId, CircleId = seededCircleId,
BlogPostId = _fixture.PostId BlogPostId = seededBlogPostId
} }
} }
) )
@ -303,17 +313,16 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
var aclEntry = acl[0]; var aclEntry = acl[0];
Assert.Equal(JsonValueKind.Object, aclEntry.ValueKind); Assert.Equal(JsonValueKind.Object, aclEntry.ValueKind);
Assert.True(aclEntry.TryGetProperty("circleId", out var circleId)); Assert.True(aclEntry.TryGetProperty("circleId", out var returnedCircleId));
Assert.Equal(_fixture.CircleId, circleId.GetInt64()); Assert.Equal(seededCircleId, returnedCircleId.GetInt64());
} }
[Fact] [Fact]
public async Task Non_owner_can_read_restricted_post_but_receives_empty_acl_in_list_and_detail() public async Task Non_owner_can_read_restricted_post_but_receives_empty_acl_in_list_and_detail()
{ {
CleanupAcl();
_fixture.SeedUser(_fixture.DefaultUserLogin); _fixture.SeedUser(_fixture.DefaultUserLogin);
_fixture.SeedUser("tester"); _fixture.SeedUser("tester");
_fixture.SeedCircle(_fixture.DefaultUserLogin, "test", false, var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"), false,
new[] { _fixture.DefaultUserLogin, "tester" }); new[] { _fixture.DefaultUserLogin, "tester" });
using var ownerHttp = NewClient(_fixture.DefaultUserLogin); using var ownerHttp = NewClient(_fixture.DefaultUserLogin);
@ -343,7 +352,7 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
BlogAclUrl(), BlogAclUrl(),
new PostAccessControlRulePayload new PostAccessControlRulePayload
{ {
CircleId = _fixture.CircleId, CircleId = seededCircleId,
BlogPostId = created.Id BlogPostId = created.Id
}, },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);

View file

@ -289,34 +289,12 @@ public sealed class BlogsWebServerFixture : WebHostFixture
public override void Dispose() public override void Dispose()
{ {
try // Keep the shared in-memory SQLite connection alive for the
{ // whole test process. Closing it from one fixture instance can
// destroy the database while other collections are still using
// it, which surfaces as intermittent "no such table" failures.
base.Dispose(); base.Dispose();
} }
finally
{
// Close the shared SQLite connection only when the
// last fixture instance goes away, matching the
// lifetime contract of WebHostFixture.Dispose. We
// rely on base.Dispose's _instanceCount decrement
// having run, so we close only if the host is gone
// (base already nulled _app when count==0).
lock (_sqliteLock)
{
if (_sharedSqliteConnection is not null)
{
// Synchronous close: SQLite's Close() is
// documented as safe to call from a sync
// context and avoids the GetAwaiter().GetResult()
// pattern that's historically caused teardown
// hangs in this repo's async pipeline.
_sharedSqliteConnection.Close();
_sharedSqliteConnection.Dispose();
_sharedSqliteConnection = null;
}
}
}
}
/// <summary>Seed an <see cref="ApplicationUser"/> in the shared /// <summary>Seed an <see cref="ApplicationUser"/> in the shared
/// SQLite store, so tests that POST/PUT/DELETE a /// SQLite store, so tests that POST/PUT/DELETE a
@ -374,7 +352,7 @@ public sealed class BlogsWebServerFixture : WebHostFixture
/// directly in the SQLite store and return its server-assigned /// directly in the SQLite store and return its server-assigned
/// id.</summary> /// id.</summary>
public long SeedCircle(string ownerId, string name, bool isPublic = false, public long SeedCircle(string ownerId, string name, bool isPublic = false,
ICollection<String> members = null ICollection<String>? members = null
) )
{ {
using var scope = Services.CreateScope(); using var scope = Services.CreateScope();