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_restrict_nonexistent_entrypoint(container_factory, rabbit_config): class Service(object): @rpc def method(self, arg): pass container = container_factory(Service, rabbit_config) with pytest.raises(DependencyNotFound): restrict_entrypoints(container, "nonexist")
def test_restrict_entrypoints(container_factory, rabbit_config): method_called = Mock() class OnceProvider(EntrypointProvider): """ Entrypoint that spawns a worker exactly once, as soon as the service container started. """ def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def start(self): self.container.spawn_worker(self, self.args, self.kwargs) @entrypoint def once(*args, **kwargs): return DependencyFactory(OnceProvider, args, kwargs) class ExampleEvent(Event): type = "eventtype" class Service(object): @rpc @once("assert not seen") def handler_one(self, arg): method_called(arg) @event_handler('srcservice', 'eventtype') def handler_two(self, msg): method_called(msg) container = container_factory(Service, rabbit_config) # disable the entrypoints on handler_one restrict_entrypoints(container, "handler_two") container.start() # verify the rpc entrypoint on handler_one is disabled with RpcProxy("service", rabbit_config) as service_proxy: with pytest.raises(MethodNotFound) as exc_info: service_proxy.handler_one("msg") assert exc_info.value.message == "handler_one" # dispatch an event to handler_two msg = "msg" with event_dispatcher('srcservice', rabbit_config) as dispatch: with entrypoint_waiter(container, 'handler_two'): dispatch(ExampleEvent(msg)) # method_called should have exactly one call, derived from the event # handler and not from the disabled @once entrypoint method_called.assert_called_once_with(msg)
def test_restrict_nonexistent_entrypoint(container_factory, rabbit_config): class Service(object): name = "service" @rpc def method(self, arg): pass # pragma: no cover container = container_factory(Service, rabbit_config) with pytest.raises(ExtensionNotFound): restrict_entrypoints(container, "nonexist")
def test_restrict_entrypoint_container_already_started(container_factory, rabbit_config): class Service(object): @rpc def method(self, arg): pass container = container_factory(Service, rabbit_config) container.start() with pytest.raises(RuntimeError): restrict_entrypoints(container, "method")
def test_restrict_entrypoint_container_already_started(container_factory, rabbit_config): class Service(object): name = "service" @rpc def method(self, arg): pass container = container_factory(Service, rabbit_config) container.start() with pytest.raises(RuntimeError): restrict_entrypoints(container, "method")
def test_rpc_consumer_unregisters_if_no_providers(container_factory, rabbit_config): class Service(object): name = "service" @rpc def method(self): pass # pragma: no cover container = container_factory(Service, rabbit_config) restrict_entrypoints(container) # disable 'method' entrypoint rpc_consumer = get_extension(container, RpcConsumer) with patch.object(rpc_consumer, 'queue_consumer') as queue_consumer: rpc_consumer.stop() assert queue_consumer.unregister_provider.called assert rpc_consumer._unregistered_from_queue_consumer.ready()
def test_shop_checkout_integration(rabbit_config, runner_factory, rpc_proxy_factory): context_data = {"user_id": "wile_e_coyote"} shop = rpc_proxy_factory("acmesshopservice", context_data=context_data) runner = runner_factory(rabbit_config, AcmeShopService, StockService, InvoiceService) shop_container = get_container(runner, AcmeShopService) fire_event, payment_rpc = replace_dependencies(shop_container, "fire_event", "payment_rpc") stock_container = get_container(runner, StockService) restrict_entrypoints(stock_container, "check_price", "check_stock") runner.start() assert shop.add_to_basket("anvil") == "anvil" assert shop.add_to_basket("invisible_paint") == "invisible_paint" with pytest.raises(RemoteError) as exc_info: shop.add_to_basket("toothpicks") assert exc_info.value.exc_type == "ItemOutOfStockError" payment_rpc.take_payment.return_value = "Payment complete." res = shop.checkout() total_amount = 100 + 10 assert res == total_amount 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.", }) assert fire_event.call_count == 3
def test_rpc_consumer_unregisters_if_no_providers( container_factory, rabbit_config ): class Service(object): name = "service" @rpc def method(self): pass container = container_factory(Service, rabbit_config) restrict_entrypoints(container) # disable 'method' entrypoint rpc_consumer = get_extension(container, RpcConsumer) with patch.object(rpc_consumer, 'queue_consumer') as queue_consumer: rpc_consumer.stop() assert queue_consumer.unregister_provider.called assert rpc_consumer._unregistered_from_queue_consumer.ready()
def test_restrict_entrypoints(container_factory, rabbit_config): method_called = Mock() class Service(object): name = "service" @rpc @once("assert not seen") def handler_one(self, arg): method_called(arg) @event_handler("srcservice", "eventtype") def handler_two(self, msg): method_called(msg) container = container_factory(Service, rabbit_config) # disable the entrypoints on handler_one restrict_entrypoints(container, "handler_two") container.start() # verify the rpc entrypoint on handler_one is disabled with ServiceRpcProxy("service", rabbit_config) as service_proxy: with pytest.raises(MethodNotFound) as exc_info: service_proxy.handler_one("msg") assert str(exc_info.value) == "handler_one" # dispatch an event to handler_two msg = "msg" dispatch = event_dispatcher(rabbit_config) with entrypoint_waiter(container, "handler_two"): dispatch("srcservice", "eventtype", msg) # method_called should have exactly one call, derived from the event # handler and not from the disabled @once entrypoint method_called.assert_called_once_with(msg)
def test_restrict_entrypoints(container_factory, rabbit_config): method_called = Mock() class Service(object): name = "service" @rpc @once("assert not seen") def handler_one(self, arg): method_called(arg) @event_handler('srcservice', 'eventtype') def handler_two(self, msg): method_called(msg) container = container_factory(Service, rabbit_config) # disable the entrypoints on handler_one restrict_entrypoints(container, "handler_two") container.start() # verify the rpc entrypoint on handler_one is disabled with ServiceRpcProxy("service", rabbit_config) as service_proxy: with pytest.raises(MethodNotFound) as exc_info: service_proxy.handler_one("msg") assert str(exc_info.value) == "handler_one" # dispatch an event to handler_two msg = "msg" dispatch = event_dispatcher(rabbit_config) with entrypoint_waiter(container, 'handler_two'): dispatch('srcservice', 'eventtype', msg) # method_called should have exactly one call, derived from the event # handler and not from the disabled @once entrypoint method_called.assert_called_once_with(msg)
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