websocket-rails 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +41 -0
- data/README.md +1 -1
- data/lib/generators/websocket_rails/install/templates/events.rb +15 -5
- data/lib/rails/tasks/websocket_rails.tasks +8 -10
- data/lib/spec_helpers/matchers/route_matchers.rb +2 -1
- data/lib/spec_helpers/spec_helper_event.rb +4 -0
- data/lib/websocket-rails.rb +50 -73
- data/lib/websocket_rails/base_controller.rb +9 -7
- data/lib/websocket_rails/channel.rb +8 -1
- data/lib/websocket_rails/channel_manager.rb +6 -0
- data/lib/websocket_rails/configuration.rb +112 -0
- data/lib/websocket_rails/connection_adapters.rb +13 -4
- data/lib/websocket_rails/connection_manager.rb +3 -2
- data/lib/websocket_rails/controller_factory.rb +70 -0
- data/lib/websocket_rails/data_store.rb +128 -62
- data/lib/websocket_rails/dispatcher.rb +17 -16
- data/lib/websocket_rails/event.rb +12 -2
- data/lib/websocket_rails/event_map.rb +6 -41
- data/lib/websocket_rails/internal_events.rb +0 -7
- data/lib/websocket_rails/logging.rb +103 -14
- data/lib/websocket_rails/synchronization.rb +6 -8
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/app/controllers/chat_controller.rb +6 -14
- data/spec/dummy/log/test.log +0 -750
- data/spec/integration/connection_manager_spec.rb +8 -1
- data/spec/spec_helper.rb +3 -16
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +2 -11
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +2 -12
- data/spec/unit/channel_manager_spec.rb +8 -0
- data/spec/unit/channel_spec.rb +16 -2
- data/spec/unit/connection_adapters_spec.rb +32 -11
- data/spec/unit/controller_factory_spec.rb +63 -0
- data/spec/unit/data_store_spec.rb +91 -24
- data/spec/unit/dispatcher_spec.rb +6 -11
- data/spec/unit/event_map_spec.rb +17 -27
- data/spec/unit/event_spec.rb +14 -0
- data/spec/unit/logging_spec.rb +122 -17
- metadata +11 -6
@@ -9,7 +9,7 @@ module WebsocketRails
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def define_test_events
|
12
|
-
WebsocketRails.route_block = nil
|
12
|
+
WebsocketRails.config.route_block = nil
|
13
13
|
WebsocketRails::EventMap.describe do
|
14
14
|
subscribe :client_connected, :to => ChatController, :with_method => :new_user
|
15
15
|
subscribe :change_username, :to => ChatController, :with_method => :change_username
|
@@ -99,6 +99,13 @@ module WebsocketRails
|
|
99
99
|
@server.call( env )
|
100
100
|
socket.on_close
|
101
101
|
end
|
102
|
+
|
103
|
+
it "should unsubscribe from channels" do
|
104
|
+
channel = WebsocketRails[:test_chan]
|
105
|
+
@server.call( env )
|
106
|
+
channel.should_receive(:unsubscribe).with(socket)
|
107
|
+
socket.on_close
|
108
|
+
end
|
102
109
|
end
|
103
110
|
end
|
104
111
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -34,21 +34,8 @@ RSpec.configure do |config|
|
|
34
34
|
# rspec-rails.
|
35
35
|
config.infer_base_class_for_anonymous_controllers = false
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@orig_stderr = $stderr
|
41
|
-
@orig_stdout = $stdout
|
42
|
-
|
43
|
-
# redirect stderr and stdout to /dev/null
|
44
|
-
$stderr = File.new('/dev/null', 'w')
|
45
|
-
$stdout = File.new('/dev/null', 'w')
|
46
|
-
end
|
37
|
+
config.before(:each) do
|
38
|
+
WebsocketRails.config.logger = Logger.new(StringIO.new)
|
39
|
+
end
|
47
40
|
|
48
|
-
# Replace stdout and stderr so anything else is output correctly.
|
49
|
-
def enable_output
|
50
|
-
$stderr = @orig_stderr
|
51
|
-
$stdout = @orig_stdout
|
52
|
-
@orig_stderr = nil
|
53
|
-
@orig_stdout = nil
|
54
41
|
end
|
@@ -19,9 +19,8 @@ describe 'Route Matchers' do
|
|
19
19
|
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
22
|
def define_route_test_events
|
24
|
-
WebsocketRails.route_block = nil
|
23
|
+
WebsocketRails.config.route_block = nil
|
25
24
|
WebsocketRails::EventMap.describe do
|
26
25
|
|
27
26
|
namespace :product do
|
@@ -106,13 +105,5 @@ describe 'Route Matchers' do
|
|
106
105
|
matcher.should produce_as_description 'be routed only to target route_spec_product#update_product'
|
107
106
|
end
|
108
107
|
|
109
|
-
|
110
108
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
109
|
+
end
|
@@ -20,7 +20,7 @@ describe 'Trigger Matchers' do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def define_test_events
|
23
|
-
WebsocketRails.route_block = nil
|
23
|
+
WebsocketRails.config.route_block = nil
|
24
24
|
WebsocketRails::EventMap.describe do
|
25
25
|
|
26
26
|
namespace :product do
|
@@ -32,16 +32,6 @@ describe 'Trigger Matchers' do
|
|
32
32
|
|
33
33
|
before { define_test_events }
|
34
34
|
|
35
|
-
around(:each) do |example|
|
36
|
-
EM.run do
|
37
|
-
example.run
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
after(:each) do
|
42
|
-
EM.stop
|
43
|
-
end
|
44
|
-
|
45
35
|
# as we have have 16 possible combinations of trigger messages and data matching pattern (data|no_data, success|failure,
|
46
36
|
# no_checking|checking_with_any|checking_with_nil|checking_with_exact_data) plus the case of no message at all
|
47
37
|
# for EACH of the matchers, resulting in a total 51 cases, we will not extensively test all cases for all matchers
|
@@ -254,4 +244,4 @@ describe 'Trigger Matchers' do
|
|
254
244
|
end
|
255
245
|
|
256
246
|
|
257
|
-
end
|
247
|
+
end
|
@@ -25,5 +25,13 @@ module WebsocketRails
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
describe "unsubscribe" do
|
29
|
+
it "should unsubscribe connection from all channels" do
|
30
|
+
subject[:awesome_channel].should_receive(:unsubscribe).with(:some_connection)
|
31
|
+
subject[:awesome_channel]
|
32
|
+
subject.unsubscribe(:some_connection)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
28
36
|
end
|
29
37
|
end
|
data/spec/unit/channel_spec.rb
CHANGED
@@ -21,6 +21,19 @@ module WebsocketRails
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
describe "#unsubscribe" do
|
25
|
+
it "should remove connection from subscriber pool" do
|
26
|
+
subject.subscribe connection
|
27
|
+
subject.unsubscribe connection
|
28
|
+
subject.subscribers.include?(connection).should be_false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should do nothing if connection is not subscribed to channel" do
|
32
|
+
subject.unsubscribe connection
|
33
|
+
subject.subscribers.include?(connection).should be_false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
24
37
|
describe "#trigger" do
|
25
38
|
it "should create a new event and trigger it on all subscribers" do
|
26
39
|
event = double('event').as_null_object
|
@@ -37,8 +50,9 @@ module WebsocketRails
|
|
37
50
|
|
38
51
|
describe "#trigger_event" do
|
39
52
|
it "should forward the event to the subscribers" do
|
40
|
-
|
41
|
-
subject.
|
53
|
+
event = double('event').as_null_object
|
54
|
+
subject.should_receive(:send_data).with(event)
|
55
|
+
subject.trigger_event event
|
42
56
|
end
|
43
57
|
end
|
44
58
|
|
@@ -1,31 +1,31 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module WebsocketRails
|
4
|
-
|
4
|
+
|
5
5
|
class ConnectionAdapters::Test < ConnectionAdapters::Base
|
6
6
|
def self.accepts?(env)
|
7
7
|
true
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
describe ConnectionAdapters do
|
12
|
-
|
12
|
+
|
13
13
|
context ".register" do
|
14
14
|
it "should store a reference to the adapter in the adapters array" do
|
15
15
|
ConnectionAdapters.register( ConnectionAdapters::Test )
|
16
16
|
ConnectionAdapters.adapters.include?( ConnectionAdapters::Test ).should be_true
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
context ".establish_connection" do
|
21
21
|
it "should return the correct connection adapter instance" do
|
22
22
|
adapter = ConnectionAdapters.establish_connection( mock_request, double('Dispatcher').as_null_object )
|
23
23
|
adapter.class.should == ConnectionAdapters::Test
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
module ConnectionAdapters
|
30
30
|
describe Base do
|
31
31
|
let(:dispatcher) { double('Dispatcher').as_null_object }
|
@@ -33,12 +33,16 @@ module WebsocketRails
|
|
33
33
|
let(:event) { double('Event').as_null_object }
|
34
34
|
before { Event.stub(:new_from_json).and_return(event) }
|
35
35
|
subject { Base.new( mock_request, dispatcher ) }
|
36
|
-
|
37
|
-
context "new
|
38
|
-
it "should register
|
36
|
+
|
37
|
+
context "new adapter" do
|
38
|
+
it "should register itself in the adapters array when inherited" do
|
39
39
|
adapter = Class.new( ConnectionAdapters::Base )
|
40
40
|
ConnectionAdapters.adapters.include?( adapter ).should be_true
|
41
41
|
end
|
42
|
+
|
43
|
+
it "should create a new DataStore::Connection instance" do
|
44
|
+
subject.data_store.should be_a DataStore::Connection
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
48
|
describe "#on_open" do
|
@@ -82,13 +86,13 @@ module WebsocketRails
|
|
82
86
|
subject.on_error("test_data")
|
83
87
|
end
|
84
88
|
end
|
85
|
-
|
89
|
+
|
86
90
|
describe "#send" do
|
87
91
|
it "should raise a NotImplementedError exception" do
|
88
92
|
expect { subject.send :message }.to raise_exception( NotImplementedError )
|
89
93
|
end
|
90
94
|
end
|
91
|
-
|
95
|
+
|
92
96
|
describe "#enqueue" do
|
93
97
|
it "should add the event to the queue" do
|
94
98
|
subject.enqueue 'event'
|
@@ -125,6 +129,23 @@ module WebsocketRails
|
|
125
129
|
subject.flush
|
126
130
|
end
|
127
131
|
end
|
132
|
+
|
133
|
+
describe "#close_connection" do
|
134
|
+
before do
|
135
|
+
@connection_manager = double('connection_manager').as_null_object
|
136
|
+
subject.stub_chain(:dispatcher, :connection_manager).and_return(@connection_manager)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "calls delegates to the conection manager" do
|
140
|
+
@connection_manager.should_receive(:close_connection).with(subject)
|
141
|
+
subject.__send__(:close_connection)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "deletes it's data_store" do
|
145
|
+
subject.data_store.should_receive(:destroy!)
|
146
|
+
subject.__send__(:close_connection)
|
147
|
+
end
|
148
|
+
end
|
128
149
|
end
|
129
150
|
end
|
130
151
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
describe ControllerFactory do
|
5
|
+
|
6
|
+
class TestController < BaseController
|
7
|
+
attr_reader :_dispatcher, :_event
|
8
|
+
|
9
|
+
def initialize_session
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:dispatcher) { double('dispatcher') }
|
15
|
+
let(:connection) { double('connection') }
|
16
|
+
let(:event) { double('event') }
|
17
|
+
|
18
|
+
subject { ControllerFactory.new(dispatcher) }
|
19
|
+
|
20
|
+
before do
|
21
|
+
connection.stub(:id).and_return(1)
|
22
|
+
event.stub(:connection).and_return(connection)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "stores a reference to the dispatcher" do
|
26
|
+
subject.dispatcher.should == dispatcher
|
27
|
+
end
|
28
|
+
|
29
|
+
it "maintains a hash of controller data stores" do
|
30
|
+
subject.controller_stores.should be_a Hash
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#new_for_event" do
|
34
|
+
it "creates and returns a new controller instance" do
|
35
|
+
controller = subject.new_for_event(event, TestController)
|
36
|
+
controller.class.should == TestController
|
37
|
+
end
|
38
|
+
|
39
|
+
it "initializes the controller with the correct data_store" do
|
40
|
+
store = double('data_store')
|
41
|
+
subject.controller_stores[TestController] = store
|
42
|
+
controller = subject.new_for_event(event, TestController)
|
43
|
+
controller.controller_store.should == store
|
44
|
+
end
|
45
|
+
|
46
|
+
it "initializes the controller with the correct event" do
|
47
|
+
controller = subject.new_for_event(event, TestController)
|
48
|
+
controller.event.should == event
|
49
|
+
end
|
50
|
+
|
51
|
+
it "initializes the controller with the correct dispatcher" do
|
52
|
+
controller = subject.new_for_event(event, TestController)
|
53
|
+
controller._dispatcher.should == dispatcher
|
54
|
+
end
|
55
|
+
|
56
|
+
it "calls #initialize_session on the controller only once" do
|
57
|
+
TestController.any_instance.should_receive(:initialize_session).once
|
58
|
+
3.times { subject.new_for_event(event, TestController) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -1,39 +1,106 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module WebsocketRails
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
module DataStore
|
5
|
+
describe Base do
|
6
|
+
it "extends Hash" do
|
7
|
+
subject.should be_a Hash
|
8
|
+
end
|
9
|
+
|
10
|
+
it "allows indifferent access" do
|
11
|
+
subject['key'] = true
|
12
|
+
subject[:key].should == true
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#instances" do
|
16
|
+
before do
|
17
|
+
Base.clear_all_instances
|
18
|
+
@connection = double('connection')
|
19
|
+
@controller = double('controller')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "keeps track of all instantiated instances" do
|
23
|
+
store_one = Base.new
|
24
|
+
store_two = Base.new
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
26
|
+
store_one.instances.count.should == 2
|
27
|
+
store_two.instances.count.should == 2
|
28
|
+
end
|
29
|
+
|
30
|
+
it "separates instances based on class name" do
|
31
|
+
2.times { Connection.new(@connection) }
|
32
|
+
4.times { Controller.new(@controller) }
|
33
|
+
|
34
|
+
Connection.new(@connection).instances.count.should == 3
|
35
|
+
Controller.new(@controller).instances.count.should == 5
|
22
36
|
end
|
23
37
|
end
|
24
38
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@
|
39
|
+
describe "#destroy!" do
|
40
|
+
before do
|
41
|
+
Base.clear_all_instances
|
42
|
+
@store = Base.new
|
43
|
+
@other = Base.new
|
44
|
+
end
|
45
|
+
|
46
|
+
it "removes itself from the instances collection" do
|
47
|
+
@other.instances.count.should == 2
|
48
|
+
@store.destroy!
|
49
|
+
@other.instances.count.should == 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#collect_all" do
|
54
|
+
before do
|
55
|
+
Base.clear_all_instances
|
56
|
+
@store_one = Base.new
|
57
|
+
@store_two = Base.new
|
58
|
+
|
59
|
+
@store_one[:secret] = 'token_one'
|
60
|
+
@store_two[:secret] = 'token_two'
|
61
|
+
end
|
62
|
+
|
63
|
+
context "called without a block" do
|
64
|
+
it "returns an array of values for the specified key from all store instances" do
|
65
|
+
secrets = @store_one.collect_all(:secret)
|
66
|
+
secrets.should == ['token_one', 'token_two']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "called with a block" do
|
71
|
+
it "yields each value to the block" do
|
72
|
+
@store_one.collect_all(:secret) do |item|
|
73
|
+
item.should be_in ['token_one', 'token_two']
|
74
|
+
end
|
75
|
+
end
|
29
76
|
end
|
30
77
|
end
|
31
78
|
end
|
32
79
|
|
33
|
-
describe
|
34
|
-
|
35
|
-
|
80
|
+
describe Connection do
|
81
|
+
before do
|
82
|
+
@connection = double('connection')
|
83
|
+
@connection.stub(:client_id).and_return(1)
|
84
|
+
end
|
85
|
+
|
86
|
+
let(:subject) { DataStore::Connection.new(@connection) }
|
87
|
+
|
88
|
+
it "stores a reference to it's connection" do
|
89
|
+
subject.connection.should == @connection
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe Controller do
|
94
|
+
before do
|
95
|
+
@controller = double('controller')
|
96
|
+
end
|
97
|
+
|
98
|
+
let(:subject) { DataStore::Controller.new(@controller) }
|
99
|
+
|
100
|
+
it "stores a reference to it's controller" do
|
101
|
+
subject.controller.should == @controller
|
36
102
|
end
|
37
103
|
end
|
38
104
|
end
|
105
|
+
|
39
106
|
end
|
@@ -4,7 +4,7 @@ require 'support/mock_web_socket'
|
|
4
4
|
module WebsocketRails
|
5
5
|
|
6
6
|
class EventTarget
|
7
|
-
attr_reader :_event, :test_method
|
7
|
+
attr_reader :_event, :_dispatcher, :test_method
|
8
8
|
|
9
9
|
def execute_observers(event_name)
|
10
10
|
true
|
@@ -13,7 +13,7 @@ module WebsocketRails
|
|
13
13
|
|
14
14
|
describe Dispatcher do
|
15
15
|
|
16
|
-
let(:event) { double('Event') }
|
16
|
+
let(:event) { double('Event').as_null_object }
|
17
17
|
let(:connection) { MockWebSocket.new }
|
18
18
|
let(:connection_manager) { double('connection_manager').as_null_object }
|
19
19
|
subject { Dispatcher.new(connection_manager) }
|
@@ -43,27 +43,22 @@ module WebsocketRails
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
context "dispatching
|
46
|
+
context "dispatching an event" do
|
47
47
|
before do
|
48
|
-
|
49
|
-
EventMap.any_instance.stub(:routes_for).with(any_args).and_yield( @target, :test_method )
|
48
|
+
EventMap.any_instance.stub(:routes_for).with(any_args).and_yield(EventTarget, :test_method)
|
50
49
|
event.stub(:name).and_return(:test_method)
|
51
50
|
event.stub(:data).and_return(:some_message)
|
52
51
|
event.stub(:connection).and_return(connection)
|
53
52
|
event.stub(:is_channel?).and_return(false)
|
54
53
|
event.stub(:is_invalid?).and_return(false)
|
54
|
+
event.stub(:is_internal?).and_return(false)
|
55
55
|
end
|
56
56
|
|
57
57
|
it "should execute the correct method on the target class" do
|
58
|
-
|
58
|
+
EventTarget.any_instance.should_receive(:test_method)
|
59
59
|
subject.dispatch(event)
|
60
60
|
end
|
61
61
|
|
62
|
-
it "should set the _event instance variable on the target object" do
|
63
|
-
subject.dispatch(event)
|
64
|
-
@target._event.should == event
|
65
|
-
end
|
66
|
-
|
67
62
|
context "channel events" do
|
68
63
|
it "should forward the data to the correct channel" do
|
69
64
|
event = Event.new 'test', :data => 'data', :channel => :awesome_channel
|