wwl-websocket-rails 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +239 -0
- data/Rakefile +72 -0
- data/bin/thin-socketrails +45 -0
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +46 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
- data/lib/assets/javascripts/websocket_rails/main.js +6 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
- data/lib/config.ru +3 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +68 -0
- data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
- data/lib/rails/config/routes.rb +7 -0
- data/lib/rails/tasks/websocket_rails.tasks +42 -0
- data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
- data/lib/spec_helpers/matchers/trigger_matchers.rb +138 -0
- data/lib/spec_helpers/spec_helper_event.rb +34 -0
- data/lib/websocket-rails.rb +108 -0
- data/lib/websocket_rails/base_controller.rb +208 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +177 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +35 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_manager.rb +119 -0
- data/lib/websocket_rails/controller_factory.rb +80 -0
- data/lib/websocket_rails/data_store.rb +145 -0
- data/lib/websocket_rails/dispatcher.rb +129 -0
- data/lib/websocket_rails/engine.rb +26 -0
- data/lib/websocket_rails/event.rb +193 -0
- data/lib/websocket_rails/event_map.rb +184 -0
- data/lib/websocket_rails/event_queue.rb +33 -0
- data/lib/websocket_rails/internal_events.rb +37 -0
- data/lib/websocket_rails/logging.rb +133 -0
- data/lib/websocket_rails/spec_helpers.rb +3 -0
- data/lib/websocket_rails/synchronization.rb +178 -0
- data/lib/websocket_rails/user_manager.rb +276 -0
- data/lib/websocket_rails/version.rb +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/chat_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/events.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/log/test.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +202 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/connection_manager_spec.rb +135 -0
- data/spec/javascripts/support/jasmine.yml +52 -0
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +81 -0
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +273 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +358 -0
- data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/mock_web_socket.rb +41 -0
- data/spec/unit/base_controller_spec.rb +74 -0
- data/spec/unit/channel_manager_spec.rb +58 -0
- data/spec/unit/channel_spec.rb +169 -0
- data/spec/unit/connection_adapters/http_spec.rb +88 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
- data/spec/unit/connection_adapters_spec.rb +259 -0
- data/spec/unit/connection_manager_spec.rb +148 -0
- data/spec/unit/controller_factory_spec.rb +76 -0
- data/spec/unit/data_store_spec.rb +106 -0
- data/spec/unit/dispatcher_spec.rb +203 -0
- data/spec/unit/event_map_spec.rb +120 -0
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +181 -0
- data/spec/unit/logging_spec.rb +162 -0
- data/spec/unit/synchronization_spec.rb +150 -0
- data/spec/unit/target_validator_spec.rb +88 -0
- data/spec/unit/user_manager_spec.rb +165 -0
- metadata +320 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
describe 'WebSocketRails.Channel:', ->
|
2
|
+
beforeEach ->
|
3
|
+
@dispatcher = new class WebSocketRailsStub
|
4
|
+
new_message: -> true
|
5
|
+
dispatch: -> true
|
6
|
+
trigger_event: (event) -> true
|
7
|
+
state: 'connected'
|
8
|
+
_conn:
|
9
|
+
connection_id: 12345
|
10
|
+
@channel = new WebSocketRails.Channel('public', @dispatcher)
|
11
|
+
sinon.spy @dispatcher, 'trigger_event'
|
12
|
+
|
13
|
+
afterEach ->
|
14
|
+
@dispatcher.trigger_event.restore()
|
15
|
+
|
16
|
+
describe '.bind', ->
|
17
|
+
it 'should add a function to the callbacks collection', ->
|
18
|
+
test_func = ->
|
19
|
+
@channel.bind 'event_name', test_func
|
20
|
+
expect(@channel._callbacks['event_name'].length).toBe 1
|
21
|
+
expect(@channel._callbacks['event_name']).toContain test_func
|
22
|
+
|
23
|
+
describe '.unbind', ->
|
24
|
+
it 'should remove the callbacks of an event', ->
|
25
|
+
callback = ->
|
26
|
+
@channel.bind 'event', callback
|
27
|
+
@channel.unbind 'event'
|
28
|
+
expect(@channel._callbacks['event']).toBeUndefined()
|
29
|
+
|
30
|
+
describe '.trigger', ->
|
31
|
+
describe 'before the channel token is set', ->
|
32
|
+
it 'queues the events', ->
|
33
|
+
@channel.trigger 'someEvent', 'someData'
|
34
|
+
queue = @channel._queue
|
35
|
+
expect(queue[0].name).toEqual 'someEvent'
|
36
|
+
expect(queue[0].data).toEqual 'someData'
|
37
|
+
|
38
|
+
describe 'when channel token is set', ->
|
39
|
+
it 'adds token to event metadata and dispatches event', ->
|
40
|
+
@channel._token = 'valid token'
|
41
|
+
@channel.trigger 'someEvent', 'someData'
|
42
|
+
expect(@dispatcher.trigger_event.calledWith(['someEvent',{token: 'valid token', data: 'someData'}]))
|
43
|
+
|
44
|
+
describe '.destroy', ->
|
45
|
+
it 'should destroy all callbacks', ->
|
46
|
+
event_callback = -> true
|
47
|
+
@channel.bind('new_message', @event_callback)
|
48
|
+
|
49
|
+
@channel.destroy()
|
50
|
+
|
51
|
+
expect(@channel._callbacks).toEqual {}
|
52
|
+
|
53
|
+
describe 'when this channel\'s connection is still active', ->
|
54
|
+
it 'should send unsubscribe event', ->
|
55
|
+
@channel.destroy()
|
56
|
+
expect(@dispatcher.trigger_event.args[0][0].name).toEqual 'websocket_rails.unsubscribe'
|
57
|
+
|
58
|
+
describe 'when this channel\'s connection is no more active', ->
|
59
|
+
beforeEach ->
|
60
|
+
@dispatcher._conn.connection_id++
|
61
|
+
|
62
|
+
it 'should not send unsubscribe event', ->
|
63
|
+
@channel.destroy()
|
64
|
+
expect(@dispatcher.trigger_event.notCalled).toEqual true
|
65
|
+
|
66
|
+
describe 'public channels', ->
|
67
|
+
beforeEach ->
|
68
|
+
@channel = new WebSocketRails.Channel('forchan', @dispatcher, false)
|
69
|
+
@event = @dispatcher.trigger_event.lastCall.args[0]
|
70
|
+
|
71
|
+
it 'should trigger an event containing the channel name', ->
|
72
|
+
expect(@event.data.channel).toEqual 'forchan'
|
73
|
+
|
74
|
+
it 'should trigger an event containing the correct connection_id', ->
|
75
|
+
expect(@event.connection_id).toEqual 12345
|
76
|
+
|
77
|
+
it 'should initialize an empty callbacks property', ->
|
78
|
+
expect(@channel._callbacks).toBeDefined()
|
79
|
+
expect(@channel._callbacks).toEqual {}
|
80
|
+
|
81
|
+
it 'should be public', ->
|
82
|
+
expect(@channel.is_private).toBeFalsy
|
83
|
+
|
84
|
+
describe 'channel tokens', ->
|
85
|
+
it 'should set token when event_name is websocket_rails.channel_token', ->
|
86
|
+
@channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
|
87
|
+
expect(@channel._token).toEqual 'abc123'
|
88
|
+
|
89
|
+
it "should refresh channel's connection_id after channel_token has been received", ->
|
90
|
+
# this is needed in case we would init the channel connection
|
91
|
+
# just before the connection has been established
|
92
|
+
@channel.connection_id = null
|
93
|
+
@channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
|
94
|
+
expect(@channel.connection_id).toEqual @dispatcher._conn.connection_id
|
95
|
+
|
96
|
+
it 'should flush the event queue after setting token', ->
|
97
|
+
@channel.trigger 'someEvent', 'someData'
|
98
|
+
@channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
|
99
|
+
expect(@channel._queue.length).toEqual(0)
|
100
|
+
|
101
|
+
describe 'private channels', ->
|
102
|
+
beforeEach ->
|
103
|
+
@channel = new WebSocketRails.Channel('forchan', @dispatcher, true)
|
104
|
+
@event = @dispatcher.trigger_event.lastCall.args[0]
|
105
|
+
|
106
|
+
it 'should trigger a subscribe_private event when created', ->
|
107
|
+
expect(@event.name).toEqual 'websocket_rails.subscribe_private'
|
108
|
+
|
109
|
+
it 'should be private', ->
|
110
|
+
expect(@channel.is_private).toBeTruthy
|
111
|
+
|
112
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
describe 'WebSocketRails.Event', ->
|
2
|
+
|
3
|
+
describe 'standard events', ->
|
4
|
+
beforeEach ->
|
5
|
+
@data = ['event', {data: { message: 'test'} }, 12345]
|
6
|
+
@event = new WebSocketRails.Event(@data)
|
7
|
+
|
8
|
+
it 'should generate an ID', ->
|
9
|
+
expect(@event.id).not.toBeNull
|
10
|
+
|
11
|
+
it 'should have a connection ID', ->
|
12
|
+
expect(@event.connection_id).toEqual 12345
|
13
|
+
|
14
|
+
it 'should assign the correct properties when passed a data array', ->
|
15
|
+
expect(@event.name).toEqual 'event'
|
16
|
+
expect(@event.data.message).toEqual 'test'
|
17
|
+
|
18
|
+
describe '.serialize()', ->
|
19
|
+
it 'should serialize the event as JSON', ->
|
20
|
+
@event.id = 1
|
21
|
+
serialized = "[\"event\",{\"id\":1,\"data\":{\"message\":\"test\"}}]"
|
22
|
+
expect(@event.serialize()).toEqual serialized
|
23
|
+
|
24
|
+
describe '.is_channel()', ->
|
25
|
+
it 'should be false', ->
|
26
|
+
expect(@event.is_channel()).toEqual false
|
27
|
+
|
28
|
+
describe 'channel events', ->
|
29
|
+
beforeEach ->
|
30
|
+
@data = ['event',{channel:'channel',data:{message: 'test'}}]
|
31
|
+
@event = new WebSocketRails.Event(@data)
|
32
|
+
|
33
|
+
it 'should assign the channel property', ->
|
34
|
+
expect(@event.channel).toEqual 'channel'
|
35
|
+
expect(@event.name).toEqual 'event'
|
36
|
+
expect(@event.data.message).toEqual 'test'
|
37
|
+
|
38
|
+
describe '.is_channel()', ->
|
39
|
+
it 'should be true', ->
|
40
|
+
expect(@event.is_channel()).toEqual true
|
41
|
+
|
42
|
+
describe '.serialize()', ->
|
43
|
+
it 'should serialize the event as JSON', ->
|
44
|
+
@event.id = 1
|
45
|
+
serialized = "[\"event\",{\"id\":1,\"channel\":\"channel\",\"data\":{\"message\":\"test\"}}]"
|
46
|
+
expect(@event.serialize()).toEqual serialized
|
47
|
+
|
48
|
+
describe '.run_callbacks()', ->
|
49
|
+
beforeEach ->
|
50
|
+
success_func = ->
|
51
|
+
failure_func = ->
|
52
|
+
@data = ['event', {data: { message: 'test'} }, 12345]
|
53
|
+
@event = new WebSocketRails.Event(@data, success_func, failure_func)
|
54
|
+
spyOn @event, 'success_callback'
|
55
|
+
spyOn @event, 'failure_callback'
|
56
|
+
|
57
|
+
describe 'when successful', ->
|
58
|
+
beforeEach ->
|
59
|
+
@event.run_callbacks 0, 'foo'
|
60
|
+
|
61
|
+
it 'should run the success callback when passed 0', ->
|
62
|
+
expect(@event.success_callback).toHaveBeenCalledWith('foo')
|
63
|
+
|
64
|
+
it 'should not run the failure callback when passed 0', ->
|
65
|
+
expect(@event.failure_callback).not.toHaveBeenCalled()
|
66
|
+
|
67
|
+
describe 'when failure', ->
|
68
|
+
beforeEach ->
|
69
|
+
@event.run_callbacks 1, 'foo'
|
70
|
+
|
71
|
+
it 'should run the failure callback when passed 1', ->
|
72
|
+
expect(@event.failure_callback).toHaveBeenCalledWith('foo')
|
73
|
+
|
74
|
+
it 'should not run the success callback when passed 1', ->
|
75
|
+
expect(@event.success_callback).not.toHaveBeenCalled()
|
76
|
+
|
77
|
+
describe 'when finished without result', ->
|
78
|
+
it 'should not run any callbacks when passed 2', ->
|
79
|
+
@event.run_callbacks 2, 'foo'
|
80
|
+
expect(@event.success_callback).not.toHaveBeenCalled()
|
81
|
+
expect(@event.failure_callback).not.toHaveBeenCalled()
|
@@ -0,0 +1,158 @@
|
|
1
|
+
describe 'WebsocketRails.WebSocketConnection:', ->
|
2
|
+
SAMPLE_EVENT_DATA = ['event','message']
|
3
|
+
SAMPLE_EVENT =
|
4
|
+
data: JSON.stringify(SAMPLE_EVENT_DATA)
|
5
|
+
|
6
|
+
beforeEach ->
|
7
|
+
@dispatcher =
|
8
|
+
new_message: -> true
|
9
|
+
dispatch: -> true
|
10
|
+
state: 'connected'
|
11
|
+
# Have to stub the WebSocket object due to Firefox error during jasmine:ci
|
12
|
+
window.WebSocket = class WebSocketStub
|
13
|
+
constructor: (@url, @dispatcher) ->
|
14
|
+
send: -> true
|
15
|
+
close: -> @onclose(null)
|
16
|
+
@connection = new WebSocketRails.WebSocketConnection('localhost:3000/websocket', @dispatcher)
|
17
|
+
@dispatcher._conn = @connection
|
18
|
+
|
19
|
+
describe 'constructor', ->
|
20
|
+
|
21
|
+
it 'should redirect onmessage events\' data from the WebSocket object to this.on_message', ->
|
22
|
+
mock_connection = sinon.mock @connection
|
23
|
+
mock_connection.expects('on_message').once().withArgs SAMPLE_EVENT_DATA
|
24
|
+
@connection._conn.onmessage(SAMPLE_EVENT)
|
25
|
+
mock_connection.verify()
|
26
|
+
|
27
|
+
it 'should redirect onclose events from the WebSocket object to this.on_close', ->
|
28
|
+
mock_connection = sinon.mock @connection
|
29
|
+
mock_connection.expects('on_close').once().withArgs SAMPLE_EVENT
|
30
|
+
@connection._conn.onclose(SAMPLE_EVENT)
|
31
|
+
mock_connection.verify()
|
32
|
+
|
33
|
+
describe 'with ssl', ->
|
34
|
+
it 'should not add the ws:// prefix to the URL', ->
|
35
|
+
connection = new WebSocketRails.WebSocketConnection('wss://localhost.com')
|
36
|
+
expect(connection.url).toEqual 'wss://localhost.com'
|
37
|
+
|
38
|
+
describe 'without ssl', ->
|
39
|
+
it 'should add the ws:// prefix to the URL', ->
|
40
|
+
expect(@connection.url).toEqual 'ws://localhost:3000/websocket'
|
41
|
+
|
42
|
+
describe '.close', ->
|
43
|
+
it 'should close the connection', ->
|
44
|
+
@connection.close()
|
45
|
+
expect(@dispatcher.state).toEqual 'disconnected'
|
46
|
+
|
47
|
+
describe '.trigger', ->
|
48
|
+
|
49
|
+
describe 'before the connection has been fully established', ->
|
50
|
+
it 'should queue up the events', ->
|
51
|
+
@connection.dispatcher.state = 'connecting'
|
52
|
+
event = new WebSocketRails.Event ['event','message']
|
53
|
+
mock_queue = sinon.mock @connection.message_queue
|
54
|
+
mock_queue.expects('push').once().withArgs event
|
55
|
+
|
56
|
+
describe 'after the connection has been fully established', ->
|
57
|
+
it 'should encode the data and send it through the WebSocket object', ->
|
58
|
+
@connection.dispatcher.state = 'connected'
|
59
|
+
event = new WebSocketRails.Event ['event','message']
|
60
|
+
@connection._conn =
|
61
|
+
send: -> true
|
62
|
+
mock_connection = sinon.mock @connection._conn
|
63
|
+
mock_connection.expects('send').once().withArgs event.serialize()
|
64
|
+
@connection.trigger event
|
65
|
+
mock_connection.verify()
|
66
|
+
|
67
|
+
describe '.on_message', ->
|
68
|
+
|
69
|
+
it 'should decode the message and pass it to the dispatcher', ->
|
70
|
+
mock_dispatcher = sinon.mock @connection.dispatcher
|
71
|
+
mock_dispatcher.expects('new_message').once().withArgs SAMPLE_EVENT_DATA
|
72
|
+
@connection.on_message SAMPLE_EVENT_DATA
|
73
|
+
mock_dispatcher.verify()
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
describe '.on_close', ->
|
78
|
+
it 'should dispatch the connection_closed event and pass the original event', ->
|
79
|
+
event = new WebSocketRails.Event ['event','message']
|
80
|
+
close_event = new WebSocketRails.Event(['connection_closed', event ])
|
81
|
+
sinon.spy @dispatcher, 'dispatch'
|
82
|
+
@connection.on_close close_event
|
83
|
+
|
84
|
+
dispatcher = @dispatcher.dispatch
|
85
|
+
lastCall = dispatcher.lastCall.args[0]
|
86
|
+
expect(dispatcher.calledOnce).toBe(true)
|
87
|
+
expect(lastCall.data).toEqual event.data
|
88
|
+
|
89
|
+
dispatcher.restore()
|
90
|
+
|
91
|
+
it 'sets the connection state on the dispatcher to disconnected', ->
|
92
|
+
close_event = new WebSocketRails.Event(['connection_closed', {} ])
|
93
|
+
@connection.on_close close_event
|
94
|
+
|
95
|
+
expect(@dispatcher.state).toEqual('disconnected')
|
96
|
+
|
97
|
+
describe '.on_error', ->
|
98
|
+
it 'should dispatch the connection_error event and pass the original event', ->
|
99
|
+
|
100
|
+
event = new WebSocketRails.Event ['event','message']
|
101
|
+
error_event = new WebSocketRails.Event(['connection_error', event ])
|
102
|
+
sinon.spy @dispatcher, 'dispatch'
|
103
|
+
@connection.on_error event
|
104
|
+
|
105
|
+
dispatcher = @dispatcher.dispatch
|
106
|
+
lastCall = dispatcher.lastCall.args[0]
|
107
|
+
expect(dispatcher.calledOnce).toBe(true)
|
108
|
+
expect(lastCall.data).toEqual event.data
|
109
|
+
|
110
|
+
dispatcher.restore()
|
111
|
+
|
112
|
+
it 'sets the connection state on the dispatcher to disconnected', ->
|
113
|
+
close_event = new WebSocketRails.Event(['connection_closed', {} ])
|
114
|
+
@connection.on_error close_event
|
115
|
+
|
116
|
+
expect(@dispatcher.state).toEqual('disconnected')
|
117
|
+
|
118
|
+
describe "it's no longer active connection", ->
|
119
|
+
beforeEach ->
|
120
|
+
@new_connection = new WebSocketRails.WebSocketConnection('localhost:3000/websocket', @dispatcher)
|
121
|
+
@dispatcher._conn = @new_connection
|
122
|
+
|
123
|
+
it ".on_error should not react to the event response", ->
|
124
|
+
mock_dispatcher = sinon.mock @connection.dispatcher
|
125
|
+
mock_dispatcher.expects('dispatch').never()
|
126
|
+
@connection.on_error SAMPLE_EVENT_DATA
|
127
|
+
mock_dispatcher.verify()
|
128
|
+
|
129
|
+
it ".on_close should not react to the event response", ->
|
130
|
+
mock_dispatcher = sinon.mock @connection.dispatcher
|
131
|
+
mock_dispatcher.expects('dispatch').never()
|
132
|
+
@connection.on_close SAMPLE_EVENT_DATA
|
133
|
+
mock_dispatcher.verify()
|
134
|
+
|
135
|
+
it ".on_message should not react to the event response", ->
|
136
|
+
mock_dispatcher = sinon.mock @connection.dispatcher
|
137
|
+
mock_dispatcher.expects('new_message').never()
|
138
|
+
@connection.on_message SAMPLE_EVENT_DATA
|
139
|
+
mock_dispatcher.verify()
|
140
|
+
|
141
|
+
describe '.flush_queue', ->
|
142
|
+
beforeEach ->
|
143
|
+
@event = new WebSocketRails.Event ['event','message']
|
144
|
+
@connection.message_queue.push @event
|
145
|
+
@connection._conn =
|
146
|
+
send: -> true
|
147
|
+
|
148
|
+
it 'should send out all of the messages in the queue', ->
|
149
|
+
mock_connection = sinon.mock @connection._conn
|
150
|
+
mock_connection.expects('send').once().withArgs @event.serialize()
|
151
|
+
@connection.flush_queue()
|
152
|
+
mock_connection.verify()
|
153
|
+
|
154
|
+
it 'should empty the queue after sending', ->
|
155
|
+
expect( @connection.message_queue.length ).toEqual 1
|
156
|
+
@connection.flush_queue()
|
157
|
+
expect( @connection.message_queue.length ).toEqual 0
|
158
|
+
|
@@ -0,0 +1,273 @@
|
|
1
|
+
describe 'WebSocketRails:', ->
|
2
|
+
beforeEach ->
|
3
|
+
@url = 'localhost:3000/websocket'
|
4
|
+
WebSocketRails.WebSocketConnection = class WebSocketConnectionStub extends WebSocketRails.AbstractConnection
|
5
|
+
connection_type: 'websocket'
|
6
|
+
WebSocketRails.HttpConnection = class HttpConnectionStub extends WebSocketRails.AbstractConnection
|
7
|
+
connection_type: 'http'
|
8
|
+
@dispatcher = new WebSocketRails @url
|
9
|
+
|
10
|
+
describe 'constructor', ->
|
11
|
+
it 'should start connection automatically', ->
|
12
|
+
expect(@dispatcher.state).toEqual 'connecting'
|
13
|
+
|
14
|
+
describe '.connect', ->
|
15
|
+
|
16
|
+
it 'should set the new_message method on connection to this.new_message', ->
|
17
|
+
expect(@dispatcher._conn.new_message).toEqual @dispatcher.new_message
|
18
|
+
|
19
|
+
it 'should set the initial state to connecting', ->
|
20
|
+
expect(@dispatcher.state).toEqual 'connecting'
|
21
|
+
|
22
|
+
describe 'when use_websockets is true', ->
|
23
|
+
it 'should use the WebSocket Connection', ->
|
24
|
+
dispatcher = new WebSocketRails @url, true
|
25
|
+
expect(dispatcher._conn.connection_type).toEqual 'websocket'
|
26
|
+
|
27
|
+
describe 'when use_websockets is false', ->
|
28
|
+
it 'should use the Http Connection', ->
|
29
|
+
dispatcher = new WebSocketRails @url, false
|
30
|
+
expect(dispatcher._conn.connection_type).toEqual 'http'
|
31
|
+
|
32
|
+
describe 'when the browser does not support WebSockets', ->
|
33
|
+
it 'should use the Http Connection', ->
|
34
|
+
window.WebSocket = 'undefined'
|
35
|
+
dispatcher = new WebSocketRails @url, true
|
36
|
+
expect(dispatcher._conn.connection_type).toEqual 'http'
|
37
|
+
|
38
|
+
describe '.disconnect', ->
|
39
|
+
beforeEach ->
|
40
|
+
@dispatcher.disconnect()
|
41
|
+
|
42
|
+
it 'should close the connection', ->
|
43
|
+
expect(@dispatcher.state).toEqual 'disconnected'
|
44
|
+
|
45
|
+
it 'existing connection should be destroyed', ->
|
46
|
+
expect(@dispatcher._conn).toBeUndefined()
|
47
|
+
|
48
|
+
describe '.reconnect', ->
|
49
|
+
OLD_CONNECTION_ID = 1
|
50
|
+
NEW_CONNECTION_ID = 2
|
51
|
+
|
52
|
+
it 'should connect, when disconnected', ->
|
53
|
+
mock_dispatcher = sinon.mock @dispatcher
|
54
|
+
mock_dispatcher.expects('connect').once()
|
55
|
+
@dispatcher.disconnect()
|
56
|
+
@dispatcher.reconnect()
|
57
|
+
mock_dispatcher.verify()
|
58
|
+
|
59
|
+
it 'should recreate the connection', ->
|
60
|
+
helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
|
61
|
+
@dispatcher.reconnect()
|
62
|
+
helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
|
63
|
+
|
64
|
+
expect(@dispatcher._conn.connection_id).toEqual NEW_CONNECTION_ID
|
65
|
+
|
66
|
+
it 'should resend all uncompleted events', ->
|
67
|
+
event = @dispatcher.trigger('create_post')
|
68
|
+
|
69
|
+
helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
|
70
|
+
@dispatcher.reconnect()
|
71
|
+
helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
|
72
|
+
|
73
|
+
expect(@dispatcher.queue[event.id].connection_id).toEqual NEW_CONNECTION_ID
|
74
|
+
|
75
|
+
it 'should not resend completed events', ->
|
76
|
+
event = @dispatcher.trigger('create_post')
|
77
|
+
event.run_callbacks(true, {})
|
78
|
+
|
79
|
+
helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
|
80
|
+
@dispatcher.reconnect()
|
81
|
+
helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
|
82
|
+
|
83
|
+
expect(@dispatcher.queue[event.id].connection_id).toEqual OLD_CONNECTION_ID
|
84
|
+
|
85
|
+
it 'should reconnect to all channels', ->
|
86
|
+
mock_dispatcher = sinon.mock @dispatcher
|
87
|
+
mock_dispatcher.expects('reconnect_channels').once()
|
88
|
+
@dispatcher.reconnect()
|
89
|
+
mock_dispatcher.verify()
|
90
|
+
|
91
|
+
describe '.reconnect_channels', ->
|
92
|
+
beforeEach ->
|
93
|
+
@channel_callback = -> true
|
94
|
+
helpers.startConnection(@dispatcher, 1)
|
95
|
+
@dispatcher.subscribe('public 4chan')
|
96
|
+
@dispatcher.subscribe_private('private 4chan')
|
97
|
+
@dispatcher.channels['public 4chan'].bind('new_post', @channel_callback)
|
98
|
+
|
99
|
+
it 'should recreate existing channels, keeping their private/public type', ->
|
100
|
+
@dispatcher.reconnect_channels()
|
101
|
+
expect(@dispatcher.channels['public 4chan'].is_private).toEqual false
|
102
|
+
expect(@dispatcher.channels['private 4chan'].is_private).toEqual true
|
103
|
+
|
104
|
+
it 'should move all existing callbacks from old channel objects to new ones', ->
|
105
|
+
old_public_channel = @dispatcher.channels['public 4chan']
|
106
|
+
|
107
|
+
@dispatcher.reconnect_channels()
|
108
|
+
|
109
|
+
expect(old_public_channel._callbacks).toEqual {}
|
110
|
+
expect(@dispatcher.channels['public 4chan']._callbacks).toEqual {new_post: [@channel_callback]}
|
111
|
+
|
112
|
+
describe '.new_message', ->
|
113
|
+
|
114
|
+
describe 'when this.state is "connecting"', ->
|
115
|
+
beforeEach ->
|
116
|
+
@connection_id = 123
|
117
|
+
|
118
|
+
it 'should call this.connection_established on the "client_connected" event', ->
|
119
|
+
mock_dispatcher = sinon.mock @dispatcher
|
120
|
+
mock_dispatcher.expects('connection_established').once().withArgs(connection_id: @connection_id)
|
121
|
+
helpers.startConnection(@dispatcher, @connection_id)
|
122
|
+
mock_dispatcher.verify()
|
123
|
+
|
124
|
+
it 'should set the state to connected', ->
|
125
|
+
helpers.startConnection(@dispatcher, @connection_id)
|
126
|
+
expect(@dispatcher.state).toEqual 'connected'
|
127
|
+
|
128
|
+
it 'should flush any messages queued before the connection was established', ->
|
129
|
+
mock_con = sinon.mock @dispatcher._conn
|
130
|
+
mock_con.expects('flush_queue').once()
|
131
|
+
helpers.startConnection(@dispatcher, @connection_id)
|
132
|
+
mock_con.verify()
|
133
|
+
|
134
|
+
it 'should set the correct connection_id', ->
|
135
|
+
helpers.startConnection(@dispatcher, @connection_id)
|
136
|
+
expect(@dispatcher._conn.connection_id).toEqual 123
|
137
|
+
|
138
|
+
it 'should call the user defined on_open callback', ->
|
139
|
+
spy = sinon.spy()
|
140
|
+
@dispatcher.on_open = spy
|
141
|
+
helpers.startConnection(@dispatcher, @connection_id)
|
142
|
+
expect(spy.calledOnce).toEqual true
|
143
|
+
|
144
|
+
describe 'after the connection has been established', ->
|
145
|
+
beforeEach ->
|
146
|
+
@dispatcher.state = 'connected'
|
147
|
+
@attributes =
|
148
|
+
data: 'message'
|
149
|
+
channel: 'channel'
|
150
|
+
|
151
|
+
it 'should dispatch channel messages', ->
|
152
|
+
data = [['event',@attributes]]
|
153
|
+
mock_dispatcher = sinon.mock @dispatcher
|
154
|
+
mock_dispatcher.expects('dispatch_channel').once()
|
155
|
+
@dispatcher.new_message data
|
156
|
+
mock_dispatcher.verify()
|
157
|
+
|
158
|
+
it 'should dispatch standard events', ->
|
159
|
+
data = [['event','message']]
|
160
|
+
mock_dispatcher = sinon.mock @dispatcher
|
161
|
+
mock_dispatcher.expects('dispatch').once()
|
162
|
+
@dispatcher.new_message data
|
163
|
+
mock_dispatcher.verify()
|
164
|
+
|
165
|
+
describe 'result events', ->
|
166
|
+
beforeEach ->
|
167
|
+
@attributes['success'] = 2
|
168
|
+
@attributes['id'] = 1
|
169
|
+
@event_data = [['event',@attributes]]
|
170
|
+
@event = new WebSocketRails.Event @event_data
|
171
|
+
@dispatcher.queue[1] = @event
|
172
|
+
|
173
|
+
it 'should run callbacks for result events', ->
|
174
|
+
spyOn(@event, 'run_callbacks')
|
175
|
+
@dispatcher.new_message @event_data
|
176
|
+
expect(@event.run_callbacks).toHaveBeenCalledWith(2, 'message')
|
177
|
+
|
178
|
+
it 'should remove the event from the queue', ->
|
179
|
+
@dispatcher.new_message @event_data
|
180
|
+
expect(@dispatcher.queue[1]).toBeUndefined()
|
181
|
+
|
182
|
+
|
183
|
+
describe '.bind', ->
|
184
|
+
|
185
|
+
it 'should store the callback on the correct event', ->
|
186
|
+
callback = ->
|
187
|
+
@dispatcher.bind 'event', callback
|
188
|
+
expect(@dispatcher.callbacks['event']).toContain callback
|
189
|
+
|
190
|
+
describe '.unbind', ->
|
191
|
+
|
192
|
+
it 'should delete the callback on the correct event', ->
|
193
|
+
callback = ->
|
194
|
+
@dispatcher.bind 'event', callback
|
195
|
+
@dispatcher.unbind 'event'
|
196
|
+
expect(@dispatcher.callbacks['event']).toBeUndefined()
|
197
|
+
|
198
|
+
describe '.dispatch', ->
|
199
|
+
|
200
|
+
it 'should execute the callback for the correct event', ->
|
201
|
+
callback = sinon.spy()
|
202
|
+
event = new WebSocketRails.Event(['event',{data: 'message'}])
|
203
|
+
@dispatcher.bind 'event', callback
|
204
|
+
@dispatcher.dispatch event
|
205
|
+
expect(callback.calledWith('message')).toEqual true
|
206
|
+
|
207
|
+
describe 'triggering events with', ->
|
208
|
+
beforeEach ->
|
209
|
+
@dispatcher._conn =
|
210
|
+
connection_id: 123
|
211
|
+
trigger: ->
|
212
|
+
|
213
|
+
describe '.trigger', ->
|
214
|
+
it 'should add the event to the queue', ->
|
215
|
+
event = @dispatcher.trigger 'event', 'message'
|
216
|
+
expect(@dispatcher.queue[event.id]).toEqual event
|
217
|
+
|
218
|
+
it 'should delegate to the connection object', ->
|
219
|
+
conn_trigger = sinon.spy @dispatcher._conn, 'trigger'
|
220
|
+
@dispatcher.trigger 'event', 'message'
|
221
|
+
expect(conn_trigger.called).toEqual true
|
222
|
+
|
223
|
+
it "should not delegate to the connection object, if it's not available", ->
|
224
|
+
@dispatcher._conn = null
|
225
|
+
@dispatcher.trigger 'event', 'message'
|
226
|
+
|
227
|
+
describe '.connection_stale', ->
|
228
|
+
describe 'when state is connected', ->
|
229
|
+
it 'should return false', ->
|
230
|
+
@dispatcher.state = 'connected'
|
231
|
+
expect(@dispatcher.connection_stale()).toEqual false
|
232
|
+
|
233
|
+
describe 'when state is disconnected', ->
|
234
|
+
it 'should return true', ->
|
235
|
+
@dispatcher.state = 'disconnected'
|
236
|
+
expect(@dispatcher.connection_stale()).toEqual true
|
237
|
+
|
238
|
+
describe 'working with channels', ->
|
239
|
+
beforeEach ->
|
240
|
+
WebSocketRails.Channel = (@name,@dispatcher,@is_private) ->
|
241
|
+
|
242
|
+
describe '.subscribe', ->
|
243
|
+
describe 'for new channels', ->
|
244
|
+
it 'should create and store a new Channel object', ->
|
245
|
+
channel = @dispatcher.subscribe 'test_channel'
|
246
|
+
expect(channel.name).toEqual 'test_channel'
|
247
|
+
|
248
|
+
describe 'for existing channels', ->
|
249
|
+
it 'should return the same Channel object', ->
|
250
|
+
channel = @dispatcher.subscribe 'test_channel'
|
251
|
+
expect(@dispatcher.subscribe('test_channel')).toEqual channel
|
252
|
+
|
253
|
+
describe '.subscribe_private', ->
|
254
|
+
it 'should create private channels', ->
|
255
|
+
private_channel = @dispatcher.subscribe_private 'private_something'
|
256
|
+
expect(private_channel.is_private).toBe true
|
257
|
+
|
258
|
+
describe '.unsubscribe', ->
|
259
|
+
describe 'for existing channels', ->
|
260
|
+
it 'should remove the Channel object', ->
|
261
|
+
@dispatcher.unsubscribe 'test_channel'
|
262
|
+
expect(@dispatcher.channels['test_channel']).toBeUndefined
|
263
|
+
|
264
|
+
describe '.dispatch_channel', ->
|
265
|
+
|
266
|
+
it 'should delegate to the Channel object', ->
|
267
|
+
channel = @dispatcher.subscribe 'test'
|
268
|
+
channel.dispatch = ->
|
269
|
+
spy = sinon.spy channel, 'dispatch'
|
270
|
+
event = new WebSocketRails.Event(['event',{channel: 'test', data: 'awesome'}])
|
271
|
+
@dispatcher.dispatch_channel event
|
272
|
+
expect(spy.calledWith('event', 'awesome')).toEqual true
|
273
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
ENV["RAILS_ENV"] ||= 'test'
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start if ENV["COVERAGE"]
|
5
|
+
|
6
|
+
require File.expand_path("../../spec/dummy/config/environment", __FILE__)
|
7
|
+
require 'rspec/rails'
|
8
|
+
require 'rspec/autorun'
|
9
|
+
require 'thin'
|
10
|
+
|
11
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
12
|
+
require 'websocket-rails'
|
13
|
+
|
14
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
15
|
+
# in spec/support/ and its subdirectories.
|
16
|
+
Dir["./spec/support/**/*.rb"].each {|f| require f}
|
17
|
+
require 'websocket_rails/spec_helpers'
|
18
|
+
require 'rspec-matchers-matchers'
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
# == Mock Framework
|
22
|
+
#
|
23
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
24
|
+
#
|
25
|
+
# config.mock_with :mocha
|
26
|
+
# config.mock_with :flexmock
|
27
|
+
# config.mock_with :rr
|
28
|
+
config.mock_with :rspec
|
29
|
+
|
30
|
+
config.include WebsocketRails::HelperMethods
|
31
|
+
|
32
|
+
# If true, the base class of anonymous controllers will be inferred
|
33
|
+
# automatically. This will be the default behavior in future versions of
|
34
|
+
# rspec-rails.
|
35
|
+
config.infer_base_class_for_anonymous_controllers = false
|
36
|
+
|
37
|
+
config.before(:each) do
|
38
|
+
WebsocketRails.config.logger = Logger.new(StringIO.new)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|