srs-api/src/whatsapp/commands.test.ts
Aswin Biju f18779e182 feat(whatsapp): take orders end to end, from first forward to Save
Wire the order parser to the WhatsApp bot. A known user forwards an order, ends it with Done, picks a shop, confirms a summary and the order is saved.
2026-11-03 17:29:44 +05:30

89 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { classify, hasOrderContent } from './commands';
const idle = { collecting: false };
const col = { collecting: true };
describe('classify: single-word commands', () => {
it.each(['done', 'DONE', 'Done', ' done ', 'done.', 'Done!', 'submit', 'Submit.'])('%j is submit', (t) => {
expect(classify(t, idle)).toEqual({ kind: 'submit', body: '' });
});
it.each(['cancel', 'Cancel', 'CANCEL!', ' cancel. '])('%j is cancel', (t) => {
expect(classify(t, idle)).toEqual({ kind: 'cancel' });
});
it.each(['?', 'help', 'Help', 'HELP'])('%j is help', (t) => {
expect(classify(t, idle)).toEqual({ kind: 'help' });
});
it.each([
['1', 1],
['2', 2],
['3', 3],
[' 4 ', 4],
['12', 12],
['1.', 1],
])('%j is number %i', (t, n) => {
expect(classify(t, idle)).toEqual({ kind: 'number', n });
});
});
describe('classify: order text and look-alikes', () => {
it.each(['0888=2', '0888', '123', 'hello', 'thanks', '👍', 'done please', 'cancelled', 'yes', ''])(
'%j is text',
(t) => {
expect(classify(t, idle)).toEqual({ kind: 'text' });
},
);
it('whitespace only is text', () => {
expect(classify(' \n \n', idle)).toEqual({ kind: 'text' });
});
it('three or more digits are never a menu number', () => {
expect(classify('100', idle)).toEqual({ kind: 'text' });
});
});
describe('classify: done as the last line', () => {
it('splits order text from Done', () => {
expect(classify('0888\n0057\ndone', idle)).toEqual({ kind: 'submit', body: '0888\n0057' });
});
it('ignores case, punctuation, CRLF and trailing blank lines', () => {
expect(classify('0888=2\r\n7810=1\r\nSubmit.\n\n', idle)).toEqual({ kind: 'submit', body: '0888=2\n7810=1' });
});
it('done only counts on the last line', () => {
expect(classify('done\n0888', idle)).toEqual({ kind: 'text' });
});
it('cancel on a later line of a longer message is not a command', () => {
expect(classify('0888\ncancel', idle)).toEqual({ kind: 'text' });
});
});
describe('classify: trailing 1 (Hermes merges quick messages)', () => {
it('is Done when the conversation was already collecting', () => {
expect(classify('0888=2\n7810=1\n1', col)).toEqual({ kind: 'submit', body: '0888=2\n7810=1' });
});
it('stays order text in the first message of an order', () => {
expect(classify('0888=2\n7810=1\n1', idle)).toEqual({ kind: 'text' });
});
it('a lone 1 is a number either way (the engine decides what it means)', () => {
expect(classify('1', col)).toEqual({ kind: 'number', n: 1 });
expect(classify('1', idle)).toEqual({ kind: 'number', n: 1 });
});
it('only 1 works this way, not other numbers', () => {
expect(classify('0888=2\n2', col)).toEqual({ kind: 'text' });
});
it('a line ending in 1 is not a trailing 1', () => {
expect(classify('0888=2\n7801', col)).toEqual({ kind: 'text' });
});
});
describe('hasOrderContent', () => {
it('is true for an entry', () => {
expect(hasOrderContent('0888=2')).toBe(true);
});
it('is true for something unreadable', () => {
expect(hasOrderContent('08?8=2')).toBe(true);
});
it('is false for chatter, emoji, a lone number and totals', () => {
for (const t of ['ok', 'thanks', '👍', '1', '12', 'Total 50', '']) expect(hasOrderContent(t)).toBe(false);
});
});