Enhance Bird class with flash call fallback logic, caller ID validation, and unit test coverage. Update gate call handling to prefer flash calls with fallback to regular calls.
This commit is contained in:
@@ -4,18 +4,11 @@ app_require('classes/bird.php');
|
||||
|
||||
use classes\bird;
|
||||
|
||||
class BirdGateCallConfigValueFake
|
||||
{
|
||||
public function __construct(private readonly mixed $value) {}
|
||||
|
||||
public function getVariableValue(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
class BirdGateCallClientFake extends bird
|
||||
{
|
||||
private string $workspaceIdValue = '';
|
||||
private string $channelIdValue = '';
|
||||
|
||||
/** @var string[] */
|
||||
public array $statusQueue = [];
|
||||
/** @var string[] */
|
||||
@@ -40,11 +33,18 @@ class BirdGateCallClientFake extends bird
|
||||
public function __construct(mixed $workspaceId = 'workspace_1', mixed $channelId = 'channel_1', array $statusQueue = ['accepted'])
|
||||
{
|
||||
$this->statusQueue = $statusQueue;
|
||||
$this->config = (object)[
|
||||
'workspaceId' => new BirdGateCallConfigValueFake($workspaceId),
|
||||
'workplaceId' => new BirdGateCallConfigValueFake($workspaceId),
|
||||
'channelId' => new BirdGateCallConfigValueFake($channelId),
|
||||
];
|
||||
$this->workspaceIdValue = is_string($workspaceId) ? $workspaceId : '';
|
||||
$this->channelIdValue = is_string($channelId) ? $channelId : '';
|
||||
}
|
||||
|
||||
protected function getConfiguredWorkspaceId(): string
|
||||
{
|
||||
return $this->workspaceIdValue;
|
||||
}
|
||||
|
||||
protected function getConfiguredChannelId(): string
|
||||
{
|
||||
return $this->channelIdValue;
|
||||
}
|
||||
|
||||
public function createVoiceCall(string $workspaceId, string $channelId, array $payload): array|object|null
|
||||
@@ -85,7 +85,11 @@ class BirdGateCallClientFake extends bird
|
||||
'channelId' => $channelId,
|
||||
'payload' => $payload,
|
||||
];
|
||||
return $this->flashCreateResponse ?? ['id' => 'flash_123', 'status' => 'starting'];
|
||||
return $this->flashCreateResponse ?? [
|
||||
'id' => 'flash_123',
|
||||
'status' => 'starting',
|
||||
'from' => '+4532330288',
|
||||
];
|
||||
}
|
||||
|
||||
public function getFlashCall(string $workspaceId, string $channelId, string $callId): array|object|null
|
||||
@@ -108,6 +112,26 @@ class BirdGateCallClientFake extends bird
|
||||
}
|
||||
}
|
||||
|
||||
class BirdGateCallFlashFallbackClientFake extends BirdGateCallClientFake
|
||||
{
|
||||
public bool $shouldFlashFail = false;
|
||||
public int $flashCallAttempts = 0;
|
||||
public int $regularCallAttempts = 0;
|
||||
|
||||
public function callGateViaFlashCall(int $countryCode, int $phone, int $ringTimeout): void
|
||||
{
|
||||
$this->flashCallAttempts++;
|
||||
if ($this->shouldFlashFail) {
|
||||
throw new \Exception('Flash call failed');
|
||||
}
|
||||
}
|
||||
|
||||
public function callGateAndHangupWhenAccepted(int $countryCode, int $phone, int $timeout)
|
||||
{
|
||||
$this->regularCallAttempts++;
|
||||
}
|
||||
}
|
||||
|
||||
it('fails fast when workspace id is missing for gate calls', function (): void {
|
||||
$client = new BirdGateCallClientFake(workspaceId: '', channelId: 'channel_1');
|
||||
|
||||
@@ -155,6 +179,7 @@ it('hangs up when call reaches accepted state', function (): void {
|
||||
expect($client->createPayloads)->toHaveCount(1);
|
||||
expect($client->createPayloads[0]['workspaceId'])->toBe('workspace_1');
|
||||
expect($client->createPayloads[0]['channelId'])->toBe('channel_1');
|
||||
expect($client->createPayloads[0]['payload']['from'])->toBe('+4532330288');
|
||||
expect($client->createPayloads[0]['payload']['to'])->toBe('+4512345678');
|
||||
expect($client->createPayloads[0]['payload']['ringTimeout'])->toBe(10);
|
||||
expect(array_key_exists('timeout', $client->createPayloads[0]['payload']))->toBeFalse();
|
||||
@@ -249,13 +274,14 @@ it('creates gate flash call with documented ringTimeout payload', function (): v
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1'
|
||||
);
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'accepted'];
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'accepted', 'from' => '+4532330288'];
|
||||
|
||||
$client->callGateViaFlashCall(45, 12345678, 10);
|
||||
|
||||
expect($client->flashCreatePayloads)->toHaveCount(1);
|
||||
expect($client->flashCreatePayloads[0]['workspaceId'])->toBe('workspace_1');
|
||||
expect($client->flashCreatePayloads[0]['channelId'])->toBe('channel_1');
|
||||
expect($client->flashCreatePayloads[0]['payload']['from'])->toBe('+4532330288');
|
||||
expect($client->flashCreatePayloads[0]['payload']['to'])->toBe('+4512345678');
|
||||
expect($client->flashCreatePayloads[0]['payload']['ringTimeout'])->toBe(10);
|
||||
expect(array_key_exists('timeout', $client->flashCreatePayloads[0]['payload']))->toBeFalse();
|
||||
@@ -266,7 +292,7 @@ it('polls flash call and succeeds once accepted', function (): void {
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1'
|
||||
);
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'starting'];
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'starting', 'from' => '+4532330288'];
|
||||
$client->flashStatusQueue = ['ringing', 'accepted', 'completed'];
|
||||
|
||||
$client->callGateViaFlashCall(45, 12345678, 10);
|
||||
@@ -279,9 +305,49 @@ it('fails flash gate flow when terminal failure status is returned', function ()
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1'
|
||||
);
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'starting'];
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'starting', 'from' => '+4532330288'];
|
||||
$client->flashStatusQueue = ['busy'];
|
||||
|
||||
expect(fn() => $client->callGateViaFlashCall(45, 12345678, 10))
|
||||
->toThrow(\Exception::class, 'Gate flash call failed with status: busy');
|
||||
});
|
||||
|
||||
it('falls back to regular gate call when flash caller id is not confirmed', function (): void {
|
||||
$client = new BirdGateCallClientFake(
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1',
|
||||
statusQueue: ['accepted']
|
||||
);
|
||||
$client->flashCreateResponse = ['id' => 'flash_123', 'status' => 'accepted'];
|
||||
|
||||
$client->callGatePreferringFlashCall(45, 12345678, 10);
|
||||
|
||||
expect($client->flashCreatePayloads)->toHaveCount(1);
|
||||
expect($client->createPayloads)->toHaveCount(1);
|
||||
expect($client->createPayloads[0]['payload']['from'])->toBe('+4532330288');
|
||||
});
|
||||
|
||||
it('does not fallback to regular gate call when flash succeeds', function (): void {
|
||||
$client = new BirdGateCallFlashFallbackClientFake(
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1'
|
||||
);
|
||||
|
||||
$client->callGatePreferringFlashCall(45, 12345678, 10);
|
||||
|
||||
expect($client->flashCallAttempts)->toBe(1);
|
||||
expect($client->regularCallAttempts)->toBe(0);
|
||||
});
|
||||
|
||||
it('falls back to regular gate call when flash fails', function (): void {
|
||||
$client = new BirdGateCallFlashFallbackClientFake(
|
||||
workspaceId: 'workspace_1',
|
||||
channelId: 'channel_1'
|
||||
);
|
||||
$client->shouldFlashFail = true;
|
||||
|
||||
$client->callGatePreferringFlashCall(45, 12345678, 10);
|
||||
|
||||
expect($client->flashCallAttempts)->toBe(1);
|
||||
expect($client->regularCallAttempts)->toBe(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user