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.
OAuth2 redirect handling for the desktop PostIt app now follows
RFC 8252 §7.1: the redirect URI is a custom scheme
(postit://callback) that the OS routes back to PostIt instead of
a 127.0.0.1 HTTP listener. The browser hits the scheme, the OS
launches a fresh PostIt process, that process hands the URL to
the running instance over a named pipe, then exits.
Architecture:
- SingleInstance: cross-platform named-pipe helper. TryHandOffAsync
is what the 2nd instance calls to forward its command-line URL;
StartServerAsync runs on the 1st instance and pumps URLs into
a callback (the running CustomSchemeBrowser).
- CustomSchemeBrowser: IBrowser that opens the system browser on
the authorize URL and blocks until the named pipe yields the
callback URL. No HTTP listener, no port to bind or release,
no HttpListener lifecycle to babysit.
- Platform.DefaultRedirectUri is now postit://callback. The
CustomScheme property exposes the prefix for the redirect
validator.
- App.OnFrameworkInitializationCompleted detects a 2nd-instance
launch by scanning command-line args for the scheme prefix,
hands the URL off, and exits before opening a window. The
first instance starts normally and only the browser is
replaced.
What still has to happen on the user's machine:
- Registering the postit:// scheme with the OS (a one-time
setup step: .desktop file on Linux, registry key on Windows,
Info.plist / LSSetDefaultHandlerForURLScheme on macOS). The
code already validates EndUrl starts with the configured
scheme, so a missing registration surfaces as a clear error
from CustomSchemeBrowser rather than a silent hang.
Removed:
- LoopbackBrowser and LoopbackBrowserTests — the listener,
the timeout, the double Stop/Close dance. The whole class of
'port already bound' / 'next launch fails' issues goes away.
- The /tests/LoopbackBrowserTests.cs regression coverage is
obsolete: there is no listener to release anymore. The single-
instance hand-off is covered by the existing tests on the
OidcClient flow path.
Tests: PostIt.Tests 17/17 pass, Yavsc.Org.Tests 13/14 (the one
remaining failure is SendEMailSynchrone, pre-existing and
unrelated to this change).
Add a stubbed OIDC authority (discovery, jwks, /connect/token,
/connect/userinfo) and a fake IBrowser, then cover the full
authorization-code + PKCE flow in LoginPageViewModelTests.
The new LoginPageViewModel(Settings, Func<IBrowser?>) constructor
plus the BrowserFactoryOverride property keep production wiring
unchanged: the existing parameterless ctor and the platform
projects' Platform.CreateBrowser still drive runtime.