# encoding=utf-8 root = File.expand_path('../../..', __FILE__) require root + '/vendor/em-rspec/lib/em-rspec' require root + '/spec/encoding_helper' require root + '/lib/faye/mixins/logging' require root + '/lib/faye/mixins/publisher' require root + '/lib/faye/mixins/timeouts' require root + '/lib/faye/protocol/channel' require root + '/lib/faye/protocol/grammar' require root + '/lib/faye/engines/proxy' EngineSteps = EM::RSpec.async_steps do def create_client(name, &resume) @inboxes ||= {} @clients ||= {} engine.create_client do |client_id| @clients[name] = client_id @inboxes[name] ||= [] resume.call end end def connect(name, engine, &resume) engine.connect(@clients[name]) do |m| m.each do |message| message.delete("id") @inboxes[name] << message end end EM.add_timer(0.01, &resume) end def destroy_client(name, &resume) engine.destroy_client(@clients[name], &resume) end def check_client_id(name, pattern, &resume) @clients[name].should =~ pattern resume.call end def check_num_clients(n, &resume) ids = Set.new @clients.each { |name,id| ids.add(id) } ids.size.should == n resume.call end def check_client_exists(name, exists, &resume) engine.client_exists(@clients[name]) do |actual| actual.should == exists resume.call end end def subscribe(name, channel, &resume) engine.subscribe(@clients[name], channel, &resume) end def unsubscribe(name, channel, &resume) engine.unsubscribe(@clients[name], channel, &resume) end def publish(messages, &resume) messages = [messages].flatten messages.each do |message| message = {"id" => Faye::Engine.random}.merge(message) engine.publish(message) end EM.add_timer(0.01, &resume) end def publish_by(name, message, &resume) message = {"clientId" => @clients[name], "id" => Faye::Engine.random}.merge(message) engine.publish(message) EM.add_timer(0.01, &resume) end def ping(name, &resume) engine.ping(@clients[name]) resume.call end def clock_tick(time, &resume) clock.tick(time) resume.call end def expect_event(name, event, args, &resume) params = [@clients[name]] + args handler = lambda { |*a| } engine.bind(event, &handler) handler.should_receive(:call).with(*params) resume.call end def expect_no_event(name, event, args, &resume) params = [@clients[name]] + args handler = lambda { |*a| } engine.bind(event, &handler) handler.should_not_receive(:call).with(*params) resume.call end def expect_message(name, messages, &resume) @inboxes[name].should == messages resume.call end def expect_no_message(name, &resume) @inboxes[name].should == [] resume.call end def check_different_messages(a, b, &resume) @inboxes[a].first.should_not be_equal(@inboxes[b].first) resume.call end end describe "Pub/sub engines" do shared_examples_for "faye engine" do include EncodingHelper include EngineSteps def create_engine opts = options.merge(engine_opts) Faye::Engine::Proxy.new(opts) end let(:options) { {:timeout => 1} } let(:engine) { create_engine } before do Faye.stub(:logger) Faye::Engine.ensure_reactor_running! create_client :alice create_client :bob create_client :carol end describe :create_client do it "returns a client id" do create_client :dave check_client_id :dave, /^[a-z0-9]+$/ end it "returns a different id every time" do 1.upto(7) { |i| create_client "client#{i}" } check_num_clients 10 end it "publishes an event" do engine.should_receive(:trigger).with(:handshake, match(/^[a-z0-9]+$/)).exactly(4) create_client :dave end end describe :client_exists do it "returns true if the client id exists" do check_client_exists :alice, true end it "returns false if the client id does not exist" do check_client_exists :anything, false end end =begin describe :ping do it "removes a client if it does not ping often enough" do clock_tick 2 check_client_exists :alice, false end it "prolongs the life of a client" do clock_tick 1 ping :alice clock_tick 1 check_client_exists :alice, true clock_tick 1 check_client_exists :alice, false end end =end describe :destroy_client do it "removes the given client" do destroy_client :alice check_client_exists :alice, false end it "publishes an event" do expect_event :alice, :disconnect, [] destroy_client :alice end describe "when the client has subscriptions" do before do @message = {"channel" => "/messages/foo", "data" => "ok"} subscribe :alice, "/messages/foo" end it "stops the client receiving messages" do connect :alice, engine destroy_client :alice publish @message expect_no_message :alice end it "publishes an event" do expect_event :alice, :disconnect, [] destroy_client :alice end end end describe :subscribe do it "publishes an event" do expect_event :alice, :subscribe, ["/messages/foo"] subscribe :alice, "/messages/foo" end describe "when the client is subscribed to the channel" do before { subscribe :alice, "/messages/foo" } it "does not publish an event" do expect_no_event :alice, :subscribe, ["/messages/foo"] subscribe :alice, "/messages/foo" end end end describe :unsubscribe do before { subscribe :alice, "/messages/bar" } it "does not publish an event" do expect_no_event :alice, :unsubscribe, ["/messages/foo"] unsubscribe :alice, "/messages/foo" end describe "when the client is subscribed to the channel" do before { subscribe :alice, "/messages/foo" } it "publishes an event" do expect_event :alice, :unsubscribe, ["/messages/foo"] unsubscribe :alice, "/messages/foo" end end end describe :publish do before do @message = {"channel" => "/messages/foo", "data" => "ok", "blank" => nil} connect :alice, engine connect :bob, engine connect :carol, engine end describe "with no subscriptions" do it "delivers no messages" do publish @message expect_no_message :alice expect_no_message :bob expect_no_message :carol end it "publishes a :publish event with a clientId" do expect_event :bob, :publish, ["/messages/foo", "ok"] publish_by :bob, @message end it "publishes a :publish event with no clientId" do expect_event nil, :publish, ["/messages/foo", "ok"] publish @message end end describe "with a subscriber" do before { subscribe :alice, "/messages/foo" } it "delivers messages to the subscribed client" do publish @message expect_message :alice, [@message] end it "delivers multibyte messages correctly" do @message["data"] = encode "Apple = " publish @message expect_message :alice, [@message] end it "publishes a :publish event" do expect_event :bob, :publish, ["/messages/foo", "ok"] publish_by :bob, @message end end describe "with a subscriber that is removed" do before do subscribe :alice, "/messages/foo" unsubscribe :alice, "/messages/foo" end it "does not deliver messages to unsubscribed clients" do publish @message expect_no_message :alice expect_no_message :bob expect_no_message :carol end it "publishes a :publish event" do expect_event :bob, :publish, ["/messages/foo", "ok"] publish_by :bob, @message end end describe "with multiple subscribers" do before do subscribe :alice, "/messages/foo" subscribe :bob, "/messages/bar" subscribe :carol, "/messages/foo" end it "delivers messages to the subscribed clients" do publish @message expect_message :alice, [@message] expect_no_message :bob expect_message :carol, [@message] end end describe "with a single wildcard" do before do subscribe :alice, "/messages/*" subscribe :bob, "/messages/bar" subscribe :carol, "/*" end it "delivers messages to matching subscriptions" do publish @message expect_message :alice, [@message] expect_no_message :bob expect_no_message :carol end end describe "with a double wildcard" do before do subscribe :alice, "/messages/**" subscribe :bob, "/messages/bar" subscribe :carol, "/**" end it "delivers messages to matching subscriptions" do publish @message expect_message :alice, [@message] expect_no_message :bob expect_message :carol, [@message] end it "delivers a unique copy of the message to each client" do publish @message check_different_messages :alice, :carol end end describe "with multiple matching subscriptions for the same client" do before do subscribe :alice, "/messages/foo" subscribe :alice, "/messages/*" end it "delivers each message once to each client" do publish @message expect_message :alice, [@message] end it "delivers the message as many times as it is published" do publish [@message, @message] expect_message :alice, [@message, @message] end end end end shared_examples_for "distributed engine" do include EngineSteps def create_engine opts = options.merge(engine_opts) Faye::Engine::Proxy.new(opts) end let(:options) { {} } let(:left) { create_engine } let(:right) { create_engine } alias :engine :left before do Faye.stub(:logger) Faye::Engine.ensure_reactor_running! create_client :alice create_client :bob connect :alice, left end describe :publish do before do subscribe :alice, "/foo" publish "channel" => "/foo", "data" => "first" end it "only delivers each message once" do expect_message :alice, ["channel" => "/foo", "data" => "first"] publish "channel" => "/foo", "data" => "second" connect :alice, right expect_message :alice, [{"channel" => "/foo", "data" => "first"}, {"channel" => "/foo", "data" => "second"}] end end end end