diff --git a/appengine/mailgun/.gitignore b/appengine/mailgun/.gitignore new file mode 100644 index 000000000000..a65b41774ad5 --- /dev/null +++ b/appengine/mailgun/.gitignore @@ -0,0 +1 @@ +lib diff --git a/appengine/mailgun/README.md b/appengine/mailgun/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/mailgun/__init__.py b/appengine/mailgun/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/mailgun/app.yaml b/appengine/mailgun/app.yaml new file mode 100644 index 000000000000..42ad35ed2a84 --- /dev/null +++ b/appengine/mailgun/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: main.app diff --git a/appengine/mailgun/appengine_config.py b/appengine/mailgun/appengine_config.py new file mode 100644 index 000000000000..4fdc5d2b60fc --- /dev/null +++ b/appengine/mailgun/appengine_config.py @@ -0,0 +1,4 @@ +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add('lib') diff --git a/appengine/mailgun/main.py b/appengine/mailgun/main.py new file mode 100644 index 000000000000..14fc7c0e3411 --- /dev/null +++ b/appengine/mailgun/main.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Sample Google App Engine application that sends mail using Mailgun. +""" +from urllib import urlencode + +import httplib2 +import webapp2 + + +# Your Mailgun Domain Name +MAILGUN_DOMAIN_NAME = 'your-mailgun-domain-name' +# Your Mailgun API key +MAILGUN_API_KEY = 'your-mailgun-api-key' + + +# [START simple_message] +def send_simple_message(recipient): + http = httplib2.Http() + http.add_credentials('api', MAILGUN_API_KEY) + + url = 'https://api.mailgun.net/v3/{}/messages'.format(MAILGUN_DOMAIN_NAME) + data = { + 'from': 'Example Sender '.format(MAILGUN_DOMAIN_NAME), + 'to': recipient, + 'subject': 'This is an example email from Mailgun', + 'text': 'Test message from Mailgun' + } + + resp, content = http.request(url, 'POST', urlencode(data)) + + if resp.status != 200: + raise RuntimeError( + 'Mailgun API error: {} {}'.format(resp.status, content)) +# [END simple_message] + + +# [START complex_message] +def send_complex_message(recipient): + http = httplib2.Http() + http.add_credentials('api', MAILGUN_API_KEY) + + url = 'https://api.mailgun.net/v3/{}/messages'.format(MAILGUN_DOMAIN_NAME) + data = { + 'from': 'Example Sender '.format(MAILGUN_DOMAIN_NAME), + 'to': recipient, + 'subject': 'This is an example email from Mailgun', + 'text': 'Test message from Mailgun', + 'html': 'HTML version of the body' + } + + resp, content = http.request(url, 'POST', urlencode(data)) + + if resp.status != 200: + raise RuntimeError( + 'Mailgun API error: {} {}'.format(resp.status, content)) +# [END complex_message] + + +class MainPage(webapp2.RequestHandler): + def get(self): + self.response.content_type = 'text/html' + self.response.write(""" + + +
+ + + +
+ +""") + + def post(self): + recipient = self.request.get('recipient') + action = self.request.get('submit') + + if action == 'Send simple email': + send_simple_message(recipient) + else: + send_complex_message(recipient) + + self.response.write('Mail sent') + + +app = webapp2.WSGIApplication([ + ('/', MainPage) +], debug=True) diff --git a/appengine/mailgun/main_test.py b/appengine/mailgun/main_test.py new file mode 100644 index 000000000000..99664fcd91c3 --- /dev/null +++ b/appengine/mailgun/main_test.py @@ -0,0 +1,55 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tests import AppEngineTestbedCase, Http2Mock +import webtest + +from . import main + + +class TestMailgunHandlers(AppEngineTestbedCase): + def setUp(self): + super(TestMailgunHandlers, self).setUp() + + self.app = webtest.TestApp(main.app) + + def test_get(self): + response = self.app.get('/') + self.assertEqual(response.status_int, 200) + + def test_post(self): + http = Http2Mock(responses=[{}]) + + with http: + response = self.app.post('/', { + 'recipient': 'jonwayne@google.com', + 'submit': 'Send simple email'}) + + self.assertEqual(response.status_int, 200) + + http = Http2Mock(responses=[{}]) + + with http: + response = self.app.post('/', { + 'recipient': 'jonwayne@google.com', + 'submit': 'Send complex email'}) + + self.assertEqual(response.status_int, 200) + + http = Http2Mock(responses=[{'status': 500, 'body': 'Test error'}]) + + with http, self.assertRaises(Exception): + self.app.post('/', { + 'recipient': 'jonwayne@google.com', + 'submit': 'Send simple email'}) diff --git a/appengine/mailgun/requirements.txt b/appengine/mailgun/requirements.txt new file mode 100644 index 000000000000..fb881ece05bf --- /dev/null +++ b/appengine/mailgun/requirements.txt @@ -0,0 +1 @@ +httplib2 diff --git a/tests/__init__.py b/tests/__init__.py index d6c5b977fb37..a7a86e842cdd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -16,6 +16,7 @@ AppEngineTestbedCase, capture_stdout, CloudBaseTest, + Http2Mock, RESOURCE_PATH) @@ -23,5 +24,6 @@ 'AppEngineTestbedCase', 'capture_stdout', 'CloudBaseTest', + 'Http2Mock', 'RESOURCE_PATH' ] diff --git a/tests/utils.py b/tests/utils.py index beba2bffa6aa..f033b1c2a9be 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -21,6 +21,7 @@ import tempfile import unittest +import httplib2 from nose.plugins.skip import SkipTest from six.moves import cStringIO @@ -135,3 +136,31 @@ def capture_stdout(): yield fake_stdout finally: sys.stdout = old_stdout + + +class Http2Mock(object): + """Mock httplib2.Http""" + + def __init__(self, responses): + self.responses = responses + + def add_credentials(self, user, pwd): + self.credentials = (user, pwd) + + def request(self, token_uri, method, body, headers=None, *args, **kwargs): + response = self.responses.pop(0) + self.status = response.get('status', 200) + self.body = response.get('body', '') + self.headers = response.get('headers', '') + return (self, self.body) + + def __enter__(self): + self.httplib2_orig = httplib2.Http + httplib2.Http = self + return self + + def __exit__(self, exc_type, exc_value, traceback): + httplib2.Http = self.httplib2_orig + + def __call__(self, *args, **kwargs): + return self