From cbac315f7c03031d164298805fc2e998f63b0a3e Mon Sep 17 00:00:00 2001
From: passion-27 <passion.dev.27@gmail.com>
Date: Sun, 29 Jan 2017 21:59:40 +0300
Subject: [PATCH 1/2] Webhook health check endpoint

---
 doc/api.md             |  1 +
 src/telegram.js        |  1 +
 src/telegramWebHook.js | 31 ++++++++++++++++++++-----------
 test/telegram.js       |  6 ++++++
 test/utils.js          | 27 +++++++++++++++++++++++----
 5 files changed, 51 insertions(+), 15 deletions(-)

diff --git a/doc/api.md b/doc/api.md
index 9fa7798..4da53b8 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -80,6 +80,7 @@ Emits `message` when a message arrives.
 | [options.webHook.pfx] | <code>String</code> |  | Path to file with PFX private key and certificate chain for webHook server.  The file is read **synchronously**! |
 | [options.webHook.autoOpen] | <code>Boolean</code> | <code>true</code> | Open webHook immediately |
 | [options.webHook.https] | <code>Object</code> |  | Options to be passed to `https.createServer()`.  Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be  used to override `key`, `cert` and `pfx` in this object, respectively.  See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. |
+| [options.webHook.healthEndpoint] | <code>String</code> | <code>/healthz</code> | An endpoint for health checks that always responds with 200 OK |
 | [options.onlyFirstMatch] | <code>Boolean</code> | <code>false</code> | Set to true to stop after first match. Otherwise, all regexps are executed |
 | [options.request] | <code>Object</code> |  | Options which will be added for all requests to telegram api.  See https://github.com/request/request#requestoptions-callback for more information. |
 | [options.baseApiUrl] | <code>String</code> | <code>https://api.telegram.org</code> | API Base URl; useful for proxying and testing |
diff --git a/src/telegram.js b/src/telegram.js
index 8f6af71..1c4be1c 100644
--- a/src/telegram.js
+++ b/src/telegram.js
@@ -59,6 +59,7 @@ class TelegramBot extends EventEmitter {
    *  Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be
    *  used to override `key`, `cert` and `pfx` in this object, respectively.
    *  See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information.
+   * @param {String} [options.webHook.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK
    * @param {Boolean} [options.onlyFirstMatch=false] Set to true to stop after first match. Otherwise, all regexps are executed
    * @param {Object} [options.request] Options which will be added for all requests to telegram api.
    *  See https://github.com/request/request#requestoptions-callback for more information.
diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js
index 6ee33f2..88e2f5b 100644
--- a/src/telegramWebHook.js
+++ b/src/telegramWebHook.js
@@ -13,6 +13,7 @@ class TelegramBotWebHook {
    * @param  {String} token Telegram API token
    * @param  {Boolean|Object} options WebHook options
    * @param  {Number} [options.port=8443] Port to bind to
+   * @param  {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK
    * @param  {Function} callback Function for process a new update
    */
   constructor(token, options, callback) {
@@ -27,6 +28,7 @@ class TelegramBotWebHook {
     this.options.https = options.https || {};
     this.callback = callback;
     this._regex = new RegExp(this.token);
+    this._healthRegex = new RegExp(options.healthEndpoint || '/healthz');
     this._webServer = null;
     this._open = false;
     this._requestListener = this._requestListener.bind(this);
@@ -133,20 +135,27 @@ class TelegramBotWebHook {
     debug('WebHook request URL: %s', req.url);
     debug('WebHook request headers: %j', req.headers);
 
-    // If there isn't token on URL
-    if (!this._regex.test(req.url)) {
+    if (this._regex.test(req.url)) {
+      if (req.method === 'POST') {
+        req
+          .pipe(bl(this._parseBody))
+          .on('finish', () => res.end('OK'));
+      } else {
+        // Authorized but not a POST
+        debug('WebHook request isn\'t a POST');
+        res.statusCode = 418; // I'm a teabot!
+        res.end();
+      }
+    } else if (this._healthRegex.test(req.url)) {
+      // If this is a health check
+      debug('WebHook health check passed');
+      res.statusCode = 200;
+      res.end('OK');
+    } else if (!this._regex.test(req.url)) {
+      // If there isn't token on URL
       debug('WebHook request unauthorized');
       res.statusCode = 401;
       res.end();
-    } else if (req.method === 'POST') {
-      req
-        .pipe(bl(this._parseBody))
-        .on('finish', () => res.end('OK'));
-    } else {
-      // Authorized but not a POST
-      debug('WebHook request isn\'t a POST');
-      res.statusCode = 418; // I'm a teabot!
-      res.end();
     }
   }
 }
diff --git a/test/telegram.js b/test/telegram.js
index b2c6059..82f2474 100644
--- a/test/telegram.js
+++ b/test/telegram.js
@@ -133,6 +133,12 @@ describe('TelegramBot', function telegramSuite() {
   });
 
   describe('WebHook', function webHookSuite() {
+    it('returns OK for healthz endpoint', function test(done) {
+      utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => {
+        assert.equal(resp, 'OK');
+        return done();
+      });
+    });
     it('returns 401 error if token is wrong', function test(done) {
       utils.sendWebHookMessage(webHookPort2, 'wrong-token').catch(resp => {
         assert.equal(resp.statusCode, 401);
diff --git a/test/utils.js b/test/utils.js
index c63c58e..044bf09 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -30,6 +30,17 @@ exports = module.exports = {
    * @return {Promise}
    */
   isPollingMockServer,
+  /**
+   * Send a message to the webhook at the specified port and path.
+   * @param  {Number} port
+   * @param  {String} path
+   * @param  {Object} [options]
+   * @param  {String} [options.method=POST] Method to use
+   * @param  {Object} [options.message] Message to send. Default to a generic text message
+   * @param  {Boolean} [options.https=false] Use https
+   * @return {Promise}
+   */
+  sendWebHookRequest,
   /**
    * Send a message to the webhook at the specified port.
    * @param  {Number} port
@@ -134,11 +145,11 @@ function hasOpenWebHook(port, reverse) {
 }
 
 
-function sendWebHookMessage(port, token, options = {}) {
+function sendWebHookRequest(port, path, options = {}) {
   assert.ok(port);
-  assert.ok(token);
+  assert.ok(path);
   const protocol = options.https ? 'https' : 'http';
-  const url = `${protocol}://127.0.0.1:${port}/bot${token}`;
+  const url = `${protocol}://127.0.0.1:${port}${path}`;
   return request({
     url,
     method: options.method || 'POST',
@@ -146,11 +157,19 @@ function sendWebHookMessage(port, token, options = {}) {
       update_id: 1,
       message: options.message || { text: 'test' }
     },
-    json: true,
+    json: options.json || true,
   });
 }
 
 
+function sendWebHookMessage(port, token, options = {}) {
+  assert.ok(port);
+  assert.ok(token);
+  const path = `/bot${token}`;
+  return sendWebHookRequest(port, path, options);
+}
+
+
 function handleRatelimit(bot, methodName, suite) {
   const backupMethodName = `__${methodName}`;
   if (!bot[backupMethodName]) bot[backupMethodName] = bot[methodName];

From 600b06312fb8ecbe2f95911020dd9046cba439bb Mon Sep 17 00:00:00 2001
From: passion-27 <passion.dev.27@gmail.com>
Date: Mon, 30 Jan 2017 13:24:15 +0300
Subject: [PATCH 2/2] pr/272: Finish on PR

---
 CHANGELOG.md           |  3 +++
 src/telegramWebHook.js | 15 ++++++---------
 test/telegram.js       |  2 +-
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc873c3..110fa9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased][Unreleased]
 
+Added:
+
+1. Add health-check endpoint (by @mironov)
 
 
 * * *
diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js
index 88e2f5b..d35c649 100644
--- a/src/telegramWebHook.js
+++ b/src/telegramWebHook.js
@@ -136,23 +136,20 @@ class TelegramBotWebHook {
     debug('WebHook request headers: %j', req.headers);
 
     if (this._regex.test(req.url)) {
-      if (req.method === 'POST') {
-        req
-          .pipe(bl(this._parseBody))
-          .on('finish', () => res.end('OK'));
-      } else {
-        // Authorized but not a POST
+      if (req.method !== 'POST') {
         debug('WebHook request isn\'t a POST');
         res.statusCode = 418; // I'm a teabot!
         res.end();
+      } else {
+        req
+          .pipe(bl(this._parseBody))
+          .on('finish', () => res.end('OK'));
       }
     } else if (this._healthRegex.test(req.url)) {
-      // If this is a health check
       debug('WebHook health check passed');
       res.statusCode = 200;
       res.end('OK');
-    } else if (!this._regex.test(req.url)) {
-      // If there isn't token on URL
+    } else {
       debug('WebHook request unauthorized');
       res.statusCode = 401;
       res.end();
diff --git a/test/telegram.js b/test/telegram.js
index 82f2474..7a5b89a 100644
--- a/test/telegram.js
+++ b/test/telegram.js
@@ -133,7 +133,7 @@ describe('TelegramBot', function telegramSuite() {
   });
 
   describe('WebHook', function webHookSuite() {
-    it('returns OK for healthz endpoint', function test(done) {
+    it('returns 200 OK for health endpoint', function test(done) {
       utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => {
         assert.equal(resp, 'OK');
         return done();