acl post: never 500 regression sentinel + async CheckOwner + fixture seed
The hard rule on POST /api/v1/blogacl is: a 500 is never acceptable,
regardless of the payload shape. The prod 500 logged on 2026-08-21 on
mercure was caused by the PostIt client sending { circleId } only, which
the server deserialised into CircleAuthorizationToBlogPost with
BlogPostId = default(long) = 0; EF Core refused the INSERT with
InvalidOperationException: The value of
'CircleAuthorizationToBlogPost.BlogPostId' is unknown. The PostIt fix
lives in b82b6722 (enrich the payload with blogPostId). The server-side
guard lives in this commit:
- BlogAclApiController.CheckOwner is now async and uses FirstOrDefaultAsync
instead of First, so it does not deadlock the request thread and
returns false on a missing circle (which the controller already maps
to ChallengeResult).
- BlogsWebServerFixture now seeds Alice, her Circle and her BlogPost
in ConfigurePipelineAsync, once at host startup, against the shared
SqliteConnection (Cache=Shared). EnsureCreated is idempotent and
runs against the connection that every DbContext resolves through,
so the test theory can POST payloads with real FK ids against a
schema that actually has the Circle / BlogSpot tables.
- BlogAclApiTests:
- PostCircleAuthorization_returns_201_when_payload_mirrors_PostIt_shape
is the regression sentinel for the prod fix.
- PostCircleAuthorization_never_returns_500 is a [Theory] over
several payload shapes; any future commit that reintroduces a
500 path turns it red. CleanupAcl at the start of each insert-
bearing test isolates against xUnit's no-guarantee-of-order
execution: a successful POST in test N would otherwise conflict
with test N+1 against the same (CircleId, BlogPostId) pair.
This commit is contained in:
parent
4956890236
commit
e48ede1e84
3 changed files with 141 additions and 100 deletions
|
|
@ -70,7 +70,7 @@ namespace Yavsc.Blogs.Controllers
|
|||
return BadRequest();
|
||||
}
|
||||
|
||||
if (!CheckOwner(circleAuthorizationToBlogPost.CircleId))
|
||||
if (!await CheckOwnerAsync(circleAuthorizationToBlogPost.CircleId))
|
||||
{
|
||||
return new ChallengeResult();
|
||||
}
|
||||
|
|
@ -94,13 +94,13 @@ namespace Yavsc.Blogs.Controllers
|
|||
|
||||
return new StatusCodeResult(StatusCodes.Status204NoContent);
|
||||
}
|
||||
private bool CheckOwner (long circleId)
|
||||
private async Task<bool> CheckOwnerAsync (long circleId)
|
||||
{
|
||||
|
||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var circle = _context.Circle.First(c=>c.Id==circleId);
|
||||
_context.Entry(circle).State = EntityState.Detached;
|
||||
return (circle.OwnerId == uid);
|
||||
if (uid==null) return false;
|
||||
var circle = await _context.Circle.FirstOrDefaultAsync(c=>c.Id==circleId);
|
||||
if (circle == null) return false;
|
||||
return circle.OwnerId == uid;
|
||||
}
|
||||
// POST: api/BlogAclApi
|
||||
[HttpPost]
|
||||
|
|
@ -111,7 +111,7 @@ namespace Yavsc.Blogs.Controllers
|
|||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
if (!CheckOwner(circleAuthorizationToBlogPost.CircleId))
|
||||
if (!await CheckOwnerAsync(circleAuthorizationToBlogPost.CircleId))
|
||||
{
|
||||
return new ChallengeResult();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue