levtor
Version:
Levtor — a smart, fuzzy, AI-powered chat command handler and guardian for wallets and AI integration.
163 lines (135 loc) • 5.43 kB
text/typescript
import { describe, it, expect } from 'vitest';
import { CommandHandler } from '../commandHandler';
describe('Levtor', () => {
it('registers and executes commands', async () => {
const levtor = new CommandHandler({});
let executed = false;
levtor.register('balance', async () => {
executed = true;
return 'Balance OK';
}, { aliases: ['bal', 'balnce'] });
const res = await levtor.handle('!balnce');
expect(executed).toBe(true);
expect(res).toBe('Balance OK');
});
it('applies preprocessors', async () => {
const levtor = new CommandHandler({});
levtor.usePreprocessor(async (msg: string) => ({ message: msg.trim().toLowerCase() }));
levtor.register('ping', async () => 'pong');
const res = await levtor.handle(' !PING ');
expect(res).toBe('pong');
});
it('aliases and fuzzy match work', async () => {
const levtor = new CommandHandler({});
levtor.register('balance', async () => '100 USD', { aliases: ['bal'] });
expect(await levtor.handle('!bal')).toBe('100 USD');
expect(await levtor.handle('!balan')).toBe('100 USD'); // fuzzy match
});
it('caching works', async () => {
const levtor = new CommandHandler({});
let callCount = 0;
levtor.register('count', async () => {
callCount++;
return callCount.toString();
}, { cacheTtlMs: 5000 });
const r1 = await levtor.handle('!count');
const r2 = await levtor.handle('!count');
expect(r1).toBe('1');
expect(r2).toBe('1'); // cached
});
it('debounce works', async () => {
const levtor = new CommandHandler({});
levtor.register('fast', async () => 'done', { debounceMs: 1000 });
const r1 = await levtor.handle('!fast');
const r2 = await levtor.handle('!fast');
expect(r1).toBe('done');
expect(r2).toBe('🕒 Please wait...');
});
it('lifecycle hooks fire', async () => {
const levtor = new CommandHandler({});
const events: string[] = [];
levtor.register('hooked', async () => 'ok');
levtor.on('before', async (cmd: string) => { events.push(`before:${cmd}`); });
levtor.on('after', async (cmd: string) => { events.push(`after:${cmd}`); });
levtor.on('error', async () => { events.push('error'); });
const res = await levtor.handle('!hooked');
expect(res).toBe('ok');
expect(events).toEqual(['before:hooked', 'after:hooked']);
});
it('formatWithAI function applied', async () => {
const levtor = new CommandHandler(
{},
async (out: string) => `[AI] ${out}`
);
levtor.register('test', async () => 'hello');
const res = await levtor.handle('!test');
expect(res).toBe('[AI] hello');
});
it('handles errors in command execution', async () => {
const levtor = new CommandHandler({});
levtor.register('fail', async () => { throw new Error('fail!'); });
let errorFired = false;
levtor.on('error', async () => { errorFired = true; });
const res = await levtor.handle('!fail');
expect(res).toBe('⚠️ An error occurred.');
expect(errorFired).toBe(true);
});
it('debounce is per argument', async () => {
const levtor = new CommandHandler({});
levtor.register('deb', async ([msg = '']) => msg, { debounceMs: 1000 });
const r1 = await levtor.handle('!deb a');
const r2 = await levtor.handle('!deb b');
expect(r1).toBe('a');
expect(r2).toBe('b');
});
it('cache is per argument', async () => {
const levtor = new CommandHandler({});
let count = 0;
levtor.register('cache', async ([msg]) => (++count).toString(), { cacheTtlMs: 5000 });
const r1 = await levtor.handle('!cache a');
const r2 = await levtor.handle('!cache b');
expect(r1).toBe('1');
expect(r2).toBe('2');
});
it('fuzzy matches with multiple close aliases', async () => {
const levtor = new CommandHandler({});
levtor.register('alpha', async () => 'A', { aliases: ['alfa', 'alfa1'] });
expect(await levtor.handle('!alfa')).toBe('A');
expect(await levtor.handle('!alfa1')).toBe('A');
expect(await levtor.handle('!alph')).toBe('A');
});
it('AI formatting receives context', async () => {
let ctxSeen: any = null;
const levtor = new CommandHandler(
{},
async (out: string, context) => {
ctxSeen = context;
return `[AI] ${out}`;
}
);
levtor.register('ctx', async () => 'ok');
const res = await levtor.handle('!ctx');
expect(res).toBe('[AI] ok');
expect(ctxSeen.command).toBe('ctx');
});
it('handles named arguments', async () => {
const levtor = new CommandHandler({});
levtor.register('named', async (_pos, named) => named.foo as string);
const res = await levtor.handle('!named foo=bar');
expect(res).toBe('bar');
});
it('handles both positional and named arguments', async () => {
const levtor = new CommandHandler({});
levtor.register('mix', async (pos, named) => `${pos[0]}-${named.x}`);
const res = await levtor.handle('!mix hello x=world');
expect(res).toBe('hello-world');
});
it('lifecycle hooks receive context', async () => {
const levtor = new CommandHandler({}, undefined);
let beforeCtx: any = null;
levtor.register('ctx', async () => 'ok');
levtor.on('before', async (_cmd, _payload, ctx) => { beforeCtx = ctx; });
await levtor.handle('!ctx', { user: 'bob' });
expect(beforeCtx).toEqual({ user: 'bob' });
});
});