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.
This commit is contained in:
novokrest 2018-06-02 20:39:31 +03:00 committed by Tim Abbott
parent 229f62a483
commit 488dcb4219
8 changed files with 99 additions and 73 deletions

View file

@ -83,9 +83,13 @@ class BeeminderHandler(object):
def initialize(self, bot_handler: Any) -> None: def initialize(self, bot_handler: Any) -> None:
self.config_info = bot_handler.get_config_info('beeminder') self.config_info = bot_handler.get_config_info('beeminder')
# Check for valid auth_token # Check for valid auth_token
result = get_beeminder_response('5', self.config_info) auth_token = self.config_info['auth_token']
if result == "Error. Check your key!": 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!') bot_handler.quit('Invalid key!')
except ConnectionError as e:
logging.exception(str(e))
def usage(self) -> str: def usage(self) -> str:
return "This plugin allows users to add datapoints towards their Beeminder goals" return "This plugin allows users to add datapoints towards their Beeminder goals"

View file

@ -1,17 +0,0 @@
{
"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"
}
}

View file

@ -1,17 +0,0 @@
{
"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"
}
}

View file

@ -0,0 +1,19 @@
{
"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"
}
}

View file

@ -1,21 +0,0 @@
{
"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"
}
}

View file

@ -0,0 +1,32 @@
{
"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"
}
}

View file

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