// tests/callTest.twiml.test.js import test from 'node:test'; import assert from 'node:assert/strict'; import { buildTwiml, initialStepForMode, CORE_STEP_ORDER, STORE_ENTRY_STEP_ORDER, } from '../services/callTest/twiml.js'; import { buildVoiceWebhookUrl, getCallTestConfig, normalizeE164, } from '../services/callTest/config.js'; import { parseCallTestArgs } from '../services/callTest/parseArgs.js'; const TEST_ID = '11111111-2222-3333-4444-555555555555'; const CFG = { dtmf: '1', greetingPauseSec: 5, answerWaitSec: 45, listenSec: 60, introTts: 'Intro message', outroTts: 'Outro message', }; test('initialStepForMode: store starts at greeting, dial at intro', () => { assert.equal(initialStepForMode('store'), 'greeting'); assert.equal(initialStepForMode('dial'), 'intro'); }); test('buildTwiml: store entry chain references next steps', () => { const greeting = buildTwiml({ mode: 'store', step: 'greeting', testId: TEST_ID, config: CFG }); assert.match(greeting, //); assert.match(greeting, /step=dtmf/); const dtmf = buildTwiml({ mode: 'store', step: 'dtmf', testId: TEST_ID, config: CFG }); assert.match(dtmf, //); assert.match(dtmf, /step=wait/); const wait = buildTwiml({ mode: 'store', step: 'wait', testId: TEST_ID, config: CFG }); assert.match(wait, //); assert.match(wait, /step=intro/); }); test('buildTwiml: shared core chain', () => { for (const step of CORE_STEP_ORDER) { const xml = buildTwiml({ mode: 'dial', step, testId: TEST_ID, config: CFG }); assert.match(xml, /^<\?xml/); assert.match(xml, //); if (step === 'done') { assert.match(xml, //); } } const intro = buildTwiml({ mode: 'dial', step: 'intro', testId: TEST_ID, config: CFG }); assert.match(intro, /Intro message/); assert.match(intro, /step=listen/); const listen = buildTwiml({ mode: 'dial', step: 'listen', testId: TEST_ID, config: CFG }); assert.match(listen, //); const outro = buildTwiml({ mode: 'dial', step: 'outro', testId: TEST_ID, config: CFG }); assert.match(outro, /Outro message/); }); test('buildTwiml: store entry order ends at intro then core', () => { assert.deepEqual(STORE_ENTRY_STEP_ORDER, ['greeting', 'dtmf', 'wait']); const last = buildTwiml({ mode: 'store', step: 'wait', testId: TEST_ID, config: CFG }); assert.match(last, /step=intro/); }); test('normalizeE164', () => { assert.equal(normalizeE164('2125550100'), '+12125550100'); assert.equal(normalizeE164('+12125550100'), '+12125550100'); assert.equal(normalizeE164('bad'), null); }); test('parseCallTestArgs', () => { assert.deepEqual(parseCallTestArgs(['782']), { kind: 'store', storeNum: '782', dialOverride: null }); assert.deepEqual(parseCallTestArgs(['store', '782']), { kind: 'store', storeNum: '782', dialOverride: null }); assert.deepEqual(parseCallTestArgs(['dial', '+12125550100']), { kind: 'dial', dial: '+12125550100' }); assert.deepEqual(parseCallTestArgs(['+12125550100']), { kind: 'dial', dial: '+12125550100' }); assert.deepEqual(parseCallTestArgs(['status', 'abc']), { kind: 'status', testId: 'abc' }); }); test('buildVoiceWebhookUrl uses TWILIO_WEBHOOK_BASE_URL', () => { const prev = process.env.TWILIO_WEBHOOK_BASE_URL; process.env.TWILIO_WEBHOOK_BASE_URL = 'https://example.com'; try { const url = buildVoiceWebhookUrl(TEST_ID, 'store', 'greeting'); assert.match(url, /^https:\/\/example\.com\/twilio\/calltest\/voice\?/); assert.match(url, /testId=11111111/); assert.match(url, /mode=store/); assert.match(url, /step=greeting/); } finally { if (prev == null) delete process.env.TWILIO_WEBHOOK_BASE_URL; else process.env.TWILIO_WEBHOOK_BASE_URL = prev; } }); test('getCallTestConfig defaults listen to 60', () => { const cfg = getCallTestConfig(); assert.equal(cfg.listenSec, 60); assert.equal(cfg.dtmf, '1'); });