feat(pairing): add PairingStore interface for persistence injection

This commit is contained in:
William Valentin
2026-02-09 21:42:17 -08:00
parent c3ca3f3776
commit 1e1a68924e
3 changed files with 79 additions and 7 deletions
+53 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PairingManager } from './pairing.js';
import { PairingManager, type PairingStore } from './pairing.js';
describe('PairingManager', () => {
let manager: PairingManager;
@@ -156,4 +156,56 @@ describe('PairingManager', () => {
});
expect(disabled.enabled).toBe(false);
});
describe('PairingManager with store', () => {
it('calls store.saveApproved when a code is validated', () => {
const store: PairingStore = {
loadApproved: () => [],
saveApproved: vi.fn(),
removeApproved: vi.fn(),
};
const mgr = new PairingManager({ enabled: true, codeTtl: 300_000, codeLength: 6 }, store);
const code = mgr.generateCode();
mgr.validateCode('telegram', '123', code);
expect(store.saveApproved).toHaveBeenCalledWith(expect.objectContaining({
channel: 'telegram',
senderId: '123',
}));
});
it('calls store.removeApproved when approval is revoked', () => {
const store: PairingStore = {
loadApproved: () => [],
saveApproved: vi.fn(),
removeApproved: vi.fn(),
};
const mgr = new PairingManager({ enabled: true, codeTtl: 300_000, codeLength: 6 }, store);
const code = mgr.generateCode();
mgr.validateCode('telegram', '123', code);
mgr.revokeApproval('telegram', '123');
expect(store.removeApproved).toHaveBeenCalledWith('telegram', '123');
});
it('loads approved senders from store on construction', () => {
const store: PairingStore = {
loadApproved: () => [
{ channel: 'telegram', senderId: '111', approvedAt: Date.now(), codeUsed: 'AAAAAA' },
{ channel: 'discord', senderId: '222', approvedAt: Date.now(), codeUsed: 'BBBBBB' },
],
saveApproved: vi.fn(),
removeApproved: vi.fn(),
};
const mgr = new PairingManager({ enabled: true, codeTtl: 300_000, codeLength: 6 }, store);
expect(mgr.isApproved('telegram', '111')).toBe(true);
expect(mgr.isApproved('discord', '222')).toBe(true);
expect(mgr.listApproved()).toHaveLength(2);
});
it('works without a store (backward-compatible)', () => {
const mgr = new PairingManager({ enabled: true, codeTtl: 300_000, codeLength: 6 });
const code = mgr.generateCode();
mgr.validateCode('telegram', '123', code);
expect(mgr.isApproved('telegram', '123')).toBe(true);
});
});
});