feat: add PairingManager and gateway lock tests (Tier 4 feature 4 foundation)
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { PairingManager } from './pairing.js';
|
||||
|
||||
describe('PairingManager', () => {
|
||||
let manager: PairingManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new PairingManager({
|
||||
enabled: true,
|
||||
codeTtl: 300_000, // 5 minutes
|
||||
codeLength: 6,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('generateCode returns a code of the configured length', () => {
|
||||
const code = manager.generateCode();
|
||||
expect(code).toHaveLength(6);
|
||||
expect(code).toMatch(/^[0-9A-F]+$/);
|
||||
});
|
||||
|
||||
it('generateCode with a label stores the label', () => {
|
||||
const code = manager.generateCode('for alice');
|
||||
const pending = manager.listPendingCodes();
|
||||
expect(pending).toHaveLength(1);
|
||||
expect(pending[0].code).toBe(code);
|
||||
expect(pending[0].label).toBe('for alice');
|
||||
});
|
||||
|
||||
it('validateCode succeeds with a valid code and approves the sender', () => {
|
||||
const code = manager.generateCode();
|
||||
const result = manager.validateCode('telegram', '12345', code);
|
||||
expect(result).toBe(true);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(true);
|
||||
});
|
||||
|
||||
it('validateCode fails with an invalid code', () => {
|
||||
manager.generateCode();
|
||||
const result = manager.validateCode('telegram', '12345', 'ZZZZZZ');
|
||||
expect(result).toBe(false);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(false);
|
||||
});
|
||||
|
||||
it('validateCode is case-insensitive', () => {
|
||||
const code = manager.generateCode();
|
||||
const result = manager.validateCode('telegram', '12345', code.toLowerCase());
|
||||
expect(result).toBe(true);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(true);
|
||||
});
|
||||
|
||||
it('validateCode fails with an expired code', () => {
|
||||
vi.useFakeTimers();
|
||||
const code = manager.generateCode();
|
||||
|
||||
// Advance time past the TTL
|
||||
vi.advanceTimersByTime(300_001);
|
||||
|
||||
const result = manager.validateCode('telegram', '12345', code);
|
||||
expect(result).toBe(false);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(false);
|
||||
});
|
||||
|
||||
it('validateCode removes the code after successful use', () => {
|
||||
const code = manager.generateCode();
|
||||
manager.validateCode('telegram', '12345', code);
|
||||
|
||||
// Code should no longer be pending
|
||||
expect(manager.listPendingCodes()).toHaveLength(0);
|
||||
|
||||
// Second use of the same code should fail
|
||||
const result = manager.validateCode('discord', '67890', code);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('isApproved returns true after validation', () => {
|
||||
const code = manager.generateCode();
|
||||
manager.validateCode('telegram', '12345', code);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(true);
|
||||
});
|
||||
|
||||
it('isApproved returns false for unapproved senders', () => {
|
||||
expect(manager.isApproved('telegram', 'unknown')).toBe(false);
|
||||
});
|
||||
|
||||
it('isApproved distinguishes between channels', () => {
|
||||
const code = manager.generateCode();
|
||||
manager.validateCode('telegram', '12345', code);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(true);
|
||||
expect(manager.isApproved('discord', '12345')).toBe(false);
|
||||
});
|
||||
|
||||
it('revokeApproval removes approval', () => {
|
||||
const code = manager.generateCode();
|
||||
manager.validateCode('telegram', '12345', code);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(true);
|
||||
|
||||
const revoked = manager.revokeApproval('telegram', '12345');
|
||||
expect(revoked).toBe(true);
|
||||
expect(manager.isApproved('telegram', '12345')).toBe(false);
|
||||
});
|
||||
|
||||
it('revokeApproval returns false for non-existent sender', () => {
|
||||
const revoked = manager.revokeApproval('telegram', 'nonexistent');
|
||||
expect(revoked).toBe(false);
|
||||
});
|
||||
|
||||
it('listApproved returns all approved senders', () => {
|
||||
const code1 = manager.generateCode();
|
||||
const code2 = manager.generateCode();
|
||||
manager.validateCode('telegram', '111', code1);
|
||||
manager.validateCode('discord', '222', code2);
|
||||
|
||||
const approved = manager.listApproved();
|
||||
expect(approved).toHaveLength(2);
|
||||
expect(approved.map(a => a.senderId)).toContain('111');
|
||||
expect(approved.map(a => a.senderId)).toContain('222');
|
||||
});
|
||||
|
||||
it('listPendingCodes returns only non-expired codes', () => {
|
||||
vi.useFakeTimers();
|
||||
const code1 = manager.generateCode('first');
|
||||
|
||||
// Advance time so the first code is almost expired
|
||||
vi.advanceTimersByTime(200_000);
|
||||
const code2 = manager.generateCode('second');
|
||||
|
||||
// Advance past first code's expiry
|
||||
vi.advanceTimersByTime(100_001);
|
||||
|
||||
const pending = manager.listPendingCodes();
|
||||
expect(pending).toHaveLength(1);
|
||||
expect(pending[0].code).toBe(code2);
|
||||
expect(pending[0].label).toBe('second');
|
||||
});
|
||||
|
||||
it('cleanup removes expired codes', () => {
|
||||
vi.useFakeTimers();
|
||||
manager.generateCode();
|
||||
|
||||
vi.advanceTimersByTime(300_001);
|
||||
manager.cleanup();
|
||||
|
||||
expect(manager.listPendingCodes()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('enabled getter reflects config', () => {
|
||||
expect(manager.enabled).toBe(true);
|
||||
|
||||
const disabled = new PairingManager({
|
||||
enabled: false,
|
||||
codeTtl: 300_000,
|
||||
codeLength: 6,
|
||||
});
|
||||
expect(disabled.enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user