# encoding: utf-8 require 'spec_helper' describe Hatetepe::Client do let(:client) { described_class.new(config) } let(:config) do { address: localhost, port: random_port, handlers: [handler_class] } end let(:connection) do double('connection', parse: nil, closed: double('closed', then: nil), serialize: nil, close: nil) end let(:handler_class) { double('handler_class', new: handler) } let(:handler) { double('handler', setup: nil, receive: nil) } before do allow(EM).to receive(:connect) { connection } end describe '#initialize' do subject! { client } its(:config) { should be(config) } specify do expect(EM).to have_received(:connect) .with(config[:address], config[:port], Hatetepe::Connection::EventMachine) expect(connection).to have_received(:parse) .with(subject.method(:receive)) expect(connection.closed).to have_received(:then) .with(subject.method(:teardown)) end specify do expect(handler_class).to have_received(:new) .with(config, subject, connection) expect(handler).to have_received(:setup) end end describe '#request' do let(:request) do double('request', body: double(closed: double('closed', fulfill: nil))) end let(:response) { double('response') } let(:served) { double('served', sync: response) } before do allow(Hatetepe::Request).to receive(:new) { request } allow(client).to receive(:perform) { served } end subject! { client.request(:head, '/wat') } specify do expect(Hatetepe::Request).to have_received(:new).with(:head, '/wat') expect(client).to have_received(:perform).with(request) expect(subject).to be(response) end end describe '#perform' do let(:request) { double('request') } before do @fibers = [] allow(handler).to receive(:perform) { @fibers << Fiber.current } allow(connection).to receive(:serialize) { @fibers << Fiber.current } end subject! { client.perform(request) } specify do expect(handler).to have_received(:perform).with(request) expect(connection).to have_received(:serialize).with(request) expect(@fibers).not_to include(Fiber.current) expect(@fibers.uniq).to be_one end end describe '#close' do subject! { client.close } specify do expect(connection).to have_received(:close) end end describe '#receive' do let(:request) { open_request } let(:response) { open_response } describe 'with outstanding request' do let!(:served) { client.perform(request) } subject! { client.receive(response) } it 'correlates response with request, and notifies handlers' do expect(served).to be_fulfilled expect(served.value).to be(response) expect(handler).to have_received(:receive).with(request, response) end end describe 'without outstanding request' do subject { client.receive(response) } it 'fails' do expect { subject }.to raise_error(Hatetepe::ClientError, /correlate/) end end [:fulfill, :reject].each do |action| describe "after #{action}ed response" do let(:request) { WeakRef.new(closed_request) } let(:response) { WeakRef.new(open_response) } before do client.perform(request.__getobj__) client.receive(response.__getobj__) end subject do response.finished.send(action) tick RSpec::Mocks.teardown GC.start end it 'cleans up' do if RUBY_VERSION == '1.9.3' && RUBY_PATCHLEVEL == 392 expect { subject }.to change { request.weakref_alive? }.to(false) else pending end end end end end describe '#teardown' do let(:requests) { [closed_request, closed_request] } let(:responses) { [open_response] } let(:reason) { double } let!(:served) do requests.map { |request| client.perform(request) } end before do client.receive(responses[0]) end subject! { client.teardown(reason) } it 'rejects outstanding requests and responses' do expect(responses[0].body.closed).to be_rejected expect(responses[0].body.closed.reason).to be(reason) expect(served[1]).to be_rejected expect(served[1].reason).to be(reason) end end end