From f28d94f6a6897d260595e3b0ca995fbe93c9e68f Mon Sep 17 00:00:00 2001 From: Eric Salter Date: Tue, 10 Mar 2015 11:02:53 -0400 Subject: [PATCH] Add Hipchat V2 support Adds support for Hipchat's V2 API by way of setting the version flag. V1 paths should remain unchanged. A setVersion() method is provided for convenience in case one does not want to override all of the default parameters. --- src/Monolog/Handler/HipChatHandler.php | 74 +++++++++++++++----- tests/Monolog/Handler/HipChatHandlerTest.php | 61 +++++++++++++++- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/src/Monolog/Handler/HipChatHandler.php b/src/Monolog/Handler/HipChatHandler.php index 185e86e0..ee8e4b20 100644 --- a/src/Monolog/Handler/HipChatHandler.php +++ b/src/Monolog/Handler/HipChatHandler.php @@ -21,12 +21,23 @@ use Monolog\Logger; * Room - HipChat Room Id or name, where messages are sent * Name - Name used to send the message (from) * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) * * @author Rafael Dohms * @see https://www.hipchat.com/docs/api */ class HipChatHandler extends SocketHandler { + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + /** * The maximum allowed length for the name used in the "from" field. */ @@ -68,22 +79,24 @@ class HipChatHandler extends SocketHandler private $host; /** - * @param string $token HipChat API Token - * @param string $room The room that should be alerted of the message (Id or Name) - * @param string $name Name used in the "from" field - * @param bool $notify Trigger a notification in clients or not - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useSSL Whether to connect via SSL. - * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) - * @param string $host The HipChat server hostname. + * @var string */ - public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com') - { - if (!$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { - throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); - } + private $version; + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. Not used for v2 + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; parent::__construct($connectionString, $level, $bubble); @@ -93,6 +106,22 @@ class HipChatHandler extends SocketHandler $this->room = $room; $this->format = $format; $this->host = $host; + + $this->setVersion($version); + } + + /** + * Convenience method for setting the API version. Valid values are + * - HipChatHandler::API_V1 + * - HipChatHandler::API_V2 + * @param $version string the API version to use + */ + public function setVersion($version) { + if ($version == self::API_V1 && !$this->validateStringLength($this->name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $this->version = $version; } /** @@ -117,14 +146,18 @@ class HipChatHandler extends SocketHandler private function buildContent($record) { $dataArray = array( - 'from' => $this->name, - 'room_id' => $this->room, 'notify' => $this->notify, 'message' => $record['formatted'], 'message_format' => $this->format, 'color' => $this->getAlertColor($record['level']), ); + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + $dataArray['from'] = $this->name; + } + return http_build_query($dataArray); } @@ -136,7 +169,14 @@ class HipChatHandler extends SocketHandler */ private function buildHeader($content) { - $header = "POST /v1/rooms/message?format=json&auth_token=".$this->token." HTTP/1.1\r\n"; + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + $header .= "Host: {$this->host}\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . strlen($content) . "\r\n"; diff --git a/tests/Monolog/Handler/HipChatHandlerTest.php b/tests/Monolog/Handler/HipChatHandlerTest.php index 49f1dfbd..bf15e547 100644 --- a/tests/Monolog/Handler/HipChatHandlerTest.php +++ b/tests/Monolog/Handler/HipChatHandlerTest.php @@ -35,6 +35,18 @@ class HipChatHandlerTest extends TestCase return $content; } + public function testCanSetVersionAfterCreate() { + $this->createHandler(); + $this->handler->setVersion('v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + public function testWriteCustomHostHeader() { $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar'); @@ -47,12 +59,50 @@ class HipChatHandlerTest extends TestCase return $content; } + public function testWriteV2() { + $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testRoomSpaces() { + $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + /** * @depends testWriteHeader */ public function testWriteContent($content) { - $this->assertRegexp('/from=Monolog&room_id=room1¬ify=0&message=test1&message_format=text&color=red$/', $content); + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2 + */ + public function testWriteContentV2($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red$/', $content); + } + + /** + * @depends testCanSetVersionAfterCreate + */ + public function testWriteContentV2AfterCreate($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red$/', $content); } public function testWriteWithComplexMessage() @@ -141,9 +191,9 @@ class HipChatHandlerTest extends TestCase ); } - private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com') + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1') { - $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host); + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version); $this->res = fopen('php://memory', 'a'); $this->handler = $this->getMock( '\Monolog\Handler\HipChatHandler', @@ -175,4 +225,9 @@ class HipChatHandlerTest extends TestCase { $hipChatHandler = new \Monolog\Handler\HipChatHandler('token', 'room', 'SixteenCharsHere'); } + + public function testCreateWithTooLongNameV2() { + // creating a handler with too long of a name but using the v2 api doesn't matter. + $hipChatHandler = new \Monolog\Handler\HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2'); + } }