import { describe, expect, it } from "@suaveplan/testing/runner"; import fc from "fast-check"; import type { CommandExecutor, CommandExecutorResult } from "../src/index.js"; import { classifyError, EXIT_AUTH_FAILED, EXIT_DUPLICATE_ITEM, EXIT_INTERACTION_NOT_ALLOWED, EXIT_INVALID_KEYCHAIN, EXIT_ITEM_NOT_FOUND, keychainAccessFailed, keychainItemExists, keychainItemInvalid, keychainLocked, keychainNotFound, keychainPermissionDenied, keychainTimeout, keychainUnavailable, MacOSKeychainBridge, } from "../src/index.js"; describe("classifyError property coverage", () => { it("every documented exit code yields a non-null KeychainError", { timeout: 30000, }, () => { fc.assert( fc.property( fc.constantFrom( EXIT_ITEM_NOT_FOUND, EXIT_DUPLICATE_ITEM, EXIT_AUTH_FAILED, EXIT_INTERACTION_NOT_ALLOWED, EXIT_INVALID_KEYCHAIN ), fc.string(), fc.string({ minLength: 1 }), (code, svc, acct) => { const err = classifyError( code, "", svc, acct.length === 0 ? "u" : acct ); expect(err).not.toBeNull(); expect(err?.code.startsWith("KEYCHAIN.")).toBe(true); } ), { numRuns: 200 } ); }); it("very large unknown exit codes return null", { timeout: 30000 }, () => { fc.assert( fc.property( fc .integer({ min: 50_000, max: 99_999 }) .filter( (n) => n !== EXIT_INTERACTION_NOT_ALLOWED && n !== EXIT_INVALID_KEYCHAIN ), fc .string({ minLength: 1 }) .filter( (s) => !/found|exists|locked|denied|invalid|unavailable|interaction|passphrase|unlocked|directory|command not found/i.test( s ) ), (code, stderr) => { const err = classifyError(code, stderr, "svc", "user"); expect(err).toBeNull(); } ), { numRuns: 100 } ); }); }); describe("error-factory idempotency", () => { it("every factory produces the documented code (idempotency)", { timeout: 30000, }, () => { fc.assert( fc.property( fc.string({ minLength: 1 }), fc.string({ minLength: 1 }), (svc, acct) => { expect(keychainNotFound(svc, acct).code).toBe( "KEYCHAIN.NOT_FOUND" ); expect(keychainItemExists(svc, acct).code).toBe( "KEYCHAIN.ITEM_EXISTS" ); expect(keychainAccessFailed(svc, acct).code).toBe( "KEYCHAIN.ACCESS_FAILED" ); expect(keychainLocked(svc, acct).code).toBe( "KEYCHAIN.LOCKED" ); expect(keychainUnavailable().code).toBe( "KEYCHAIN.UNAVAILABLE" ); expect(keychainPermissionDenied(svc, acct).code).toBe( "KEYCHAIN.PERMISSION_DENIED" ); expect(keychainItemInvalid(svc, acct, "r").code).toBe( "KEYCHAIN.ITEM_INVALID" ); expect(keychainTimeout(svc, acct, 1).code).toBe( "KEYCHAIN.TIMEOUT" ); } ), { numRuns: 100 } ); }); it("calling a factory twice produces equal code and message", { timeout: 30000, }, () => { fc.assert( fc.property( fc.string({ minLength: 1 }), fc.string({ minLength: 1 }), (svc, acct) => { const a = keychainNotFound(svc, acct); const b = keychainNotFound(svc, acct); expect(a.code).toBe(b.code); expect(a.message).toBe(b.message); } ), { numRuns: 50 } ); }); }); describe("round-trip via stub executor", () => { it("addItem then findItem returns the original password", { timeout: 30000, }, async () => { await fc.assert( fc.asyncProperty( fc .string({ minLength: 1, maxLength: 32 }) .filter((s) => /^[A-Za-z0-9_-]+$/.test(s)), fc .string({ minLength: 1, maxLength: 32 }) .filter((s) => /^[A-Za-z0-9_-]+$/.test(s)), fc .string({ minLength: 1, maxLength: 64 }) .filter((s) => !s.includes('"') && !s.includes("\\")), async (svc, acct, password) => { let stored: string | undefined; const runner: CommandExecutor = async (_cmd, args) => { if (args[0] === "add-generic-password") { stored = String(args[args.indexOf("-w") + 1]); return { stdout: "", stderr: "", exitCode: 0, } satisfies CommandExecutorResult; } if (args[0] === "find-generic-password") { return { stdout: "", stderr: `password: "${stored ?? ""}"`, exitCode: 0, } satisfies CommandExecutorResult; } return { stdout: "", stderr: "", exitCode: 0, } satisfies CommandExecutorResult; }; const bridge = new MacOSKeychainBridge({}, runner); await bridge.addItem({ service: svc, account: acct, password, }); const item = await bridge.findItem(svc, acct); expect(item.password).toBe(password); } ), { numRuns: 25 } ); }); }); describe("timeout detection", () => { it("timeout via AbortSignal yields KEYCHAIN.TIMEOUT", { timeout: 30000, }, async () => { await fc.assert( fc.asyncProperty( fc.integer({ min: 1, max: 50 }), async (timeoutMs) => { const slowRunner: CommandExecutor = ( _cmd, _args, options ) => new Promise((_, reject) => { options?.signal?.addEventListener("abort", () => reject(new Error("aborted")) ); }); const bridge = new MacOSKeychainBridge( { timeoutMs }, slowRunner ); await expect( bridge.findItem("svc", "user") ).rejects.toMatchObject({ code: "KEYCHAIN.TIMEOUT", }); } ), { numRuns: 10 } ); }); });