strelka 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +3293 -3058
  5. data/History.rdoc +17 -0
  6. data/Manifest.txt +3 -0
  7. data/Rakefile +2 -2
  8. data/contrib/hoetemplate/lib/file_name.rb.erb +3 -2
  9. data/contrib/hoetemplate/spec/file_name_spec.rb.erb +1 -1
  10. data/examples/apps/auth-demo +1 -2
  11. data/examples/apps/auth-demo2 +1 -2
  12. data/examples/apps/sessions-demo +1 -2
  13. data/examples/gen-config.rb +1 -2
  14. data/lib/strelka.rb +92 -17
  15. data/lib/strelka/app.rb +7 -6
  16. data/lib/strelka/app/auth.rb +5 -5
  17. data/lib/strelka/app/errors.rb +1 -1
  18. data/lib/strelka/app/filters.rb +1 -1
  19. data/lib/strelka/app/negotiation.rb +1 -1
  20. data/lib/strelka/app/parameters.rb +1 -1
  21. data/lib/strelka/app/restresources.rb +14 -21
  22. data/lib/strelka/app/routing.rb +5 -6
  23. data/lib/strelka/app/sessions.rb +3 -1
  24. data/lib/strelka/app/templating.rb +1 -1
  25. data/lib/strelka/authprovider.rb +1 -1
  26. data/lib/strelka/authprovider/basic.rb +1 -0
  27. data/lib/strelka/authprovider/hostaccess.rb +1 -0
  28. data/lib/strelka/behavior/plugin.rb +2 -2
  29. data/lib/strelka/cli.rb +2 -1
  30. data/lib/strelka/command/config.rb +2 -1
  31. data/lib/strelka/command/discover.rb +2 -1
  32. data/lib/strelka/command/start.rb +2 -1
  33. data/lib/strelka/constants.rb +1 -1
  34. data/lib/strelka/cookie.rb +1 -1
  35. data/lib/strelka/cookieset.rb +1 -1
  36. data/lib/strelka/discovery.rb +1 -1
  37. data/lib/strelka/httprequest.rb +4 -4
  38. data/lib/strelka/httprequest/acceptparams.rb +1 -1
  39. data/lib/strelka/httprequest/auth.rb +3 -1
  40. data/lib/strelka/httprequest/negotiation.rb +1 -1
  41. data/lib/strelka/httprequest/session.rb +3 -1
  42. data/lib/strelka/httpresponse.rb +2 -3
  43. data/lib/strelka/httpresponse/negotiation.rb +1 -1
  44. data/lib/strelka/httpresponse/session.rb +1 -1
  45. data/lib/strelka/mixins.rb +26 -5
  46. data/lib/strelka/multipartparser.rb +3 -3
  47. data/lib/strelka/paramvalidator.rb +4 -4
  48. data/lib/strelka/plugins.rb +14 -5
  49. data/lib/strelka/router.rb +1 -1
  50. data/lib/strelka/router/default.rb +1 -1
  51. data/lib/strelka/router/exclusive.rb +1 -1
  52. data/lib/strelka/session.rb +1 -0
  53. data/lib/strelka/session/db.rb +1 -0
  54. data/lib/strelka/session/default.rb +1 -0
  55. data/lib/strelka/testing.rb +454 -14
  56. data/lib/strelka/websocketserver.rb +150 -36
  57. data/lib/strelka/websocketserver/heartbeat.rb +163 -0
  58. data/lib/strelka/websocketserver/routing.rb +46 -19
  59. data/spec/constants.rb +1 -1
  60. data/spec/helpers.rb +15 -6
  61. data/spec/strelka/app/auth_spec.rb +5 -3
  62. data/spec/strelka/app/errors_spec.rb +2 -2
  63. data/spec/strelka/app/filters_spec.rb +2 -2
  64. data/spec/strelka/app/negotiation_spec.rb +2 -2
  65. data/spec/strelka/app/parameters_spec.rb +5 -5
  66. data/spec/strelka/app/restresources_spec.rb +8 -6
  67. data/spec/strelka/app/routing_spec.rb +3 -3
  68. data/spec/strelka/app/sessions_spec.rb +4 -2
  69. data/spec/strelka/app/templating_spec.rb +2 -2
  70. data/spec/strelka/app_spec.rb +5 -24
  71. data/spec/strelka/authprovider/basic_spec.rb +3 -2
  72. data/spec/strelka/authprovider/hostaccess_spec.rb +3 -2
  73. data/spec/strelka/authprovider_spec.rb +3 -2
  74. data/spec/strelka/cli_spec.rb +7 -4
  75. data/spec/strelka/cookie_spec.rb +2 -2
  76. data/spec/strelka/cookieset_spec.rb +2 -2
  77. data/spec/strelka/discovery_spec.rb +2 -2
  78. data/spec/strelka/exceptions_spec.rb +2 -2
  79. data/spec/strelka/httprequest/acceptparams_spec.rb +2 -2
  80. data/spec/strelka/httprequest/auth_spec.rb +3 -2
  81. data/spec/strelka/httprequest/negotiation_spec.rb +2 -2
  82. data/spec/strelka/httprequest/session_spec.rb +3 -2
  83. data/spec/strelka/httprequest_spec.rb +7 -2
  84. data/spec/strelka/httpresponse/negotiation_spec.rb +6 -5
  85. data/spec/strelka/httpresponse/session_spec.rb +3 -2
  86. data/spec/strelka/httpresponse_spec.rb +4 -3
  87. data/spec/strelka/mixins_spec.rb +85 -2
  88. data/spec/strelka/multipartparser_spec.rb +5 -4
  89. data/spec/strelka/paramvalidator_spec.rb +15 -10
  90. data/spec/strelka/plugins_spec.rb +24 -2
  91. data/spec/strelka/router/default_spec.rb +2 -2
  92. data/spec/strelka/router/exclusive_spec.rb +2 -2
  93. data/spec/strelka/router_spec.rb +2 -2
  94. data/spec/strelka/session/db_spec.rb +3 -2
  95. data/spec/strelka/session/default_spec.rb +3 -2
  96. data/spec/strelka/session_spec.rb +3 -2
  97. data/spec/strelka/testing_spec.rb +772 -0
  98. data/spec/strelka/websocketserver/heartbeat_spec.rb +19 -0
  99. data/spec/strelka/websocketserver/routing_spec.rb +31 -29
  100. data/spec/strelka/websocketserver_spec.rb +210 -75
  101. data/spec/strelka_spec.rb +172 -2
  102. metadata +43 -36
  103. metadata.gz.sig +0 -0
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen-string-literal: true
4
+
5
+ require_relative '../../helpers'
6
+
7
+ require 'strelka'
8
+ require 'strelka/plugins'
9
+ require 'strelka/websocketserver/heartbeat'
10
+
11
+ require 'strelka/behavior/plugin'
12
+
13
+
14
+ RSpec.describe Strelka::WebSocketServer::Heartbeat do
15
+
16
+ it_should_behave_like( "A Strelka Plugin" )
17
+
18
+ end
19
+
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
3
+ # frozen-string-literal: true
4
4
 
5
5
  require_relative '../../helpers'
6
6
 
@@ -17,10 +17,10 @@ require 'strelka/behavior/plugin'
17
17
  ### C O N T E X T S
18
18
  #####################################################################
19
19
 
20
- describe Strelka::WebSocketServer::Routing do
20
+ RSpec.describe Strelka::WebSocketServer::Routing do
21
21
 
22
22
  before( :all ) do
23
- @frame_factory = Mongrel2::WebSocketFrameFactory.new( route: '/chat' )
23
+ @request_factory = Mongrel2::WebSocketRequestFactory.new( route: '/chat' )
24
24
  end
25
25
 
26
26
 
@@ -29,8 +29,8 @@ describe Strelka::WebSocketServer::Routing do
29
29
 
30
30
  describe "an including App" do
31
31
 
32
- before( :each ) do
33
- @app = Class.new( Strelka::WebSocketServer ) do
32
+ let( :app_class ) do
33
+ Class.new( Strelka::WebSocketServer ) do
34
34
  plugin :routing
35
35
  def initialize( appid='chat-test', sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
36
36
  super
@@ -40,32 +40,34 @@ describe Strelka::WebSocketServer::Routing do
40
40
 
41
41
 
42
42
  it "has an Hash of raw routes" do
43
- expect( @app.op_callbacks ).to be_a( Hash )
43
+ expect( app_class.op_callbacks ).to be_a( Hash )
44
44
  end
45
45
 
46
+
46
47
  it "knows what its route methods are" do
47
- expect( @app.op_callbacks ).to eq( {} )
48
- @app.class_eval do
48
+ expect( app_class.op_callbacks ).to eq( {} )
49
+ app_class.class_eval do
49
50
  on_text() {}
50
51
  on_binary() {}
51
52
  on_ping() {}
52
53
  end
53
54
 
54
- expect( @app.op_callbacks.keys ).to eq([ :text, :binary, :ping ])
55
+ expect( app_class.op_callbacks.keys ).to eq([ :text, :binary, :ping ])
55
56
  end
56
57
 
58
+
57
59
  it "allows the declaration of custom opcodes" do
58
- @app.opcodes( 0x3 => :nick )
59
- @app.class_eval do
60
+ app_class.opcodes( nick: 0x3 )
61
+ app_class.class_eval do
60
62
  on_nick() {}
61
63
  end
62
- expect( @app.op_callbacks.size ).to eq( 1 )
63
- expect( @app.op_callbacks[ :nick ] ).to be_a( UnboundMethod )
64
+ expect( app_class.op_callbacks.size ).to eq( 1 )
65
+ expect( app_class.op_callbacks[ :nick ] ).to be_a( UnboundMethod )
64
66
  end
65
67
 
66
68
 
67
69
  it "dispatches TEXT frames to a text handler if one is declared" do
68
- @app.class_eval do
70
+ app_class.class_eval do
69
71
  on_text do |frame|
70
72
  res = frame.response
71
73
  res.puts( "#{frame.body.read} Yep!" )
@@ -73,38 +75,38 @@ describe Strelka::WebSocketServer::Routing do
73
75
  end
74
76
  end
75
77
 
76
- frame = @frame_factory.text( "/chat", "Clowns?" )
77
- response = @app.new.handle_websocket( frame )
78
+ frame = @request_factory.text( "/chat", "Clowns?" )
79
+ response = app_class.new.handle_websocket( frame )
78
80
 
79
- expect( response ).to be_a( Mongrel2::WebSocket::Frame )
81
+ expect( response ).to be_a( Mongrel2::WebSocket::Response )
80
82
  expect( response.opcode ).to eq( :text )
81
- response.body.rewind
82
- expect( response.body.read ).to eq( "Clowns? Yep!\n" )
83
+ response.payload.rewind
84
+ expect( response.payload.read ).to eq( "Clowns? Yep!\n" )
83
85
  end
84
86
 
87
+
85
88
  it "dispatches custom frame type to its handler if one is declared" do
86
- @app.class_eval do
87
- opcodes 0xB => :refresh
89
+ app_class.class_eval do
90
+ opcodes refresh: 0xb
88
91
 
89
92
  on_refresh do |frame|
90
93
  return frame.response
91
94
  end
92
95
  end
93
96
 
94
- frame = @frame_factory.create( '/chat', '', 0xB )
95
- response = @app.new.handle_websocket( frame )
97
+ frame = @request_factory.create( '/chat', '', 0xb )
98
+ response = app_class.new.handle_websocket( frame )
96
99
 
97
- expect( response ).to be_a( Mongrel2::WebSocket::Frame )
98
- expect( response.opcode ).to eq( :reserved )
99
- expect( response.numeric_opcode ).to eq( 0xB )
100
+ expect( response ).to be_a( Mongrel2::WebSocket::Response )
101
+ expect( response.numeric_opcode ).to eq( 0xb )
100
102
  end
101
103
 
102
104
 
103
105
  it "falls back to the defaults if a handler isn't declared for a frame" do
104
- frame = @frame_factory.text( '/chat', '' )
105
- response = @app.new.handle_websocket( frame )
106
+ frame = @request_factory.text( '/chat', '' )
107
+ response = app_class.new.handle_websocket( frame )
106
108
 
107
- expect( response ).to be_a( Mongrel2::WebSocket::Frame )
109
+ expect( response ).to be_a( Mongrel2::WebSocket::Response )
108
110
  expect( response.opcode ).to eq( :close )
109
111
  end
110
112
 
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
3
+ # frozen-string-literal: true
4
4
 
5
5
  require_relative '../helpers'
6
6
 
@@ -15,11 +15,12 @@ require 'strelka/websocketserver'
15
15
  ### C O N T E X T S
16
16
  #####################################################################
17
17
 
18
- describe Strelka::WebSocketServer do
18
+ RSpec.describe Strelka::WebSocketServer do
19
19
 
20
20
  before( :all ) do
21
21
  @initial_registry = described_class.loaded_plugins.dup
22
- @frame_factory = Mongrel2::WebSocketFrameFactory.new( route: '/chat' )
22
+ @request_factory = Mongrel2::WebSocketRequestFactory.new( route: '/chat' )
23
+
23
24
  Mongrel2::Config.db = Mongrel2::Config.in_memory_db
24
25
  Mongrel2::Config.init_database
25
26
 
@@ -28,12 +29,19 @@ describe Strelka::WebSocketServer do
28
29
  strelkaspec = make_gemspec( 'strelka', Strelka::VERSION, false )
29
30
  loaded_specs = Gem.instance_variable_get( :@loaded_specs )
30
31
  loaded_specs['strelka'] = strelkaspec
31
-
32
32
  end
33
33
 
34
34
  before( :each ) do
35
35
  described_class.loaded_plugins.clear
36
- @app = Class.new( described_class ) do
36
+ end
37
+
38
+ after( :all ) do
39
+ described_class.loaded_plugins = @initial_registry
40
+ end
41
+
42
+
43
+ let( :app_class ) do
44
+ return Class.new( described_class ) do
37
45
  def initialize( appid=TEST_APPID, sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
38
46
  super
39
47
  end
@@ -43,104 +51,231 @@ describe Strelka::WebSocketServer do
43
51
  end
44
52
  end
45
53
 
46
- after( :each ) do
47
- @app = nil
54
+
55
+ it "uses the app's ID constant for the appid if .run is called without one" do
56
+ app_class.const_set( :ID, 'chat-server' )
57
+ expect( app_class.default_appid ).to eq( 'chat-server' )
48
58
  end
49
59
 
50
- after( :all ) do
51
- described_class.loaded_plugins = @initial_registry
60
+
61
+ it "uses the app's name for the appid it has no ID constant" do
62
+ app_class.class_eval do
63
+ def self::name; "Acme::Valhalla::EventServer" ; end
64
+ end
65
+
66
+ expect( app_class.default_appid ).to eq( 'acme-valhalla-eventserver' )
52
67
  end
53
68
 
54
69
 
55
- #
56
- # Control frame defaults
57
- #
70
+ it "accepts websocket handshakes" do
71
+ app = app_class.new
72
+ request = @request_factory.handshake( '/chat' )
58
73
 
59
- it "returns a PONG for PING frames" do
60
- ping = @frame_factory.ping( '/chat' )
61
- res = @app.new.handle_websocket( ping )
74
+ res = app.dispatch_request( request )
62
75
 
63
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
64
- expect( res.opcode ).to eq( :pong )
65
- expect( res.socket_id ).to eq( ping.socket_id )
76
+ expect( res ).to be_a( Mongrel2::WebSocket::ServerHandshake )
66
77
  end
67
78
 
68
- it "ignores PONG frames" do
69
- pong = @frame_factory.pong( '/chat' )
70
- res = @app.new.handle_websocket( pong )
71
79
 
72
- expect( res ).to be_nil()
73
- end
80
+ describe "control frame defaults" do
74
81
 
75
- it "closes the connection on CLOSE frames" do
76
- app = @app.new
77
- close = @frame_factory.close( '/chat' )
82
+ it "returns a PONG response for PING requests" do
83
+ ping = @request_factory.ping( '/chat' )
78
84
 
79
- expect( app.conn ).to receive( :reply_close ).with( close )
85
+ res = app_class.new.dispatch_request( ping )
80
86
 
81
- res = app.handle_websocket( close )
82
- expect( res ).to be_nil()
83
- end
87
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
88
+ expect( res.opcode ).to eq( :pong )
89
+ expect( res.socket_id ).to eq( ping.socket_id )
90
+ end
84
91
 
85
- it "closes the connection with an appropriate error for reserved control opcodes" do
86
- reserved = @frame_factory.create( '/chat', '', 0xB )
87
- res = @app.new.handle_websocket( reserved )
88
92
 
89
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
90
- expect( res.opcode ).to eq( :close )
91
- res.payload.rewind
92
- expect( res.payload.read ).to match( /Unhandled data type/i )
93
- expect( res.socket_id ).to eq( reserved.socket_id )
94
- end
93
+ it "ignores PONG requests" do
94
+ pong = @request_factory.pong( '/chat' )
95
95
 
96
- #
97
- # Content frame defaults
98
- #
96
+ res = app_class.new.dispatch_request( pong )
99
97
 
100
- it "replies with a close frame with a bad data type error for TEXT frames" do
101
- app = @app.new
102
- frame = @frame_factory.text( '/chat' )
98
+ expect( res ).to be_nil()
99
+ end
103
100
 
104
- res = app.handle_websocket( frame )
105
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
106
- expect( res.opcode ).to eq( :close )
107
- res.payload.rewind
108
- expect( res.payload.read ).to match( /Unhandled data type/i )
109
- end
110
101
 
111
- it "replies with a close frame with a bad data type error for BINARY frames" do
112
- app = @app.new
113
- frame = @frame_factory.binary( '/chat' )
102
+ it "closes the connection on CLOSE requests" do
103
+ app = app_class.new
104
+ close = @request_factory.close( '/chat' )
105
+
106
+ expect( app.conn ).to receive( :reply_close ).with( close )
107
+
108
+ res = app.dispatch_request( close )
109
+
110
+ expect( res ).to be_nil()
111
+ end
112
+
113
+
114
+ it "closes the connection with an appropriate error for reserved control opcodes" do
115
+ reserved = @request_factory.create( '/chat', '', 0xB )
116
+
117
+ res = app_class.new.dispatch_request( reserved )
118
+
119
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
120
+ expect( res.opcode ).to eq( :close )
121
+
122
+ res.payload.rewind
123
+ expect( res.payload.read ).to match( /Unhandled data type/i )
124
+ expect( res.socket_id ).to eq( reserved.socket_id )
125
+ end
114
126
 
115
- res = app.handle_websocket( frame )
116
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
117
- expect( res.opcode ).to eq( :close )
118
- res.payload.rewind
119
- expect( res.payload.read ).to match( /Unhandled data type/i )
120
127
  end
121
128
 
122
- it "replies with a close frame with a bad data type error for CONTINUATION frames" do
123
- app = @app.new
124
- frame = @frame_factory.continuation( '/chat' )
125
129
 
126
- res = app.handle_websocket( frame )
127
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
128
- expect( res.opcode ).to eq( :close )
129
- res.payload.rewind
130
- expect( res.payload.read ).to match( /Unhandled data type/i )
130
+ describe "content frame defaults" do
131
+
132
+ it "replies with a close frame with a bad data type error for TEXT requests" do
133
+ app = app_class.new
134
+ frame = @request_factory.text( '/chat' )
135
+
136
+ res = app.dispatch_request( frame )
137
+
138
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
139
+ expect( res.opcode ).to eq( :close )
140
+
141
+ res.payload.rewind
142
+ expect( res.payload.read ).to match( /Unhandled data type/i )
143
+ end
144
+
145
+
146
+ it "replies with a close frame with a bad data type error for BINARY requests" do
147
+ app = app_class.new
148
+ frame = @request_factory.binary( '/chat' )
149
+
150
+ res = app.dispatch_request( frame )
151
+
152
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
153
+ expect( res.opcode ).to eq( :close )
154
+
155
+ res.payload.rewind
156
+ expect( res.payload.read ).to match( /Unhandled data type/i )
157
+ end
158
+
159
+
160
+ it "replies with a close frame with a bad data type error for CONTINUATION requests" do
161
+ app = app_class.new
162
+ frame = @request_factory.continuation( '/chat' )
163
+
164
+ res = app.dispatch_request( frame )
165
+
166
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
167
+ expect( res.opcode ).to eq( :close )
168
+
169
+ res.payload.rewind
170
+ expect( res.payload.read ).to match( /Unhandled data type/i )
171
+ end
172
+
173
+
174
+ it "closes the connection with an appropriate error for reserved content opcodes" do
175
+ reserved = @request_factory.create( '/chat', '', 0x3 )
176
+
177
+ res = app_class.new.dispatch_request( reserved )
178
+
179
+ expect( res ).to be_a( Mongrel2::WebSocket::Response )
180
+ expect( res.opcode ).to eq( :close )
181
+
182
+ res.payload.rewind
183
+ expect( res.payload.read ).to match( /Unhandled data type/i )
184
+ expect( res.socket_id ).to eq( reserved.socket_id )
185
+ end
186
+
131
187
  end
132
188
 
133
- it "closes the connection with an appropriate error for reserved content opcodes" do
134
- reserved = @frame_factory.create( '/chat', '', 0x3 )
135
- res = @app.new.handle_websocket( reserved )
136
189
 
137
- expect( res ).to be_a( Mongrel2::WebSocket::Frame )
138
- expect( res.opcode ).to eq( :close )
139
- res.payload.rewind
140
- expect( res.payload.read ).to match( /Unhandled data type/i )
141
- expect( res.socket_id ).to eq( reserved.socket_id )
190
+ describe "connection registry" do
191
+
192
+ it "maintains a table of connections" do
193
+ app = app_class.new
194
+ request = @request_factory.handshake( '/chat' )
195
+
196
+ app.dispatch_request( request )
197
+
198
+ expect( app.connections ).to include( request.sender_id )
199
+ expect( app.connections[request.sender_id] ).to include( request.conn_id )
200
+ end
201
+
202
+
203
+ it "tracks when the last request from a client was" do
204
+ app = app_class.new
205
+
206
+ request = @request_factory.handshake( '/chat' )
207
+ app.dispatch_request( request )
208
+
209
+ request = @request_factory.ping( '/chat', "Hey, I'm awake!" )
210
+ app.dispatch_request( request )
211
+
212
+ timestamp = app.last_connection_time( request )
213
+
214
+ expect( timestamp ).to be_a( Time )
215
+ expect( timestamp ).to be_within( 1 ).of( Time.now )
216
+ end
217
+
142
218
  end
143
219
 
144
220
 
221
+ describe "broadcasting" do
222
+
223
+ let( :sender1 ) { '2ac271e0-6dfe-11e9-904a-177944ec9af0' }
224
+ let( :sender2 ) { '91562be6-6dfd-11e9-bcf6-63596c9a6a08' }
225
+
226
+ let( :factory1 ) do
227
+ Mongrel2::WebSocketRequestFactory.new( route: '/chat', sender_id: sender1, conn_id: 1 )
228
+ end
229
+ let( :factory2 ) do
230
+ Mongrel2::WebSocketRequestFactory.new( route: '/chat', sender_id: sender1, conn_id: 2 )
231
+ end
232
+ let( :factory3 ) do
233
+ Mongrel2::WebSocketRequestFactory.new( route: '/chat', sender_id: sender2, conn_id: 1 )
234
+ end
235
+
236
+
237
+ it "can broadcast a frame to all current connections" do
238
+ app = app_class.new
239
+
240
+ request1 = factory1.handshake( '/chat' )
241
+ app.dispatch_request( request1 )
242
+
243
+ request2 = factory2.handshake( '/chat' )
244
+ app.dispatch_request( request2 )
245
+
246
+ request3 = factory3.handshake( '/chat' )
247
+ app.dispatch_request( request3 )
248
+
249
+ frame = Mongrel2::WebSocket::Frame.text( 'Server running.' )
250
+
251
+ expect( app.conn ).to receive( :broadcast ).with( sender1, [1, 2], frame.to_s )
252
+ expect( app.conn ).to receive( :broadcast ).with( sender2, [1], frame.to_s )
253
+
254
+ app.broadcast( frame )
255
+ end
256
+
257
+
258
+ it "can broadcast a frame to all current connections with exceptions" do
259
+ app = app_class.new
260
+
261
+ request1 = factory1.handshake( '/chat' )
262
+ app.dispatch_request( request1 )
263
+
264
+ request2 = factory2.handshake( '/chat' )
265
+ app.dispatch_request( request2 )
266
+
267
+ request3 = factory3.handshake( '/chat' )
268
+ app.dispatch_request( request3 )
269
+
270
+ frame = Mongrel2::WebSocket::Frame.text( 'Server running.' )
271
+
272
+ expect( app.conn ).to receive( :broadcast ).with( sender1, [1, 2], frame.to_s )
273
+ expect( app.conn ).to_not receive( :broadcast ).with( sender2, [1], frame.to_s )
274
+
275
+ app.broadcast( frame, except: [sender2, 1] )
276
+ end
277
+
278
+ end
279
+
145
280
  end
146
281