beeminder: Check auth_token in `initialize` by idempotent request.
Change checking auth_token in `initialize` method by calling
request to get user's information instead of calling POST request
which modifies progress of user's goal.
MODIFY
zulip_bots/zulip_bots/bots/beeminder/beeminder.py
zulip_bots/zulip_bots/bots/beeminder/beeminder.py
@@ -83,9 +83,13 @@ class BeeminderHandler(object):
def initialize(self, bot_handler: Any) -> None:
self.config_info = bot_handler.get_config_info('beeminder')
# Check for valid auth_token
- result = get_beeminder_response('5', self.config_info)
- if result == "Error. Check your key!":
+ auth_token = self.config_info['auth_token']
+ try:
+ r = requests.get("https://www.beeminder.com/api/v1/users/me.json", params={'auth_token': auth_token})
+ if r.status_code == 401:
bot_handler.quit('Invalid key!')
+ except ConnectionError as e:
+ logging.exception(str(e))
def usage(self) -> str:
return "This plugin allows users to add datapoints towards their Beeminder goals"

DELETE
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_blank_input.json
-{
- "request": {
- "api_url": "https://www.beeminder.com/api/v1/users/aaron/goals/goal/datapoints.json",
- "method": "POST",
- "json": {
- "auth_token": "XXXXXX",
- "value": "5"
- }
- },
- "response": {
- "help_message": "This is help message."
- },
- "response-headers": {
- "status": 200,
- "content-type": "application/json; charset=utf-8"
- }
-}

DELETE
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_help_message.json
-{
- "request": {
- "api_url": "https://www.beeminder.com/api/v1/users/aaron/goals/goal/datapoints.json",
- "method": "POST",
- "json": {
- "auth_token": "XXXXXX",
- "value": "5"
- }
- },
- "response": {
- "help_message": "This is help message."
- },
- "response-headers": {
- "status": 200,
- "content-type": "application/json; charset=utf-8"
- }
-}

RENAME
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_invalid.json
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_invalid_when_handle_message.json
ADD
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_invalid_when_initialize.json
+{
+ "request": {
+ "api_url": "https://www.beeminder.com/api/v1/users/me.json",
+ "method": "GET",
+ "params": {
+ "auth_token": "someInvalidKey"
+ }
+ },
+ "response": {
+ "errors": {
+ "auth_token": "bad_token",
+ "message": "No such auth_token found. (Did you mix up auth_token and access_token?)"
+ }
+ },
+ "response-headers": {
+ "status": 401,
+ "content-type": "application/json; charset=utf-8"
+ }
+}

DELETE
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_syntax_error.json
-{
- "request": {
- "api_url": "https://www.beeminder.com/api/v1/users/aaron/goals/goal/datapoints.json",
- "method": "POST",
- "json": {
- "auth_token": "XXXXXX",
- "value": "5"
- }
- },
- "response": {
- "errors": {
- "value": [
- "syntax error"
- ]
- }
- },
- "response-headers": {
- "status": 422,
- "content-type": "application/json; charset=utf-8"
- }
-}

ADD
zulip_bots/zulip_bots/bots/beeminder/fixtures/test_valid_auth_token.json
+{
+ "request": {
+ "api_url": "https://www.beeminder.com/api/v1/users/me.json",
+ "method": "GET",
+ "params": {
+ "auth_token": "XXXXXX"
+ }
+ },
+ "response": {
+ "username": "testuser",
+ "timezone": "Asia/Kolkata",
+ "goals": [
+ "gainweight"
+ ],
+ "updated_at": 1527962842,
+ "deadbeat": false,
+ "has_authorized_fitbit": false,
+ "default_leadtime": 0,
+ "default_alertstart": 82620,
+ "default_deadline": -3000,
+ "subscription": null,
+ "subs_downto": null,
+ "subs_freq": null,
+ "subs_lifetime": null,
+ "remaining_subs_credit": 0,
+ "id": "5a7c8509bfec03755800544a"
+ },
+ "response-headers": {
+ "status": 200,
+ "content-type": "application/json; charset=utf-8"
+ }
+}

MODIFY
zulip_bots/zulip_bots/bots/beeminder/test_beeminder.py
zulip_bots/zulip_bots/bots/beeminder/test_beeminder.py
@@ -22,61 +22,87 @@ following the syntax shown below :smile:.\n \
def test_bot_responds_to_empty_message(self) -> None:
with self.mock_config_info(self.normal_config), \
- self.mock_http_conversation('test_blank_input'):
+ self.mock_http_conversation('test_valid_auth_token'):
self.verify_reply('', self.help_message)
def test_help_message(self) -> None:
with self.mock_config_info(self.normal_config), \
- self.mock_http_conversation('test_help_message'):
+ self.mock_http_conversation('test_valid_auth_token'):
self.verify_reply('help', self.help_message)
def test_message_with_daystamp_and_value(self) -> None:
bot_response = '[Datapoint](https://www.beeminder.com/aaron/goal) created.'
with self.mock_config_info(self.normal_config), \
+ self.mock_http_conversation('test_valid_auth_token'), \
self.mock_http_conversation('test_message_with_daystamp_and_value'):
self.verify_reply('20180602, 2', bot_response)
def test_message_with_value_and_comment(self) -> None:
bot_response = '[Datapoint](https://www.beeminder.com/aaron/goal) created.'
with self.mock_config_info(self.normal_config), \
+ self.mock_http_conversation('test_valid_auth_token'), \
self.mock_http_conversation('test_message_with_value_and_comment'):
self.verify_reply('2, hi there!', bot_response)
def test_message_with_daystamp_and_value_and_comment(self) -> None:
bot_response = '[Datapoint](https://www.beeminder.com/aaron/goal) created.'
with self.mock_config_info(self.normal_config), \
+ self.mock_http_conversation('test_valid_auth_token'), \
self.mock_http_conversation('test_message_with_daystamp_and_value_and_comment'):
self.verify_reply('20180602, 2, hi there!', bot_response)
def test_syntax_error(self) -> None:
with self.mock_config_info(self.normal_config), \
- self.mock_http_conversation('test_syntax_error'):
+ self.mock_http_conversation('test_valid_auth_token'):
bot_response = "Make sure you follow the syntax.\n You can take a look \
at syntax by: @mention-botname help"
self.verify_reply("20180303, 50, comment, redundant comment", bot_response)
- def test_connection_error(self) -> None:
+ def test_connection_error_when_handle_message(self) -> None:
with self.mock_config_info(self.normal_config), \
+ self.mock_http_conversation('test_valid_auth_token'), \
patch('requests.post', side_effect=ConnectionError()), \
patch('logging.exception'):
self.verify_reply('?$!', 'Uh-Oh, couldn\'t process the request \
right now.\nPlease try again later')
+ def test_invalid_when_handle_message(self) -> None:
+ bot = get_bot_message_handler(self.bot_name)
+ bot_handler = StubBotHandler()
+
+ with self.mock_config_info({'auth_token': 'someInvalidKey',
+ 'username': 'aaron',
+ 'goalname': 'goal'}), \
+ patch('requests.get', side_effect=ConnectionError()), \
+ self.mock_http_conversation('test_invalid_when_handle_message'), \
+ patch('logging.exception'):
+ self.verify_reply('5', 'Error. Check your key!')
+
def test_error(self) -> None:
bot_request = 'notNumber'
bot_response = "Error occured : 422"
with self.mock_config_info(self.normal_config), \
+ self.mock_http_conversation('test_valid_auth_token'), \
self.mock_http_conversation('test_error'):
self.verify_reply(bot_request, bot_response)
- def test_invalid(self) -> None:
+ def test_invalid_when_initialize(self) -> None:
bot = get_bot_message_handler(self.bot_name)
bot_handler = StubBotHandler()
with self.mock_config_info({'auth_token': 'someInvalidKey',
'username': 'aaron',
- 'goalname': 'goal',
- "value": "5"}), \
- self.mock_http_conversation('test_invalid'), \
+ 'goalname': 'goal'}), \
+ self.mock_http_conversation('test_invalid_when_initialize'), \
self.assertRaises(bot_handler.BotQuitException):
bot.initialize(bot_handler)
+
+ def test_connection_error_during_initialize(self) -> None:
+ bot = get_bot_message_handler(self.bot_name)
+ bot_handler = StubBotHandler()
+
+ with self.mock_config_info(self.normal_config), \
+ patch('requests.get', side_effect=ConnectionError()), \
+ patch('logging.exception') as mock_logging:
+ bot.initialize(bot_handler)
+ self.assertTrue(mock_logging.called)

beeminder: Check auth_token in `initialize` by idempotent request.

Change checking auth_token in `initialize` method by calling
request to get user's information instead of calling POST request
which modifies progress of user's goal.