123 lines
4.8 KiB
JavaScript
123 lines
4.8 KiB
JavaScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|