Context
PR #36 added RootKeyStore.swift which throws RootKeyError.interactionNotAllowed when SecItemCopyMatching returns errSecInteractionNotAllowed — the case where the device has been booted but never unlocked since.
The XCTest suite added in PR #36 covers round-trip and wrong-length, but not the locked-keychain path. That branch is reachable in production (cold reboot, app launches via background trigger before user has unlocked once) and has its own native ERROR transition (phase: "rootkey"), so it's worth pinning down with a test.
Why deferred
Simulating errSecInteractionNotAllowed in XCTest is fiddly:
- The simulator's keychain is always "unlocked" once the simulator has booted. There's no public API to lock it.
- One workaround is creating an item with
kSecAttrAccessible = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly on a simulator without a passcode, but the resulting error is errSecAuthFailed, not errSecInteractionNotAllowed.
- The cleanest approach is probably a small protocol-based mock injected at
RootKeyStore construction (e.g. a KeychainAccessor typealias), with the production path going through the real SecItem* functions and the test using a fake that returns the desired OSStatus.
That refactor is a couple of hundred lines and changes the public API of RootKeyStore. PR #36 review explicitly deferred it.
Acceptance
Context
PR #36 added
RootKeyStore.swiftwhich throwsRootKeyError.interactionNotAllowedwhen SecItemCopyMatching returnserrSecInteractionNotAllowed— the case where the device has been booted but never unlocked since.The XCTest suite added in PR #36 covers round-trip and wrong-length, but not the locked-keychain path. That branch is reachable in production (cold reboot, app launches via background trigger before user has unlocked once) and has its own native ERROR transition (
phase: "rootkey"), so it's worth pinning down with a test.Why deferred
Simulating
errSecInteractionNotAllowedin XCTest is fiddly:kSecAttrAccessible = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyon a simulator without a passcode, but the resulting error iserrSecAuthFailed, noterrSecInteractionNotAllowed.RootKeyStoreconstruction (e.g. aKeychainAccessortypealias), with the production path going through the realSecItem*functions and the test using a fake that returns the desired OSStatus.That refactor is a couple of hundred lines and changes the public API of
RootKeyStore. PR #36 review explicitly deferred it.Acceptance
RootKeyStoreaccepts a keychain-accessor seam (protocol or closure) for tests..interactionNotAllowed..interactionNotAllowed.