diff --git a/README.md b/README.md index 2c1940be..6f37ebf3 100644 --- a/README.md +++ b/README.md @@ -413,22 +413,66 @@ foreach($client->calls($filter) as $call){ Application are configuration containers. You can create one using a simple array structure: ```php -$application = $client->applications()->create([ - 'name' => 'My Application', - 'answer_url' => 'https://example.com/answer', - 'event_url' => 'https://example.com/event' -]); +$application = [ + 'name' => 'test application', + 'keys' => [ + 'public_key' => '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCA\nKOxjsU4pf/sMFi9N0jqcSLcjxu33G\nd/vynKnlw9SENi+UZR44GdjGdmfm1\ntL1eA7IBh2HNnkYXnAwYzKJoa4eO3\n0kYWekeIZawIwe/g9faFgkev+1xsO\nOUNhPx2LhuLmgwWSRS4L5W851Xe3f\nUQIDAQAB\n-----END PUBLIC KEY-----\n' + ], + 'capabilities' => [ + 'voice' => [ + 'webhooks' => [ + 'answer_url' => [ + 'address' => 'https://example.com/answer', + 'http_method' => 'GET', + ], + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST', + ], + ] + ], + 'messages' => [ + 'webhooks' => [ + 'inbound_url' => [ + 'address' => 'https://example.com/inbound', + 'http_method' => 'POST' + + ], + 'status_url' => [ + 'address' => 'https://example.com/status', + 'http_method' => 'POST' + ] + ] + ], + 'rtc' => [ + 'webhooks' => [ + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST', + ], + ] + ], + 'vbc' => [] + ] +]; + +$client->applications()->create($application); ``` You can also pass the client an application object: ```php -$application = new Nexmo\Application\Application(); -$application->setName('My Application'); -$application->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'https://example.com/answer'); -$application->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'https://example.com/event'); +$a = new Nexmo\Application\Application; + +$a->setName('PHP Client Example'); +$a->getVoiceConfig()->setWebhook('answer_url', 'https://example.com/answer', 'GET'); +$a->getVoiceConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->getMessagesConfig()->setWebhook('status_url', 'https://example.com/status', 'POST'); +$a->getMessagesConfig()->setWebhook('inbound_url', 'https://example.com/inbound', 'POST'); +$a->getRtcConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->disableVbc(); -$client->appliations()->create($application); +$client->applications()->create($a); ``` ### Fetching Applications @@ -466,8 +510,6 @@ You can also pass an array and the application UUID to the client: ```php $application = $client->applications()->update([ 'name' => 'Updated Application', - 'answer_url' => 'https://example.com/v2/answer', - 'event_url' => 'https://example.com/v2/event' ], '1a20a124-1775-412b-b623-e6985f4aace0'); ``` diff --git a/examples/create-application.php b/examples/create-application.php new file mode 100644 index 00000000..1d01174a --- /dev/null +++ b/examples/create-application.php @@ -0,0 +1,19 @@ +setName('PHP Client Example'); +$a->getVoiceConfig()->setWebhook('answer_url', 'https://example.com/answer', 'GET'); +$a->getVoiceConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->getMessagesConfig()->setWebhook('status_url', 'https://example.com/status', 'POST'); +$a->getMessagesConfig()->setWebhook('inbound_url', 'https://example.com/inbound', 'POST'); +$a->getRtcConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->getVbcConfig()->enable(); + +$r = $client->applications()->create($a); + +print_r($r); diff --git a/examples/delete-application.php b/examples/delete-application.php new file mode 100644 index 00000000..240344c6 --- /dev/null +++ b/examples/delete-application.php @@ -0,0 +1,10 @@ +applications()->get(APPLICATION_ID); + +$client->applications()->delete($a); + diff --git a/examples/get-application.php b/examples/get-application.php new file mode 100644 index 00000000..b8d2c3b5 --- /dev/null +++ b/examples/get-application.php @@ -0,0 +1,25 @@ +applications()->get(APPLICATION_ID); +echo $a->getName().PHP_EOL; + +echo "\nPUBLIC KEY\n-----\n"; +echo $a->getPublicKey(); + +echo "\nVOICE\n-----\n"; +echo $a->getVoiceConfig()->getWebhook('answer_url').PHP_EOL; +echo $a->getVoiceConfig()->getWebhook('event_url').PHP_EOL; + +echo "\nMessages\n-----\n"; +echo $a->getMessagesConfig()->getWebhook('inbound_url').PHP_EOL; +echo $a->getMessagesConfig()->getWebhook('status_url').PHP_EOL; + +echo "\nRTC\n-----\n"; +echo $a->getRtcConfig()->getWebhook('event_url').PHP_EOL; + +echo "\nVBC\n-----\n"; +echo $a->getVbcConfig()->isEnabled() ? 'Enabled' : 'Disabled'; +echo "\n\n"; diff --git a/examples/list-applications.php b/examples/list-applications.php new file mode 100644 index 00000000..add179d9 --- /dev/null +++ b/examples/list-applications.php @@ -0,0 +1,8 @@ +applications() as $application){ + echo $application->getName() .' - '.$application->getId() . PHP_EOL; +} diff --git a/examples/make-call-ncco.php b/examples/make-call-ncco.php new file mode 100644 index 00000000..c4567ef3 --- /dev/null +++ b/examples/make-call-ncco.php @@ -0,0 +1,24 @@ +setTo('447908249481') + ->setFrom('123456') + ->setNcco([ + [ + 'action' => 'talk', + 'text' => 'This is a text to speech call from Nexmo' + ] + ]); + +$response = $client->calls()->create($call); + +echo $response->getId(); diff --git a/examples/update-application.php b/examples/update-application.php new file mode 100644 index 00000000..e9db5d44 --- /dev/null +++ b/examples/update-application.php @@ -0,0 +1,16 @@ +applications()->get('a8f93ca3-2a6f-4722-a53e-b7cccabc0bb9'); + +$a->getVoiceConfig()->setWebhook('answer_url', 'https://example.com/answer', 'GET'); +$a->getVoiceConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->getMessagesConfig()->setWebhook('status_url', 'https://example.com/status', 'POST'); +$a->getMessagesConfig()->setWebhook('inbound_url', 'https://example.com/inbound', 'POST'); +$a->getRtcConfig()->setWebhook('event_url', 'https://example.com/event', 'POST'); +$a->getVbcConfig()->disable(); + +$client->applications()->update($a); diff --git a/src/Application/Application.php b/src/Application/Application.php index 013c488c..798b4535 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -22,6 +22,9 @@ class Application implements EntityInterface, \JsonSerializable, JsonUnserializa use JsonResponseTrait; protected $voiceConfig; + protected $messagesConfig; + protected $rtcConfig; + protected $vbcConfig; protected $name; @@ -45,6 +48,24 @@ public function setVoiceConfig(VoiceConfig $config) return $this; } + public function setMessagesConfig(MessagesConfig $config) + { + $this->messagesConfig = $config; + return $this; + } + + public function setRtcConfig(RtcConfig $config) + { + $this->rtcConfig = $config; + return $this; + } + + public function setVbcConfig(VbcConfig $config) + { + $this->vbcConfig = $config; + return $this; + } + /** * @return VoiceConfig */ @@ -63,6 +84,60 @@ public function getVoiceConfig() return $this->voiceConfig; } + /** + * @return MessagesConfig + */ + public function getMessagesConfig() + { + if(!isset($this->messagesConfig)){ + $this->setMessagesConfig(new MessagesConfig()); + $data = $this->getResponseData(); + if(isset($data['messages']) AND isset($data['messages']['webhooks'])){ + foreach($data['messages']['webhooks'] as $webhook){ + $this->getMessagesConfig()->setWebhook($webhook['endpoint_type'], $webhook['endpoint'], $webhook['http_method']); + } + } + } + + return $this->messagesConfig; + } + + /** + * @return RtcConfig + */ + public function getRtcConfig() + { + if(!isset($this->rtcConfig)){ + $this->setRtcConfig(new RtcConfig()); + $data = $this->getResponseData(); + if(isset($data['rtc']) AND isset($data['rtc']['webhooks'])){ + foreach($data['rtc']['webhooks'] as $webhook){ + $this->getRtcConfig()->setWebhook($webhook['endpoint_type'], $webhook['endpoint'], $webhook['http_method']); + } + } + } + + return $this->rtcConfig; + } + + /** + * @return RtcConfig + */ + public function getVbcConfig() + { + if(!isset($this->vbcConfig)){ + $this->setVbcConfig(new VbcConfig()); + } + + return $this->vbcConfig; + } + + public function setPublicKey($key) + { + $this->keys['public_key'] = $key; + return $this; + } + public function getPublicKey() { if(isset($this->keys['public_key'])){ @@ -94,23 +169,83 @@ public function jsonUnserialize(array $json) $this->id = $json['id']; $this->keys = $json['keys']; - //todo: make voice hydrate-able - $this->voiceConfig = new VoiceConfig(); - if(isset($json['voice']) AND isset($json['voice']['webhooks'])){ - foreach($json['voice']['webhooks'] as $webhook){ - $this->voiceConfig->setWebhook($webhook['endpoint_type'], new Webhook($webhook['endpoint'], $webhook['http_method'])); + if (isset($json['capabilities'])) { + $capabilities = $json['capabilities']; + + //todo: make voice hydrate-able + $this->voiceConfig = new VoiceConfig(); + if (isset($capabilities['voice']) AND isset($capabilities['voice']['webhooks'])) { + foreach ($capabilities['voice']['webhooks'] as $name => $details) { + $this->voiceConfig->setWebhook($name, new Webhook($details['address'], $details['http_method'])); + } + } + + //todo: make messages hydrate-able + $this->messagesConfig = new MessagesConfig(); + if (isset($capabilities['messages']) AND isset($capabilities['messages']['webhooks'])) { + foreach ($capabilities['messages']['webhooks'] as $name => $details) { + $this->messagesConfig->setWebhook($name, new Webhook($details['address'], $details['http_method'])); + } + } + + //todo: make rtc hydrate-able + $this->rtcConfig = new RtcConfig(); + if (isset($capabilities['rtc']) AND isset($capabilities['rtc']['webhooks'])) { + foreach ($capabilities['rtc']['webhooks'] as $name => $details) { + $this->rtcConfig->setWebhook($name, new Webhook($details['address'], $details['http_method'])); + } + } + + if (isset($capabilities['vbc'])) { + $this->getVbcConfig()->enable(); } } } public function jsonSerialize() { + + // Build up capabilities that are set + $availableCapabilities = [ + 'voice' => [VoiceConfig::ANSWER, VoiceConfig::EVENT], + 'messages' => [MessagesConfig::INBOUND, MessagesConfig::STATUS], + 'rtc' => [RtcConfig::EVENT] + ]; + + $capabilities = []; + foreach ($availableCapabilities as $type => $values) { + $configAccessorMethod = 'get'.ucfirst($type).'Config'; + foreach ($values as $constant) { + $webhook = $this->$configAccessorMethod()->getWebhook($constant); + if ($webhook) { + if (!isset($capabilities[$type])) { + $capabilities[$type]['webhooks'] = []; + } + $capabilities[$type]['webhooks'][$constant] = [ + 'address' => $webhook->getUrl(), + 'http_method' => $webhook->getMethod(), + ]; + } + } + } + + // Handle VBC specifically + if ($this->getVbcConfig()->isEnabled()) { + $capabilities['vbc'] = new \StdClass; + } + + // Workaround API bug. It expects an object and throws 500 + // if it gets an array + if (!count($capabilities)) { + $capabilities = (object) $capabilities; + } + return [ 'name' => $this->getName(), - //currently, the request data does not match the response data - 'event_url' => (string) $this->getVoiceConfig()->getWebhook(VoiceConfig::EVENT), - 'answer_url' => (string) $this->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER), - 'type' => 'voice' //currently the only type + 'keys' => [ + 'public_key' => $this->getPublicKey() + ], + 'capabilities' => $capabilities ]; } diff --git a/src/Application/Client.php b/src/Application/Client.php index 6a07573e..6a1b7728 100644 --- a/src/Application/Client.php +++ b/src/Application/Client.php @@ -8,10 +8,11 @@ namespace Nexmo\Application; +use Nexmo\ApiErrorHandler; use Nexmo\Client\ClientAwareInterface; use Nexmo\Client\ClientAwareTrait; use Nexmo\Entity\CollectionInterface; -use Nexmo\Entity\CollectionTrait; +use Nexmo\Entity\ModernCollectionTrait; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Request; use Nexmo\Client\Exception; @@ -19,7 +20,7 @@ class Client implements ClientAwareInterface, CollectionInterface { use ClientAwareTrait; - use CollectionTrait; + use ModernCollectionTrait; public static function getCollectionName() { @@ -28,7 +29,7 @@ public static function getCollectionName() public static function getCollectionPath() { - return '/v1/' . self::getCollectionName(); + return '/v2/' . self::getCollectionName(); } public function hydrateEntity($data, $id) @@ -46,7 +47,9 @@ public function get($application) $request = new Request( $this->getClient()->getApiUrl() . $this->getCollectionPath() . '/' . $application->getId() - ,'GET' + ,'GET', + 'php://memory', + ['Content-Type' => 'application/json'] ); $application->setRequest($request); @@ -77,7 +80,7 @@ public function post($application) $this->getClient()->getApiUrl() . $this->getCollectionPath() ,'POST', 'php://temp', - ['content-type' => 'application/json'] + ['Content-Type' => 'application/json'] ); $request->getBody()->write(json_encode($body)); @@ -113,7 +116,7 @@ public function put($application, $id = null) $this->getClient()->getApiUrl() . $this->getCollectionPath() . '/' . $id, 'PUT', 'php://temp', - ['content-type' => 'application/json'] + ['Content-Type' => 'application/json'] ); $request->getBody()->write(json_encode($body)); @@ -138,7 +141,9 @@ public function delete($application) $request = new Request( $this->getClient()->getApiUrl(). $this->getCollectionPath() . '/' . $id - ,'DELETE' + ,'DELETE', + 'php://temp', + ['Content-Type' => 'application/json'] ); if($application instanceof Application){ @@ -163,18 +168,15 @@ protected function getException(ResponseInterface $response, $application = null $body = json_decode($response->getBody()->getContents(), true); $status = $response->getStatusCode(); - if($status >= 400 AND $status < 500) { - $e = new Exception\Request($body['error_title'], $status); - } elseif($status >= 500 AND $status < 600) { - $e = new Exception\Server($body['error_title'], $status); - } else { - $e = new Exception\Exception('Unexpected HTTP Status Code'); - throw $e; - } - - //todo use interfaces here - if(($application instanceof Application) AND (($e instanceof Exception\Request) OR ($e instanceof Exception\Server))){ - $e->setEntity($application); + // Handle new style errors + $e = null; + try { + ApiErrorHandler::check($body, $status); + } catch (Exception\Exception $e) { + //todo use interfaces here + if(($application instanceof Application) AND (($e instanceof Exception\Request) OR ($e instanceof Exception\Server))){ + $e->setEntity($application); + } } return $e; @@ -182,6 +184,14 @@ protected function getException(ResponseInterface $response, $application = null protected function createFromArray($array) { + if (isset($array['answer_url']) || isset($array['event_url'])) { + return $this->createFromArrayV1($array); + } + + return $this->createFromArrayV2($array); + } + + protected function createFromArrayV1($array) { if(!is_array($array)){ throw new \RuntimeException('application must implement `' . ApplicationInterface::class . '` or be an array`'); } @@ -195,6 +205,12 @@ protected function createFromArray($array) $application = new Application(); $application->setName($array['name']); + // Public key? + if (isset($array['public_key'])) { + $application->setPublicKey($array['public_key']); + } + + // Voice foreach(['event', 'answer'] as $type){ if(isset($array[$type . '_url'])){ $method = isset($array[$type . '_method']) ? $array[$type . '_method'] : null; @@ -202,6 +218,94 @@ protected function createFromArray($array) } } + // Messages + foreach(['status', 'inbound'] as $type){ + if(isset($array[$type . '_url'])){ + $method = isset($array[$type . '_method']) ? $array[$type . '_method'] : null; + $application->getMessagesConfig()->setWebhook($type . '_url', new Webhook($array[$type . '_url'], $method)); + } + } + + // RTC + foreach(['event'] as $type){ + if(isset($array[$type . '_url'])){ + $method = isset($array[$type . '_method']) ? $array[$type . '_method'] : null; + $application->getRtcConfig()->setWebhook($type . '_url', new Webhook($array[$type . '_url'], $method)); + } + } + + // VBC + if (isset($array['vbc']) && $array['vbc']) { + $application->getVbcConfig()->enable(); + } + + return $application; + } + + protected function createFromArrayV2($array) { + if(!is_array($array)){ + throw new \RuntimeException('application must implement `' . ApplicationInterface::class . '` or be an array`'); + } + + foreach(['name',] as $param){ + if(!isset($array[$param])){ + throw new \InvalidArgumentException('missing expected key `' . $param . '`'); + } + } + + $application = new Application(); + $application->setName($array['name']); + + // Is there a public key? + if (isset($array['keys']['public_key'])) { + $application->setPublicKey($array['keys']['public_key']); + } + + // How about capabilities? + if (!isset($array['capabilities'])) { + return $application; + } + + $capabilities = $array['capabilities']; + + // Handle voice + if (isset($capabilities['voice'])) { + $voiceCapabilities = $capabilities['voice']['webhooks']; + + foreach(['answer', 'event'] as $type) + $application->getVoiceConfig()->setWebhook($type.'_url', new Webhook( + $voiceCapabilities[$type.'_url']['address'], + $voiceCapabilities[$type.'_url']['http_method'] + )); + } + + // Handle messages + if (isset($capabilities['messages'])) { + $messagesCapabilities = $capabilities['messages']['webhooks']; + + foreach(['status', 'inbound'] as $type) + $application->getMessagesConfig()->setWebhook($type.'_url', new Webhook( + $messagesCapabilities[$type.'_url']['address'], + $messagesCapabilities[$type.'_url']['http_method'] + )); + } + + // Handle RTC + if (isset($capabilities['rtc'])) { + $rtcCapabilities = $capabilities['rtc']['webhooks']; + + foreach(['event'] as $type) + $application->getRtcConfig()->setWebhook($type.'_url', new Webhook( + $rtcCapabilities[$type.'_url']['address'], + $rtcCapabilities[$type.'_url']['http_method'] + )); + } + + // Handle VBC + if (isset($capabilities['vbc'])) { + $application->getVbcConfig()->enable(); + } + return $application; } } diff --git a/src/Application/MessagesConfig.php b/src/Application/MessagesConfig.php new file mode 100644 index 00000000..af24043e --- /dev/null +++ b/src/Application/MessagesConfig.php @@ -0,0 +1,34 @@ +webhooks[$type] = $url; + return $this; + } + + public function getWebhook($type) + { + if(isset($this->webhooks[$type])){ + return $this->webhooks[$type]; + } + } +} diff --git a/src/Application/RtcConfig.php b/src/Application/RtcConfig.php new file mode 100644 index 00000000..a5dec603 --- /dev/null +++ b/src/Application/RtcConfig.php @@ -0,0 +1,33 @@ +webhooks[$type] = $url; + return $this; + } + + public function getWebhook($type) + { + if(isset($this->webhooks[$type])){ + return $this->webhooks[$type]; + } + } +} diff --git a/src/Application/VbcConfig.php b/src/Application/VbcConfig.php new file mode 100644 index 00000000..1c73643b --- /dev/null +++ b/src/Application/VbcConfig.php @@ -0,0 +1,26 @@ +enabled = true; + } + + public function disable() { + $this->enabled = false; + } + + public function isEnabled() { + return $this->enabled; + } +} diff --git a/src/Client.php b/src/Client.php index 899f6c86..0e10944f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -225,6 +225,7 @@ public static function authRequest(RequestInterface $request, Basic $credentials $body->rewind(); $content = $body->getContents(); $params = json_decode($content, true); + if (!$params) { $params = []; } $params = array_merge($params, $credentials->asArray()); $body->rewind(); $body->write(json_encode($params)); @@ -485,8 +486,9 @@ protected static function requiresBasicAuth(\Psr\Http\Message\RequestInterface $ { $path = $request->getUri()->getPath(); $isSecretManagementEndpoint = strpos($path, '/accounts') === 0 && strpos($path, '/secrets') !== false; + $isApplicationV2 = strpos($path, '/v2/applications') === 0; - return $isSecretManagementEndpoint; + return $isSecretManagementEndpoint || $isApplicationV2; } protected static function requiresAuthInUrlNotBody(\Psr\Http\Message\RequestInterface $request) diff --git a/src/Entity/ModernCollectionTrait.php b/src/Entity/ModernCollectionTrait.php new file mode 100644 index 00000000..6bb1a1c5 --- /dev/null +++ b/src/Entity/ModernCollectionTrait.php @@ -0,0 +1,252 @@ +hydrateEntity($this->page['_embedded'][$this->getCollectionName()][$this->current], $this->key()); + } + + /** + * No checks here, just advance the index. + */ + public function next() + { + $this->current++; + } + + /** + * Return the ID of the resource, in some cases this is `id`, in others `uuid`. + * @return string + */ + public function key() + { + if(isset($this->page['_embedded'][$this->getCollectionName()][$this->current]['id'])){ + return $this->page['_embedded'][$this->getCollectionName()][$this->current]['id']; + } elseif(isset($this->page['_embedded'][$this->getCollectionName()][$this->current]['uuid'])) { + return $this->page['_embedded'][$this->getCollectionName()][$this->current]['uuid']; + } + + return $this->current; + } + + /** + * Handle pagination automatically (unless configured not to). + * @return bool + */ + public function valid() + { + //can't be valid if there's not a page (rewind sets this) + if(!isset($this->page)){ + return false; + } + + //all hal collections have an `_embedded` object, we expect there to be a property matching the collection name + if(!isset($this->page['_embedded']) OR !isset($this->page['_embedded'][$this->getCollectionName()])){ + return false; + } + + //if we have a page with no items, we've gone beyond the end of the collection + if(!count($this->page['_embedded'][$this->getCollectionName()])){ + return false; + } + + //index the start of a page at 0 + if(is_null($this->current)){ + $this->current = 0; + } + + //if our current index is past the current page, fetch the next page if possible and reset the index + if(!isset($this->page['_embedded'][$this->getCollectionName()][$this->current])){ + if(isset($this->page['_links']) AND isset($this->page['_links']['next'])){ + $this->fetchPage($this->page['_links']['next']['href']); + $this->current = 0; + + return true; + } + + return false; + } + + return true; + } + + /** + * Fetch the initial page + */ + public function rewind() + { + $this->fetchPage($this->getCollectionPath()); + } + + /** + * Count of total items + * @return integer + */ + public function count() + { + if(isset($this->page)){ + return (int) $this->page['total_items']; + } + } + + public function setPage($index) + { + $this->index = (int) $index; + return $this; + } + + public function getPage() + { + if(isset($this->page)){ + return $this->page['page']; + } + + if(isset($this->index)){ + return $this->index; + } + + throw new \RuntimeException('page not set'); + } + + public function getSize() + { + if(isset($this->page)){ + return $this->page['page_size']; + } + + if(isset($this->size)){ + return $this->size; + } + + throw new \RuntimeException('size not set'); + } + + public function setSize($size) + { + $this->size = (int) $size; + return $this; + } + + /** + * Filters reduce to query params and include paging settings. + * + * @param FilterInterface $filter + * @return $this + */ + public function setFilter(FilterInterface $filter) + { + $this->filter = $filter; + return $this; + } + + public function getFilter() + { + if(!isset($this->filter)){ + $this->setFilter(new EmptyFilter()); + } + + return $this->filter; + } + + /** + * Fetch a page using the current filter if no query is provided. + * + * @param $absoluteUri + */ + protected function fetchPage($absoluteUri) + { + //use filter if no query provided + if(false === strpos($absoluteUri, '?')){ + $query = []; + + if(isset($this->size)){ + $query['page_size'] = $this->size; + } + + if(isset($this->index)){ + $query['page_index'] = $this->index; + } + + if(isset($this->filter)){ + $query = array_merge($this->filter->getQuery(), $query); + } + + $absoluteUri .= '?' . http_build_query($query); + } + + // + $request = new Request( + $this->getClient()->getApiUrl() . $absoluteUri, + 'GET', + 'php://memory', + ['Content-Type' => 'application/json'] + ); + + $response = $this->client->send($request); + + if($response->getStatusCode() != '200'){ + throw $this->getException($response); + } + + $this->response = $response; + $this->page = json_decode($this->response->getBody()->getContents(), true); + } +} diff --git a/test/Application/ApplicationTest.php b/test/Application/ApplicationTest.php index 34be5298..f161d879 100644 --- a/test/Application/ApplicationTest.php +++ b/test/Application/ApplicationTest.php @@ -9,6 +9,8 @@ namespace NexmoTest\Application; use Nexmo\Application\Application; +use Nexmo\Application\MessagesConfig; +use Nexmo\Application\RtcConfig; use Nexmo\Application\VoiceConfig; use Zend\Diactoros\Response; use PHPUnit\Framework\TestCase; @@ -37,57 +39,85 @@ public function testNameIsSet() $this->assertEquals('test', $this->app->getRequestData()['name']); } - public function testAllAppsAreVoice() - { - $this->assertEquals('voice', $this->app->getRequestData()['type']); - } - public function testVoiceWebhookParams() { $this->app->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'http://example.com/event'); $this->app->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'http://example.com/answer'); $params = $this->app->getRequestData(); - $this->assertArrayHasKey('event_url', $params); - $this->assertArrayHasKey('answer_url', $params); - $this->assertEquals('http://example.com/event', $params['event_url']); - $this->assertEquals('http://example.com/answer', $params['answer_url']); + $capabilities = $params['capabilities']; + $this->assertArrayHasKey('event_url', $capabilities['voice']['webhooks']); + $this->assertArrayHasKey('answer_url', $capabilities['voice']['webhooks']); + + $this->assertEquals('http://example.com/event', $capabilities['voice']['webhooks']['event_url']['address']); + $this->assertEquals('http://example.com/answer', $capabilities['voice']['webhooks']['answer_url']['address']); } public function testResponseSetsProperties() { $this->app->setResponse($this->getResponse()); - $this->assertEquals('client_test', $this->app->getName()); + $this->assertEquals('My Application', $this->app->getName()); $this->assertEquals('public_key', $this->app->getPublicKey()); $this->assertEquals('private_key', $this->app->getPrivateKey()); } - public function testResponseSetsConfigs() + public function testResponseSetsVoiceConfigs() { $this->app->setResponse($this->getResponse()); $webhook = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER); $method = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER)->getMethod(); - $this->assertEquals('http://test.runscope.net/answer', $webhook); + $this->assertEquals('https://example.com/webhooks/answer', $webhook); $this->assertEquals('GET', $method); $webhook = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::EVENT); $method = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::EVENT)->getMethod(); - $this->assertEquals('http://test.runscope.net/event', $webhook); + $this->assertEquals('https://example.com/webhooks/event', $webhook); + $this->assertEquals('POST', $method); + } + + public function testResponseSetsMessagesConfigs() + { + $this->app->setResponse($this->getResponse()); + + $webhook = $this->app->getMessagesConfig()->getWebhook(MessagesConfig::INBOUND); + $method = $this->app->getMessagesConfig()->getWebhook(MessagesConfig::INBOUND)->getMethod(); + $this->assertEquals('https://example.com/webhooks/inbound', $webhook); + $this->assertEquals('POST', $method); + + $webhook = $this->app->getMessagesConfig()->getWebhook(MessagesConfig::STATUS); + $method = $this->app->getMessagesConfig()->getWebhook(MessagesConfig::STATUS)->getMethod(); + $this->assertEquals('https://example.com/webhooks/status', $webhook); $this->assertEquals('POST', $method); } + public function testResponseSetsRtcConfigs() + { + $this->app->setResponse($this->getResponse()); + + $webhook = $this->app->getRtcConfig()->getWebhook(RtcConfig::EVENT); + $method = $this->app->getRtcConfig()->getWebhook(RtcConfig::EVENT)->getMethod(); + $this->assertEquals('https://example.com/webhooks/event', $webhook); + $this->assertEquals('POST', $method); + } + + public function testResponseSetsVbcConfigs() + { + $this->app->setResponse($this->getResponse()); + $this->assertEquals(true, $this->app->getVbcConfig()->isEnabled()); + } + public function testCanGetDirtyValues() { $this->app->setResponse($this->getResponse()); - $this->assertEquals('client_test', $this->app->getName()); + $this->assertEquals('My Application', $this->app->getName()); $this->app->setName('new'); $this->assertEquals('new', $this->app->getName()); $webhook = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER); - $this->assertEquals('http://test.runscope.net/answer', (string) $webhook); + $this->assertEquals('https://example.com/webhooks/answer', $webhook); $this->app->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'http://example.com'); $webhook = $this->app->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER); @@ -104,7 +134,7 @@ public function testConfigCanBeCopied() $otherapp->setVoiceConfig($this->app->getVoiceConfig()); $webhook = $otherapp->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER); - $this->assertEquals('http://test.runscope.net/answer', $webhook); + $this->assertEquals('https://example.com/webhooks/answer', $webhook); } /** diff --git a/test/Application/ClientTest.php b/test/Application/ClientTest.php index 77226e43..b2bda72f 100644 --- a/test/Application/ClientTest.php +++ b/test/Application/ClientTest.php @@ -11,6 +11,8 @@ use Nexmo\Application\Application; use Nexmo\Application\Client; use Nexmo\Application\Filter; +use Nexmo\Application\MessagesConfig; +use Nexmo\Application\RtcConfig; use Nexmo\Application\VoiceConfig; use Nexmo\Client\Exception\Exception; use NexmoTest\Psr7AssertionTrait; @@ -56,7 +58,7 @@ public function testSetFilter() $filter = new Filter(new \DateTime('yesterday'), new \DateTime('tomorrow')); $this->nexmoClient->send(Argument::that(function(RequestInterface $request) use ($filter){ - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); foreach($filter->getQuery() as $key => $value){ @@ -76,7 +78,7 @@ public function testSetFilter() public function testSetPage() { $this->nexmoClient->send(Argument::that(function(RequestInterface $request) { - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); $this->assertRequestQueryContains('page_index', '1', $request); @@ -92,7 +94,7 @@ public function testSetPage() public function testSetSize() { $this->nexmoClient->send(Argument::that(function(RequestInterface $request) { - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); $this->assertRequestQueryContains('page_size', '5', $request); @@ -134,7 +136,7 @@ public function testIteratePages() $last = $request; } - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); @@ -156,7 +158,7 @@ public function testIteratePages() public function testCanIterateClient() { $this->nexmoClient->send(Argument::that(function(RequestInterface $request){ - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); return true; @@ -181,7 +183,7 @@ public function testCanIterateClient() public function testGetApplication($payload, $id) { $this->nexmoClient->send(Argument::that(function(RequestInterface $request) use ($id){ - $this->assertEquals('/v1/applications/' . $id, $request->getUri()->getPath()); + $this->assertEquals('/v2/applications/' . $id, $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('GET', $request->getMethod()); return true; @@ -209,14 +211,37 @@ public function getApplication() public function testUpdateApplication($payload, $method, $id, $expectedId) { $this->nexmoClient->send(Argument::that(function(RequestInterface $request) use ($expectedId){ - $this->assertEquals('/v1/applications/' . $expectedId, $request->getUri()->getPath()); + $this->assertEquals('/v2/applications/' . $expectedId, $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('PUT', $request->getMethod()); $this->assertRequestJsonBodyContains('name', 'updated application', $request); - $this->assertRequestJsonBodyContains('type', 'voice', $request); - $this->assertRequestJsonBodyContains('answer_url', 'https://example.com/new_answer', $request); - $this->assertRequestJsonBodyContains('event_url' , 'https://example.com/new_event' , $request); + + // And check all other capabilities + $capabilities = [ + 'voice' => [ + 'webhooks' => [ + 'answer_url' => [ + 'address' => 'https://example.com/new_answer', + 'http_method' => null + + ], + 'event_url' => [ + 'address' => 'https://example.com/new_event', + 'http_method' => null + ] + ] + ], + 'rtc' => [ + 'webhooks' => [ + 'event_url' => [ + 'address' => 'https://example.com/new_event', + 'http_method' => null + ] + ] + ], + ]; + $this->assertRequestJsonBodyContains('capabilities', $capabilities, $request); return true; }))->willReturn($this->getResponse()); @@ -241,11 +266,13 @@ public function updateApplication() $existing->setName('updated application'); $existing->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'https://example.com/new_answer'); $existing->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'https://example.com/new_event'); + $existing->getRtcConfig()->setWebhook(RtcConfig::EVENT, 'https://example.com/new_event'); $new = new Application(); $new->setName('updated application'); $new->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'https://example.com/new_answer'); $new->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'https://example.com/new_event'); + $new->getRtcConfig()->setWebhook(RtcConfig::EVENT, 'https://example.com/new_event'); $raw = [ 'name' => 'updated application', @@ -276,7 +303,7 @@ public function updateApplication() public function testDeleteApplication($payload, $id) { $this->nexmoClient->send(Argument::that(function(Request $request) use($id){ - $this->assertEquals('/v1/applications/' . $id, $request->getUri()->getPath()); + $this->assertEquals('/v2/applications/' . $id, $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('DELETE', $request->getMethod()); return true; @@ -310,16 +337,24 @@ public function testThrowsException($method, $response, $code) $data = json_decode($response->getBody()->getContents(), true); $class = substr($code, 0, 1); + if (!isset($data['title'])) { + print_r($data);die; + } + $msg = $data['title']; + if ($data['detail']) { + $msg .= ': '.$data['detail'].'. See '.$data['type'].' for more information'; + } + switch($class){ case '4': $this->assertInstanceOf('Nexmo\Client\Exception\Request', $e); - $this->assertEquals($data['error_title'], $e->getMessage()); + $this->assertEquals($msg, $e->getMessage()); $this->assertEquals($code, $e->getCode()); $this->assertSame($application, $e->getEntity()); break; case '5': $this->assertInstanceOf('Nexmo\Client\Exception\Server', $e); - $this->assertEquals($data['error_title'], $e->getMessage()); + $this->assertEquals($msg, $e->getMessage()); $this->assertEquals($code, $e->getCode()); $this->assertSame($application, $e->getEntity()); break; @@ -336,13 +371,10 @@ public function exceptions() //todo: add server error return [ //post / create are aliases - ['post', 'success', '200'], //should be 201 ['post', 'bad', '400'], ['post', 'unauthorized', '401'], - ['create', 'success', '200'], //should be 201 ['create', 'bad', '400'], ['create', 'unauthorized', '401'], - ['delete', 'success', '200'], //should be 204 ['delete', 'bad', '400'], ['delete', 'unauthorized', '401'], ]; @@ -354,14 +386,61 @@ public function exceptions() public function testCreateApplication($payload, $method) { $this->nexmoClient->send(Argument::that(function(Request $request){ - $this->assertEquals('/v1/applications', $request->getUri()->getPath()); + $this->assertEquals('/v2/applications', $request->getUri()->getPath()); $this->assertEquals('api.nexmo.com', $request->getUri()->getHost()); $this->assertEquals('POST', $request->getMethod()); $this->assertRequestJsonBodyContains('name', 'test application', $request); - $this->assertRequestJsonBodyContains('type', 'voice', $request); - $this->assertRequestJsonBodyContains('answer_url', 'https://example.com/answer', $request); - $this->assertRequestJsonBodyContains('event_url' , 'https://example.com/event' , $request); + + // Check for VBC as an object explicitly + $request->getBody()->rewind(); + $this->assertContains('"vbc":{}', $request->getBody()->getContents()); + + // And check all other capabilities + $capabilities = [ + 'voice' => [ + 'webhooks' => [ + 'answer_url' => [ + 'address' => 'https://example.com/answer', + 'http_method' => 'GET' + + ], + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST' + ] + ] + ], + 'messages' => [ + 'webhooks' => [ + 'inbound_url' => [ + 'address' => 'https://example.com/inbound', + 'http_method' => 'POST' + + ], + 'status_url' => [ + 'address' => 'https://example.com/status', + 'http_method' => 'POST' + ] + ] + ], + 'rtc' => [ + 'webhooks' => [ + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST', + ], + ] + ], + 'vbc' => [] + ]; + $this->assertRequestJsonBodyContains('capabilities', $capabilities, $request); + + // And the public key + $keys = [ + 'public_key' => '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCA\nKOxjsU4pf/sMFi9N0jqcSLcjxu33G\nd/vynKnlw9SENi+UZR44GdjGdmfm1\ntL1eA7IBh2HNnkYXnAwYzKJoa4eO3\n0kYWekeIZawIwe/g9faFgkev+1xsO\nOUNhPx2LhuLmgwWSRS4L5W851Xe3f\nUQIDAQAB\n-----END PUBLIC KEY-----\n' + ]; + $this->assertRequestJsonBodyContains('keys', $keys, $request); return true; }))->willReturn($this->getResponse('success', '201')); @@ -378,20 +457,78 @@ public function createApplication() { $application = new Application(); $application->setName('test application'); - $application->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'https://example.com/answer'); - $application->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'https://example.com/event'); - - $raw = [ + $application->getVoiceConfig()->setWebhook(VoiceConfig::ANSWER, 'https://example.com/answer', 'GET'); + $application->getVoiceConfig()->setWebhook(VoiceConfig::EVENT, 'https://example.com/event', 'POST'); + $application->getMessagesConfig()->setWebhook(MessagesConfig::STATUS, 'https://example.com/status', 'POST'); + $application->getMessagesConfig()->setWebhook(MessagesConfig::INBOUND, 'https://example.com/inbound', 'POST'); + $application->getRtcConfig()->setWebhook(RtcConfig::EVENT, 'https://example.com/event', 'POST'); + $application->setPublicKey('-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCA\nKOxjsU4pf/sMFi9N0jqcSLcjxu33G\nd/vynKnlw9SENi+UZR44GdjGdmfm1\ntL1eA7IBh2HNnkYXnAwYzKJoa4eO3\n0kYWekeIZawIwe/g9faFgkev+1xsO\nOUNhPx2LhuLmgwWSRS4L5W851Xe3f\nUQIDAQAB\n-----END PUBLIC KEY-----\n'); + $application->getVbcConfig()->enable(); + + $rawV1 = [ 'name' => 'test application', 'answer_url' => 'https://example.com/answer', - 'event_url' => 'https://example.com/event' + 'answer_method' => 'GET', + 'event_url' => 'https://example.com/event', + 'event_method' => 'POST', + 'status_url' => 'https://example.com/status', + 'status_method' => 'POST', + 'inbound_url' => 'https://example.com/inbound', + 'inbound_method' => 'POST', + 'vbc' => true, + 'public_key' => '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCA\nKOxjsU4pf/sMFi9N0jqcSLcjxu33G\nd/vynKnlw9SENi+UZR44GdjGdmfm1\ntL1eA7IBh2HNnkYXnAwYzKJoa4eO3\n0kYWekeIZawIwe/g9faFgkev+1xsO\nOUNhPx2LhuLmgwWSRS4L5W851Xe3f\nUQIDAQAB\n-----END PUBLIC KEY-----\n' + ]; + + $rawV2 = [ + 'name' => 'test application', + 'keys' => [ + 'public_key' => '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCA\nKOxjsU4pf/sMFi9N0jqcSLcjxu33G\nd/vynKnlw9SENi+UZR44GdjGdmfm1\ntL1eA7IBh2HNnkYXnAwYzKJoa4eO3\n0kYWekeIZawIwe/g9faFgkev+1xsO\nOUNhPx2LhuLmgwWSRS4L5W851Xe3f\nUQIDAQAB\n-----END PUBLIC KEY-----\n' + ], + 'capabilities' => [ + 'voice' => [ + 'webhooks' => [ + 'answer_url' => [ + 'address' => 'https://example.com/answer', + 'http_method' => 'GET', + ], + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST', + ], + ] + ], + 'messages' => [ + 'webhooks' => [ + 'inbound_url' => [ + 'address' => 'https://example.com/inbound', + 'http_method' => 'POST' + + ], + 'status_url' => [ + 'address' => 'https://example.com/status', + 'http_method' => 'POST' + ] + ] + ], + 'rtc' => [ + 'webhooks' => [ + 'event_url' => [ + 'address' => 'https://example.com/event', + 'http_method' => 'POST', + ], + ] + ], + 'vbc' => [] + ] ]; return [ - [clone $application, 'create'], - [clone $application, 'post'], - [$raw, 'create'], - [$raw, 'post'], + 'createApplication' => [clone $application, 'create'], + 'postApplication' => [clone $application, 'post'], + 'createRawV1' => [$rawV1, 'create'], + 'postRawV1' => [$rawV1, 'post'], + 'createRawV2' => [$rawV2, 'create'], + 'postRawV2' => [$rawV2, 'post'], ]; } diff --git a/test/Application/responses/bad.json b/test/Application/responses/bad.json index e5c7d762..056ce495 100644 --- a/test/Application/responses/bad.json +++ b/test/Application/responses/bad.json @@ -1,7 +1,12 @@ { - "type": "BAD_REQUEST", - "error_title": "Bad Request", - "invalid_parameters": { - "name": "Is required." - } + "type": "https://developer.nexmo.com/api-errors/application#payload-validation", + "title": "Bad Request", + "detail": "The request failed due to validation errors", + "invalid_parameters": [ + { + "name": "capabilities.voice.webhooks.answer_url.http_method", + "reason": "must be one of: GET, POST" + } + ], + "instance": "797a8f199c45014ab7b08bfe9cc1c12c" } \ No newline at end of file diff --git a/test/Application/responses/list.json b/test/Application/responses/list.json index 26af3784..ab853c63 100644 --- a/test/Application/responses/list.json +++ b/test/Application/responses/list.json @@ -1,104 +1,69 @@ { - "count": 7, "page_size": 3, - "page_index": 2, + "page": 2, + "total_items": 7, + "total_pages": 1, "_embedded": { "applications": [ { - "id": "53ebf9f4-575b-4155-b268-2cb1c8d22718", - "name": "MyFirstApplication", - "voice": { - "webhooks": [ - { - "endpoint_type": "answer_url", - "endpoint": "https://example.com/ncco", - "http_method": "GET" - }, - { - "endpoint_type": "event_url", - "endpoint": "https://example.com/call_status", - "http_method": "POST" + "id": "78d335fa323d01149c3dd6f0d48968cf", + "name": "My Application", + "capabilities": { + "voice": { + "webhooks": { + "answer_url": { + "address": "https://example.com/webhooks/answer", + "http_method": "POST" + }, + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST" + } } - ] - }, - "keys": { - "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2QuZmErNpj5PYB4QDBZf\nDX0VvBoGzOWCghMItoncvux8uHm5QUJyr23ecrhA9+IpdzhjNM4jMNH3CQ6xbd0c\nvMdt1PvR/8/tUB7GMEGqTmjyp+aCPscs3MalDDr5ch7idV0fGebXdhXBRJ1ErqTh\nIDFdxd+yuSPs2va5Vly/idgeZ1JdL7ABBbug1lmft/d4teI3/lTUj+CMdw3ZTWir\neBpecUcS4EP6sZ3PX7EplaH0F/PWDGoSLJOKi0YGBADVeCkQ/Kcy7TpumOLwm3SV\n/0k9HBdeTuPK8Yu2ComLHovqpy2M+Bxw593FTWduOeqOH6p0lHoPlAt36scZvKtj\ngwIDAQAB\n-----END PUBLIC KEY-----\n" - }, - "_links": { - "self": { - "href": "/applications/53ebf9f4-575b-4155-b268-2cb1c8d22718" - } - } - }, - { - "id": "7568c82a-0852-4974-b5c7-19078a4020c3", - "name": "client_test", - "voice": { - "webhooks": [ - { - "endpoint_type": "answer_url", - "endpoint": "http://jsr9dhgmu4ax.runscope.net/aswer", - "http_method": "GET" - }, - { - "endpoint_type": "event_url", - "endpoint": "http://jsr9dhgmu4ax.runscope.net/event", - "http_method": "POST" + }, + "messages": { + "webhooks": { + "inbound_url": { + "address": "https://example.com/webhooks/inbound", + "http_method": "POST" + }, + "status_url": { + "address": "https://example.com/webhooks/status", + "http_method": "POST" + } } - ] - }, - "keys": { - "public_key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFqSXhEbUpZWTFJY0hsMWNXODR6QgpRREdIZTFVZ3dZRlk3UUc4VTk5Y2RFS252MzdPQnNub3Y5R20yODRoOFJlUW4yYktWMTZlK3M1WklzQXRBdDNWCjNpejRIakRuN3BqTkN5QzdzZHRMd1N1VkI5Q1gyMW02OTVYWkpIc0orNXpLZ1F4R2ttUC8zcmdMNXpWdHNjYkUKNnIvdlV3WnJvcTFndnpDTlpHbkVWZU1CRXc1U2ZSNytkMmsvTUo2UEd2bEV2eHpsZjZ6aE5TVGFGRzFoellQTApCNVpHVTlwRDBCS3VjS3E3OENrdVNZWVJoMnUxN090WUdYM2tIMnZJeEhzQjlhMFJURWJoSW9vY0ZLYXZISWNOCm5OblgwNEVaM1gzT3lwUkp4Q04xTjU0QW0xSENtK01EdEZXNGI2Z3F2UmlCc3JqZlhiVVpxR1pISGppK0JlZ0sKRVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" - }, - "_links": { - "self": { - "href": "/applications/7568c82a-0852-4974-b5c7-19078a4020c3" - } - } - }, - { - "id": "b5eab0a9-2c8b-42ad-9883-9d94efde261f", - "name": "IVR", - "voice": { - "webhooks": [ - { - "endpoint_type": "answer_url", - "endpoint": "https://tjlytle-ngrok-io-jsr9dhgmu4ax.runscope.net/answer/", - "http_method": "GET" - }, - { - "endpoint_type": "event_url", - "endpoint": "https://tjlytle-ngrok-io-jsr9dhgmu4ax.runscope.net/event/", - "http_method": "POST" + }, + "rtc": { + "webhooks": { + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST" + } } - ] + } }, "keys": { - "public_key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0VlZodXJYdjU0Uk9DSEovYmpyagprNnBMZElzUWFzeHA5MDFWdnJPL2kyQmNGRGtPVGZENEMwcm1OSG5iWkNHVWFLRCtqZ1VoMWZCQ1NHZ055b1BtCitIUi8wN3RXUkpNZlBReGRjZE9CbjN5MkR6bVBWcGQydUFSbFYyVGFkUCtCQ2ZmSk1ZSzVDZVRncWZDNE13UlMKVzVmTmZQWFlyMFhlandVWVdORmxuTkxmNCtJKzVMTFlmOWpPQUFJUlZ4OEtBTW5UZmo4TlVXdGJ0bnZvL1BWSApXVTFoV1VvRU9Xcm9oZE5ZSkdoM2RNeVQvOFNJbFF0V2xKRmQyQzJDUWU0ZFNhaGp1V0ZSQnFzeDRCYUN4QVZsCjNtdGxuN3dEbEhUSk5TallxN3NXdVZJTlIvd3Z1T2lTMHlUTmtJM2JrdlVNNW9jYkRRdjExSmZIWnoxRU9rZm8KTlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" - }, - "_links": { - "self": { - "href": "/applications/b5eab0a9-2c8b-42ad-9883-9d94efde261f" - } + "public_key": "public_key", + "private_key": "private_key" } } ] }, "_links": { "self": { - "href": "/v1/applications?page_size=3&page_index=2" + "href": "/v2/applications?page_size=3&page_index=2" }, "first": { - "href": "/v1/applications?page_size=3" + "href": "/v2/applications?page_size=3" }, "last": { - "href": "/v1/applications?page_size=3&page_index=3" + "href": "/v2/applications?page_size=3&page_index=3" }, "next": { - "href": "/v1/applications?page_size=3&page_index=3" + "href": "/v2/applications?page_size=3&page_index=3" }, "prev": { - "href": "/v1/applications?page_size=3&page_index=1" + "href": "/v2/applications?page_size=3&page_index=1" } } } \ No newline at end of file diff --git a/test/Application/responses/success.json b/test/Application/responses/success.json index 3f00e52e..57895841 100644 --- a/test/Application/responses/success.json +++ b/test/Application/responses/success.json @@ -1,27 +1,43 @@ { - "id": "7568c82a-0852-4974-b5c7-19078a4020c3", - "name": "client_test", - "voice": { - "webhooks": [ - { - "endpoint_type": "event_url", - "endpoint": "http://test.runscope.net/event", - "http_method": "POST" - }, - { - "endpoint_type": "answer_url", - "endpoint": "http://test.runscope.net/answer", - "http_method": "GET" + "id": "78d335fa323d01149c3dd6f0d48968cf", + "name": "My Application", + "capabilities": { + "voice": { + "webhooks": { + "answer_url": { + "address": "https://example.com/webhooks/answer", + "http_method": "GET" + }, + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST" + } } - ] + }, + "messages": { + "webhooks": { + "inbound_url": { + "address": "https://example.com/webhooks/inbound", + "http_method": "POST" + }, + "status_url": { + "address": "https://example.com/webhooks/status", + "http_method": "POST" + } + } + }, + "rtc": { + "webhooks": { + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST" + } + } + }, + "vbc": {} }, "keys": { "public_key": "public_key", "private_key": "private_key" - }, - "_links": { - "self": { - "href": "/v1/applications/7568c82a-0852-4974-b5c7-19078a4020c3" - } } } \ No newline at end of file diff --git a/test/Application/responses/unauthorized.json b/test/Application/responses/unauthorized.json index 7c9bbbee..1ae1fe57 100644 --- a/test/Application/responses/unauthorized.json +++ b/test/Application/responses/unauthorized.json @@ -1,4 +1,6 @@ { - "type": "UNAUTHORIZED", - "error_title": "Unauthorized" + "type": "https://developer.nexmo.com/api-errors#unauthorized", + "title": "Invalid credentials supplied", + "detail": "You did not provide correct credentials.", + "instance": "797a8f199c45014ab7b08bfe9cc1c12c" } \ No newline at end of file