// 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');
});