From ebb6fb09280f828258432223fd543de9dfda6370 Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Sat, 25 Jun 2005 06:27:39 +0000 Subject: [PATCH] fix WSDL generation, change the way protocols are instantiated, and add the ability to override the namespace used in WSDL instead of always forcing 'urn:ActionWebService' git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1501 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionwebservice/CHANGELOG | 4 ++ actionwebservice/README | 31 ++++++++++--- .../action_web_service/client/soap_client.rb | 15 +++---- .../action_controller_dispatcher.rb | 7 +-- .../action_web_service/protocol/abstract.rb | 3 ++ .../action_web_service/protocol/discovery.rb | 4 +- .../protocol/soap_protocol.rb | 16 +++++-- .../protocol/soap_protocol/marshaler.rb | 12 ++--- .../protocol/xmlrpc_protocol.rb | 4 ++ .../lib/action_web_service/scaffolding.rb | 4 +- .../lib/action_web_service/test_invoke.rb | 6 +-- actionwebservice/test/abstract_dispatcher.rb | 44 +++++++++++++------ .../dispatcher_action_controller_soap_test.rb | 16 +++---- ...ispatcher_action_controller_xmlrpc_test.rb | 2 +- 14 files changed, 112 insertions(+), 56 deletions(-) diff --git a/actionwebservice/CHANGELOG b/actionwebservice/CHANGELOG index f5e6e575b4..fc7243bf77 100644 --- a/actionwebservice/CHANGELOG +++ b/actionwebservice/CHANGELOG @@ -1,5 +1,9 @@ *SVN* +* Fix WSDL generation by aliasing #inherited instead of trying to overwrite it, or the WSDL action may end up not being defined in the controller + +* Add ActionController::Base.wsdl_namespace option, to allow overriding of the namespace used in generated WSDL and SOAP messages. This is equivalent to the [WebService(Namespace = "Value")] attribute in .NET. + * Add workaround for Ruby 1.8.3's SOAP4R changing the return value of SOAP::Mapping::Registry#find_mapped_soap_class #1414 [Shugo Maeda] * Fix moduled controller URLs in WSDL, and add unit test to verify the generated URL #1428 diff --git a/actionwebservice/README b/actionwebservice/README index 8f4588cdf9..a725f9db71 100644 --- a/actionwebservice/README +++ b/actionwebservice/README @@ -78,15 +78,15 @@ modes of dispatching protocol requests, _Direct_, and _Delegated_. === Direct dispatching -This is the default mode. In this mode, controller actions implement the API -methods, and parameters for incoming method calls will be placed in -@params (keyed by name), and @method_params (ordered list). +This is the default mode. In this mode, public controller instance methods +implement the API methods, and parameters are passed through to the methods in +accordance with the API specification. -The return value of the action is sent back as the return value to the +The return value of the method is sent back as the return value to the caller. In this mode, a special api action is generated in the target -controller to unwrap the protocol request, forward it on to the relevant action +controller to unwrap the protocol request, forward it on to the relevant method and send back the wrapped return value. This action must not be overridden. @@ -108,7 +108,7 @@ overridden. For this example, protocol requests for +Add+ and +Remove+ methods sent to -/person/api will be routed to the actions +add+ and +remove+. +/person/api will be routed to the controller methods +add+ and +remove+. === Delegated dispatching @@ -196,6 +196,25 @@ For this example, an XML-RPC call for a method with a name like method on the :mt service. +== Customizing WSDL generation + +You can customize the names used for the SOAP bindings in the generated +WSDL by using the wsdl_service_name option in a controller: + + class WsController < ApplicationController + wsdl_service_name 'MyApp' + end + +You can also customize the namespace used in the generated WSDL for +custom types and message definition types: + + class WsController < ApplicationController + wsdl_namespace 'http://my.company.com/app/wsapi' + end + +The default namespace used is 'urn:ActionWebService', if you don't supply +one. + == Testing your APIs diff --git a/actionwebservice/lib/action_web_service/client/soap_client.rb b/actionwebservice/lib/action_web_service/client/soap_client.rb index 79edac0b71..f5ebe1629c 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -24,10 +24,10 @@ module ActionWebService # :nodoc: # will be sent with HTTP POST. # # Valid options: - # [:type_namespace] If the remote server has used a custom namespace to - # declare its custom types, you can specify it here - # [:method_namespace] If the remote server has used a custom namespace to - # declare its methods, you can specify it here + # [:namespace] If the remote server has used a custom namespace to + # declare its custom types, you can specify it here. This would + # be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute + # in .NET, for example. # [:driver_options] If you want to supply any custom SOAP RPC driver # options, you can provide them as a Hash here # @@ -43,10 +43,9 @@ module ActionWebService # :nodoc: # client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts) def initialize(api, endpoint_uri, options={}) super(api, endpoint_uri) - @type_namespace = options[:type_namespace] || 'urn:ActionWebService' - @method_namespace = options[:method_namespace] || 'urn:ActionWebService' + @namespace = options[:namespace] || 'urn:ActionWebService' @driver_options = options[:driver_options] || {} - @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace @soap_action_base = options[:soap_action_base] @soap_action_base ||= URI.parse(endpoint_uri).path @driver = create_soap_rpc_driver(api, endpoint_uri) @@ -73,7 +72,7 @@ module ActionWebService # :nodoc: driver = SoapDriver.new(endpoint_uri, nil) driver.mapping_registry = @protocol.marshaler.registry api.api_methods.each do |name, method| - qname = XSD::QName.new(@method_namespace, method.public_name) + qname = XSD::QName.new(@namespace, method.public_name) action = soap_action(method.public_name) expects = method.expects returns = method.returns diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb index 21e3ccedbf..d3d74be289 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -6,9 +6,11 @@ module ActionWebService # :nodoc: module ActionController # :nodoc: def self.append_features(base) # :nodoc: super + base.extend(ClassMethods) base.class_eval do class << self alias_method :inherited_without_action_controller, :inherited + alias_method :inherited, :inherited_with_action_controller end alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke end @@ -24,12 +26,11 @@ module ActionWebService # :nodoc: klass.class_eval 'def api; dispatch_web_service_request; end' end end - base.extend(ClassMethods) base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods) end module ClassMethods # :nodoc: - def inherited(child) + def inherited_with_action_controller(child) inherited_without_action_controller(child) child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction) end @@ -174,7 +175,7 @@ module ActionWebService # :nodoc: xml = '' dispatching_mode = web_service_dispatching_mode global_service_name = wsdl_service_name - namespace = 'urn:ActionWebService' + namespace = wsdl_namespace || 'urn:ActionWebService' soap_action_base = "/#{controller_name}" marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace) diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 3819aa2ade..be5bda2d41 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -4,6 +4,9 @@ module ActionWebService # :nodoc: end class AbstractProtocol # :nodoc: + def setup(controller) + end + def decode_action_pack_request(action_pack_request) end diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb index a911c7d017..3d4e0818da 100644 --- a/actionwebservice/lib/action_web_service/protocol/discovery.rb +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -16,7 +16,7 @@ module ActionWebService # :nodoc: private def discover_web_service_request(action_pack_request) (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| - protocol = protocol.new + protocol = protocol.create(self) request = protocol.decode_action_pack_request(action_pack_request) return request unless request.nil? end @@ -25,7 +25,7 @@ module ActionWebService # :nodoc: def create_web_service_client(api, protocol_name, endpoint_uri, options) (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| - protocol = protocol.new + protocol = protocol.create(self) client = protocol.protocol_client(api, protocol_name, endpoint_uri, options) return client unless client.nil? end diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index 3e5bad0086..e6bb5488ab 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -7,13 +7,21 @@ module ActionWebService # :nodoc: def self.included(base) base.register_protocol(SoapProtocol) base.class_inheritable_option(:wsdl_service_name) + base.class_inheritable_option(:wsdl_namespace) end class SoapProtocol < AbstractProtocol # :nodoc: DefaultEncoding = 'utf-8' - def marshaler - @marshaler ||= SoapMarshaler.new + attr :marshaler + + def initialize(namespace=nil) + namespace ||= 'urn:ActionWebService' + @marshaler = SoapMarshaler.new namespace + end + + def self.create(controller) + SoapProtocol.new(controller.wsdl_namespace) end def decode_action_pack_request(action_pack_request) @@ -47,7 +55,7 @@ module ActionWebService # :nodoc: def encode_request(method_name, params, param_types) param_types.each{ |type| marshaler.register_type(type) } if param_types - qname = XSD::QName.new(marshaler.type_namespace, method_name) + qname = XSD::QName.new(marshaler.namespace, method_name) param_def = [] if param_types params = param_types.zip(params).map do |type, param| @@ -79,7 +87,7 @@ module ActionWebService # :nodoc: return_binding = marshaler.register_type(return_type) marshaler.annotate_arrays(return_binding, return_value) end - qname = XSD::QName.new(marshaler.type_namespace, method_name) + qname = XSD::QName.new(marshaler.namespace, method_name) if return_value.nil? response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) else diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb index 78eee620eb..b36e029669 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb @@ -17,11 +17,11 @@ module ActionWebService end class SoapMarshaler - attr :type_namespace + attr :namespace attr :registry - def initialize(type_namespace=nil) - @type_namespace = type_namespace || 'urn:ActionWebService' + def initialize(namespace=nil) + @namespace = namespace || 'urn:ActionWebService' @registry = Registry.new @type2binding = {} register_static_factories @@ -46,7 +46,7 @@ module ActionWebService qname ||= soap_base_type_name(mapping[0]) type_binding = SoapBinding.new(self, qname, type_type, mapping) else - qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + qname = XSD::QName.new(@namespace, soap_type_name(type_class.name)) @registry.add(type_class, SOAP::SOAPStruct, typed_struct_factory(type_class), @@ -58,7 +58,7 @@ module ActionWebService array_binding = nil if type.array? array_mapping = @registry.find_mapped_soap_class(Array) - qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array') + qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array') array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding) end @@ -88,7 +88,7 @@ module ActionWebService def typed_struct_factory(type_class) if Object.const_defined?('ActiveRecord') if type_class.ancestors.include?(ActiveRecord::Base) - qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + qname = XSD::QName.new(@namespace, soap_type_name(type_class.name)) type_class.instance_variable_set('@qname', qname) return SoapActiveRecordStructFactory.new end diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index dec94ccad0..f09f89142a 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -12,6 +12,10 @@ module ActionWebService # :nodoc: end class XmlRpcProtocol < AbstractProtocol # :nodoc: + def self.create(controller) + XmlRpcProtocol.new + end + def decode_action_pack_request(action_pack_request) service_name = action_pack_request.parameters['action'] decode_request(action_pack_request.raw_post, service_name) diff --git a/actionwebservice/lib/action_web_service/scaffolding.rb b/actionwebservice/lib/action_web_service/scaffolding.rb index ff9a23b1e8..da3d6c137c 100644 --- a/actionwebservice/lib/action_web_service/scaffolding.rb +++ b/actionwebservice/lib/action_web_service/scaffolding.rb @@ -61,9 +61,9 @@ module ActionWebService protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap case protocol_name when :soap - @protocol = Protocol::Soap::SoapProtocol.new + @protocol = Protocol::Soap::SoapProtocol.create(self) when :xmlrpc - @protocol = Protocol::XmlRpc::XmlRpcProtocol.new + @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self) end @invocation_cgi = request.respond_to?(:cgi) ? request.cgi : nil bm = Benchmark.measure do diff --git a/actionwebservice/lib/action_web_service/test_invoke.rb b/actionwebservice/lib/action_web_service/test_invoke.rb index 02ed9ea586..cd9cb115ff 100644 --- a/actionwebservice/lib/action_web_service/test_invoke.rb +++ b/actionwebservice/lib/action_web_service/test_invoke.rb @@ -81,13 +81,13 @@ module Test # :nodoc: def protocol if @protocol.nil? - @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new + @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new(@controller) else case @protocol when :xmlrpc - @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new + @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller) when :soap - @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller) else @protocol end diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb index 78243ee497..12ab08019f 100644 --- a/actionwebservice/test/abstract_dispatcher.rb +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -4,6 +4,8 @@ require 'stringio' class ActionController::Base; def rescue_action(e) raise e end; end module DispatcherTest + WsdlNamespace = 'http://rubyonrails.com/some/namespace' + class Node < ActiveRecord::Base def initialize(*args) super(*args) @@ -135,12 +137,14 @@ module DispatcherTest class DelegatedController < AbstractController web_service_dispatching_mode :delegated + wsdl_namespace WsdlNamespace web_service(:test_service) { @service ||= Service.new; @service } end class LayeredController < AbstractController web_service_dispatching_mode :layered + wsdl_namespace WsdlNamespace web_service(:mt) { @mt_service ||= MTService.new; @mt_service } web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service } @@ -149,6 +153,7 @@ module DispatcherTest class DirectController < AbstractController web_service_api DirectAPI web_service_dispatching_mode :direct + wsdl_namespace WsdlNamespace before_filter :alwaysfail, :only => [:before_filtered] after_filter :alwaysok, :only => [:after_filtered] @@ -239,6 +244,7 @@ module DispatcherTest class VirtualController < AbstractController web_service_api VirtualAPI + wsdl_namespace WsdlNamespace def fallback "fallback!" @@ -307,7 +313,7 @@ module DispatcherCommonTests controller.class.web_service_exception_reporting = true send_garbage_request = lambda do service_name = service_name(controller) - request = @protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest) + request = protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest) response = ActionController::TestResponse.new controller.process(request, response) # puts response.body @@ -348,21 +354,21 @@ module DispatcherCommonTests assert_equal person, @direct_controller.struct_pass_value assert !person.equal?(@direct_controller.struct_pass_value) result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test'}) - case @protocol - when ActionWebService::Protocol::Soap::SoapProtocol + case + when soap? assert_equal(person, @direct_controller.struct_pass_value) assert !person.equal?(@direct_controller.struct_pass_value) - when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol + when xmlrpc? assert_equal(person, @direct_controller.struct_pass_value) assert !person.equal?(@direct_controller.struct_pass_value) end assert_equal person, do_method_call(@direct_controller, 'HashStructReturn')[0] result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test', 'nonexistent_attribute' => 'value'}) - case @protocol - when ActionWebService::Protocol::Soap::SoapProtocol + case + when soap? assert_equal(person, @direct_controller.struct_pass_value) assert !person.equal?(@direct_controller.struct_pass_value) - when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol + when xmlrpc? assert_equal(person, @direct_controller.struct_pass_value) assert !person.equal?(@direct_controller.struct_pass_value) end @@ -398,6 +404,18 @@ module DispatcherCommonTests def check_response(ap_response) end + def protocol + @protocol + end + + def soap? + protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol + end + + def xmlrpc? + protocol.is_a? ActionWebService::Protocol::XmlRpc::XmlRpcProtocol + end + def do_method_call(container, public_method_name, *params) request_env = {} mode = container.web_service_dispatching_mode @@ -417,7 +435,7 @@ module DispatcherCommonTests service_name = $1 real_method_name = $2 end - if @protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol + if soap? public_method_name = real_method_name request_env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{real_method_name}" end @@ -425,26 +443,26 @@ module DispatcherCommonTests method = api.public_api_method_instance(real_method_name) service_name = self.service_name(container) end - @protocol.register_api(api) + protocol.register_api(api) virtual = false unless method virtual = true method ||= ActionWebService::API::Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil) end - body = @protocol.encode_request(public_method_name, params.dup, method.expects) + body = protocol.encode_request(public_method_name, params.dup, method.expects) # puts body - ap_request = @protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest) + ap_request = protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest) ap_request.env.update(request_env) update_request(ap_request) ap_response = ActionController::TestResponse.new container.process(ap_request, ap_response) # puts ap_response.body check_response(ap_response) - public_method_name, return_value = @protocol.decode_response(ap_response.body) + public_method_name, return_value = protocol.decode_response(ap_response.body) unless is_exception?(return_value) || virtual return_value = method.cast_returns(return_value) end - if @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol) + if soap? # http://dev.rubyonrails.com/changeset/920 assert_match(/Response$/, public_method_name) unless public_method_name == "fault" end diff --git a/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/actionwebservice/test/dispatcher_action_controller_soap_test.rb index aa57765b2c..6e332828f2 100644 --- a/actionwebservice/test/dispatcher_action_controller_soap_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -27,12 +27,12 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase @delegated_controller = DelegatedController.new @virtual_controller = VirtualController.new @layered_controller = LayeredController.new - @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@direct_controller) end def test_wsdl_generation - ensure_valid_wsdl_generation DelegatedController.new - ensure_valid_wsdl_generation DirectController.new + ensure_valid_wsdl_generation DelegatedController.new, DispatcherTest::WsdlNamespace + ensure_valid_wsdl_generation DirectController.new, DispatcherTest::WsdlNamespace end def test_wsdl_action @@ -90,12 +90,12 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase container.is_a?(DelegatedController) ? 'test_service' : 'api' end - def ensure_valid_wsdl_generation(controller) + def ensure_valid_wsdl_generation(controller, expected_namespace) wsdl = controller.generate_wsdl - ensure_valid_wsdl(controller, wsdl) + ensure_valid_wsdl(controller, wsdl, expected_namespace) end - def ensure_valid_wsdl(controller, wsdl) + def ensure_valid_wsdl(controller, wsdl, expected_namespace) definitions = WSDL::Parser.new.parse(wsdl) assert(definitions.is_a?(WSDL::Definitions)) definitions.bindings.each do |binding| @@ -108,7 +108,7 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase end types = definitions.collect_complextypes.map{|x| x.name} types.each do |type| - assert(type.namespace == 'urn:ActionWebService') + assert(type.namespace == expected_namespace) end location = definitions.services[0].ports[0].soap_address.location if controller.is_a?(DelegatedController) @@ -125,6 +125,6 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase test_request.env['HTTP_HOST'] = 'localhost' test_response = ActionController::TestResponse.new wsdl = controller.process(test_request, test_response).body - ensure_valid_wsdl(controller, wsdl) + ensure_valid_wsdl(controller, wsdl, DispatcherTest::WsdlNamespace) end end diff --git a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb index f1dc992818..be8d553fc9 100644 --- a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -5,11 +5,11 @@ class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase include DispatcherCommonTests def setup - @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new @direct_controller = DirectController.new @delegated_controller = DelegatedController.new @layered_controller = LayeredController.new @virtual_controller = VirtualController.new + @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@direct_controller) end def test_layered_dispatching