232 lines
8.1 KiB
PHP
232 lines
8.1 KiB
PHP
<?php
|
|
|
|
$_SERVER['REQUEST_URI'] = $_SERVER['REQUEST_URI'] ?? '/';
|
|
|
|
app_require('routes/birdVoiceWebhooksRoute.php');
|
|
|
|
final class BirdVoiceWebhookRouteTestDouble extends \routes\birdVoiceWebhooksRoute
|
|
{
|
|
public array $departmentNames = [];
|
|
public array $rawState = [];
|
|
public array $ttls = [];
|
|
|
|
public function mapDepartments(array $ids): array
|
|
{
|
|
return $this->buildDepartmentOptionMap($ids);
|
|
}
|
|
|
|
public function departmentPrompt(array $map): string
|
|
{
|
|
return $this->buildDepartmentPromptText($map);
|
|
}
|
|
|
|
public function gatePrompt(string $name): string
|
|
{
|
|
return $this->buildGateTypePromptText($name);
|
|
}
|
|
|
|
public function gatePromptForOptions(string $name, array $options): string
|
|
{
|
|
return $this->buildGateTypePromptText($name, $options);
|
|
}
|
|
|
|
public function selectDepartment(array $map, string $digit): ?int
|
|
{
|
|
return $this->resolveDepartmentIdByDigit($map, $digit);
|
|
}
|
|
|
|
public function selectGateType(string $digit, array $options = []): ?string
|
|
{
|
|
return $this->resolveGateTypeByDigit($digit, $options);
|
|
}
|
|
|
|
public function mapGateOptions(array $types): array
|
|
{
|
|
return $this->buildGateOptionMap($types);
|
|
}
|
|
|
|
public function normalizeMenuDigit(?string $input): ?string
|
|
{
|
|
return $this->normalizeMenuDigitInput($input);
|
|
}
|
|
|
|
public function storeState(string $callId, array $state): void
|
|
{
|
|
$this->saveIvrState($callId, $state);
|
|
}
|
|
|
|
public function loadState(string $callId): ?array
|
|
{
|
|
return $this->readIvrState($callId);
|
|
}
|
|
|
|
public function removeState(string $callId): void
|
|
{
|
|
$this->clearIvrState($callId);
|
|
}
|
|
|
|
public function stateKey(string $callId): string
|
|
{
|
|
return $this->ivrStateKey($callId);
|
|
}
|
|
|
|
protected function getDepartmentNameById(int $departmentId): string
|
|
{
|
|
return $this->departmentNames[$departmentId] ?? ('Department ' . $departmentId);
|
|
}
|
|
|
|
protected function writeIvrStateRaw(string $key, string $value, int $ttlSeconds): void
|
|
{
|
|
$this->rawState[$key] = $value;
|
|
$this->ttls[$key] = $ttlSeconds;
|
|
}
|
|
|
|
protected function readIvrStateRaw(string $key): ?string
|
|
{
|
|
return $this->rawState[$key] ?? null;
|
|
}
|
|
|
|
protected function deleteIvrStateRaw(string $key): void
|
|
{
|
|
unset($this->rawState[$key], $this->ttls[$key]);
|
|
}
|
|
}
|
|
|
|
it('builds deterministic department digit map without capping options', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
$route->departmentNames = [
|
|
11 => 'Nord',
|
|
22 => 'Syd',
|
|
33 => 'Vest',
|
|
44 => 'Ost',
|
|
55 => 'Midt',
|
|
66 => 'Aalborg',
|
|
77 => 'Odense',
|
|
88 => 'Esbjerg',
|
|
99 => 'Kolding',
|
|
111 => 'Roskilde',
|
|
];
|
|
|
|
$map = $route->mapDepartments([
|
|
['department_id' => 11, 'department_name' => 'Nord', 'has_entrance_gate' => true, 'has_exit_gate' => false],
|
|
['department_id' => 22, 'department_name' => 'Syd', 'has_entrance_gate' => false, 'has_exit_gate' => true],
|
|
0,
|
|
-5,
|
|
['department_id' => 33, 'department_name' => 'Vest', 'has_entrance_gate' => true, 'has_exit_gate' => true],
|
|
44,
|
|
55,
|
|
66,
|
|
77,
|
|
88,
|
|
99,
|
|
111,
|
|
]);
|
|
|
|
expect($map)->toHaveCount(10);
|
|
expect(array_keys($map))->toBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
expect($map['1']['department_id'])->toBe(11);
|
|
expect($map['2']['department_id'])->toBe(22);
|
|
expect($map['3']['department_id'])->toBe(33);
|
|
expect($map['10']['department_id'])->toBe(111);
|
|
expect($map['1']['department_name'])->toBe('Nord');
|
|
expect($map['2']['department_name'])->toBe('Syd');
|
|
expect($map['3']['department_name'])->toBe('Vest');
|
|
expect($map['10']['department_name'])->toBe('Roskilde');
|
|
expect($map['1']['has_entrance_gate'])->toBeTrue();
|
|
expect($map['1']['has_exit_gate'])->toBeFalse();
|
|
expect($map['2']['has_entrance_gate'])->toBeFalse();
|
|
expect($map['2']['has_exit_gate'])->toBeTrue();
|
|
});
|
|
|
|
it('resolves department ids by selected digit', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
$map = [
|
|
'1' => ['department_id' => 101, 'department_name' => 'A'],
|
|
'2' => ['department_id' => 202, 'department_name' => 'B'],
|
|
];
|
|
|
|
expect($route->selectDepartment($map, '1'))->toBe(101);
|
|
expect($route->selectDepartment($map, '2'))->toBe(202);
|
|
expect($route->selectDepartment($map + ['10' => ['department_id' => 1001, 'department_name' => 'J']], '10'))->toBe(1001);
|
|
expect($route->selectDepartment($map, '9'))->toBeNull();
|
|
expect($route->selectDepartment($map, ''))->toBeNull();
|
|
});
|
|
|
|
it('builds compact gate option maps and resolves selected gate type by digit', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
$both = $route->mapGateOptions(['entrance', 'exit']);
|
|
$exitOnly = $route->mapGateOptions(['exit']);
|
|
$entranceOnly = $route->mapGateOptions(['entrance']);
|
|
|
|
expect($both)->toBe(['1' => 'entrance', '2' => 'exit']);
|
|
expect($exitOnly)->toBe(['1' => 'exit']);
|
|
expect($entranceOnly)->toBe(['1' => 'entrance']);
|
|
|
|
expect($route->selectGateType('1', $both))->toBe('entrance');
|
|
expect($route->selectGateType('2', $both))->toBe('exit');
|
|
expect($route->selectGateType('1', $exitOnly))->toBe('exit');
|
|
expect($route->selectGateType('2', $exitOnly))->toBeNull();
|
|
expect($route->selectGateType('0', $both))->toBeNull();
|
|
expect($route->selectGateType('x', $both))->toBeNull();
|
|
});
|
|
|
|
it('stores, loads and clears ivr state with ttl', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
$callId = 'abc-123';
|
|
$state = [
|
|
'stage' => 'department_select',
|
|
'department_options' => ['1' => ['department_id' => 5, 'department_name' => 'North']],
|
|
'gate_options' => ['1' => 'entrance'],
|
|
'gather_template' => ['timeout' => 30],
|
|
];
|
|
|
|
$route->storeState($callId, $state);
|
|
|
|
$key = $route->stateKey($callId);
|
|
expect($route->loadState($callId))->toBe($state);
|
|
expect($route->ttls[$key] ?? null)->toBe(1200);
|
|
|
|
$route->removeState($callId);
|
|
expect($route->loadState($callId))->toBeNull();
|
|
});
|
|
|
|
it('builds department prompts with multi-digit guidance and compact gate prompts', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
|
|
$prompt = $route->departmentPrompt([
|
|
'1' => ['department_id' => 1, 'department_name' => 'Nord'],
|
|
'2' => ['department_id' => 2, 'department_name' => 'Syd'],
|
|
'3' => ['department_id' => 3, 'department_name' => 'Vest'],
|
|
'4' => ['department_id' => 4, 'department_name' => 'Ost'],
|
|
'5' => ['department_id' => 5, 'department_name' => 'Midt'],
|
|
'6' => ['department_id' => 6, 'department_name' => 'Aalborg'],
|
|
'7' => ['department_id' => 7, 'department_name' => 'Odense'],
|
|
'8' => ['department_id' => 8, 'department_name' => 'Esbjerg'],
|
|
'9' => ['department_id' => 9, 'department_name' => 'Kolding'],
|
|
'10' => ['department_id' => 10, 'department_name' => 'Roskilde'],
|
|
]);
|
|
|
|
expect($prompt)->toContain('Choose department.');
|
|
expect($prompt)->toContain('Enter the option number followed by pound.');
|
|
expect($prompt)->toContain('Press 1 for Nord');
|
|
expect($prompt)->toContain('Press 10 for Roskilde');
|
|
|
|
$gatePrompt = $route->gatePromptForOptions('Nord', ['1' => 'exit']);
|
|
expect($gatePrompt)->toContain('You selected Nord.');
|
|
expect($gatePrompt)->toContain('Press 1 for exit.');
|
|
expect($gatePrompt)->not->toContain('Press 2 for exit.');
|
|
});
|
|
|
|
it('normalizes menu digit input from Bird dtmf payload values', function (): void {
|
|
$route = new BirdVoiceWebhookRouteTestDouble();
|
|
|
|
expect($route->normalizeMenuDigit('1'))->toBe('1');
|
|
expect($route->normalizeMenuDigit('1#'))->toBe('1');
|
|
expect($route->normalizeMenuDigit(' 2## '))->toBe('2');
|
|
expect($route->normalizeMenuDigit('10#'))->toBe('10');
|
|
expect($route->normalizeMenuDigit('12#'))->toBe('12');
|
|
expect($route->normalizeMenuDigit('09'))->toBeNull();
|
|
expect($route->normalizeMenuDigit('*'))->toBeNull();
|
|
expect($route->normalizeMenuDigit(null))->toBeNull();
|
|
});
|