-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #255 from MycroftAI/bugfix/send-email
Fix email sending logic
- Loading branch information
Showing
8 changed files
with
174 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
Feature: Send email to a to the account that owns a device | ||
Test the email endpoint | ||
Feature: Device requests email sent to the account holder | ||
Some skills have the ability to send email upon request. One example of | ||
this is the support skill, which emails device diagnostics. | ||
|
||
Scenario: an email payload is passed to the email endpoint | ||
When an email message is sent to the email endpoint | ||
Then an email should be sent to the user's account that owns the device | ||
Scenario: Email sent to account holder | ||
When a user interaction with a device causes an email to be sent | ||
Then the request will be successful | ||
And an email should be sent to the account that owns the device | ||
And the device's last contact time is updated | ||
|
||
Scenario: an email payload is passed to the the email endpoint using a not allowed device | ||
When the email endpoint is called by a not allowed device | ||
Then 401 status code should be returned by the email endpoint | ||
Scenario: Email request sent by unauthorized device | ||
When an unpaired or unauthenticated device attempts to send an email | ||
Then the request will fail with an unauthorized error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,59 +16,79 @@ | |
# | ||
# You should have received a copy of the GNU Affero General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
"""Step functions for the device API call to send an email.""" | ||
import json | ||
import uuid | ||
from http import HTTPStatus | ||
from unittest.mock import patch, MagicMock | ||
|
||
from behave import when, then | ||
from hamcrest import assert_that, equal_to | ||
from behave import when, then # pylint: disable=no-name-in-module | ||
|
||
email_request = dict( | ||
title='this is a test', | ||
sender='[email protected]', | ||
body='body message' | ||
) | ||
|
||
@when("a user interaction with a device causes an email to be sent") | ||
def send_email(context): | ||
"""Call the email endpoint to send an email as specified by a device. | ||
@when('an email message is sent to the email endpoint') | ||
@patch('smtplib.SMTP') | ||
def send_email(context, email_client): | ||
context.client_config['EMAIL_CLIENT'] = email_client | ||
login = context.device_login | ||
device_id = login['uuid'] | ||
access_token = login['accessToken'] | ||
context.email_response = context.client.put( | ||
'/v1/device/{uuid}/message'.format(uuid=device_id), | ||
data=json.dumps(email_request), | ||
content_type='application_json', | ||
headers=dict(Authorization='Bearer {token}'.format(token=access_token)) | ||
) | ||
The SendGrid call to send email is mocked out because we trust that the library | ||
is coded to correctly send email via sendgrid. That, and testing sent emails is | ||
a real pain in the arse. | ||
""" | ||
with patch("selene.util.email.email.Mail") as message_patch: | ||
context.message_patch = message_patch | ||
with patch("selene.util.email.email.SendGridAPIClient") as sendgrid_patch: | ||
sendgrid_patch.return_value, post_mock = _define_sendgrid_mock() | ||
context.sendgrid_patch = sendgrid_patch | ||
context.post_mock = post_mock | ||
_call_email_endpoint(context) | ||
|
||
|
||
@then('an email should be sent to the user\'s account that owns the device') | ||
def validate_response(context): | ||
response = context.email_response | ||
assert_that(response.status_code, equal_to(HTTPStatus.OK)) | ||
email_client: MagicMock = context.client_config['EMAIL_CLIENT'] | ||
email_client.send_message.assert_called() | ||
@when("an unpaired or unauthenticated device attempts to send an email") | ||
def send_email_invalid_device(context): | ||
"""Call the email endpoint with an invalid device ID to test authentication.""" | ||
_call_email_endpoint(context, device_id=str(uuid.uuid4())) | ||
|
||
|
||
def _define_sendgrid_mock(): | ||
"""Go through some insane hoops to setup the correct Mock for the Sendgrid code.""" | ||
post_return_value = MagicMock(name="post_return") | ||
post_return_value.status_code = 200 | ||
post_mock = MagicMock(name="post_mock") | ||
post_mock.return_value = post_return_value | ||
send_mock = MagicMock(name="send_mock") | ||
send_mock.post = post_mock | ||
mail_mock = MagicMock(name="mail_mock") | ||
mail_mock.send = send_mock | ||
client_mock = MagicMock(name="client_mock") | ||
client_mock.mail = mail_mock | ||
sendgrid_mock = MagicMock(name="sendgrid_mock") | ||
sendgrid_mock.client = client_mock | ||
|
||
@when('the email endpoint is called by a not allowed device') | ||
@patch('smtplib.SMTP') | ||
def send_email_invalid_device(context, email_client): | ||
context.client_config['EMAIL_CLIENT'] = email_client | ||
context.email_invalid_response = context.client.put( | ||
'/v1/device/{uuid}/email'.format(uuid=str(uuid.uuid4())), | ||
data=json.dumps(email_request), | ||
content_type='application_json' | ||
return sendgrid_mock, post_mock | ||
|
||
|
||
def _call_email_endpoint(context, device_id=None): | ||
"""Build the request to the email endpoint and issue a call.""" | ||
if device_id is None: | ||
login = context.device_login | ||
request_device_id = login["uuid"] | ||
request_headers = dict(Authorization=f"Bearer {login['accessToken']}") | ||
else: | ||
request_device_id = device_id | ||
request_headers = dict(Authorization=f"Bearer thisisadummybearertoken") | ||
request_data = dict( | ||
title="this is a test", sender="[email protected]", body="body message" | ||
) | ||
context.response = context.client.put( | ||
f"/v1/device/{request_device_id}/message", | ||
data=json.dumps(request_data), | ||
content_type="application/json", | ||
headers=request_headers, | ||
) | ||
|
||
|
||
@then('401 status code should be returned by the email endpoint') | ||
def validate_response_invalid_device(context): | ||
response = context.email_invalid_response | ||
assert_that(response.status_code, equal_to(HTTPStatus.UNAUTHORIZED)) | ||
email_client: MagicMock = context.client_config['EMAIL_CLIENT'] | ||
email_client.send_message.assert_not_called() | ||
@then("an email should be sent to the account that owns the device") | ||
def validate_response(context): | ||
"""Validate that the SendGrid API was called as expected.""" | ||
sendgrid_patch = context.sendgrid_patch | ||
sendgrid_patch.assert_called_with(api_key="test_sendgrid_key") | ||
post_mock = context.post_mock | ||
post_mock.assert_called_with(request_body=context.message_patch().get()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
DELETE FROM geography.city WHERE id IN %(city_ids)s | ||
DELETE FROM | ||
geography.city | ||
WHERE | ||
id IN %(city_ids)s | ||
and (population is null or population != %(max_population)s) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.