From bf81c1ae84da170158dfc0065cdc4dd34e2d5fcd Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 12:43:14 -0400 Subject: [PATCH 1/7] Implement BRTC Endpoints API with models, controllers, and tests --- src/Voice/Bxml/Connect.php | 54 ++++++++ src/Voice/Bxml/Endpoint.php | 41 ++++++ src/Voice/Controllers/EndpointsController.php | 118 ++++++++++++++++++ src/Voice/Models/CreateEndpointRequest.php | 34 +++++ src/Voice/Models/CreateEndpointResponse.php | 40 ++++++ src/Voice/Models/Device.php | 31 +++++ src/Voice/Models/Endpoint.php | 46 +++++++ src/Voice/Models/EndpointEvent.php | 34 +++++ src/Voice/Models/Enums.php | 38 ++++++ src/Voice/Models/ErrorResponse.php | 25 ++++ src/Voice/Models/Page.php | 28 +++++ src/Voice/VoiceClient.php | 16 +++ tests/BxmlTest.php | 14 +++ tests/EndpointsControllerTest.php | 118 ++++++++++++++++++ 14 files changed, 637 insertions(+) create mode 100644 src/Voice/Bxml/Connect.php create mode 100644 src/Voice/Bxml/Endpoint.php create mode 100644 src/Voice/Controllers/EndpointsController.php create mode 100644 src/Voice/Models/CreateEndpointRequest.php create mode 100644 src/Voice/Models/CreateEndpointResponse.php create mode 100644 src/Voice/Models/Device.php create mode 100644 src/Voice/Models/Endpoint.php create mode 100644 src/Voice/Models/EndpointEvent.php create mode 100644 src/Voice/Models/Enums.php create mode 100644 src/Voice/Models/ErrorResponse.php create mode 100644 src/Voice/Models/Page.php create mode 100644 tests/EndpointsControllerTest.php diff --git a/src/Voice/Bxml/Connect.php b/src/Voice/Bxml/Connect.php new file mode 100644 index 0000000..f31ec43 --- /dev/null +++ b/src/Voice/Bxml/Connect.php @@ -0,0 +1,54 @@ +endpoints = $endpoints; + } + + /** + * Add an Endpoint to the Connect verb + * + * @param Endpoint $endpoint + * @return $this + */ + public function addEndpoint(Endpoint $endpoint): Connect { + $this->endpoints[] = $endpoint; + return $this; + } + + /** + * Converts the Connect verb into a DOMElement + * + * @param DOMDocument $doc + * @return DOMElement + */ + protected function toBxml(DOMDocument $doc): DOMElement { + $element = $doc->createElement("Connect"); + foreach ($this->endpoints as $endpoint) { + $element->appendChild($endpoint->toBxml($doc)); + } + return $element; + } +} diff --git a/src/Voice/Bxml/Endpoint.php b/src/Voice/Bxml/Endpoint.php new file mode 100644 index 0000000..143190d --- /dev/null +++ b/src/Voice/Bxml/Endpoint.php @@ -0,0 +1,41 @@ +id = $id; + } + + /** + * Converts the Endpoint verb into a DOMElement + * + * @param DOMDocument $doc + * @return DOMElement + */ + protected function toBxml(DOMDocument $doc): DOMElement { + $element = $doc->createElement("Endpoint"); + $element->setAttribute("id", $this->id); + return $element; + } +} diff --git a/src/Voice/Controllers/EndpointsController.php b/src/Voice/Controllers/EndpointsController.php new file mode 100644 index 0000000..0bf63e6 --- /dev/null +++ b/src/Voice/Controllers/EndpointsController.php @@ -0,0 +1,118 @@ +apiClient = $apiClient; + $this->accountId = $accountId; + } + + /** + * Create a new BRTC endpoint + * @param CreateEndpointRequest $request + * @return CreateEndpointResponse + */ + public function createEndpoint(CreateEndpointRequest $request) { + $url = "/accounts/{$this->accountId}/endpoints"; + $response = $this->apiClient->post($url, $request); + return new CreateEndpointResponse( + $response['endpointId'] ?? null, + $response['type'] ?? null, + $response['status'] ?? null, + $response['createdTime'] ?? null, + $response['updatedTime'] ?? null, + $response['tag'] ?? null, + $response['devices'] ?? null, + $response['token'] ?? null + ); + } + + /** + * List endpoints + * @param array $queryParams (optional) + * @return Endpoint[] + */ + public function listEndpoints($queryParams = []) { + $url = "/accounts/{$this->accountId}/endpoints"; + $response = $this->apiClient->get($url, $queryParams); + $endpoints = []; + foreach ($response['endpoints'] ?? [] as $ep) { + $endpoints[] = new Endpoint( + $ep['id'] ?? null, + $ep['type'] ?? null, + $ep['status'] ?? null, + $ep['direction'] ?? null, + $ep['eventCallbackUrl'] ?? null, + $ep['eventFallbackUrl'] ?? null, + $ep['tag'] ?? null, + $ep['devices'] ?? null, + $ep['createdTime'] ?? null, + $ep['updatedTime'] ?? null + ); + } + return $endpoints; + } + + /** + * Get a specific endpoint + * @param string $endpointId + * @return Endpoint + */ + public function getEndpoint($endpointId) { + $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}"; + $ep = $this->apiClient->get($url); + return new Endpoint( + $ep['id'] ?? null, + $ep['type'] ?? null, + $ep['status'] ?? null, + $ep['direction'] ?? null, + $ep['eventCallbackUrl'] ?? null, + $ep['eventFallbackUrl'] ?? null, + $ep['tag'] ?? null, + $ep['devices'] ?? null, + $ep['createdTime'] ?? null, + $ep['updatedTime'] ?? null + ); + } + + /** + * Delete an endpoint + * @param string $endpointId + * @return bool + */ + public function deleteEndpoint($endpointId) { + $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}"; + $this->apiClient->delete($url); + return true; + } + + /** + * Update endpoint BXML + * @param string $endpointId + * @param string $bxml + * @return bool + */ + public function updateEndpointBxml($endpointId, $bxml) { + $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}/bxml"; + $this->apiClient->put($url, ['bxml' => $bxml]); + return true; + } +} diff --git a/src/Voice/Models/CreateEndpointRequest.php b/src/Voice/Models/CreateEndpointRequest.php new file mode 100644 index 0000000..26a5caa --- /dev/null +++ b/src/Voice/Models/CreateEndpointRequest.php @@ -0,0 +1,34 @@ +type = $type; + $this->direction = $direction; + $this->eventCallbackUrl = $eventCallbackUrl; + $this->eventFallbackUrl = $eventFallbackUrl; + $this->tag = $tag; + $this->connectionMetadata = $connectionMetadata; + } +} diff --git a/src/Voice/Models/CreateEndpointResponse.php b/src/Voice/Models/CreateEndpointResponse.php new file mode 100644 index 0000000..eae422a --- /dev/null +++ b/src/Voice/Models/CreateEndpointResponse.php @@ -0,0 +1,40 @@ +endpointId = $endpointId; + $this->type = $type; + $this->status = $status; + $this->createdTime = $createdTime; + $this->updatedTime = $updatedTime; + $this->tag = $tag; + $this->devices = $devices; + $this->token = $token; + } +} diff --git a/src/Voice/Models/Device.php b/src/Voice/Models/Device.php new file mode 100644 index 0000000..5ae0687 --- /dev/null +++ b/src/Voice/Models/Device.php @@ -0,0 +1,31 @@ +id = $id; + $this->status = $status; + $this->type = $type; + $this->createdTime = $createdTime; + $this->updatedTime = $updatedTime; + } +} diff --git a/src/Voice/Models/Endpoint.php b/src/Voice/Models/Endpoint.php new file mode 100644 index 0000000..f6b1a92 --- /dev/null +++ b/src/Voice/Models/Endpoint.php @@ -0,0 +1,46 @@ +id = $id; + $this->type = $type; + $this->status = $status; + $this->direction = $direction; + $this->eventCallbackUrl = $eventCallbackUrl; + $this->eventFallbackUrl = $eventFallbackUrl; + $this->tag = $tag; + $this->devices = $devices; + $this->createdTime = $createdTime; + $this->updatedTime = $updatedTime; + } +} diff --git a/src/Voice/Models/EndpointEvent.php b/src/Voice/Models/EndpointEvent.php new file mode 100644 index 0000000..2360f74 --- /dev/null +++ b/src/Voice/Models/EndpointEvent.php @@ -0,0 +1,34 @@ +eventType = $eventType; + $this->endpointId = $endpointId; + $this->timestamp = $timestamp; + $this->status = $status; + $this->reason = $reason; + $this->details = $details; + } +} diff --git a/src/Voice/Models/Enums.php b/src/Voice/Models/Enums.php new file mode 100644 index 0000000..0060c21 --- /dev/null +++ b/src/Voice/Models/Enums.php @@ -0,0 +1,38 @@ +message = $message; + $this->code = $code; + $this->details = $details; + } +} diff --git a/src/Voice/Models/Page.php b/src/Voice/Models/Page.php new file mode 100644 index 0000000..b400d2e --- /dev/null +++ b/src/Voice/Models/Page.php @@ -0,0 +1,28 @@ +page = $page; + $this->size = $size; + $this->total = $total; + $this->totalPages = $totalPages; + } +} diff --git a/src/Voice/VoiceClient.php b/src/Voice/VoiceClient.php index 089b842..37756c7 100644 --- a/src/Voice/VoiceClient.php +++ b/src/Voice/VoiceClient.php @@ -34,4 +34,20 @@ public function getClient() } return $this->client; } + + private $endpointsController; + + /** + * Provides access to Endpoints controller + * @return Controllers\EndpointsController + */ + public function getEndpointsController() + { + if ($this->endpointsController == null) { + $apiClient = $this->config->getApiClient ? $this->config->getApiClient() : $this->config; + $accountId = $this->config->accountId ?? null; + $this->endpointsController = new Controllers\EndpointsController($apiClient, $accountId); + } + return $this->endpointsController; + } } diff --git a/tests/BxmlTest.php b/tests/BxmlTest.php index 44bdb2f..e9f1b85 100644 --- a/tests/BxmlTest.php +++ b/tests/BxmlTest.php @@ -540,4 +540,18 @@ public function testStopTranscription() { $responseXml = $response->toBxml(); $this->assertEquals($expectedXml, $responseXml); } + + public function testConnectAndEndpoint() { + $endpoint1 = new BandwidthLib\Voice\Bxml\Endpoint("endpoint-123"); + $endpoint2 = new BandwidthLib\Voice\Bxml\Endpoint("endpoint-456"); + $connect = new BandwidthLib\Voice\Bxml\Connect([ + $endpoint1, + $endpoint2 + ]); + $response = new BandwidthLib\Voice\Bxml\Response(); + $response->addVerb($connect); + $expectedXml = ''; + $responseXml = $response->toBxml(); + $this->assertEquals($expectedXml, $responseXml); + } } diff --git a/tests/EndpointsControllerTest.php b/tests/EndpointsControllerTest.php new file mode 100644 index 0000000..356b5cc --- /dev/null +++ b/tests/EndpointsControllerTest.php @@ -0,0 +1,118 @@ +apiClient = $this->createMock(stdClass::class); + $this->controller = new EndpointsController($this->apiClient, $this->accountId); + } + + public function testCreateEndpoint() + { + $request = new CreateEndpointRequest('WEBRTC', 'INBOUND', 'https://callback', null, 'tag1', ['meta' => 'data']); + $responseData = [ + 'endpointId' => 'ep-123', + 'type' => 'WEBRTC', + 'status' => 'ACTIVE', + 'createdTime' => '2026-03-11T00:00:00Z', + 'updatedTime' => '2026-03-11T00:00:00Z', + 'tag' => 'tag1', + 'devices' => [], + 'token' => 'tok-abc', + ]; + $this->apiClient->expects($this->once()) + ->method('post') + ->with("/accounts/{$this->accountId}/endpoints", $request) + ->willReturn($responseData); + $resp = $this->controller->createEndpoint($request); + $this->assertInstanceOf(CreateEndpointResponse::class, $resp); + $this->assertEquals('ep-123', $resp->endpointId); + $this->assertEquals('WEBRTC', $resp->type); + $this->assertEquals('ACTIVE', $resp->status); + } + + public function testListEndpoints() + { + $responseData = [ + 'endpoints' => [ + [ + 'id' => 'ep-1', + 'type' => 'WEBRTC', + 'status' => 'ACTIVE', + 'direction' => 'INBOUND', + 'eventCallbackUrl' => 'https://cb', + 'eventFallbackUrl' => null, + 'tag' => 'tag', + 'devices' => [], + 'createdTime' => '2026-03-11T00:00:00Z', + 'updatedTime' => '2026-03-11T00:00:00Z', + ] + ] + ]; + $this->apiClient->expects($this->once()) + ->method('get') + ->with("/accounts/{$this->accountId}/endpoints", []) + ->willReturn($responseData); + $resp = $this->controller->listEndpoints(); + $this->assertIsArray($resp); + $this->assertInstanceOf(Endpoint::class, $resp[0]); + $this->assertEquals('ep-1', $resp[0]->id); + } + + public function testGetEndpoint() + { + $endpointId = 'ep-1'; + $responseData = [ + 'id' => $endpointId, + 'type' => 'WEBRTC', + 'status' => 'ACTIVE', + 'direction' => 'INBOUND', + 'eventCallbackUrl' => 'https://cb', + 'eventFallbackUrl' => null, + 'tag' => 'tag', + 'devices' => [], + 'createdTime' => '2026-03-11T00:00:00Z', + 'updatedTime' => '2026-03-11T00:00:00Z', + ]; + $this->apiClient->expects($this->once()) + ->method('get') + ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}") + ->willReturn($responseData); + $resp = $this->controller->getEndpoint($endpointId); + $this->assertInstanceOf(Endpoint::class, $resp); + $this->assertEquals($endpointId, $resp->id); + } + + public function testDeleteEndpoint() + { + $endpointId = 'ep-1'; + $this->apiClient->expects($this->once()) + ->method('delete') + ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}"); + $resp = $this->controller->deleteEndpoint($endpointId); + $this->assertTrue($resp); + } + + public function testUpdateEndpointBxml() + { + $endpointId = 'ep-1'; + $bxml = 'Hello'; + $this->apiClient->expects($this->once()) + ->method('put') + ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}/bxml", ['bxml' => $bxml]); + $resp = $this->controller->updateEndpointBxml($endpointId, $bxml); + $this->assertTrue($resp); + } +} From e4e438263700cfab9189788b03b073a2a7cef040 Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 12:53:53 -0400 Subject: [PATCH 2/7] Refactor Connect and Endpoint classes to make toBxml method public; add integration test for endpoint management --- src/Voice/Bxml/Connect.php | 2 +- src/Voice/Bxml/Endpoint.php | 2 +- tests/ApiTest.php | 203 +++++++++++++++++++++++++++++- tests/EndpointsControllerTest.php | 118 ----------------- 4 files changed, 202 insertions(+), 123 deletions(-) delete mode 100644 tests/EndpointsControllerTest.php diff --git a/src/Voice/Bxml/Connect.php b/src/Voice/Bxml/Connect.php index f31ec43..1cd34e9 100644 --- a/src/Voice/Bxml/Connect.php +++ b/src/Voice/Bxml/Connect.php @@ -44,7 +44,7 @@ public function addEndpoint(Endpoint $endpoint): Connect { * @param DOMDocument $doc * @return DOMElement */ - protected function toBxml(DOMDocument $doc): DOMElement { + public function toBxml(DOMDocument $doc): DOMElement { $element = $doc->createElement("Connect"); foreach ($this->endpoints as $endpoint) { $element->appendChild($endpoint->toBxml($doc)); diff --git a/src/Voice/Bxml/Endpoint.php b/src/Voice/Bxml/Endpoint.php index 143190d..445dff2 100644 --- a/src/Voice/Bxml/Endpoint.php +++ b/src/Voice/Bxml/Endpoint.php @@ -33,7 +33,7 @@ public function __construct(string $id) { * @param DOMDocument $doc * @return DOMElement */ - protected function toBxml(DOMDocument $doc): DOMElement { + public function toBxml(DOMDocument $doc): DOMElement { $element = $doc->createElement("Endpoint"); $element->setAttribute("id", $this->id); return $element; diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 44c9bc9..f984fcd 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -72,7 +72,7 @@ public function testUploadDownloadMedia() { $mediaId = "text-media-id-" . uniqid() . ".txt"; $content = "Hello world"; $contentType = 'text/plain'; - + //media upload self::$messagingMFAClient->getMessaging()->getClient()->uploadMedia(getenv("BW_ACCOUNT_ID"), $mediaId, $content, $contentType); @@ -130,7 +130,7 @@ public function testCreateCallWithAmdAndGetCallState() { //get phone call information // $response = self::$bandwidthClient->getVoice()->getClient()->getCall(getenv("BW_ACCOUNT_ID"), $callId); - // if (($response->getStatus() == 404) ) { + // if (($response->getStatus() == 404) ) { // $this->assertTrue(is_a($response->getResult()->enqueuedTime, 'DateTime')); // } } @@ -230,7 +230,7 @@ public function testAsyncTnLookup() { $this->assertIsString($statusResponse->getResult()->data->results[0]->countryCodeA3); $this->assertIsArray($statusResponse->getResult()->errors); } - + public function testSyncTnLookup() { $body = new BandwidthLib\PhoneNumberLookup\Models\CreateLookupRequest(); $body->phoneNumbers = [getenv("USER_NUMBER")]; @@ -251,4 +251,201 @@ public function testSyncTnLookup() { $this->assertIsString($response->getResult()->data->results[0]->countryCodeA3); $this->assertIsArray($response->getResult()->errors); } + + public function testCreateListGetDeleteEndpoint() { + $accountId = getenv("BW_ACCOUNT_ID"); + $voiceClient = self::$bandwidthClient->getVoice(); + $endpointsController = $voiceClient->getEndpointsController(); + + // Create endpoint + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + 'php-sdk-test', + ["meta" => "data"] + ); + $createResp = $endpointsController->createEndpoint($createReq); + $this->assertNotNull($createResp->endpointId); + $this->assertEquals('WEBRTC', $createResp->type); + + // List endpoints + $endpoints = $endpointsController->listEndpoints(); + $this->assertIsArray($endpoints); + $found = false; + foreach ($endpoints as $ep) { + if ($ep->id === $createResp->endpointId) { + $found = true; + break; + } + } + $this->assertTrue($found, 'Created endpoint should be in list'); + + // Get endpoint + $endpoint = $endpointsController->getEndpoint($createResp->endpointId); + $this->assertEquals($createResp->endpointId, $endpoint->id); + $this->assertEquals('WEBRTC', $endpoint->type); + + // Update endpoint BXML + // TODO: This endpoint currently is not implemented, commenting out for until it is + // $bxml = 'Test BRTC'; + // $result = $endpointsController->updateEndpointBxml($createResp->endpointId, $bxml); + // $this->assertTrue($result); + + // Delete endpoint + $result = $endpointsController->deleteEndpoint($createResp->endpointId); + $this->assertTrue($result); + } + + public function testCreateEndpointResponseFields() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + getenv("BASE_CALLBACK_URL") . "/brtc/fallback", + 'php-sdk-fields-test' + ); + $createResp = $endpointsController->createEndpoint($createReq); + + $this->assertNotNull($createResp->endpointId); + $this->assertIsString($createResp->endpointId); + $this->assertEquals('WEBRTC', $createResp->type); + $this->assertNotNull($createResp->status); + $this->assertNotNull($createResp->createdTime); + $this->assertNotNull($createResp->updatedTime); + $this->assertEquals('php-sdk-fields-test', $createResp->tag); + + // Cleanup + $endpointsController->deleteEndpoint($createResp->endpointId); + } + + public function testGetEndpointFields() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + 'php-sdk-get-test' + ); + $createResp = $endpointsController->createEndpoint($createReq); + $endpointId = $createResp->endpointId; + + $endpoint = $endpointsController->getEndpoint($endpointId); + $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $endpoint); + $this->assertEquals($endpointId, $endpoint->id); + $this->assertEquals('WEBRTC', $endpoint->type); + $this->assertNotNull($endpoint->status); + $this->assertNotNull($endpoint->direction); + $this->assertNotNull($endpoint->createdTime); + $this->assertNotNull($endpoint->updatedTime); + $this->assertEquals('php-sdk-get-test', $endpoint->tag); + + // Cleanup + $endpointsController->deleteEndpoint($endpointId); + } + + public function testListEndpointsContainsCreated() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + 'php-sdk-list-test' + ); + $createResp = $endpointsController->createEndpoint($createReq); + $endpointId = $createResp->endpointId; + + $endpoints = $endpointsController->listEndpoints(); + $this->assertIsArray($endpoints); + $this->assertNotEmpty($endpoints); + + $ids = array_map(fn($ep) => $ep->id, $endpoints); + $this->assertContains($endpointId, $ids, 'Newly created endpoint should appear in list'); + + // Cleanup + $endpointsController->deleteEndpoint($endpointId); + } + + public function testListEndpointsEachItemIsEndpointInstance() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events" + ); + $createResp = $endpointsController->createEndpoint($createReq); + + $endpoints = $endpointsController->listEndpoints(); + foreach ($endpoints as $ep) { + $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $ep); + $this->assertNotNull($ep->id); + $this->assertNotNull($ep->type); + $this->assertNotNull($ep->status); + } + + // Cleanup + $endpointsController->deleteEndpoint($createResp->endpointId); + } + + public function testCreateMultipleEndpointsAndDeleteAll() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createdIds = []; + for ($i = 0; $i < 3; $i++) { + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + "php-sdk-multi-{$i}" + ); + $createResp = $endpointsController->createEndpoint($createReq); + $this->assertNotNull($createResp->endpointId); + $createdIds[] = $createResp->endpointId; + } + + $this->assertCount(3, $createdIds); + + // Delete all created endpoints + foreach ($createdIds as $id) { + $result = $endpointsController->deleteEndpoint($id); + $this->assertTrue($result); + } + } + + public function testDeleteEndpointRemovedFromList() { + $accountId = getenv("BW_ACCOUNT_ID"); + $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + + $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + 'php-sdk-delete-check' + ); + $createResp = $endpointsController->createEndpoint($createReq); + $endpointId = $createResp->endpointId; + + // Delete it + $endpointsController->deleteEndpoint($endpointId); + + // Should no longer appear in list (or be marked deleted/absent) + $endpoints = $endpointsController->listEndpoints(); + $ids = array_map(fn($ep) => $ep->id, $endpoints); + $this->assertNotContains($endpointId, $ids, 'Deleted endpoint should not appear in list'); + } } diff --git a/tests/EndpointsControllerTest.php b/tests/EndpointsControllerTest.php deleted file mode 100644 index 356b5cc..0000000 --- a/tests/EndpointsControllerTest.php +++ /dev/null @@ -1,118 +0,0 @@ -apiClient = $this->createMock(stdClass::class); - $this->controller = new EndpointsController($this->apiClient, $this->accountId); - } - - public function testCreateEndpoint() - { - $request = new CreateEndpointRequest('WEBRTC', 'INBOUND', 'https://callback', null, 'tag1', ['meta' => 'data']); - $responseData = [ - 'endpointId' => 'ep-123', - 'type' => 'WEBRTC', - 'status' => 'ACTIVE', - 'createdTime' => '2026-03-11T00:00:00Z', - 'updatedTime' => '2026-03-11T00:00:00Z', - 'tag' => 'tag1', - 'devices' => [], - 'token' => 'tok-abc', - ]; - $this->apiClient->expects($this->once()) - ->method('post') - ->with("/accounts/{$this->accountId}/endpoints", $request) - ->willReturn($responseData); - $resp = $this->controller->createEndpoint($request); - $this->assertInstanceOf(CreateEndpointResponse::class, $resp); - $this->assertEquals('ep-123', $resp->endpointId); - $this->assertEquals('WEBRTC', $resp->type); - $this->assertEquals('ACTIVE', $resp->status); - } - - public function testListEndpoints() - { - $responseData = [ - 'endpoints' => [ - [ - 'id' => 'ep-1', - 'type' => 'WEBRTC', - 'status' => 'ACTIVE', - 'direction' => 'INBOUND', - 'eventCallbackUrl' => 'https://cb', - 'eventFallbackUrl' => null, - 'tag' => 'tag', - 'devices' => [], - 'createdTime' => '2026-03-11T00:00:00Z', - 'updatedTime' => '2026-03-11T00:00:00Z', - ] - ] - ]; - $this->apiClient->expects($this->once()) - ->method('get') - ->with("/accounts/{$this->accountId}/endpoints", []) - ->willReturn($responseData); - $resp = $this->controller->listEndpoints(); - $this->assertIsArray($resp); - $this->assertInstanceOf(Endpoint::class, $resp[0]); - $this->assertEquals('ep-1', $resp[0]->id); - } - - public function testGetEndpoint() - { - $endpointId = 'ep-1'; - $responseData = [ - 'id' => $endpointId, - 'type' => 'WEBRTC', - 'status' => 'ACTIVE', - 'direction' => 'INBOUND', - 'eventCallbackUrl' => 'https://cb', - 'eventFallbackUrl' => null, - 'tag' => 'tag', - 'devices' => [], - 'createdTime' => '2026-03-11T00:00:00Z', - 'updatedTime' => '2026-03-11T00:00:00Z', - ]; - $this->apiClient->expects($this->once()) - ->method('get') - ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}") - ->willReturn($responseData); - $resp = $this->controller->getEndpoint($endpointId); - $this->assertInstanceOf(Endpoint::class, $resp); - $this->assertEquals($endpointId, $resp->id); - } - - public function testDeleteEndpoint() - { - $endpointId = 'ep-1'; - $this->apiClient->expects($this->once()) - ->method('delete') - ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}"); - $resp = $this->controller->deleteEndpoint($endpointId); - $this->assertTrue($resp); - } - - public function testUpdateEndpointBxml() - { - $endpointId = 'ep-1'; - $bxml = 'Hello'; - $this->apiClient->expects($this->once()) - ->method('put') - ->with("/accounts/{$this->accountId}/endpoints/{$endpointId}/bxml", ['bxml' => $bxml]); - $resp = $this->controller->updateEndpointBxml($endpointId, $bxml); - $this->assertTrue($resp); - } -} From 69f330573705611f3ca5e5e4bdac80e2a658470c Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 13:12:44 -0400 Subject: [PATCH 3/7] Refactor APIController to integrate BRTC endpoint methods; remove EndpointsController and update tests accordingly --- src/Voice/Controllers/APIController.php | 216 +++++++++++++++++- src/Voice/Controllers/EndpointsController.php | 118 ---------- src/Voice/VoiceClient.php | 15 -- tests/ApiTest.php | 83 +++---- 4 files changed, 244 insertions(+), 188 deletions(-) delete mode 100644 src/Voice/Controllers/EndpointsController.php diff --git a/src/Voice/Controllers/APIController.php b/src/Voice/Controllers/APIController.php index 82a7461..48e6cb2 100644 --- a/src/Voice/Controllers/APIController.php +++ b/src/Voice/Controllers/APIController.php @@ -929,7 +929,7 @@ public function getDownloadCallRecording( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/media'; //process optional query parameters @@ -1040,7 +1040,7 @@ public function deleteRecordingMedia( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/media'; //process optional query parameters @@ -1149,7 +1149,7 @@ public function getCallTranscription( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription'; //process optional query parameters @@ -1266,7 +1266,7 @@ public function createTranscribeCallRecording( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription'; //process optional query parameters @@ -1386,7 +1386,7 @@ public function deleteCallTranscription( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription'; //process optional query parameters @@ -1952,7 +1952,7 @@ public function getConferenceMember( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/conferences/{conferenceId}/members/{memberId}'; //process optional query parameters @@ -2179,7 +2179,7 @@ public function getConferenceRecording( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/conferences/{conferenceId}/recordings/{recordingId}'; //process optional query parameters @@ -2294,7 +2294,7 @@ public function getDownloadConferenceRecording( ) { //prepare query string for API call - $_queryBuilder = + $_queryBuilder = '/api/v2/accounts/{accountId}/conferences/{conferenceId}/recordings/{recordingId}/media'; //process optional query parameters @@ -2514,4 +2514,204 @@ public function getQueryCallRecordings( ); return new ApiResponse($response->code, $response->headers, $deserializedResponse); } + + /** + * Creates a BRTC endpoint. + * + * @param string $accountId + * @param Models\CreateEndpointRequest $body + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function createEndpoint( + string $accountId, + Models\CreateEndpointRequest $body + ) { + $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints'; + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + )); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json', + 'content-type' => 'application/json; charset=utf-8' + ); + $_bodyJson = Request\Body::Json($body); + $this->configureAuth($_headers, 'voice'); + $_httpRequest = new HttpRequest(HttpMethod::POST, $_headers, $_queryUrl); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + Request::timeout($this->config->getTimeout()); + $response = Request::post($_queryUrl, $_headers, $_bodyJson); + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + $this->validateResponse($_httpResponse, $_httpContext); + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClass($response->body, 'BandwidthLib\\Voice\\Models\\CreateEndpointResponse'); + return new ApiResponse($response->code, $response->headers, $deserializedResponse); + } + + /** + * Lists BRTC endpoints for an account. + * + * @param string $accountId + * @param array $queryParams Optional filter/pagination params + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function listEndpoints( + string $accountId, + array $queryParams = [] + ) { + $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints'; + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + )); + if (!empty($queryParams)) { + $_queryBuilder = APIHelper::appendUrlWithQueryParameters($_queryBuilder, $queryParams); + } + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json' + ); + $this->configureAuth($_headers, 'voice'); + $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + Request::timeout($this->config->getTimeout()); + $response = Request::get($_queryUrl, $_headers); + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + $this->validateResponse($_httpResponse, $_httpContext); + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClassArray($response->body, 'BandwidthLib\\Voice\\Models\\Endpoint'); + return new ApiResponse($response->code, $response->headers, $deserializedResponse); + } + + /** + * Gets details for a specific BRTC endpoint. + * + * @param string $accountId + * @param string $endpointId + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function getEndpoint( + string $accountId, + string $endpointId + ) { + $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}'; + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json' + ); + $this->configureAuth($_headers, 'voice'); + $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + Request::timeout($this->config->getTimeout()); + $response = Request::get($_queryUrl, $_headers); + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + $this->validateResponse($_httpResponse, $_httpContext); + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClass($response->body, 'BandwidthLib\\Voice\\Models\\Endpoint'); + return new ApiResponse($response->code, $response->headers, $deserializedResponse); + } + + /** + * Deletes a BRTC endpoint. + * + * @param string $accountId + * @param string $endpointId + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function deleteEndpoint( + string $accountId, + string $endpointId + ) { + $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}'; + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_headers = array( + 'user-agent' => BaseController::USER_AGENT + ); + $this->configureAuth($_headers, 'voice'); + $_httpRequest = new HttpRequest(HttpMethod::DELETE, $_headers, $_queryUrl); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + Request::timeout($this->config->getTimeout()); + $response = Request::delete($_queryUrl, $_headers); + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + $this->validateResponse($_httpResponse, $_httpContext); + return new ApiResponse($response->code, $response->headers, null); + } + + /** + * Updates the BXML for a BRTC endpoint. + * + * @param string $accountId + * @param string $endpointId + * @param string $body Valid BXML string + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function updateEndpointBxml( + string $accountId, + string $endpointId, + string $body + ) { + $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}/bxml'; + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'content-type' => 'application/xml; charset=utf-8' + ); + $this->configureAuth($_headers, 'voice'); + $_httpRequest = new HttpRequest(HttpMethod::PUT, $_headers, $_queryUrl); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + Request::timeout($this->config->getTimeout()); + $response = Request::put($_queryUrl, $_headers, $body); + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + $this->validateResponse($_httpResponse, $_httpContext); + return new ApiResponse($response->code, $response->headers, null); + } } diff --git a/src/Voice/Controllers/EndpointsController.php b/src/Voice/Controllers/EndpointsController.php deleted file mode 100644 index 0bf63e6..0000000 --- a/src/Voice/Controllers/EndpointsController.php +++ /dev/null @@ -1,118 +0,0 @@ -apiClient = $apiClient; - $this->accountId = $accountId; - } - - /** - * Create a new BRTC endpoint - * @param CreateEndpointRequest $request - * @return CreateEndpointResponse - */ - public function createEndpoint(CreateEndpointRequest $request) { - $url = "/accounts/{$this->accountId}/endpoints"; - $response = $this->apiClient->post($url, $request); - return new CreateEndpointResponse( - $response['endpointId'] ?? null, - $response['type'] ?? null, - $response['status'] ?? null, - $response['createdTime'] ?? null, - $response['updatedTime'] ?? null, - $response['tag'] ?? null, - $response['devices'] ?? null, - $response['token'] ?? null - ); - } - - /** - * List endpoints - * @param array $queryParams (optional) - * @return Endpoint[] - */ - public function listEndpoints($queryParams = []) { - $url = "/accounts/{$this->accountId}/endpoints"; - $response = $this->apiClient->get($url, $queryParams); - $endpoints = []; - foreach ($response['endpoints'] ?? [] as $ep) { - $endpoints[] = new Endpoint( - $ep['id'] ?? null, - $ep['type'] ?? null, - $ep['status'] ?? null, - $ep['direction'] ?? null, - $ep['eventCallbackUrl'] ?? null, - $ep['eventFallbackUrl'] ?? null, - $ep['tag'] ?? null, - $ep['devices'] ?? null, - $ep['createdTime'] ?? null, - $ep['updatedTime'] ?? null - ); - } - return $endpoints; - } - - /** - * Get a specific endpoint - * @param string $endpointId - * @return Endpoint - */ - public function getEndpoint($endpointId) { - $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}"; - $ep = $this->apiClient->get($url); - return new Endpoint( - $ep['id'] ?? null, - $ep['type'] ?? null, - $ep['status'] ?? null, - $ep['direction'] ?? null, - $ep['eventCallbackUrl'] ?? null, - $ep['eventFallbackUrl'] ?? null, - $ep['tag'] ?? null, - $ep['devices'] ?? null, - $ep['createdTime'] ?? null, - $ep['updatedTime'] ?? null - ); - } - - /** - * Delete an endpoint - * @param string $endpointId - * @return bool - */ - public function deleteEndpoint($endpointId) { - $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}"; - $this->apiClient->delete($url); - return true; - } - - /** - * Update endpoint BXML - * @param string $endpointId - * @param string $bxml - * @return bool - */ - public function updateEndpointBxml($endpointId, $bxml) { - $url = "/accounts/{$this->accountId}/endpoints/{$endpointId}/bxml"; - $this->apiClient->put($url, ['bxml' => $bxml]); - return true; - } -} diff --git a/src/Voice/VoiceClient.php b/src/Voice/VoiceClient.php index 37756c7..c79a0bd 100644 --- a/src/Voice/VoiceClient.php +++ b/src/Voice/VoiceClient.php @@ -35,19 +35,4 @@ public function getClient() return $this->client; } - private $endpointsController; - - /** - * Provides access to Endpoints controller - * @return Controllers\EndpointsController - */ - public function getEndpointsController() - { - if ($this->endpointsController == null) { - $apiClient = $this->config->getApiClient ? $this->config->getApiClient() : $this->config; - $accountId = $this->config->accountId ?? null; - $this->endpointsController = new Controllers\EndpointsController($apiClient, $accountId); - } - return $this->endpointsController; - } } diff --git a/tests/ApiTest.php b/tests/ApiTest.php index f984fcd..ca98f64 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -254,8 +254,7 @@ public function testSyncTnLookup() { public function testCreateListGetDeleteEndpoint() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice(); - $endpointsController = $voiceClient->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); // Create endpoint $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( @@ -266,41 +265,34 @@ public function testCreateListGetDeleteEndpoint() { 'php-sdk-test', ["meta" => "data"] ); - $createResp = $endpointsController->createEndpoint($createReq); + $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); $this->assertNotNull($createResp->endpointId); $this->assertEquals('WEBRTC', $createResp->type); // List endpoints - $endpoints = $endpointsController->listEndpoints(); + $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); $this->assertIsArray($endpoints); - $found = false; - foreach ($endpoints as $ep) { - if ($ep->id === $createResp->endpointId) { - $found = true; - break; - } - } - $this->assertTrue($found, 'Created endpoint should be in list'); + $ids = array_map(fn($ep) => $ep->id, $endpoints); + $this->assertContains($createResp->endpointId, $ids, 'Created endpoint should be in list'); // Get endpoint - $endpoint = $endpointsController->getEndpoint($createResp->endpointId); + $endpoint = $voiceClient->getEndpoint($accountId, $createResp->endpointId)->getResult(); $this->assertEquals($createResp->endpointId, $endpoint->id); $this->assertEquals('WEBRTC', $endpoint->type); // Update endpoint BXML - // TODO: This endpoint currently is not implemented, commenting out for until it is + // TODO: This endpoint currently is not implemented, commenting out until it is // $bxml = 'Test BRTC'; - // $result = $endpointsController->updateEndpointBxml($createResp->endpointId, $bxml); - // $this->assertTrue($result); + // $voiceClient->updateEndpointBxml($accountId, $createResp->endpointId, $bxml); // Delete endpoint - $result = $endpointsController->deleteEndpoint($createResp->endpointId); - $this->assertTrue($result); + $deleteResp = $voiceClient->deleteEndpoint($accountId, $createResp->endpointId); + $this->assertEquals(204, $deleteResp->getStatusCode()); } public function testCreateEndpointResponseFields() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -309,7 +301,7 @@ public function testCreateEndpointResponseFields() { getenv("BASE_CALLBACK_URL") . "/brtc/fallback", 'php-sdk-fields-test' ); - $createResp = $endpointsController->createEndpoint($createReq); + $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); $this->assertNotNull($createResp->endpointId); $this->assertIsString($createResp->endpointId); @@ -320,12 +312,12 @@ public function testCreateEndpointResponseFields() { $this->assertEquals('php-sdk-fields-test', $createResp->tag); // Cleanup - $endpointsController->deleteEndpoint($createResp->endpointId); + $voiceClient->deleteEndpoint($accountId, $createResp->endpointId); } public function testGetEndpointFields() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -334,10 +326,9 @@ public function testGetEndpointFields() { null, 'php-sdk-get-test' ); - $createResp = $endpointsController->createEndpoint($createReq); - $endpointId = $createResp->endpointId; + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; - $endpoint = $endpointsController->getEndpoint($endpointId); + $endpoint = $voiceClient->getEndpoint($accountId, $endpointId)->getResult(); $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $endpoint); $this->assertEquals($endpointId, $endpoint->id); $this->assertEquals('WEBRTC', $endpoint->type); @@ -348,12 +339,12 @@ public function testGetEndpointFields() { $this->assertEquals('php-sdk-get-test', $endpoint->tag); // Cleanup - $endpointsController->deleteEndpoint($endpointId); + $voiceClient->deleteEndpoint($accountId, $endpointId); } public function testListEndpointsContainsCreated() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -362,10 +353,9 @@ public function testListEndpointsContainsCreated() { null, 'php-sdk-list-test' ); - $createResp = $endpointsController->createEndpoint($createReq); - $endpointId = $createResp->endpointId; + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; - $endpoints = $endpointsController->listEndpoints(); + $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); $this->assertIsArray($endpoints); $this->assertNotEmpty($endpoints); @@ -373,21 +363,21 @@ public function testListEndpointsContainsCreated() { $this->assertContains($endpointId, $ids, 'Newly created endpoint should appear in list'); // Cleanup - $endpointsController->deleteEndpoint($endpointId); + $voiceClient->deleteEndpoint($accountId, $endpointId); } public function testListEndpointsEachItemIsEndpointInstance() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', 'INBOUND', getenv("BASE_CALLBACK_URL") . "/brtc/events" ); - $createResp = $endpointsController->createEndpoint($createReq); + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; - $endpoints = $endpointsController->listEndpoints(); + $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); foreach ($endpoints as $ep) { $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $ep); $this->assertNotNull($ep->id); @@ -396,12 +386,12 @@ public function testListEndpointsEachItemIsEndpointInstance() { } // Cleanup - $endpointsController->deleteEndpoint($createResp->endpointId); + $voiceClient->deleteEndpoint($accountId, $endpointId); } public function testCreateMultipleEndpointsAndDeleteAll() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createdIds = []; for ($i = 0; $i < 3; $i++) { @@ -412,23 +402,23 @@ public function testCreateMultipleEndpointsAndDeleteAll() { null, "php-sdk-multi-{$i}" ); - $createResp = $endpointsController->createEndpoint($createReq); - $this->assertNotNull($createResp->endpointId); - $createdIds[] = $createResp->endpointId; + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + $this->assertNotNull($endpointId); + $createdIds[] = $endpointId; } $this->assertCount(3, $createdIds); // Delete all created endpoints foreach ($createdIds as $id) { - $result = $endpointsController->deleteEndpoint($id); - $this->assertTrue($result); + $deleteResp = $voiceClient->deleteEndpoint($accountId, $id); + $this->assertEquals(204, $deleteResp->getStatusCode()); } } public function testDeleteEndpointRemovedFromList() { $accountId = getenv("BW_ACCOUNT_ID"); - $endpointsController = self::$bandwidthClient->getVoice()->getEndpointsController(); + $voiceClient = self::$bandwidthClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -437,14 +427,13 @@ public function testDeleteEndpointRemovedFromList() { null, 'php-sdk-delete-check' ); - $createResp = $endpointsController->createEndpoint($createReq); - $endpointId = $createResp->endpointId; + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; // Delete it - $endpointsController->deleteEndpoint($endpointId); + $voiceClient->deleteEndpoint($accountId, $endpointId); - // Should no longer appear in list (or be marked deleted/absent) - $endpoints = $endpointsController->listEndpoints(); + // Should no longer appear in list + $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); $ids = array_map(fn($ep) => $ep->id, $endpoints); $this->assertNotContains($endpointId, $ids, 'Deleted endpoint should not appear in list'); } From 3c425ff95d94717ca90c967dda65bfda48395016 Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 13:29:31 -0400 Subject: [PATCH 4/7] Update APIController to use PHONENUMBERLOOKUPDEFAULT server for endpoint URLs --- src/Voice/Controllers/APIController.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Voice/Controllers/APIController.php b/src/Voice/Controllers/APIController.php index 48e6cb2..3af06fd 100644 --- a/src/Voice/Controllers/APIController.php +++ b/src/Voice/Controllers/APIController.php @@ -2527,11 +2527,11 @@ public function createEndpoint( string $accountId, Models\CreateEndpointRequest $body ) { - $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints'; + $_queryBuilder = '/accounts/{accountId}/endpoints'; $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( 'accountId' => $accountId, )); - $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder); $_headers = array( 'user-agent' => BaseController::USER_AGENT, 'Accept' => 'application/json', @@ -2568,14 +2568,14 @@ public function listEndpoints( string $accountId, array $queryParams = [] ) { - $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints'; + $_queryBuilder = '/accounts/{accountId}/endpoints'; $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( 'accountId' => $accountId, )); if (!empty($queryParams)) { $_queryBuilder = APIHelper::appendUrlWithQueryParameters($_queryBuilder, $queryParams); } - $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder); $_headers = array( 'user-agent' => BaseController::USER_AGENT, 'Accept' => 'application/json' @@ -2610,12 +2610,12 @@ public function getEndpoint( string $accountId, string $endpointId ) { - $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}'; + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}'; $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( 'accountId' => $accountId, 'endpointId' => $endpointId, )); - $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder); $_headers = array( 'user-agent' => BaseController::USER_AGENT, 'Accept' => 'application/json' @@ -2650,12 +2650,12 @@ public function deleteEndpoint( string $accountId, string $endpointId ) { - $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}'; + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}'; $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( 'accountId' => $accountId, 'endpointId' => $endpointId, )); - $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder); $_headers = array( 'user-agent' => BaseController::USER_AGENT ); @@ -2689,12 +2689,12 @@ public function updateEndpointBxml( string $endpointId, string $body ) { - $_queryBuilder = '/api/v2/accounts/{accountId}/endpoints/{endpointId}/bxml'; + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}/bxml'; $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( 'accountId' => $accountId, 'endpointId' => $endpointId, )); - $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::VOICEDEFAULT) . $_queryBuilder); + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder); $_headers = array( 'user-agent' => BaseController::USER_AGENT, 'content-type' => 'application/xml; charset=utf-8' From e77d05d316b6f026487b7bbca03e4391dcca1dc2 Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 14:21:14 -0400 Subject: [PATCH 5/7] Add error handling for createEndpoint in ApiTest to improve test reliability --- tests/ApiTest.php | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/ApiTest.php b/tests/ApiTest.php index ca98f64..f3ab1f7 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -265,7 +265,11 @@ public function testCreateListGetDeleteEndpoint() { 'php-sdk-test', ["meta" => "data"] ); - $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); + try { + $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $this->assertNotNull($createResp->endpointId); $this->assertEquals('WEBRTC', $createResp->type); @@ -301,7 +305,11 @@ public function testCreateEndpointResponseFields() { getenv("BASE_CALLBACK_URL") . "/brtc/fallback", 'php-sdk-fields-test' ); - $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); + try { + $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult(); + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $this->assertNotNull($createResp->endpointId); $this->assertIsString($createResp->endpointId); @@ -326,7 +334,11 @@ public function testGetEndpointFields() { null, 'php-sdk-get-test' ); - $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + try { + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $endpoint = $voiceClient->getEndpoint($accountId, $endpointId)->getResult(); $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $endpoint); @@ -353,7 +365,11 @@ public function testListEndpointsContainsCreated() { null, 'php-sdk-list-test' ); - $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + try { + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); $this->assertIsArray($endpoints); @@ -375,7 +391,11 @@ public function testListEndpointsEachItemIsEndpointInstance() { 'INBOUND', getenv("BASE_CALLBACK_URL") . "/brtc/events" ); - $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + try { + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $endpoints = $voiceClient->listEndpoints($accountId)->getResult(); foreach ($endpoints as $ep) { @@ -402,7 +422,11 @@ public function testCreateMultipleEndpointsAndDeleteAll() { null, "php-sdk-multi-{$i}" ); - $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + try { + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } $this->assertNotNull($endpointId); $createdIds[] = $endpointId; } @@ -427,7 +451,11 @@ public function testDeleteEndpointRemovedFromList() { null, 'php-sdk-delete-check' ); - $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + try { + $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId; + } catch (BandwidthLib\APIException $e) { + $this->fail('createEndpoint failed with HTTP ' . $e->getCode() . ': ' . $e->getContext()->getResponse()->getRawBody()); + } // Delete it $voiceClient->deleteEndpoint($accountId, $endpointId); From 8bcf942b894800558325ebeabad7fa7d2cbd8d82 Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Wed, 11 Mar 2026 14:28:05 -0400 Subject: [PATCH 6/7] Refactor ApiTest to use endpointClient for endpoint-related tests --- tests/ApiTest.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/ApiTest.php b/tests/ApiTest.php index f3ab1f7..26a0515 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -14,7 +14,7 @@ final class ApiTest extends TestCase { protected static $bandwidthClient; protected static $messagingMFAClient; - + protected static $endpointClient; public static function setUpBeforeClass(): void { $config = new BandwidthLib\Configuration( array( @@ -37,6 +37,14 @@ public static function setUpBeforeClass(): void { ) ); self::$messagingMFAClient = new BandwidthLib\BandwidthClient($messagingMFAConfig); + + $endpointConfig = new BandwidthLib\Configuration( + array( + 'clientId' => getenv("BW_CLIENT_ID"), + 'clientSecret' => getenv("BW_CLIENT_SECRET"), + ) + ); + self::$endpointClient = new BandwidthLib\BandwidthClient($endpointConfig); } public function testCreateMessage() { @@ -254,7 +262,7 @@ public function testSyncTnLookup() { public function testCreateListGetDeleteEndpoint() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); // Create endpoint $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( @@ -296,7 +304,7 @@ public function testCreateListGetDeleteEndpoint() { public function testCreateEndpointResponseFields() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -325,7 +333,7 @@ public function testCreateEndpointResponseFields() { public function testGetEndpointFields() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -356,7 +364,7 @@ public function testGetEndpointFields() { public function testListEndpointsContainsCreated() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -384,7 +392,7 @@ public function testListEndpointsContainsCreated() { public function testListEndpointsEachItemIsEndpointInstance() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', @@ -411,7 +419,7 @@ public function testListEndpointsEachItemIsEndpointInstance() { public function testCreateMultipleEndpointsAndDeleteAll() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createdIds = []; for ($i = 0; $i < 3; $i++) { @@ -442,7 +450,7 @@ public function testCreateMultipleEndpointsAndDeleteAll() { public function testDeleteEndpointRemovedFromList() { $accountId = getenv("BW_ACCOUNT_ID"); - $voiceClient = self::$bandwidthClient->getVoice()->getClient(); + $voiceClient = self::$endpointClient->getVoice()->getClient(); $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest( 'WEBRTC', From c5a405a4c54e55eaa60245201cd4ca4a40fd5ca2 Mon Sep 17 00:00:00 2001 From: Sudhanshu Moghe Date: Thu, 12 Mar 2026 09:32:22 -0400 Subject: [PATCH 7/7] Trigger build