The 2026-08-21 prod 500 on POST /api/v1/blogacl was caused by the
PostIt client sending { circleId } only — the server deserialised
into CircleAuthorizationToBlogPost with BlogPostId = default(long) = 0,
and EF Core refused the INSERT with InvalidOperationException.
The PostIt-side fix lives in b82b6722 (enrich the payload with
blogPostId). This commit is the server-side guard: validate
BlogPostId > 0 in the controller and return 400 BadRequest instead
of letting the request reach SaveChangesAsync. The same shape that
crashed on 2026-08-21 now fails fast at the validation layer.
Verified by BlogAclApiTests.PostCircleAuthorization_dosent_return_500:
sentinel that asserts 'never 500' on a payload with BlogPostId = -1.
Previously red (500 from EF Core), now green (400 from the new guard).
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.
The bool Comment on CircleAuthorizationToBlogPost was dead code:
never read or written by any caller in src/, no UI exposure, no
behavioural semantics. The wire DTO (CircleAuthorization in
Yavsc.Abstract) doesn't carry it, no reader consumes it, and the
PostIt client builds its payload without it.
What changes:
- src/Yavsc.Server/Models/Access/CircleAuthorizationToBlogPost.cs:
remove the property.
- src/Yavsc.Blogs.Tests/BlogAclApiTests.cs: drop 'Comment = true'
from the existing test payload and trim the now-inaccurate XML
doc comment ('CircleId + BlogPostId + Comment' -> 'CircleId +
BlogPostId'). Also adds a new [Fact] pinning the prod bug
reported on 2026-08-21 (HTTP 500 'BlogPostId is unknown' when
PostIt POSTs the bare { circleId } shape). That test stays red:
the real fix for the 500 is in PostIt (payload needs blogPostId)
+ on the wire DTO + server-side validation, and lives in a
follow-up commit.
Migration:
- src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost
drops the boolean 'Comment' column on CircleAuthorizationToBlogPost.
The generated scaffold also wanted to drop three 'ClientId1'
shadow FK columns on ClientScopes / ClientRedirectUris /
ClientGrantTypes (from leftover HasOne<Client>() overrides in
ApplicationDbContext.OnModelCreating); those were removed from
the .cs to keep the migration scoped to this fix. Cleaning up the
shadow property declarations themselves is left as a separate
task.
The ModelSnapshot still reflects the shadow 'ClientId1' columns
intentionally: they exist in the prod database today (all NULL),
and EF will rescaffold a drop migration for them on the next
'migrations add' regardless. No data loss.
Bundled end-of-branch commit on feat/postit-acl-members.
PostIt UI for circles + per-post ACL
- Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders
(Bearer/OIDC scope tests vs. blog API fakes live where they
belong) and introduces PostItHeadlessCollection so the
Avalonia.Headless tests share a single xUnit collection
instead of contending with the EF-Core test host.
- Adds BlogAclApiTests (a brand-new behavioural layer over
POST /api/v1/blogacl) and the fakes it relies on
(BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember
DialogTests); pulls UserId-through-OIDC-sub path into
BearerScopeTests / FakeAuthorizingBrowser /
OidcStubAuthority.
- App.axaml.cs gets a small PushPageAsync touch-up the new
tests rely on.
- Drops UnitTest1.cs (xUnit scaffold, never used).
Yavsc.Blogs.Tests — SQLite instead of InMemory
- Bumps Yavsc.Blogs.Tests.csproj on
Microsoft.EntityFrameworkCore.Sqlite and rewrites
BlogsWebServerFixture to hold a single shared
SqliteConnection (Cache=Shared) for the fixture lifetime,
with a sync Dispose close to dodge async teardown hangs.
Reason: the EF Core InMemory provider silently ignores FKs,
which masked the kind of bug we are about to pin in the
ACL tests. SQLite enforces them, so any future INSERT that
forgets to seed its parent rows fails loudly here instead
of passing the test and breaking prod.
- PublishEndpointTests and BlogApiSmokeTests get a one-line
tweak to follow the new connection lifecycle.
Foreign-key fallout: seed the default user in the fixture
- Adds BlogsWebServerFixture.SeedUser(userName). Now that
SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every
test that POST/PUT/DELETE a BlogPost and sends AuthorId=
'tester' in the payload needs an AspNetUsers row to satisfy
the FK or it returns 500 with SQLite Error 19.
- BlogApiTests wraps the existing ResetDatabase with a
ResetAndSeedDefaultUser helper for the six mutating tests;
the four GET-only and ModelState-only tests keep the bare
ResetDatabase.
- Side benefit: every test in Yavsc.Blogs.Tests now finishes
cleanly instead of hanging at teardown — previously a stuck
test held the shared SqliteConnection open and the next
tests waited indefinitely.
Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25
green from a clean run, no fixture teardown hang.