def test_healthcheck(config, db, web_session): container = ServiceContainer(MonitoringService) replace_dependencies(container, storage=StorageWrapper(db, Storage.collections)) container.start() response = web_session.get("/health-check") assert response.status_code == 200
def test_replace_dependencies(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") @rpc def method(self, arg): self.foo_proxy.remote_method(arg) @rpc def foo(self): return "bar" container = container_factory(Service, rabbit_config) # replace a single dependency foo_proxy = replace_dependencies(container, "foo_proxy") # replace multiple dependencies replacements = replace_dependencies(container, "bar_proxy", "baz_proxy") assert len([x for x in replacements]) == 2 # verify that container.extensions doesn't include an RpcProxy anymore assert all([not isinstance(dependency, RpcProxy) for dependency in container.extensions]) container.start() # verify that the mock dependency collects calls msg = "msg" with ServiceRpcProxy("service", rabbit_config) as service_proxy: service_proxy.method(msg) foo_proxy.remote_method.assert_called_once_with(msg)
def test_replace_dependencies_args(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") @rpc def method(self, arg): self.foo_proxy.remote_method(arg) container = container_factory(Service, rabbit_config) # replace a single dependency foo_proxy = replace_dependencies(container, "foo_proxy") # replace multiple dependencies replacements = replace_dependencies(container, "bar_proxy", "baz_proxy") assert len([x for x in replacements]) == 2 # verify that container.extensions doesn't include an RpcProxy anymore assert all([not isinstance(dependency, RpcProxy) for dependency in container.extensions]) container.start() # verify that the mock dependency collects calls msg = "msg" with ServiceRpcProxy("service", rabbit_config) as service_proxy: service_proxy.method(msg) foo_proxy.remote_method.assert_called_once_with(msg)
def image_service(config): image_svc = ServiceContainer(ImageService, config) replace_dependencies(image_svc, 'image_host') image_svc.start() yield image_svc image_svc.stop()
def test_replace_dependencies_container_already_started(container_factory, rabbit_config): class Service(object): name = "service" proxy = RpcProxy("foo_service") container = container_factory(Service, rabbit_config) container.start() with pytest.raises(RuntimeError): replace_dependencies(container, "proxy")
def test_replace_dependencies_container_already_started( container_factory, rabbit_config): class Service(object): name = "service" proxy = RpcProxy("foo_service") container = container_factory(Service, rabbit_config) container.start() with pytest.raises(RuntimeError): replace_dependencies(container, "proxy")
def test_replace_dependencies_in_both_args_and_kwargs_error(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") container = container_factory(Service, rabbit_config) with pytest.raises(RuntimeError) as exc: replace_dependencies( container, 'bar_proxy', 'foo_proxy', foo_proxy='foo' ) assert "Cannot replace the same dependency" in str(exc)
def test_get_successful_with_deleted_datetime(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage = replace_dependencies(container, "storage") runner.start() user_id = 123 email = "*****@*****.**" created_datetime_utc = datetime.datetime.utcnow() deleted_datetime_utc = datetime.datetime.utcnow() storage.users.get.return_value = { "id": user_id, "email": email, "created_datetime_utc": created_datetime_utc, "deleted_datetime_utc": deleted_datetime_utc, "verified": False, } with entrypoint_hook(container, "get_user") as get_user: result = get_user(user_id=user_id) assert storage.users.get.call_args == call(123) assert result == { "id": user_id, "email": email, "created_datetime_utc": created_datetime_utc.isoformat(), "deleted_datetime_utc": deleted_datetime_utc.isoformat(), "verified": False, }
def create(*dependencies, **dependency_map): """ Create service instance with specified dependencies mocked Dependencies named in *dependencies will be replaced with a `MockDependencyProvider`, which injects a `MagicMock` instead of the dependency. Alternatively, you may use `dependency_map` keyword arguments to name a dependency and provide the replacement value that the `MockDependencyProvider` should inject. For more information read: https://github.com/onefinestay/nameko/blob/master/nameko/testing/services.py#L325 """ dependency_names = list(dependencies) + list(dependency_map.keys()) ServiceMeta = namedtuple( 'ServiceMeta', ['container'] + dependency_names ) container = container_factory(OrdersService, config) mocked_dependencies = replace_dependencies( container, *dependencies, **dependency_map ) if len(dependency_names) == 1: mocked_dependencies = (mocked_dependencies, ) container.start() return ServiceMeta(container, *mocked_dependencies, **dependency_map)
def test_webhook_timeout_retry(web_container_config, container_factory, rmock=None): test_url = 'http://example.org' test_cb_url = 'http://example.org/cb' web_container_config['WEBHOOK_DELAY_INTERVAL'] = 1 web_container_config['WEBHOOK_BACKOFF_FACTOR'] = 1 container = container_factory(WebhookService, web_container_config) storage = replace_dependencies(container, 'storage') storage.get_webhooks_for_url = lambda url: [test_cb_url] container.start() dispatch = event_dispatcher(web_container_config) # 1 failed response and then a valid one rmock.post(test_cb_url, [{ 'exc': requests.exceptions.ConnectTimeout }, { 'status_code': 200 }]) with entrypoint_waiter(container, 'send_response'): dispatch('url_crawler', 'url_crawled', {'checked-url': test_url}) requests_l = filter_mock_requests(test_cb_url, rmock.request_history) assert len(requests_l) == 2 request = requests_l[-1] assert request.method == 'POST' assert request.url == test_cb_url assert request.json() == {'data': {'checked-url': test_url}}
def test_resend_user_token(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage, send_grid = replace_dependencies(container, "storage", "send_grid") runner.start() email = "*****@*****.**" password = "******" user_id = 1 storage.users.is_correct_password.return_value = True storage.users.get_from_email.return_value = { "id": user_id, "email": email, "verified": False, } storage.user_tokens.create.return_value = None send_grid.send_signup_verification.return_value = None with entrypoint_hook(container, "resend_user_token") as resend_user_token: result = resend_user_token(email, password) assert storage.users.is_correct_password.call_args == call(email, password) assert storage.users.get_from_email.call_args == call(email) assert storage.user_tokens.create.call_args == call(user_id, ANY) assert send_grid.send_signup_verification.call_args == call(email, ANY) assert result is None
def test_resend_user_token_already_verified(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage, send_grid = replace_dependencies(container, "storage", "send_grid") runner.start() email = "*****@*****.**" password = "******" user_id = 1 storage.users.is_correct_password.return_value = True storage.users.get_from_email.return_value = { "id": user_id, "email": email, "verified": True, } with entrypoint_hook(container, "resend_user_token") as resend_user_token: with pytest.raises(UserNotAuthorised): resend_user_token(email, password) assert storage.users.is_correct_password.call_args == call(email, password) assert storage.users.get_from_email.call_args == call(email)
def test_create_stripe_checkout_session_unsuccessful(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) stripe = replace_dependencies(container, "stripe") runner.start() user_id = 123 email = "*****@*****.**" plan = "plan1" success_url = "http://success.com" cancel_url = "http://cancel.com" project_id = 1 stripe.checkout.Session.create.side_effect = stripe_payment.error.StripeError() with entrypoint_hook( container, "create_stripe_checkout_session" ) as create_stripe_checkout_session: with pytest.raises(UnableToCreateCheckoutSession): create_stripe_checkout_session( { "user_id": user_id, "email": email, "plan": plan, "success_url": success_url, "cancel_url": cancel_url, "project_id": project_id, } )
def create(*dependencies, **dependency_map): """ Create service instance with specified dependencies mocked Dependencies named in *dependencies will be replaced with a `MockDependencyProvider`, which injects a `MagicMock` instead of the dependency. Alternatively, you may use `dependency_map` keyword arguments to name a dependency and provide the replacement value that the `MockDependencyProvider` should inject. For more information read: https://github.com/onefinestay/nameko/blob/master/nameko/testing/services.py#L325 """ dependency_names = list(dependencies) + list(dependency_map.keys()) ServiceMeta = namedtuple('ServiceMeta', ['container'] + dependency_names) container = container_factory(GatewayService, config) mocked_dependencies = replace_dependencies(container, *dependencies, **dependency_map) if len(dependency_names) == 1: mocked_dependencies = (mocked_dependencies, ) container.start() return ServiceMeta(container, *mocked_dependencies, **dependency_map)
def test_auth_successful(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage = replace_dependencies(container, "storage") runner.start() user_id = 123 email = "*****@*****.**" password = "******" storage.users.is_correct_password.return_value = True storage.users.get_from_email.return_value = { "id": user_id, "email": email, "verified": True, } with entrypoint_hook(container, "auth_user") as auth_user: result = auth_user(email=email, password=password) assert result == {"JWT": ANY} assert storage.users.is_correct_password.call_args == call( email, password) assert storage.users.get_from_email.call_args == call(email)
def test_create_user_successful(config, db, runner_factory): runner = runner_factory(MonitoringService) container = get_container(runner, MonitoringService) storage = replace_dependencies(container, "storage") runner.start() storage.api_requests.append.return_value = None with entrypoint_hook( container, "consume_monitoring_stream") as consume_monitoring_stream: consume_monitoring_stream( "123", { "__MONITOR_NAME": "API_REQUEST", "url": "url", "method": "method", "duration": "duration", "status": "status", "status_code": 1, "remote_addr": "remote_addr", }, ) assert storage.api_requests.append.call_args == call( "123", "url", "method", "duration", "status", 1, "remote_addr")
def test_fetching(container_factory, rpc_proxy_factory, web_container_config): http_container = container_factory(HttpService, web_container_config) http_server = rpc_proxy_factory('http_server') dispatch = replace_dependencies(http_container, 'dispatch') http_container.start() http_server.fetch('http://example.org/test_fetching') assert dispatch.call_count == 1
def test_create_user_successful(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage, send_grid = replace_dependencies(container, "storage", "send_grid") runner.start() storage.users.create.return_value = 1 storage.user_tokens.create.return_value = "randomtoken" payload = { "email": "*****@*****.**", "password": "******", "display_name": "Test Account", } with entrypoint_hook(container, "create_user") as create_user: with patch("accounts.service.users.generate_token", return_value="token"): result = create_user(user_details=payload) assert result == 1 assert storage.users.create.call_args == call(payload["email"], payload["password"], payload["display_name"]) assert send_grid.send_signup_verification.call_args == call( payload["email"], "token")
def test_shop_checkout_integration( rabbit_config, runner_factory, rpc_proxy_factory ): """ Simulate a checkout flow as an integration test. Requires instances of AcmeShopService, StockService and InvoiceService to be running. Explicitly replaces the rpc proxy to PaymentService so that service doesn't need to be hosted. Also replaces the event dispatcher dependency on AcmeShopService and disables the timer entrypoint on StockService. Limiting the interactions of services in this way reduces the scope of the integration test and eliminates undesirable side-effects (e.g. processing events unnecessarily). """ context_data = {'user_id': 'wile_e_coyote'} shop = rpc_proxy_factory('acmeshopservice', context_data=context_data) runner = runner_factory( rabbit_config, AcmeShopService, StockService, InvoiceService) # replace ``event_dispatcher`` and ``payment_rpc`` dependencies on # AcmeShopService with ``MockDependencyProvider``\s shop_container = get_container(runner, AcmeShopService) fire_event, payment_rpc = replace_dependencies( shop_container, "fire_event", "payment_rpc") # restrict entrypoints on StockService stock_container = get_container(runner, StockService) restrict_entrypoints(stock_container, "check_price", "check_stock") runner.start() # add some items to the basket assert shop.add_to_basket("anvil") == "anvil" assert shop.add_to_basket("invisible_paint") == "invisible_paint" # try to buy something that's out of stock with pytest.raises(RemoteError) as exc_info: shop.add_to_basket("toothpicks") assert exc_info.value.exc_type == "ItemOutOfStockError" # provide a mock response from the payment service payment_rpc.take_payment.return_value = "Payment complete." # checkout res = shop.checkout() total_amount = 100 + 10 assert res == total_amount # verify integration with mocked out payment service payment_rpc.take_payment.assert_called_once_with({ 'customer': "wile_e_coyote", 'address': "12 Long Road, High Cliffs, Utah", 'amount': total_amount, 'message': "Dear Wile E Coyote. Please pay $110 to ACME Corp." }) # verify events fired as expected assert fire_event.call_count == 3
def test_retrieve_group_excluded(container_factory, web_session, web_container_config): http_container = container_factory(HttpService, web_container_config) storage = replace_dependencies(http_container, 'storage') storage.get_group = lambda group_hash: { 'url': group_hash, 'name': 'datagouvfr', 'url_hash1': 'url1', 'url_hash2': 'url1', } def get_url(url_hash): result = {'url': url_hash} if url_hash == 'url_hash1': result['metadata'] = 'meta' return result storage.get_url = get_url http_container.start() rv = web_session.get('/group', data=json.dumps({ 'group': 'datagouvfr', 'exclude_metadata': 'meta' })) result = rv.json() assert result['name'] == 'datagouvfr' assert 'url_hash1' not in result['urls'][0].values() assert 'url_hash2' in result['urls'][0].values()
def test_replace_dependencies_kwargs(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") @rpc def method(self, arg): self.foo_proxy.remote_method(arg) class FakeDependency(object): def __init__(self): self.processed = [] def remote_method(self, arg): self.processed.append(arg) container = container_factory(Service, rabbit_config) # customise a single dependency fake_foo_proxy = FakeDependency() replace_dependencies(container, foo_proxy=fake_foo_proxy) assert 2 == len([ dependency for dependency in container.extensions if isinstance(dependency, RpcProxy) ]) # customise multiple dependencies res = replace_dependencies(container, bar_proxy=Mock(), baz_proxy=Mock()) assert list(res) == [] # verify that container.extensions doesn't include an RpcProxy anymore assert all([ not isinstance(dependency, RpcProxy) for dependency in container.extensions ]) container.start() # verify that the fake dependency collected calls msg = "msg" with ServiceRpcProxy("service", rabbit_config) as service_proxy: service_proxy.method(msg) assert fake_foo_proxy.processed == [msg]
def service_container(config, container_factory, mocked_dependencies): container = container_factory(OrdersService, config) mocked_dependencies["event_dispatcher"] = replace_dependencies( container, "event_dispatcher") container.start() yield container container.stop()
def test_email_failed(runner_factory, rabbit_config): config = { 'AMQP_URI': 'pyamqp://*****:*****@localhost', 'api_key': None, 'sandbox': None } runner = runner_factory(rabbit_config, EmailSender) container = get_container(runner, EmailSender) replace_dependencies(container, config=config) container.start() with entrypoint_hook(container, "send_email") as entrypoint: assert entrypoint(PAYLOAD) == 401 container.stop()
def test_email_successful(runner_factory, rabbit_config): config = { 'AMQP_URI': 'pyamqp://*****:*****@localhost', 'api_key': 'key-9e3925925d8652ea483d1f99c470650f', 'sandbox': 'sandboxa335ce01177d450eafe21b96efc07255.mailgun.org' } runner = runner_factory(rabbit_config, EmailSender) container = get_container(runner, EmailSender) replace_dependencies(container, config=config) container.start() with entrypoint_hook(container, "send_email") as entrypoint: assert entrypoint(PAYLOAD) == 200 container.stop()
def test_health_check(config, web_session): container = ServiceContainer(GatewayService) redis = replace_dependencies(container, "redis") container.start() redis.set.return_value = "1" response = web_session.get("/health-check") assert response.status_code == 200
def test_replace_non_dependency(container_factory, rabbit_config): class Service(object): name = "service" proxy = RpcProxy("foo_service") @rpc def method(self): pass # pragma: no cover container = container_factory(Service, rabbit_config) # error if dependency doesn't exit with pytest.raises(ExtensionNotFound): replace_dependencies(container, "nonexist") # error if dependency is not an dependency with pytest.raises(ExtensionNotFound): replace_dependencies(container, "method")
def test_checking_one(container_factory, web_session, web_container_config): http_container = container_factory(HttpService, web_container_config) dispatch = replace_dependencies(http_container, 'dispatch') http_container.start() rv = web_session.post('/check/one', data=json.dumps( {'url': 'http://example.org/test_checking_one'})) assert rv.json()['url-hash'] == 'u:a55f9fb5' assert dispatch.call_count == 1
def test_replace_non_dependency(container_factory, rabbit_config): class Service(object): name = "service" proxy = RpcProxy("foo_service") @rpc def method(self): pass container = container_factory(Service, rabbit_config) # error if dependency doesn't exit with pytest.raises(ExtensionNotFound): replace_dependencies(container, "nonexist") # error if dependency is not an dependency with pytest.raises(ExtensionNotFound): replace_dependencies(container, "method")
def test_fetching_w_webhook(container_factory, rpc_proxy_factory, web_container_config): http_container = container_factory(HttpService, web_container_config) http_server = rpc_proxy_factory('http_server') storage = replace_dependencies(http_container, 'storage') http_container.start() http_server.fetch('http://example.org/test_fetching', callback_url='http://example.org/cb') assert storage.store_webhook.call_count == 1
def test_retrieve_url(container_factory, web_session, web_container_config): http_container = container_factory(HttpService, web_container_config) storage = replace_dependencies(http_container, 'storage') storage.get_url = lambda url_hash: {'url': url_hash} http_container.start() rv = web_session.get('/url', data=json.dumps( {'url': 'http://example.org/test_retrieve_url'})) assert rv.json()['url'] == 'u:9c01c218' assert 'group' not in rv.json()
def test_replace_dependencies_kwargs(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") @rpc def method(self, arg): self.foo_proxy.remote_method(arg) class FakeDependency(object): def __init__(self): self.processed = [] def remote_method(self, arg): self.processed.append(arg) container = container_factory(Service, rabbit_config) # customise a single dependency fake_foo_proxy = FakeDependency() replace_dependencies(container, foo_proxy=fake_foo_proxy) assert 2 == len([dependency for dependency in container.extensions if isinstance(dependency, RpcProxy)]) # customise multiple dependencies res = replace_dependencies(container, bar_proxy=Mock(), baz_proxy=Mock()) assert list(res) == [] # verify that container.extensions doesn't include an RpcProxy anymore assert all([not isinstance(dependency, RpcProxy) for dependency in container.extensions]) container.start() # verify that the fake dependency collected calls msg = "msg" with ServiceRpcProxy("service", rabbit_config) as service_proxy: service_proxy.method(msg) assert fake_foo_proxy.processed == [msg]
def test_checking_one_webhook_wrong_url(container_factory, web_session, web_container_config): http_container = container_factory(HttpService, web_container_config) storage = replace_dependencies(http_container, 'storage') http_container.start() web_session.post('/check/one', data=json.dumps({ 'url': 'http://example.org/test_checking_one', 'callback_url': 'not-an-url' })) assert storage.store_webhook.call_count == 0
def test_image_not_found(self, config, web_session): image_svc = ServiceContainer(ImageService, config) mock_image_host = replace_dependencies(image_svc, 'image_host') mock_image_host.download_image.side_effect = ImageNotFound('not found') image_svc.start() response = web_session.get('/image/abc123') assert response.status_code == 404 assert response.text == 'not found'
def test_auth_user_incorrect_schema(config, web_session): container = ServiceContainer(GatewayService) accounts = replace_dependencies(container, "accounts_rpc") container.start() accounts.auth_user.side_effect = UserNotAuthorised() response = web_session.post("/v1/user/auth", data=json.dumps({})) assert response.status_code == 400 assert response.json() == {"error": "VALIDATION_ERROR", "message": ANY}
def test_get_unsuccessful(config, runner_factory): runner = runner_factory(AccountsService) container = get_container(runner, AccountsService) storage = replace_dependencies(container, "storage") runner.start() storage.users.get.side_effect = orm_exc.NoResultFound() with entrypoint_hook(container, "get_user") as get_user: with pytest.raises(UserDoesNotExist): get_user(user_id=123)
def test_verify_user_token_incorrect_schema(config, web_session): container = ServiceContainer(GatewayService) accounts = replace_dependencies(container, "accounts_rpc") container.start() email = "*****@*****.**" response = web_session.post("/v1/user/token", data=json.dumps({"email": email})) assert response.status_code == 400 assert response.json() == {"error": "VALIDATION_ERROR", "message": ANY}
def test_crawler_triggers_webhook(runner_factory, web_container_config): """Is crawler_container dispatching to webhook_container?""" runner = runner_factory(web_container_config, CrawlerService, WebhookService) webhook_container = get_container(runner, WebhookService) storage_w = replace_dependencies(webhook_container, 'storage') dispatch = event_dispatcher(web_container_config) runner.start() with entrypoint_waiter(webhook_container, 'send_response'): dispatch('http_server', 'url_to_check', ['http://example.org/test_crawling_group', 'datagouvfr', None]) assert storage_w.get_webhooks_for_url.call_count == 1
def test_crawling_url(container_factory, rabbit_config, web_container_config): crawler_container = container_factory(CrawlerService, web_container_config) storage, dispatch_dep = replace_dependencies(crawler_container, 'storage', 'dispatch') crawler_container.start() dispatch = event_dispatcher(rabbit_config) with entrypoint_waiter(crawler_container, 'check_url'): dispatch('http_server', 'url_to_check', ['http://example.org/test_crawling_url', None, None]) assert storage.store_url.call_count == 1 assert storage.store_group.call_count == 0 assert storage.store_metadata.call_count == 1 # fired 'url_crawled' assert dispatch_dep.call_count == 1
def test_replace_dependencies_args_and_kwargs(container_factory, rabbit_config): class Service(object): name = "service" foo_proxy = RpcProxy("foo_service") bar_proxy = RpcProxy("bar_service") baz_proxy = RpcProxy("baz_service") @rpc def method(self, arg): self.foo_proxy.remote_method(arg) self.bar_proxy.bar() self.baz_proxy.baz() class FakeDependency(object): def __init__(self): self.processed = [] def remote_method(self, arg): self.processed.append(arg) container = container_factory(Service, rabbit_config) fake_foo_proxy = FakeDependency() mock_bar_proxy, mock_baz_proxy = replace_dependencies( container, 'bar_proxy', 'baz_proxy', foo_proxy=fake_foo_proxy ) # verify that container.extensions doesn't include an RpcProxy anymore assert all([not isinstance(dependency, RpcProxy) for dependency in container.extensions]) container.start() # verify that the fake dependency collected calls msg = "msg" with ServiceRpcProxy("service", rabbit_config) as service_proxy: service_proxy.method(msg) assert fake_foo_proxy.processed == [msg] assert mock_bar_proxy.bar.call_count == 1 assert mock_baz_proxy.baz.call_count == 1
def test_crawling_head_offender_url( container_factory, web_container_config, rmock=None): url_to_check = 'http://example-head.com/test_crawling_url' rmock.head(url_to_check) rmock.get(url_to_check, text='xxx') web_container_config['HEAD_DOMAINS_BLACKLIST'] = ['example-head.com'] crawler_container = container_factory(CrawlerService, web_container_config) storage = replace_dependencies(crawler_container, 'storage') crawler_container.start() dispatch = event_dispatcher(web_container_config) with entrypoint_waiter(crawler_container, 'check_url'): dispatch('http_server', 'url_to_check', [url_to_check, None, None]) assert storage.store_url.call_count == 1 assert storage.store_group.call_count == 0 assert storage.store_metadata.call_count == 1 # check that no HEAD method was called requests_l = filter_mock_requests(url_to_check, rmock.request_history) assert len(requests_l) == 1 assert requests_l[0].method == 'GET'