Add workspace support to Bird numbers API and update routes, methods, and tests accordingly
This commit is contained in:
@@ -305,6 +305,12 @@ paths:
|
||||
summary: List your numbers
|
||||
operationId: birdListNumbers
|
||||
parameters:
|
||||
- in: query
|
||||
name: workspaceId
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Bird Workspace identifier (optional if configured)
|
||||
- in: query
|
||||
name: page
|
||||
required: false
|
||||
@@ -331,6 +337,12 @@ paths:
|
||||
summary: Get a number by ID
|
||||
operationId: birdGetNumber
|
||||
parameters:
|
||||
- in: query
|
||||
name: workspaceId
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Bird Workspace identifier (optional if configured)
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
@@ -350,6 +362,12 @@ paths:
|
||||
summary: Delete/release a number by ID
|
||||
operationId: birdDeleteNumber
|
||||
parameters:
|
||||
- in: query
|
||||
name: workspaceId
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Bird Workspace identifier (optional if configured)
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
|
||||
@@ -296,16 +296,22 @@ class bird
|
||||
return $this->sendPostRequest($base . '/' . rawurlencode($callId) . '/hangup', $payload);
|
||||
}
|
||||
|
||||
public function listNumbers(array $query = []): array|object|null
|
||||
public function listNumbers(string $workspaceId, array $query = []): array|object|null
|
||||
{
|
||||
$this->logBirdAction('BIRD_NUMBERS_LIST', 'query_keys=' . implode(',', array_keys($query)));
|
||||
return $this->sendGetRequest('/numbers', $query);
|
||||
$this->logBirdAction('BIRD_NUMBERS_LIST', 'workspace=' . $workspaceId . ' query_keys=' . implode(',', array_keys($query)));
|
||||
return $this->sendGetRequest('/workspaces/' . rawurlencode($workspaceId) . '/numbers', $query);
|
||||
}
|
||||
|
||||
public function getNumber(string $numberId): array|object|null
|
||||
public function getNumber(string $workspaceId, string $numberId): array|object|null
|
||||
{
|
||||
$this->logBirdAction('BIRD_NUMBER_GET', 'number=' . $numberId);
|
||||
return $this->sendGetRequest('/numbers/' . rawurlencode($numberId));
|
||||
$this->logBirdAction('BIRD_NUMBER_GET', 'workspace=' . $workspaceId . ' number=' . $numberId);
|
||||
return $this->sendGetRequest('/workspaces/' . rawurlencode($workspaceId) . '/numbers/' . rawurlencode($numberId));
|
||||
}
|
||||
|
||||
public function deleteNumber(string $workspaceId, string $numberId): array|object|null
|
||||
{
|
||||
$this->logBirdAction('BIRD_NUMBER_DELETE', 'workspace=' . $workspaceId . ' number=' . $numberId);
|
||||
return $this->sendDeleteRequest('/workspaces/' . rawurlencode($workspaceId) . '/numbers/' . rawurlencode($numberId));
|
||||
}
|
||||
|
||||
public function createOutboundTestCallAndHangupWhenAccepted(string $workspaceId, string $channelId, array $options = []): array
|
||||
|
||||
@@ -9,6 +9,37 @@ class birdNumbersRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
private function normalizeOptionalString(mixed $value): string
|
||||
{
|
||||
if (!is_scalar($value)) {
|
||||
return '';
|
||||
}
|
||||
$normalized = trim((string)$value);
|
||||
if ($normalized === '') {
|
||||
return '';
|
||||
}
|
||||
$lower = strtolower($normalized);
|
||||
if ($lower === 'undefined' || $lower === 'null') {
|
||||
return '';
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private function getConfiguredWorkspaceId(bird $client): string
|
||||
{
|
||||
$workspaceConfig = null;
|
||||
if (property_exists($client->config, 'workspaceId')) {
|
||||
$workspaceConfig = $client->config->workspaceId;
|
||||
} elseif (property_exists($client->config, 'workplaceId')) {
|
||||
// Backward compatibility with existing config key naming.
|
||||
$workspaceConfig = $client->config->workplaceId;
|
||||
}
|
||||
if (is_object($workspaceConfig) && method_exists($workspaceConfig, 'getVariableValue')) {
|
||||
return $this->normalizeOptionalString($workspaceConfig->getVariableValue());
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
// List owned numbers
|
||||
@@ -17,8 +48,16 @@ class birdNumbersRoute
|
||||
// Permission: list numbers via Bird
|
||||
self::requirePermission('modules_bird_numbers_list');
|
||||
$client = new bird();
|
||||
$ws = $this->normalizeOptionalString($this->fromQuery('workspaceId'));
|
||||
if ($ws === '') {
|
||||
$ws = $this->getConfiguredWorkspaceId($client);
|
||||
}
|
||||
if ($ws === '') {
|
||||
$response->error('Missing required parameter: workspaceId', 400);
|
||||
}
|
||||
$query = $this->getParametersAsArray();
|
||||
$res = $client->listNumbers($query);
|
||||
unset($query['workspaceId']);
|
||||
$res = $client->listNumbers($ws, $query);
|
||||
$response->success($res ?? []);
|
||||
}, [
|
||||
'modules_bird_numbers_list' => 'List your numbers via Bird',
|
||||
@@ -33,7 +72,14 @@ class birdNumbersRoute
|
||||
$response->error('Missing id', 400);
|
||||
}
|
||||
$client = new bird();
|
||||
$res = $client->getNumber($id);
|
||||
$ws = $this->normalizeOptionalString($this->fromQuery('workspaceId'));
|
||||
if ($ws === '') {
|
||||
$ws = $this->getConfiguredWorkspaceId($client);
|
||||
}
|
||||
if ($ws === '') {
|
||||
$response->error('Missing required parameter: workspaceId', 400);
|
||||
}
|
||||
$res = $client->getNumber($ws, $id);
|
||||
$response->success($res ?? []);
|
||||
}, [
|
||||
'modules_bird_numbers_get' => 'Get a number by ID via Bird',
|
||||
@@ -48,7 +94,14 @@ class birdNumbersRoute
|
||||
$response->error('Missing id', 400);
|
||||
}
|
||||
$client = new bird();
|
||||
$res = $client->sendDeleteRequest('/numbers/' . rawurlencode($id));
|
||||
$ws = $this->normalizeOptionalString($this->fromRequest('workspaceId') ?? $this->fromQuery('workspaceId'));
|
||||
if ($ws === '') {
|
||||
$ws = $this->getConfiguredWorkspaceId($client);
|
||||
}
|
||||
if ($ws === '') {
|
||||
$response->error('Missing required parameter: workspaceId', 400);
|
||||
}
|
||||
$res = $client->deleteNumber($ws, $id);
|
||||
$response->success($res ?? ['status' => 'ok']);
|
||||
}, [
|
||||
'modules_bird_numbers_delete' => 'Delete/release a number via Bird',
|
||||
|
||||
@@ -18,11 +18,13 @@ class DummyConfig2
|
||||
public DummyVar2 $enabled;
|
||||
public DummyVar2 $api_key;
|
||||
public DummyVar2 $server_url;
|
||||
public DummyVar2 $workplaceId;
|
||||
public function __construct()
|
||||
{
|
||||
$this->enabled = new DummyVar2('true');
|
||||
$this->api_key = new DummyVar2('test_api_key');
|
||||
$this->server_url = new DummyVar2('https://example.test');
|
||||
$this->workplaceId = new DummyVar2('test_workspace_id');
|
||||
}
|
||||
public function getModuleName(): string { return 'bird'; }
|
||||
}
|
||||
@@ -55,16 +57,28 @@ function assert_true2($cond, $msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: list numbers (GET /numbers)
|
||||
// Test: list numbers (GET /workspaces/{ws}/numbers)
|
||||
$client = new FakeBird2();
|
||||
$res = $client->sendGetRequest('/numbers', ['limit' => 10]);
|
||||
$res = $client->listNumbers('test_workspace_id', ['limit' => 10]);
|
||||
assert_true2(is_object($res) || is_array($res), 'Numbers response is JSON-decodable');
|
||||
assert_true2(str_starts_with($client->last['url'], 'https://example.test/numbers'), 'Numbers GET URL composed correctly');
|
||||
assert_true2(str_starts_with($client->last['url'], 'https://example.test/workspaces/test_workspace_id/numbers'), 'Numbers GET URL composed correctly');
|
||||
assert_true2(str_contains($client->last['url'], 'limit=10'), 'Query string encoded correctly');
|
||||
assert_true2($client->last['method'] === 'GET', 'HTTP method is GET (numbers)');
|
||||
assert_true2(in_array('Authorization: AccessKey test_api_key', $client->last['headers'], true), 'Authorization header is set (numbers)');
|
||||
assert_true2(empty($client->last['body']), 'GET body is empty (numbers)');
|
||||
|
||||
// Test: get number (GET /workspaces/{ws}/numbers/{id})
|
||||
$clientNum = new FakeBird2();
|
||||
$resNum = $clientNum->getNumber('test_workspace_id', 'num_123');
|
||||
assert_true2(str_ends_with($clientNum->last['url'], '/workspaces/test_workspace_id/numbers/num_123'), 'Get number URL composed correctly');
|
||||
assert_true2($clientNum->last['method'] === 'GET', 'HTTP method is GET (get number)');
|
||||
|
||||
// Test: delete number (DELETE /workspaces/{ws}/numbers/{id})
|
||||
$clientDel = new FakeBird2();
|
||||
$resDel = $clientDel->deleteNumber('test_workspace_id', 'num_123');
|
||||
assert_true2(str_ends_with($clientDel->last['url'], '/workspaces/test_workspace_id/numbers/num_123'), 'Delete number URL composed correctly');
|
||||
assert_true2($clientDel->last['method'] === 'DELETE', 'HTTP method is DELETE (delete number)');
|
||||
|
||||
// Test: create flash call (POST /voice/flash-calls)
|
||||
$client2 = new FakeBird2();
|
||||
$payload = [
|
||||
|
||||
Reference in New Issue
Block a user