UNPKG

trash-cleaner

Version:

Finds and deletes trash email in the mailbox

143 lines (124 loc) 5.94 kB
import { expect } from 'chai'; import sinon from 'sinon'; import { SecureConfigStore, SERVICE_NAME } from '../../lib/store/secure-config-store.js'; describe('SecureConfigStore', () => { let store: SecureConfigStore; let fileStore: any; let mockKeychain: any; beforeEach(() => { fileStore = { get: sinon.stub(), getJson: sinon.stub(), put: sinon.stub(), putJson: sinon.stub() }; mockKeychain = { getPassword: sinon.stub(), setPassword: sinon.stub(), deletePassword: sinon.stub() }; store = new SecureConfigStore(fileStore, mockKeychain); }); afterEach(() => { sinon.restore(); }); describe('get', () => { it('should read sensitive keys from keychain first', async () => { mockKeychain.getPassword.resolves('{"user":"test"}'); const result = await store.get('imap.credentials.json'); expect(result).to.equal('{"user":"test"}'); expect(mockKeychain.getPassword.calledWith(SERVICE_NAME, 'imap.credentials.json')).to.be.true; expect(fileStore.get.called).to.be.false; }); it('should fall back to file store when keychain returns null', async () => { mockKeychain.getPassword.resolves(null); fileStore.get.resolves('{"user":"file"}'); const result = await store.get('imap.credentials.json'); expect(result).to.equal('{"user":"file"}'); }); it('should fall back to file store when keychain throws', async () => { mockKeychain.getPassword.rejects(new Error('no keychain')); fileStore.get.resolves('{"user":"file"}'); const result = await store.get('gmail.token.json'); expect(result).to.equal('{"user":"file"}'); }); it('should use file store directly for non-sensitive keys', async () => { fileStore.get.resolves('["keyword1"]'); const result = await store.get('keywords.json'); expect(result).to.equal('["keyword1"]'); expect(mockKeychain.getPassword.called).to.be.false; }); }); describe('getJson', () => { it('should parse JSON from keychain for credential files', async () => { mockKeychain.getPassword.resolves('{"host":"imap.gmail.com"}'); const result = await store.getJson('imap.credentials.json'); expect(result).to.deep.equal({ host: 'imap.gmail.com' }); }); it('should return null when no value found', async () => { mockKeychain.getPassword.resolves(null); fileStore.getJson.resolves(null); const result = await store.getJson('imap.credentials.json'); expect(result).to.be.null; }); }); describe('put', () => { it('should save sensitive keys to keychain', async () => { mockKeychain.setPassword.resolves(); await store.put('outlook.credentials.json', '{"client_id":"abc"}'); expect(mockKeychain.setPassword.calledWith( SERVICE_NAME, 'outlook.credentials.json', '{"client_id":"abc"}' )).to.be.true; expect(fileStore.put.called).to.be.false; }); it('should fall back to file store when keychain write fails', async () => { mockKeychain.setPassword.rejects(new Error('denied')); fileStore.put.resolves(); await store.put('imap.credentials.json', '{"user":"test"}'); expect(fileStore.put.calledWith('imap.credentials.json', '{"user":"test"}')).to.be.true; }); it('should use file store for non-sensitive keys', async () => { fileStore.put.resolves(); await store.put('keywords.json', '[]'); expect(mockKeychain.setPassword.called).to.be.false; expect(fileStore.put.calledWith('keywords.json', '[]')).to.be.true; }); }); describe('putJson', () => { it('should serialize and save to keychain', async () => { mockKeychain.setPassword.resolves(); await store.putJson('imap.credentials.json', { host: 'test' }); expect(mockKeychain.setPassword.calledWith( SERVICE_NAME, 'imap.credentials.json', '{"host":"test"}' )).to.be.true; }); }); describe('remove', () => { it('should remove from keychain', async () => { mockKeychain.deletePassword.resolves(true); const result = await store.remove('imap.credentials.json'); expect(result).to.be.true; expect(mockKeychain.deletePassword.calledWith(SERVICE_NAME, 'imap.credentials.json')).to.be.true; }); it('should return false when keychain throws', async () => { mockKeychain.deletePassword.rejects(new Error('not found')); const result = await store.remove('imap.credentials.json'); expect(result).to.be.false; }); }); describe('_isSensitive', () => { it('should identify credential files as sensitive', () => { expect((store as any)._isSensitive('imap.credentials.json')).to.be.true; expect((store as any)._isSensitive('gmail.credentials.json')).to.be.true; expect((store as any)._isSensitive('outlook.credentials.work.json')).to.be.true; }); it('should identify token files as sensitive', () => { expect((store as any)._isSensitive('gmail.token.json')).to.be.true; expect((store as any)._isSensitive('outlook.token.work.json')).to.be.true; }); it('should not flag non-sensitive files', () => { expect((store as any)._isSensitive('keywords.json')).to.be.false; expect((store as any)._isSensitive('allowlist.json')).to.be.false; }); }); });