feat/files-control #52
3 changed files with 40 additions and 65 deletions
commit
67715f9d67
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue