Skip to content

fix(test): assert unlock persistence, not the keychain fallback file - #31

Merged
bsreeram08 merged 1 commit into
bsreeram08:masterfrom
kugesh-Rajasekaran:issue-30-macos-unlock-test-keychain
Sep 18, 2026
Merged

bsreeram08 merged 1 commit into
bsreeram08:masterfrom
kugesh-Rajasekaran:issue-30-macos-unlock-test-keychain

Conversation

@kugesh-Rajasekaran

@kugesh-Rajasekaran kugesh-Rajasekaran commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

go test ./... is red on macOS on a clean master checkout. This makes it green on both
platforms without weakening the test.

TestCollectionMigrateCommandCanPassphraseProtectFiles asserted that a local
collection.key file exists after UnlockCollection. That file is only written on the
keychain-unavailable fallback path — internal/storage/collection_crypto.go:151:

if err := cacheCollectionPassphraseKey(collection.ID, collection.Encryption, key); err != nil {
    return s.saveCollectionWithLocalKeyFallback(collection, err)  // writes collection.key
}
return nil                                                        // keychain worked, no file

So on any machine with a working OS keychain the key is cached there, no file is written,
and the assertion fails:

--- FAIL: TestCollectionMigrateCommandCanPassphraseProtectFiles (0.23s)
    collection_test.go:242: expected local collection key after unlock: stat
    .../collections/payments/collection.key: no such file or directory

Why CI stayed green. The only workflow that runs the suite is review.yml on
ubuntu-latest. There the keychain call fails, the fallback fires, collection.key is
written, and the test passes. The failure is only visible to someone running the suite on a
Mac — which is what CONTRIBUTING.md tells contributors to do.

The test predates the keychain cache (#27) and encodes the old persistence mechanism.

The change. Assert the contract rather than the implementation: after UnlockCollection,
a freshly constructed FileStore reads the collection and its decrypted secrets without the
passphrase. That is true on both paths — keychain on macOS, local key file on Linux.

No coverage is lost, and none was unique to this test. The keychain/fallback branching is
already tested deterministically at the layer that owns it, using the project's existing
in-memory keyring stub (withMemoryCollectionKeyring / withFailingSetCollectionKeyring in
internal/storage/collection_crypto_test.go):

  • TestFileStoreUnlockCachesPassphraseKey — the keychain-success path
  • TestFileStoreUnlockFallsBackToLocalKeyWhenKeychainUnavailable — the fallback path, including the local collection.key assertion

Those pin the keyring explicitly, so they verify both branches on every platform. The
command-level test was duplicating that assertion one layer up without the stub, which is
why it was at the mercy of whatever the host's keychain happened to do. Removing the
duplicated assertion leaves the mechanism covered where it is covered properly, and leaves
the command test asserting what a command test should: the user-visible outcome.

Deliberately unchanged: the earlier assertion that collection.key is absent before
unlock (correct on both platforms), and all production code. The keychain-first design looks
right; only the test's expectation was wrong.

Fixes #30

Testing

macOS 15.7.5 arm64, go1.26.3 darwin/arm64.

Before, on master:

--- FAIL: TestCollectionMigrateCommandCanPassphraseProtectFiles (0.23s)
FAIL	github.com/sreeram/gurl/internal/cli/commands	2.446s

After, targeted:

=== RUN   TestCollectionMigrateCommandCanPassphraseProtectFiles
--- PASS: TestCollectionMigrateCommandCanPassphraseProtectFiles (0.35s)
ok  	github.com/sreeram/gurl/internal/cli/commands	0.838s

Full suite, plus formatting and vet:

$ go build ./...          # clean
$ go test ./...           # no FAIL lines
$ gofmt -l internal/cli/commands/collection_test.go   # no output
$ go vet ./internal/cli/commands/                     # clean

I could not directly verify the Linux fallback path on this machine — I have no Linux box
here. The reasoning is that saveCollectionWithLocalKeyFallback clears Encryption and
saves the collection with a local key, so GetCollectionByName from a fresh store succeeds
there too; the review.yml run on this PR should confirm it on ubuntu-latest.

TestCollectionMigrateCommandCanPassphraseProtectFiles asserted that a local
collection.key exists after UnlockCollection. That file is only written when
caching the derived key in the OS keychain fails, so the assertion fails on
any machine with a working keychain and `go test ./...` is red on macOS.

CI never caught it: the only workflow running the suite is review.yml on
ubuntu-latest, where the keychain call fails, the fallback writes the file,
and the test passes.

Assert what both paths guarantee instead -- a freshly constructed FileStore
reads the collection and its decrypted secrets without the passphrase. That
holds for the keychain path and the local-key fallback alike, so the suite is
green on both platforms without weakening the check.

Issue: bsreeram08#30
@bsreeram08
bsreeram08 merged commit 7cf11f5 into bsreeram08:master Sep 18, 2026
6 checks passed
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.

go test ./... fails on macOS: unlock test asserts the keychain-unavailable fallback

2 participants