// tools/keychain-read.test.mjs // Синхронное чтение OS-keychain через async-only keytar (у keytar НЕТ getPasswordSync). // Обёртка запускает короткий node-subprocess (execFileSync блокирует), который await'ит // keytar.getPassword и печатает значение → resolveReceiptKey/resolveJudgeKey остаются синхронными. import { describe, it, expect } from 'vitest'; import { buildKeychainReadScript, parseKeychainStdout, readKeychainSync } from './keychain-read.mjs'; describe('buildKeychainReadScript (pure)', () => { it('строит node-скрипт с async getPassword по абсолютному пути keytar', () => { const s = buildKeychainReadScript('/abs/keytar', 'svc', 'acct'); expect(s).toContain('getPassword'); expect(s).toContain(JSON.stringify('/abs/keytar')); expect(s).toContain(JSON.stringify('svc')); expect(s).toContain(JSON.stringify('acct')); expect(s).not.toContain('getPasswordSync'); }); it('JSON-экранирует аргументы (кавычки безопасны)', () => { const s = buildKeychainReadScript('/k', 'a"b', 'c'); expect(s).toContain(JSON.stringify('a"b')); }); }); describe('parseKeychainStdout (pure)', () => { it('непустой stdout → значение', () => { expect(parseKeychainStdout('secret123')).toBe('secret123'); }); it('пустой stdout → null', () => { expect(parseKeychainStdout('')).toBe(null); }); it('null/undefined → null', () => { expect(parseKeychainStdout(null)).toBe(null); expect(parseKeychainStdout(undefined)).toBe(null); }); }); describe('readKeychainSync (sync-обёртка над async keytar)', () => { const fakeRequire = { resolve: () => '/abs/keytar' }; it('exec вернул значение → оно же', () => { const r = readKeychainSync('svc', 'acct', { requireImpl: fakeRequire, exec: () => 'the-key' }); expect(r).toBe('the-key'); }); it('exec вернул пусто → null', () => { const r = readKeychainSync('svc', 'acct', { requireImpl: fakeRequire, exec: () => '' }); expect(r).toBe(null); }); it('exec бросил → null (fail-closed, не кидает)', () => { const r = readKeychainSync('svc', 'acct', { requireImpl: fakeRequire, exec: () => { throw new Error('spawn fail'); } }); expect(r).toBe(null); }); it('require.resolve бросил (keytar не найден) → null', () => { const req = { resolve: () => { throw new Error('MODULE_NOT_FOUND'); } }; const r = readKeychainSync('svc', 'acct', { requireImpl: req, exec: () => 'x' }); expect(r).toBe(null); }); });