websocket-rails 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/CHANGELOG.md +34 -0
  2. data/Gemfile +1 -0
  3. data/README.md +34 -34
  4. data/assets/javascripts/http_dispatcher.js +6 -5
  5. data/assets/javascripts/websocket_dispatcher.js +7 -6
  6. data/lib/websocket-rails.rb +22 -2
  7. data/lib/websocket_rails/base_controller.rb +24 -8
  8. data/lib/websocket_rails/connection_adapters.rb +43 -16
  9. data/lib/websocket_rails/connection_adapters/http.rb +14 -10
  10. data/lib/websocket_rails/connection_adapters/web_socket.rb +13 -12
  11. data/lib/websocket_rails/connection_manager.rb +19 -47
  12. data/lib/websocket_rails/dispatcher.rb +39 -33
  13. data/lib/websocket_rails/event.rb +71 -0
  14. data/lib/websocket_rails/event_map.rb +120 -0
  15. data/lib/websocket_rails/version.rb +1 -1
  16. data/spec/dummy/config/initializers/events.rb +2 -2
  17. data/spec/dummy/{public/stylesheets/.gitkeep → log/development.log} +0 -0
  18. data/spec/dummy/log/production.log +0 -0
  19. data/spec/dummy/log/server.log +0 -0
  20. data/spec/dummy/log/test.log +454 -0
  21. data/spec/integration/connection_manager_spec.rb +29 -13
  22. data/spec/spec_helper.rb +3 -3
  23. data/spec/support/helper_methods.rb +29 -0
  24. data/spec/support/mock_web_socket.rb +6 -2
  25. data/spec/unit/connection_adapters/http_spec.rb +1 -3
  26. data/spec/unit/connection_adapters/web_socket_spec.rb +1 -11
  27. data/spec/unit/connection_adapters_spec.rb +51 -19
  28. data/spec/unit/connection_manager_spec.rb +22 -46
  29. data/spec/unit/dispatcher_spec.rb +56 -25
  30. data/spec/unit/event_map_spec.rb +96 -0
  31. data/spec/unit/event_spec.rb +99 -0
  32. metadata +23 -25
  33. data/.gitignore +0 -11
  34. data/.rspec +0 -2
  35. data/.travis.yml +0 -3
  36. data/Gemfile.lock +0 -144
  37. data/Guardfile +0 -9
  38. data/lib/websocket_rails/events.rb +0 -53
  39. data/spec/unit/events_spec.rb +0 -70
  40. data/websocket-rails.gemspec +0 -26
@@ -3,14 +3,24 @@ require 'support/mock_web_socket'
3
3
 
4
4
  module WebsocketRails
5
5
  describe ConnectionManager, "integration test" do
6
+
7
+ class ProductController < BaseController
8
+ def update_list; true; end
9
+ end
6
10
 
7
11
  def define_test_events
8
12
  WebsocketRails.route_block = nil
9
- WebsocketRails::Events.describe_events do
13
+ WebsocketRails::EventMap.describe do
10
14
  subscribe :client_connected, to: ChatController, with_method: :new_user
11
15
  subscribe :change_username, to: ChatController, with_method: :change_username
12
16
  subscribe :client_error, to: ChatController, with_method: :error_occurred
13
17
  subscribe :client_disconnected, to: ChatController, with_method: :delete_user
18
+
19
+ subscribe :update_list, to: ChatController, with_method: :update_user_list
20
+
21
+ namespace :products do
22
+ subscribe :update_list, to: ProductController, with_method: :update_list
23
+ end
14
24
  end
15
25
  end
16
26
 
@@ -34,13 +44,22 @@ module WebsocketRails
34
44
  let(:test_message) { ['change_username',{user_name: 'Joe User'}] }
35
45
  let(:encoded_message) { test_message.to_json }
36
46
 
37
- before(:each) { MockEvent = Struct.new(:data) }
38
-
39
47
  it "should execute the controller action associated with the received event" do
40
- mock_event = MockEvent.new( encoded_message )
41
48
  ChatController.any_instance.should_receive(:change_username)
42
49
  @server.call( env )
43
- socket.onmessage( mock_event )
50
+ socket.on_message( encoded_message )
51
+ end
52
+ end
53
+
54
+ context "new message from client under a namespace" do
55
+ let(:test_message) { ['products.update_list',{product: 'x-ray-vision'}] }
56
+ let(:encoded_message) { test_message.to_json }
57
+
58
+ it "should execute the controller action under the correct namespace" do
59
+ ChatController.any_instance.should_not_receive(:update_user_list)
60
+ ProductController.any_instance.should_receive(:update_list)
61
+ @server.call( env )
62
+ socket.on_message( encoded_message )
44
63
  end
45
64
  end
46
65
 
@@ -48,7 +67,7 @@ module WebsocketRails
48
67
  it "should execute the controller action associated with the 'client_error' event" do
49
68
  ChatController.any_instance.should_receive(:error_occurred)
50
69
  @server.call( env )
51
- socket.onerror
70
+ socket.on_error
52
71
  end
53
72
  end
54
73
 
@@ -56,20 +75,18 @@ module WebsocketRails
56
75
  it "should execute the controller action associated with the 'client_disconnected' event" do
57
76
  ChatController.any_instance.should_receive(:delete_user)
58
77
  @server.call( env )
59
- socket.onclose
78
+ socket.on_close
60
79
  end
61
80
  end
62
81
  end
63
82
  end
64
83
 
65
- let(:env) { Rack::MockRequest.env_for('/websocket') }
66
-
67
84
  context "WebSocket Adapter" do
68
- let(:socket) { MockWebSocket.new }
85
+ let(:socket) { @server.connections.first }
69
86
 
70
87
  before do
71
88
  ::Faye::WebSocket.stub(:websocket?).and_return(true)
72
- ::Faye::WebSocket.stub(:new).and_return(socket)
89
+ ::Faye::WebSocket.stub(:new).and_return(MockWebSocket.new)
73
90
  @server = ConnectionManager.new
74
91
  end
75
92
 
@@ -77,10 +94,9 @@ module WebsocketRails
77
94
  end
78
95
 
79
96
  describe "HTTP Adapter" do
80
- let(:socket) { ConnectionAdapters::Http.new( env ) }
97
+ let(:socket) { @server.connections.first }
81
98
 
82
99
  before do
83
- ConnectionAdapters.stub(:establish_connection).and_return(socket)
84
100
  @server = ConnectionManager.new
85
101
  end
86
102
 
data/spec/spec_helper.rb CHANGED
@@ -13,7 +13,7 @@ require 'websocket-rails'
13
13
 
14
14
  # Requires supporting ruby files with custom matchers and macros, etc,
15
15
  # in spec/support/ and its subdirectories.
16
- Dir["../../spec/support/**/*.rb"].each {|f| require f}
16
+ Dir["./spec/support/**/*.rb"].each {|f| require f}
17
17
 
18
18
  RSpec.configure do |config|
19
19
  # == Mock Framework
@@ -25,7 +25,7 @@ RSpec.configure do |config|
25
25
  # config.mock_with :rr
26
26
  config.mock_with :rspec
27
27
 
28
- #config.include FactoryGirl::Syntax::Methods
28
+ config.include WebsocketRails::HelperMethods
29
29
 
30
30
  # If true, the base class of anonymous controllers will be inferred
31
31
  # automatically. This will be the default behavior in future versions of
@@ -56,4 +56,4 @@ def enable_output
56
56
  $stdout = @orig_stdout
57
57
  @orig_stderr = nil
58
58
  @orig_stdout = nil
59
- end
59
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ module WebsocketRails
4
+ module HelperMethods
5
+ def env
6
+ @_env ||= begin
7
+ env = Rack::MockRequest.env_for('/websocket')
8
+ env['async.callback'] = Proc.new { |response| true }
9
+ env
10
+ end
11
+ end
12
+
13
+ def encoded_message
14
+ ['test_event',{user_name: 'Joe User'}].to_json
15
+ end
16
+
17
+ def received_encoded_message(connection_id)
18
+ [connection_id,'test_event',{user_name: 'Joe User'}].to_json
19
+ end
20
+
21
+ MockEvent = Struct.new(:name,:namespace)
22
+ end
23
+ end
24
+
25
+ module EM
26
+ def self.next_tick(&block)
27
+ block.call if block.respond_to?(:call)
28
+ end
29
+ end
@@ -23,7 +23,11 @@ module WebsocketRails
23
23
 
24
24
  def send(*args)
25
25
  true
26
- end
26
+ end
27
+
28
+ def id
29
+ object_id.to_i
30
+ end
27
31
  end
28
32
 
29
- end
33
+ end
@@ -4,9 +4,7 @@ module WebsocketRails
4
4
  module ConnectionAdapters
5
5
  describe Http do
6
6
 
7
- let(:env) { Rack::MockRequest.env_for('/websocket') }
8
-
9
- subject { Http.new( env ) }
7
+ subject { Http.new( env, double('Dispatcher').as_null_object ) }
10
8
 
11
9
  it "should be a subclass of ConnectionAdapters::Base" do
12
10
  subject.class.superclass.should == ConnectionAdapters::Base
@@ -8,17 +8,7 @@ module WebsocketRails
8
8
  before do
9
9
  @socket = MockWebSocket.new
10
10
  Faye::WebSocket.stub(:new).and_return(@socket)
11
- @adapter = WebSocket.new( Hash.new )
12
- end
13
-
14
- WebSocket::ADAPTER_EVENTS.each do |event|
15
- it "should delegate ##{event} and ##{event}= to the Faye::WebSocket object" do
16
- @socket.should_receive(event)
17
- @socket.should_receive("#{event}=".to_sym)
18
-
19
- @adapter.__send__( "#{event}=".to_sym, Proc.new {|e| true } )
20
- @adapter.__send__( event )
21
- end
11
+ @adapter = WebSocket.new( env, double('Dispatcher').as_null_object )
22
12
  end
23
13
 
24
14
  context "#send" do
@@ -10,47 +10,79 @@ module WebsocketRails
10
10
 
11
11
  describe ConnectionAdapters do
12
12
 
13
- let(:env) { Rack::MockRequest.env_for('/websocket') }
14
-
15
- context ".register_adapter" do
13
+ context ".register" do
16
14
  it "should store a reference to the adapter in the adapters array" do
17
- ConnectionAdapters.register_adapter( ConnectionAdapters::Test )
15
+ ConnectionAdapters.register( ConnectionAdapters::Test )
18
16
  ConnectionAdapters.adapters.include?( ConnectionAdapters::Test ).should be_true
19
17
  end
20
18
  end
21
19
 
22
20
  context ".establish_connection" do
23
21
  it "should return the correct connection adapter instance" do
24
- adapter = ConnectionAdapters.establish_connection( env )
22
+ adapter = ConnectionAdapters.establish_connection( env, double('Dispatcher').as_null_object )
25
23
  adapter.class.should == ConnectionAdapters::Test
26
- end
24
+ end
27
25
  end
28
26
 
29
27
  end
30
28
 
31
29
  module ConnectionAdapters
32
30
  describe Base do
33
-
34
- let(:env) { Rack::MockRequest.env_for('/websocket') }
35
-
36
- subject { Base.new( env ) }
31
+ let(:dispatcher) { double('Dispatcher').as_null_object }
32
+ let(:event) { double('Event').as_null_object }
33
+ before { Event.stub(:new_from_json).and_return(event) }
34
+ subject { Base.new( env, dispatcher ) }
37
35
 
38
36
  context "new adapters" do
39
37
  it "should register themselves in the adapters array when inherited" do
40
38
  adapter = Class.new( ConnectionAdapters::Base )
41
39
  ConnectionAdapters.adapters.include?( adapter ).should be_true
42
40
  end
43
-
44
- Base::ADAPTER_EVENTS.each do |event|
45
- it "should define accessor methods for #{event}" do
46
- proc = lambda { |event| true }
47
- subject.__send__("#{event}=".to_sym,proc)
48
- subject.__send__(event).should == true
49
- end
41
+ end
42
+
43
+ describe "#on_open" do
44
+ it "should dispatch an on_open event" do
45
+ on_open_event = double('event').as_null_object
46
+ subject.stub(:send)
47
+ Event.should_receive(:new_on_open).and_return(on_open_event)
48
+ dispatcher.should_receive(:dispatch).with(on_open_event)
49
+ subject.on_open
50
+ end
51
+ end
52
+
53
+ describe "#on_message" do
54
+ it "should forward the data to the dispatcher" do
55
+ dispatcher.should_receive(:dispatch).with(event)
56
+ subject.on_message encoded_message
57
+ end
58
+ end
59
+
60
+ describe "#on_close" do
61
+ it "should dispatch an on_close event" do
62
+ on_close_event = double('event')
63
+ Event.should_receive(:new_on_close).and_return(on_close_event)
64
+ dispatcher.should_receive(:dispatch).with(on_close_event)
65
+ subject.on_close("data")
66
+ end
67
+ end
68
+
69
+ describe "#on_error" do
70
+ it "should dispatch an on_error event" do
71
+ subject.stub(:on_close)
72
+ on_error_event = double('event').as_null_object
73
+ Event.should_receive(:new_on_error).and_return(on_error_event)
74
+ dispatcher.should_receive(:dispatch).with(on_error_event)
75
+ subject.on_error("data")
76
+ end
77
+
78
+ it "should fire the on_close event" do
79
+ data = "test_data"
80
+ subject.should_receive(:on_close).with(data)
81
+ subject.on_error("test_data")
50
82
  end
51
83
  end
52
84
 
53
- context "#send" do
85
+ describe "#send" do
54
86
  it "should raise a NotImplementedError exception" do
55
87
  expect { subject.send :message }.to raise_exception( NotImplementedError )
56
88
  end
@@ -58,4 +90,4 @@ module WebsocketRails
58
90
 
59
91
  end
60
92
  end
61
- end
93
+ end
@@ -13,13 +13,12 @@ module WebsocketRails
13
13
  end
14
14
 
15
15
  let(:connections) { subject.connections }
16
- let(:env) { Rack::MockRequest.env_for('/websocket') }
16
+ let(:dispatcher) { subject.dispatcher }
17
17
 
18
18
  before(:each) do
19
- @mock_socket = ConnectionAdapters::Base.new(env)
19
+ ConnectionAdapters::Base.any_instance.stub(:send)
20
+ @mock_socket = ConnectionAdapters::Base.new(env,dispatcher)
20
21
  ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket)
21
- @dispatcher = double('dispatcher').as_null_object
22
- Dispatcher.stub(:new).and_return(@dispatcher)
23
22
  end
24
23
 
25
24
  context "new connections" do
@@ -27,14 +26,6 @@ module WebsocketRails
27
26
  expect { open_connection }.to change { connections.count }.by(1)
28
27
  end
29
28
 
30
- it "should execute the :client_connected event" do
31
- @dispatcher.should_receive(:dispatch) do |event,data,connection|
32
- event.should == 'client_connected'
33
- connection.should == @mock_socket
34
- end
35
- open_connection
36
- end
37
-
38
29
  it "should store the new connection in the @connections array" do
39
30
  open_connection
40
31
  connections.include?(@mock_socket).should be_true
@@ -47,75 +38,60 @@ module WebsocketRails
47
38
 
48
39
  context "new POST event" do
49
40
  before(:each) do
50
- @mock_http = ConnectionAdapters::Http.new(env)
41
+ @mock_http = ConnectionAdapters::Http.new(env,dispatcher)
51
42
  app.connections << @mock_http
52
43
  end
53
44
 
54
45
  it "should receive the new event for the correct connection" do
55
- @dispatcher.should_receive(:receive).with('data',@mock_http)
56
- post '/websocket', {:client_id => @mock_http.id, :data => 'data'}
46
+ @mock_http.should_receive(:on_message).with(encoded_message)
47
+ post '/websocket', {:client_id => @mock_http.id, :data => encoded_message}
57
48
  end
58
49
  end
59
50
 
60
51
  context "open connections" do
61
52
  before(:each) do
62
- ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket,ConnectionAdapters::Base.new(env))
53
+ ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket,ConnectionAdapters::Base.new(env,dispatcher))
63
54
  4.times { open_connection }
64
55
  end
65
56
 
66
57
  context "when receiving a new event" do
67
- before(:all) { MockEvent = Struct.new(:data) }
68
- before(:each) { open_connection }
58
+ before(:each) { open_connection }
69
59
 
70
60
  it "should dispatch the appropriate event through the Dispatcher" do
71
- mock_event = MockEvent.new(:new_message)
72
- @dispatcher.should_receive(:receive) do |event,connection|
73
- event.should == :new_message
74
- connection.should == @mock_socket
61
+ mock_event = ["new_message","data"].to_json
62
+ dispatcher.should_receive(:dispatch) do |event|
63
+ event.name.should == :new_message
64
+ event.data.should == "data"
65
+ event.connection.should == @mock_socket
75
66
  end
76
- @mock_socket.onmessage(mock_event)
67
+ @mock_socket.on_message(mock_event)
77
68
  end
78
69
  end
79
70
 
80
- context "when closing" do
71
+ context "when closing" do
81
72
  it "should remove the connection object from the @connections array" do
82
- @mock_socket.onclose
73
+ @mock_socket.on_close
83
74
  connections.include?(@mock_socket).should be_false
84
75
  end
85
76
 
86
77
  it "should decrement the connection count by one" do
87
- expect { @mock_socket.onclose }.to change { connections.count }.by(-1)
78
+ expect { @mock_socket.on_close }.to change { connections.count }.by(-1)
88
79
  end
89
80
 
90
81
  it "should dispatch the :client_disconnected event" do
91
- @dispatcher.should_receive(:dispatch) do |event,data,connection|
92
- event.should == 'client_disconnected'
93
- connection.should == @mock_socket
82
+ dispatcher.should_receive(:dispatch) do |event|
83
+ event.name.should == :client_disconnected
84
+ event.connection.should == @mock_socket
94
85
  end
95
- @mock_socket.onclose
86
+ @mock_socket.on_close
96
87
  end
97
88
  end
98
89
 
99
- context "when experiencing errors" do
100
- it "should dispatch the :client_error event" do
101
- @mock_socket.stub(:onclose)
102
- @dispatcher.should_receive(:dispatch) do |event,data,connection|
103
- event.should == 'client_error'
104
- connection.should == @mock_socket
105
- end
106
- @mock_socket.onerror
107
- end
108
-
109
- it "should execute the #onclose procedure on connection" do
110
- @mock_socket.should_receive(:onclose)
111
- @mock_socket.onerror
112
- end
113
- end
114
90
  end
115
91
 
116
92
  context "invalid connections" do
117
93
  before(:each) do
118
- ConnectionAdapters.stub(:establish_connection).and_return(false)
94
+ ConnectionAdapters.stub(:establish_connection).and_raise(InvalidConnectionError)
119
95
  end
120
96
 
121
97
  it "should return a 400 bad request error code" do
@@ -1,58 +1,89 @@
1
1
  require 'spec_helper'
2
2
  require 'support/mock_web_socket'
3
- require 'json'
4
3
 
5
4
  module WebsocketRails
6
5
 
7
6
  class EventTarget
8
- attr_reader :_connection, :_message, :test_method
7
+ attr_reader :_event, :test_method
9
8
 
10
9
  def execute_observers(event_name)
11
10
  true
12
- end
11
+ end
13
12
  end
14
13
 
15
14
  describe Dispatcher do
16
15
 
16
+ let(:event) { double('Event') }
17
17
  let(:connection) { MockWebSocket.new }
18
18
  let(:connection_manager) { double('connection_manager').as_null_object }
19
19
  subject { Dispatcher.new(connection_manager) }
20
20
 
21
- let(:test_message) { ['new_message',{message: 'this is a message'}] }
22
- let(:encoded_message) { test_message.to_json }
23
-
24
- context "receiving a new message" do
25
- it "should be decoded from JSON and dispatched" do
26
- subject.stub(:dispatch) do |event,data,con|
27
- event.should == 'new_message'
28
- data['message'].should == 'this is a message'
29
- con.should == connection
21
+ describe "#receive_encoded" do
22
+ context "receiving a new message" do
23
+ before do
24
+ Event.stub(:new_from_json).and_return( event )
25
+ end
26
+
27
+ it "should be decoded from JSON and dispatched" do
28
+ subject.stub(:dispatch) do |dispatch_event|
29
+ dispatch_event.should == event
30
+ end
31
+ subject.receive_encoded(encoded_message,connection)
30
32
  end
31
- subject.receive(encoded_message,connection)
33
+ end
34
+ end
35
+
36
+ describe "#receive" do
37
+ before { Event.stub(:new).and_return( event ) }
38
+ it "should dispatch a new event" do
39
+ subject.stub(:dispatch) do |dispatch_event|
40
+ dispatch_event.should == event
41
+ end
42
+ subject.receive(:test_event,{},connection)
32
43
  end
33
44
  end
34
45
 
35
46
  context "dispatching a message for an event" do
36
- before(:each) do
47
+ before do
37
48
  @target = EventTarget.new
38
- Events.any_instance.stub(:routes_for).with(any_args).and_yield( @target, :test_method )
49
+ EventMap.any_instance.stub(:routes_for).with(any_args).and_yield( @target, :test_method )
50
+ event.stub(:name).and_return(:test_method)
51
+ event.stub(:data).and_return(:some_message)
52
+ event.stub(:connection).and_return(connection)
39
53
  end
40
54
 
41
55
  it "should execute the correct method on the target class" do
42
56
  @target.should_receive(:test_method)
43
- subject.dispatch('test_method',{},connection)
57
+ subject.dispatch(event)
44
58
  end
45
59
 
46
- it "should set the _message instance variable on the target object" do
47
- subject.dispatch('test_method',:some_message,connection)
48
- @target._message.should == :some_message
60
+ it "should set the _event instance variable on the target object" do
61
+ subject.dispatch(event)
62
+ @target._event.should == event
49
63
  end
50
-
51
- it "should set the _connection instance variable on the target object" do
52
- subject.dispatch('test_method',:some_message,connection)
53
- @target._connection.should == connection
64
+ end
65
+
66
+ describe "#send_message" do
67
+ before do
68
+ @event = Event.new_from_json( encoded_message, connection )
69
+ end
70
+
71
+ it "should send a message to the event's connection object" do
72
+ connection.should_receive(:send).with(@event.serialize)
73
+ subject.send_message @event
74
+ end
75
+ end
76
+
77
+ describe "#broadcast_message" do
78
+ before do
79
+ connection_manager.stub(:connections).and_return([connection])
80
+ @event = Event.new_from_json( encoded_message, connection )
81
+ end
82
+
83
+ it "should send a message to all connected clients" do
84
+ connection.should_receive(:send).with(@event.serialize)
85
+ subject.broadcast_message @event
54
86
  end
55
87
  end
56
-
57
88
  end
58
- end
89
+ end