require 'spec_helper' require 'protobuf/rpc/service_dispatcher' describe Protobuf::Rpc::ServiceDispatcher do let(:service_name) { 'Test::ResourceService' } let(:method_name) { 'find' } let(:request) { Test::ResourceFindRequest.new(:name => 'resource') } let(:request_bytes) { request.to_s } let(:response) { Test::Resource.new } let(:wrapper) do Protobuf::Socketrpc::Request.new({ :service_name => service_name, :method_name => method_name, :request_proto => request_bytes }) end subject { described_class.new(wrapper) } context 'creating a new dispatcher' do its(:service) { should be_instance_of service_name.constantize } its(:callable_method) { should respond_to(:call)} its(:outer_request) { should eq wrapper } its(:error) { should be_nil } context 'when service name is not a valid constant' do let(:service_name) { 'FlibbityGibbit' } its(:success?) { should be_false } its(:error) { should be_instance_of(Protobuf::Rpc::ServiceNotFound) } end context 'when method is not defined by the service' do let(:method_name) { 'holly_hooby_whaty' } its(:success?) { should be_false } its(:error) { should be_instance_of(Protobuf::Rpc::MethodNotFound) } end context 'when method is defined but is not an rpc method' do before do class Test::Resource def non_rpc_method; end end end let(:method_name) { 'non_rpc_method' } its(:success?) { should be_false } its(:error) { should be_instance_of(Protobuf::Rpc::MethodNotFound) } end end describe '#invoke!' do context 'regular invocation' do before { subject.callable_method.should_receive(:call) } before { subject.service.stub(:response).and_return(response) } before { subject.invoke! } its(:response) { should be_instance_of Test::Resource } its(:success?) { should be_true } end context 'when service responds with' do context 'a hash object' do before { subject.callable_method.should_receive(:call) } before { subject.service.stub(:response).and_return({ :name => 'returned' }) } before { subject.invoke! } its(:success?) { should be_true } its(:response) { should eq Test::Resource.new(:name => 'returned') } end context 'an object that responds to to_hash but is not a hash' do let(:hashable) do mock('hashable', :to_hash => { :name => 'hashable' }) end before { subject.callable_method.should_receive(:call) } before { subject.service.stub(:response).and_return(hashable) } before { subject.invoke! } its(:success?) { should be_true } its(:response) { should eq Test::Resource.new(:name => 'hashable') } end context 'an object that responds to to_proto' do let(:protoable) do mock('protoable', :to_proto => Test::Resource.new(:name => 'protoable')) end before { subject.callable_method.should_receive(:call) } before { subject.service.stub(:response).and_return(protoable) } before { subject.invoke! } its(:success?) { should be_true } its(:response) { should eq Test::Resource.new(:name => 'protoable') } end context 'a type not identified by the rpc definition' do before { subject.callable_method.should_receive(:call) } before { subject.service.stub(:response).and_return("I'm not a valid response") } before { subject.invoke! } its(:error) { should be_instance_of(Protobuf::Rpc::BadResponseProto) } end end context 'when service invokes rpc failed callback' do before(:all) do class Test::ResourceService rpc :find_with_rpc_failed, Test::ResourceFindRequest, Test::Resource def find_with_rpc_failed rpc_failed('Find failed') end end end let(:method_name) { 'find_with_rpc_failed' } before { subject.service.find_with_rpc_failed } its(:success?) { should be_false } its(:error) { should be_instance_of(Protobuf::Rpc::RpcFailed) } end end end