import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { createDispatcher, parseCommand } from '../src/core/dispatcher.js'; import { CommandRegistry } from '../src/core/registry.js'; /** Telegram сам размечает только латинские команды — здесь это воспроизводим. */ const entities = (text) => { const match = /^\/[a-zA-Z0-9_@]+/.exec(text); return match ? [{ type: 'bot_command', offset: 0, length: match[0].length }] : []; }; const ctxFor = (text, extra = {}) => ({ me: { username: 'MyBot' }, from: { id: 1 }, chat: { id: 1, type: 'private' }, msg: { text, entities: entities(text), date: 0 }, business: null, ...extra, }); describe('parseCommand', () => { it('разбирает команду без аргументов', () => { assert.deepEqual(parseCommand(ctxFor('/start')), { name: 'start', rest: '', args: [] }); }); it('отделяет аргументы от имени', () => { const parsed = parseCommand(ctxFor('/pause 60 now')); assert.equal(parsed.name, 'pause'); assert.deepEqual(parsed.args, ['60', 'now']); assert.equal(parsed.rest, '60 now'); }); it('понимает кириллический алиас без bot_command entity', () => { assert.equal(parseCommand(ctxFor('/старт')).name, 'старт'); }); it('принимает /cmd@своего_бота', () => { assert.equal(parseCommand(ctxFor('/ping@mybot')).name, 'ping'); }); it('игнорирует /cmd@чужого_бота', () => { assert.equal(parseCommand(ctxFor('/ping@OtherBot')), null); }); it('игнорирует обычный текст и команду не в начале строки', () => { assert.equal(parseCommand(ctxFor('привет')), null); assert.equal(parseCommand(ctxFor('см. /help')), null); }); it('читает команду из подписи к медиа', () => { const ctx = { me: { username: 'MyBot' }, msg: { caption: '/start', caption_entities: entities('/start') } }; assert.equal(parseCommand(ctx).name, 'start'); }); }); describe('createDispatcher', () => { const build = (definition) => { const registry = new CommandRegistry(); const calls = []; registry.register({ handler: (ctx, args) => calls.push(args), ...definition }, 'test.js'); return { dispatch: createDispatcher(registry), calls }; }; const noop = async () => {}; it('вызывает обработчик и передаёт аргументы', async () => { const { dispatch, calls } = build({ command: 'echo' }); await dispatch(ctxFor('/echo раз два'), noop); assert.deepEqual(calls, [['раз', 'два']]); }); it('срабатывает на алиас', async () => { const { dispatch, calls } = build({ command: 'help', aliases: ['помощь'] }); await dispatch(ctxFor('/помощь'), noop); assert.equal(calls.length, 1); }); it('пропускает дальше неизвестную команду', async () => { const { dispatch, calls } = build({ command: 'help' }); let nexted = false; await dispatch(ctxFor('/nope'), async () => { nexted = true; }); assert.equal(calls.length, 0); assert.ok(nexted); }); it('не выполняет команду повторно при редактировании сообщения', async () => { const { dispatch, calls } = build({ command: 'start' }); await dispatch({ ...ctxFor('/start'), editedMessage: { text: '/start' } }, noop); assert.equal(calls.length, 0); }); it('scope business не работает в личке с ботом', async () => { const { dispatch, calls } = build({ command: 'pause', scope: 'business', adminOnly: false }); await dispatch(ctxFor('/pause'), noop); assert.equal(calls.length, 0); }); it('scope private не работает в бизнес-чате', async () => { const { dispatch, calls } = build({ command: 'start', scope: 'private' }); await dispatch(ctxFor('/start', { businessConnectionId: 'conn-1' }), noop); assert.equal(calls.length, 0); }); it('adminOnly пускает владельца бизнес-аккаунта и блокирует клиента', async () => { const owner = build({ command: 'pause', scope: 'business', adminOnly: true }); await owner.dispatch( ctxFor('/pause', { businessConnectionId: 'conn-1', business: { isOwner: true } }), noop, ); assert.equal(owner.calls.length, 1); const client = build({ command: 'pause', scope: 'business', adminOnly: true }); await client.dispatch( ctxFor('/pause', { businessConnectionId: 'conn-1', business: { isOwner: false } }), noop, ); assert.equal(client.calls.length, 0); }); });