Expand Bird Voice Webhook tests to cover scenarios with shared gates and distinct entrance/exit gates, refine payload validation, and enhance gate resolution logic.

This commit is contained in:
Jeppe Bundgaard
2026-04-13 17:55:53 +02:00
parent 8465ee794d
commit d267fb0f09
3 changed files with 192 additions and 11 deletions
@@ -122,6 +122,17 @@ final class BirdVoiceWebhookLifecycleRouteTestDouble extends \routes\birdVoiceWe
return $this->gatesByDepartmentAndType[$departmentId . ':' . $gateType] ?? null;
}
protected function resolvePhoneCallGateById(int $gateId): ?department_gates_o
{
foreach ($this->gatesByDepartmentAndType as $gate) {
if ((int)$gate->id === $gateId) {
return $gate;
}
}
return null;
}
protected function shouldAcceptInboundCall(bird $client): bool
{
return true;
@@ -321,7 +332,7 @@ it('accepts legacy initial webhook payloads with top-level call identifiers', fu
expect($state['wait_timeout'] ?? null)->toBe('PT10M');
});
it('returns a flow-data gather payload after department selection in native flow mode', function (): void {
it('returns a flow-data gather payload after department selection in native flow mode when distinct gate choices exist', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();
$route->runLifecycle(bird_webhook_legacy_initial_payload());
@@ -342,6 +353,39 @@ it('returns a flow-data gather payload after department selection in native flow
expect($state['selected_department_id'] ?? null)->toBe(11);
});
it('opens the gate immediately after department selection when entrance and exit resolve to the same gate in native flow mode', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();
$route->eligibleDepartments = [
[
'department_id' => 44,
'department_name' => 'Shared',
'order_priority' => 1,
'has_entrance_gate' => true,
'has_exit_gate' => true,
],
];
$sharedGate = new BirdVoiceWebhookLifecycleGateFake(4401);
$route->gatesByDepartmentAndType = [
'44:entrance' => $sharedGate,
'44:exit' => $sharedGate,
];
$route->runLifecycle(bird_webhook_legacy_initial_payload());
$result = $route->runLifecycle(bird_webhook_flow_selection_payload('1'));
expect($result['statusCode'])->toBe(200);
expect($result['status'] ?? null)->toBe('completed');
expect($result['completed'] ?? null)->toBeTrue();
expect($result['action'] ?? null)->toBe('gate_opened');
expect($result['gateOpened'] ?? null)->toBeTrue();
expect($result['departmentId'] ?? null)->toBe(44);
expect($result['departmentName'] ?? null)->toBe('Shared');
expect($result['gateType'] ?? null)->toBeNull();
expect($result['gateId'] ?? null)->toBe(4401);
expect($route->openedGates)->toBe([4401]);
expect($route->loadState('call_1'))->toBeNull();
});
it('returns a flow-data completion payload after gate confirmation in native flow mode', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();
@@ -362,7 +406,7 @@ it('returns a flow-data completion payload after gate confirmation in native flo
expect($route->loadState('call_1'))->toBeNull();
});
it('returns a second-stage raw 202 gather response after department selection', function (): void {
it('returns a second-stage raw 202 gather response after department selection when distinct gate choices exist', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();
$route->runLifecycle(bird_webhook_initial_payload());
@@ -381,6 +425,36 @@ it('returns a second-stage raw 202 gather response after department selection',
expect($route->openedGates)->toBe([]);
});
it('opens the shared gate immediately after department selection in raw command mode', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();
$route->eligibleDepartments = [
[
'department_id' => 44,
'department_name' => 'Shared',
'order_priority' => 1,
'has_entrance_gate' => true,
'has_exit_gate' => true,
],
];
$sharedGate = new BirdVoiceWebhookLifecycleGateFake(4401);
$route->gatesByDepartmentAndType = [
'44:entrance' => $sharedGate,
'44:exit' => $sharedGate,
];
$route->runLifecycle(bird_webhook_initial_payload());
$result = $route->runLifecycle(bird_webhook_resumed_payload('1'));
expect($result['statusCode'])->toBe(200);
expect($result['result']['action'] ?? null)->toBe('gate_opened');
expect($result['result']['gateOpened'] ?? null)->toBeTrue();
expect($result['result']['departmentId'] ?? null)->toBe(44);
expect($result['result']['gateType'] ?? null)->toBeNull();
expect($result['result']['gateId'] ?? null)->toBe(4401);
expect($route->openedGates)->toBe([4401]);
expect($route->loadState('call_1'))->toBeNull();
});
it('opens the selected gate and clears redis state on final selection', function (): void {
$route = new BirdVoiceWebhookLifecycleRouteTestDouble();