diff --git a/vumi/application/tests/test_http_relay.py b/vumi/application/tests/test_http_relay.py index faa7f1f34..26c1e9f7e 100644 --- a/vumi/application/tests/test_http_relay.py +++ b/vumi/application/tests/test_http_relay.py @@ -1,11 +1,10 @@ from base64 import b64decode -from twisted.internet.defer import inlineCallbacks, returnValue +from twisted.internet.defer import inlineCallbacks from twisted.web import http from vumi.application.tests.utils import ApplicationTestCase -from vumi.tests.utils import TestResourceWorker, get_stubbed_worker -from vumi.application.tests.test_http_relay_stubs import TestResource +from vumi.tests.utils import MockHttpServer from vumi.application.http_relay import HTTPRelayApplication from vumi.message import TransportEvent from vumi.tests.helpers import MessageHelper @@ -14,7 +13,6 @@ class HTTPRelayTestCase(ApplicationTestCase): application_class = HTTPRelayApplication - timeout = 1 @inlineCallbacks def setUp(self): @@ -24,35 +22,23 @@ def setUp(self): @inlineCallbacks def setup_resource_with_callback(self, callback): - self.resource = yield self.make_resource_worker(callback=callback) - self.app = yield self.setup_app(self.path, self.resource) - - @inlineCallbacks - def setup_resource(self, code, content, headers): - self.resource = yield self.make_resource_worker(code, content, - headers) - self.app = yield self.setup_app(self.path, self.resource) - - @inlineCallbacks - def setup_app(self, path, resource): - app = yield self.get_application({ - 'url': 'http://localhost:%s%s' % ( - resource.port, - path), + self.mock_server = MockHttpServer(callback) + self.addCleanup(self.mock_server.stop) + yield self.mock_server.start() + self.app = yield self.get_application({ + 'url': '%s%s' % (self.mock_server.url, self.path), 'username': 'username', 'password': 'password', }) - returnValue(app) - @inlineCallbacks - def make_resource_worker(self, code=http.OK, content='', - headers={}, callback=None): - w = get_stubbed_worker(TestResourceWorker, {}) - w.set_resources([ - (self.path, TestResource, (code, content, headers, callback))]) - self._workers.append(w) - yield w.startWorker() - returnValue(w) + def setup_resource(self, code, content, headers): + def handler(request): + request.setResponseCode(code) + for key, value in headers.items(): + request.setHeader(key, value) + return content + + return self.setup_resource_with_callback(handler) @inlineCallbacks def test_http_relay_success_with_no_reply(self): diff --git a/vumi/application/tests/test_http_relay_stubs.py b/vumi/application/tests/test_http_relay_stubs.py deleted file mode 100644 index 5ec326a19..000000000 --- a/vumi/application/tests/test_http_relay_stubs.py +++ /dev/null @@ -1,23 +0,0 @@ -from twisted.python import log -from twisted.web.resource import Resource -from twisted.web import http - - -class TestResource(Resource): - isLeaf = True - - def __init__(self, code=http.OK, content='', headers={}, callback=None): - self.code = code - self.content = content - self.headers = headers - self.callback = callback - - def render_POST(self, request): - log.msg(request.content.read()) - if self.callback: - return self.callback(request) - - request.setResponseCode(self.code) - for key, value in self.headers.items(): - request.setHeader(key, value) - return self.content diff --git a/vumi/application/tests/test_rapidsms_relay.py b/vumi/application/tests/test_rapidsms_relay.py index 58d09ccb8..4e71ec1b2 100644 --- a/vumi/application/tests/test_rapidsms_relay.py +++ b/vumi/application/tests/test_rapidsms_relay.py @@ -2,28 +2,17 @@ import json -from twisted.internet.defer import inlineCallbacks, returnValue +from twisted.internet.defer import inlineCallbacks from twisted.web import http -from twisted.web.resource import Resource from vumi.application.tests.test_base import ApplicationTestCase -from vumi.tests.utils import TestResourceWorker, LogCatcher, get_stubbed_worker +from vumi.tests.utils import LogCatcher, MockHttpServer from vumi.application.rapidsms_relay import RapidSMSRelay, BadRequestError from vumi.utils import http_request_full, basic_auth_string, to_kwargs from vumi.message import TransportUserMessage, from_json from vumi.tests.helpers import MessageHelper -class DummyRapidResource(Resource): - isLeaf = True - - def __init__(self, callback): - self.callback = callback - - def render_POST(self, request): - return self.callback(request) - - class RapidSMSRelayTestCase(ApplicationTestCase): application_class = RapidSMSRelay @@ -38,14 +27,16 @@ def setUp(self): def setup_resource(self, callback=None, auth=None): if callback is None: callback = lambda r: self.fail("No RapidSMS requests expected.") - self.resource = yield self.setup_dummy_rapidsms(callback=callback) - self.app = yield self.setup_app(self.path, self.resource, auth=auth) + self.mock_server = MockHttpServer(callback) + self.addCleanup(self.mock_server.stop) + yield self.mock_server.start() + url = '%s%s' % (self.mock_server.url, self.path) + self.app = yield self.setup_app(url, auth=auth) - @inlineCallbacks - def setup_app(self, path, resource, auth=None): + def setup_app(self, url, auth=None): vumi_username, vumi_password = auth if auth else (None, None) - app = yield self.get_application({ - 'rapidsms_url': 'http://localhost:%s%s' % (resource.port, path), + return self.get_application({ + 'rapidsms_url': url, 'web_path': '/send/', 'web_port': '0', 'rapidsms_username': 'username', @@ -53,15 +44,6 @@ def setup_app(self, path, resource, auth=None): 'vumi_username': vumi_username, 'vumi_password': vumi_password, }) - returnValue(app) - - @inlineCallbacks - def setup_dummy_rapidsms(self, callback): - w = get_stubbed_worker(TestResourceWorker, {}) - w.set_resources([(self.path, DummyRapidResource, (callback,))]) - self._workers.append(w) - yield w.startWorker() - returnValue(w) def get_response_msgs(self, response): payloads = from_json(response.delivered_body) diff --git a/vumi/tests/utils.py b/vumi/tests/utils.py index e8930d087..460853adb 100644 --- a/vumi/tests/utils.py +++ b/vumi/tests/utils.py @@ -89,23 +89,6 @@ def get_stubbed_channel(broker=None, id=0): return amq_client.channel(id) -class TestResourceWorker(Worker): - port = 9999 - _resources = () - - def set_resources(self, resources): - self._resources = resources - - def startWorker(self): - resources = [(cls(*args), path) for path, cls, args in self._resources] - self.resources = self.start_web_resources(resources, self.port) - return defer.succeed(None) - - def stopWorker(self): - if self.resources: - self.resources.stopListening() - - def FakeRedis(): warnings.warn("Use of FakeRedis is deprecated. " "Use persist.tests.fake_redis instead.", diff --git a/vumi/transports/vas2nets/tests/test_failures.py b/vumi/transports/vas2nets/tests/test_failures.py index 852bf5f30..b33bb1d05 100644 --- a/vumi/transports/vas2nets/tests/test_failures.py +++ b/vumi/transports/vas2nets/tests/test_failures.py @@ -2,38 +2,20 @@ from datetime import datetime from twisted.web import http -from twisted.web.resource import Resource from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks, returnValue, Deferred from vumi.message import from_json from vumi.tests.utils import ( - get_stubbed_worker, TestResourceWorker, PersistenceMixin) + PersistenceMixin, get_stubbed_worker, MockHttpServer) from vumi.tests.fake_amqp import FakeAMQPBroker -from vumi.transports.failures import (FailureMessage, FailureWorker, - TemporaryFailure) -from vumi.transports.vas2nets.vas2nets import (Vas2NetsTransport, - Vas2NetsTransportError) +from vumi.transports.failures import ( + FailureMessage, FailureWorker, TemporaryFailure) +from vumi.transports.vas2nets.vas2nets import ( + Vas2NetsTransport, Vas2NetsTransportError) from vumi.tests.helpers import MessageHelper -class BadVas2NetsResource(Resource): - isLeaf = True - - def __init__(self, body, headers=None, code=http.OK): - self.body = body - self.code = code - if headers is None: - headers = {'X-Nth-Smsid': 'message_id'} - self.headers = headers - - def render_POST(self, request): - request.setResponseCode(self.code) - for k, v in self.headers.items(): - request.setHeader(k, v) - return self.body - - class FailureCounter(object): def __init__(self, count): self.count = count @@ -54,11 +36,9 @@ class Vas2NetsFailureWorkerTestCase(unittest.TestCase, PersistenceMixin): def setUp(self): self._persist_setUp() self.today = datetime.utcnow().date() - self.port = 9999 - self.path = '/api/v1/sms/vas2nets/receive/' self.config = self.mk_config({ 'transport_name': 'vas2nets', - 'url': 'http://localhost:%s%s' % (self.port, self.path), + 'url': None, 'username': 'username', 'password': 'password', 'owner': 'owner', @@ -66,7 +46,7 @@ def setUp(self): 'subservice': 'subservice', 'web_receive_path': '/receive', 'web_receipt_path': '/receipt', - 'web_port': 9998, + 'web_port': 0, }) self.fail_config = self.mk_config({ 'transport_name': 'vas2nets', @@ -104,13 +84,20 @@ def mk_failure_worker(self, config, broker): returnValue(w) @inlineCallbacks - def mk_resource_worker(self, body, headers=None, code=http.OK): - w = get_stubbed_worker(TestResourceWorker, {}, self.broker) - self.workers.append(w) - w.set_resources([(self.path, BadVas2NetsResource, - (body, headers, code))]) - yield w.startWorker() - returnValue(w) + def mk_mock_server(self, body, headers=None, code=http.OK): + if headers is None: + headers = {'X-Nth-Smsid': 'message_id'} + + def handler(request): + request.setResponseCode(code) + for k, v in headers.items(): + request.setHeader(k, v) + return body + + self.mock_server = MockHttpServer(handler) + self.addCleanup(self.mock_server.stop) + yield self.mock_server.start() + self.worker.config['url'] = self.mock_server.url def get_dispatched(self, rkey): return self.broker.get_dispatched('vumi', rkey) @@ -133,7 +120,7 @@ def assert_dispatched_count(self, count, routing_key): @inlineCallbacks def test_send_sms_success(self): - yield self.mk_resource_worker("Result_code: 00, Message OK") + yield self.mk_mock_server("Result_code: 00, Message OK") yield self.worker._process_message(self.make_outbound("outbound")) self.assert_dispatched_count(1, 'vas2nets.event') self.assert_dispatched_count(0, 'vas2nets.failures') @@ -144,7 +131,7 @@ def test_send_sms_fail(self): A 'No SmsId Header' error should not be retried. """ self.worker.failure_published = FailureCounter(1) - yield self.mk_resource_worker("Result_code: 04, Internal system error " + yield self.mk_mock_server("Result_code: 04, Internal system error " "occurred while processing message", {}) yield self.worker._process_message(self.make_outbound("outbound")) @@ -176,6 +163,10 @@ def test_send_sms_noconn(self): """ A 'connection refused' error should be retried. """ + # TODO: Figure out a solution that doesn't require hoping that + # nothing's listening on this port. + self.worker.config['url'] = 'http://localhost:9999/' + self.worker.failure_published = FailureCounter(1) msg = self.make_outbound("outbound") yield self.worker._process_message(msg) diff --git a/vumi/transports/vas2nets/tests/test_vas2nets.py b/vumi/transports/vas2nets/tests/test_vas2nets.py index f95f7c57f..d8d46c7b1 100644 --- a/vumi/transports/vas2nets/tests/test_vas2nets.py +++ b/vumi/transports/vas2nets/tests/test_vas2nets.py @@ -4,13 +4,11 @@ from urllib import urlencode from twisted.web import http -from twisted.web.resource import Resource from twisted.python import log from twisted.internet.defer import inlineCallbacks from vumi.utils import http_request_full from vumi.transports.tests.utils import TransportTestCase -from vumi.tests.utils import get_stubbed_worker, TestResourceWorker from vumi.message import TransportMessage from vumi.transports.failures import TemporaryFailure, PermanentFailure from vumi.transports.base import FailureMessage @@ -18,36 +16,7 @@ Vas2NetsTransport, validate_characters, Vas2NetsTransportError, Vas2NetsEncodingError, normalize_outbound_msisdn) from vumi.tests.helpers import MessageHelper - - -class TestResource(Resource): - isLeaf = True - - def __init__(self, message_id, message, code=http.OK, send_id=None): - self.message_id = message_id - self.message = message - self.code = code - self.send_id = send_id - - def render_POST(self, request): - log.msg(request.content.read()) - request.setResponseCode(self.code) - required_fields = [ - 'username', 'password', 'call-number', 'origin', 'text', - 'messageid', 'provider', 'tariff', 'owner', 'service', - 'subservice' - ] - log.msg('request.args', request.args) - for key in required_fields: - log.msg('checking for %s' % key) - assert key in request.args - - if self.send_id is not None: - assert request.args['messageid'] == [self.send_id] - - if self.message_id: - request.setHeader('X-Nth-Smsid', self.message_id) - return self.message +from vumi.tests.utils import MockHttpServer class Vas2NetsTransportTestCase(TransportTestCase): @@ -59,11 +28,9 @@ class Vas2NetsTransportTestCase(TransportTestCase): @inlineCallbacks def setUp(self): yield super(Vas2NetsTransportTestCase, self).setUp() - self.path = '/api/v1/sms/vas2nets/receive/' - self.port = 9999 self.config = { 'transport_name': 'vas2nets', - 'url': 'http://localhost:%s%s' % (self.port, self.path), + 'url': None, 'username': 'username', 'password': 'password', 'owner': 'owner', @@ -71,23 +38,42 @@ def setUp(self): 'subservice': 'subservice', 'web_receive_path': '/receive', 'web_receipt_path': '/receipt', - 'web_port': 9998, + 'web_port': 0, } - self.worker = yield self.get_transport(self.config) + self.transport = yield self.get_transport(self.config) + self.transport_url = self.transport.get_transport_url() self.today = datetime.utcnow().date() self.msg_helper = MessageHelper(transport_name=self.transport_name) - @inlineCallbacks - def tearDown(self): - yield self.worker.stopWorker() - yield super(Vas2NetsTransportTestCase, self).tearDown() + def _make_handler(self, message_id, message, code, send_id): + def handler(request): + log.msg(request.content.read()) + request.setResponseCode(code) + required_fields = [ + 'username', 'password', 'call-number', 'origin', 'text', + 'messageid', 'provider', 'tariff', 'owner', 'service', + 'subservice' + ] + log.msg('request.args', request.args) + for key in required_fields: + log.msg('checking for %s' % key) + self.assertTrue(key in request.args) + + if send_id is not None: + self.assertEqual(request.args['messageid'], [send_id]) + + if message_id: + request.setHeader('X-Nth-Smsid', message_id) + return message + return handler - def make_resource_worker(self, msg_id, msg, code=http.OK, send_id=None): - w = get_stubbed_worker(TestResourceWorker, {}) - w.set_resources([ - (self.path, TestResource, (msg_id, msg, code, send_id))]) - self._workers.append(w) - return w.startWorker() + @inlineCallbacks + def start_mock_server(self, msg_id, msg, code=http.OK, send_id=None): + self.mock_server = MockHttpServer( + self._make_handler(msg_id, msg, code, send_id)) + self.addCleanup(self.mock_server.stop) + yield self.mock_server.start() + self.transport.config['url'] = self.mock_server.url def make_request(self, path, qparams): """ @@ -105,8 +91,7 @@ def make_request(self, path, qparams): 'keyword': '', } args.update(qparams) - url = "http://localhost:%s/%s" % ( - self.config['web_port'], path.lstrip('/')) + url = self.transport_url + path return http_request_full(url, urlencode(args), { 'Content-Type': ['application/x-www-form-urlencoded'], }) @@ -158,8 +143,7 @@ def assert_messages_equal(self, expected, received): @inlineCallbacks def test_health_check(self): - url = "http://localhost:%s/health" % (self.config['web_port'],) - response = yield http_request_full(url) + response = yield http_request_full(self.transport_url + "/health") self.assertEqual('OK', response.delivered_body) self.assertEqual(response.code, http.OK) @@ -251,7 +235,7 @@ def test_send_sms_success(self): # open an HTTP resource that mocks the Vas2Nets response for the # duration of this test - yield self.make_resource_worker(mocked_message_id, mocked_message) + yield self.start_mock_server(mocked_message_id, mocked_message) sent_msg = self.make_outbound("hello") yield self.dispatch(sent_msg) @@ -269,7 +253,7 @@ def test_send_sms_reply_success(self): # open an HTTP resource that mocks the Vas2Nets response for the # duration of this test - yield self.make_resource_worker(mocked_message_id, mocked_message, + yield self.start_mock_server(mocked_message_id, mocked_message, send_id=reply_to_msgid) sent_msg = self.make_outbound("hello", in_reply_to=reply_to_msgid) @@ -285,7 +269,7 @@ def test_send_sms_fail(self): mocked_message_id = False mocked_message = ("Result_code: 04, Internal system error occurred " "while processing message") - yield self.make_resource_worker(mocked_message_id, mocked_message) + yield self.start_mock_server(mocked_message_id, mocked_message) msg = self.make_outbound("hello") d = self.dispatch(msg) @@ -307,6 +291,9 @@ def test_send_sms_fail(self): @inlineCallbacks def test_send_sms_noconn(self): + # TODO: Figure out a solution that doesn't require hoping that + # nothing's listening on this port. + self.transport.config['url'] = 'http://localhost:9999/' msg = self.make_outbound("hello") d = self.dispatch(msg) yield d @@ -324,7 +311,7 @@ def test_send_sms_noconn(self): @inlineCallbacks def test_send_sms_not_OK(self): mocked_message = "Page not found." - yield self.make_resource_worker(None, mocked_message, http.NOT_FOUND) + yield self.start_mock_server(None, mocked_message, http.NOT_FOUND) msg = self.make_outbound("hello") deferred = self.dispatch(msg) diff --git a/vumi/transports/vas2nets/vas2nets.py b/vumi/transports/vas2nets/vas2nets.py index 20cb0bde4..5e98fa03b 100644 --- a/vumi/transports/vas2nets/vas2nets.py +++ b/vumi/transports/vas2nets/vas2nets.py @@ -218,6 +218,17 @@ def setup_transport(self): self.receipt_resource = yield self.start_web_resources( resources, self.config['web_port'], LogFilterSite) + def get_transport_url(self): + """ + Get the URL for the HTTP resource. Requires the worker to be started. + + This is mostly useful in tests, and probably shouldn't be used in + non-test code, because the API might live behind a load balancer or + proxy. + """ + addr = self.receipt_resource.getHost() + return "http://%s:%s" % (addr.host, addr.port) + @inlineCallbacks def handle_outbound_message(self, message): """