Skip to content

Add native-store tests, split EnumerateKeys, bump to 2.0.0#61

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/finish-cross-platform-cleanup
May 12, 2026
Merged

Add native-store tests, split EnumerateKeys, bump to 2.0.0#61
matt-edmondson merged 1 commit into
mainfrom
claude/finish-cross-platform-cleanup

Conversation

@matt-edmondson
Copy link
Copy Markdown
Contributor

Follow-up to #59. Closes the four loose ends I called out post-merge.

What's in this PR

1. Native store integration tests (CredentialCache.Test/NativeCredentialStoreTests.cs)

The native ICredentialStore implementations were only compile-checked in #59 — only InMemoryCredentialStore had test coverage. This adds four tests that exercise whichever store CredentialStoreFactory.CreateDefault(...) returns for the host OS:

  • NativeStoreRoundTripsCredentialWithToken — basic Save / TryLoad / value preservation.
  • NativeStoreSaveOverwritesExistingEntry — Save-after-Save replaces, not duplicates.
  • NativeStoreRemoveReturnsFalseForUnknownPersona — idempotent Remove.
  • NativeStoreSurvivesAcrossStoreInstances — cross-instance persistence (proves it really hits the OS keyring, not just an in-memory dictionary).
  • WindowsStoreEnumerateKeysReturnsWrittenPersonas — checks the ISearchableCredentialStore cast on Windows.

Each test scopes itself to a unique service name (Guid.NewGuid()) so it cannot collide with the user's real credentials, and uses try { Save; assert } finally { Remove } so a failure halfway through doesn't leak entries into the host keyring.

AssertNativeStoreAvailableOrInconclusive probes the store with a no-op Remove and turns missing platform dependencies (DLL not found, libsecret schema init failure, Secret Service unreachable) into Assert.Inconclusive rather than a red failure — so a developer without a keyring set up doesn't see scary CI red just from running locally. It walks the inner-exception chain because libsecret's secret_schema_new cctor failure arrives wrapped in TypeInitializationException.

2. ISearchableCredentialStore (interface split)

EnumerateKeys is gone from ICredentialStore. It lives on a new ISearchableCredentialStore : ICredentialStore interface that only WindowsCredentialStore (via CredEnumerateW) and InMemoryCredentialStore implement. Previously the macOS/Linux implementations returned an empty IEnumerable — documented as a limitation but a quiet "no data" footgun. Callers now have to opt in:

if (store is ISearchableCredentialStore searchable)
{
    foreach (PersonaGUID key in searchable.EnumerateKeys()) { ... }
}

Anyone on macOS/Linux who tries to cast will get null and know to track persona GUIDs themselves instead of being silently lied to. (Implementing the real enumeration via SecItemCopyMatching / secret_search_sync would require non-trivial CoreFoundation / GLib marshalling that isn't justified by the use case.)

3. Linux CI now actually runs the libsecret store

.github/workflows/cross-platform.yml:

  • Installs dbus, dbus-x11, and gnome-keyring alongside libsecret-1-0 on the Linux runner.
  • Splits the Test step into Linux vs non-Linux variants. The Linux variant wraps dotnet test in dbus-run-session and unlocks gnome-keyring-daemon with an empty password first, so the Secret Service is reachable for the duration of the test run.

Verified locally on Ubuntu 24.04 inside dbus-run-session: 21/22 pass, 1 inconclusive (the Windows-only EnumerateKeys test). Without the daemon, the native tests cleanly fall through to inconclusive instead of crashing.

4. SemVer-major bump (1.3.0 -> 2.0.0)

This is a breaking change relative to main. The API surface that consumers of 1.x depended on is gone (ConfigurePersistenceProvider, Save / SaveAsync, on-disk JSON layout from #59) and now EnumerateKeys moves off ICredentialStore. A major bump is the honest signal.

CHANGELOG.md looks like it's regenerated by the release pipeline (the entries all match a "Changes since vX.Y.Z" template), so I haven't hand-edited it — if that's wrong, point me at the right place.

Test plan

  • dotnet build CredentialCache.sln clean on net9.0 + net10.0.
  • dotnet test locally: 17 pass + 5 inconclusive when no Secret Service is available.
  • dbus-run-session + gnome-keyring-daemon locally: 21 pass + 1 inconclusive (Windows-only).
  • CI matrix on Linux / macOS / Windows runners against the real keystores.

https://claude.ai/code/session_017B9mN9F7C3pWGZRyoKBd6R


Generated by Claude Code

Follow-up to PR #59. Closes out the four items called out in the post-merge
audit:

1. The native ICredentialStore implementations (Windows / macOS / Linux) were
   only compile-checked previously. Adds NativeCredentialStoreTests that
   exercise Save / TryLoad / Remove / cross-instance round-trip and an
   EnumerateKeys check against whichever store CredentialStoreFactory.CreateDefault
   returns for the host OS. Each test scopes itself to a unique service name
   (Guid.NewGuid()) so it can't collide with the user's real credentials.

2. EnumerateKeys was a silent no-op on macOS/Linux. Moved it onto a new
   ISearchableCredentialStore interface that only WindowsCredentialStore and
   InMemoryCredentialStore implement. Callers needing enumeration on other
   platforms now have to declare the dependency explicitly with a runtime cast,
   instead of getting an empty IEnumerable that looks like "no data".

3. AssertNativeStoreAvailableOrInconclusive turns "libsecret not installed" or
   "Secret Service unreachable" into Assert.Inconclusive rather than a red
   failure, so developers without a keyring set up don't see scary CI red.
   Detects via DllNotFoundException / CredentialStoreException, walking inner
   exceptions (libsecret schema init failures arrive as TypeInitializationException).

4. Cross-platform CI workflow now installs dbus + gnome-keyring on Linux and
   runs the test step inside dbus-run-session, unlocking gnome-keyring with an
   empty password first. Verified locally: 21/22 pass under that harness, the
   remaining one is the Windows-only EnumerateKeys test reported as
   inconclusive.

VERSION.md bumped 1.3.0 -> 2.0.0 because removing EnumerateKeys from
ICredentialStore (and the API rewrite in #59) is a SemVer-major break.

https://claude.ai/code/session_017B9mN9F7C3pWGZRyoKBd6R
@matt-edmondson matt-edmondson merged commit c63e937 into main May 12, 2026
4 checks passed
@matt-edmondson matt-edmondson deleted the claude/finish-cross-platform-cleanup branch May 12, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants