faye 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye might be problematic. Click here for more details.

Files changed (62) hide show
  1. data/History.txt +14 -0
  2. data/README.rdoc +98 -0
  3. data/Rakefile +17 -15
  4. data/lib/faye-browser-min.js +1 -1
  5. data/lib/faye.rb +14 -5
  6. data/lib/faye/adapters/rack_adapter.rb +12 -5
  7. data/lib/faye/engines/base.rb +62 -0
  8. data/lib/faye/engines/connection.rb +63 -0
  9. data/lib/faye/engines/memory.rb +89 -0
  10. data/lib/faye/engines/redis.rb +141 -0
  11. data/lib/faye/error.rb +16 -4
  12. data/lib/faye/mixins/publisher.rb +6 -0
  13. data/lib/faye/protocol/channel.rb +34 -86
  14. data/lib/faye/protocol/client.rb +36 -52
  15. data/lib/faye/protocol/extensible.rb +3 -0
  16. data/lib/faye/protocol/server.rb +119 -169
  17. data/lib/faye/transport/http.rb +45 -0
  18. data/lib/faye/transport/local.rb +15 -0
  19. data/lib/faye/{network → transport}/transport.rb +36 -49
  20. data/spec/browser.html +35 -0
  21. data/spec/install.sh +48 -0
  22. data/spec/javascript/channel_spec.js +15 -0
  23. data/spec/javascript/client_spec.js +610 -0
  24. data/spec/javascript/engine_spec.js +319 -0
  25. data/spec/javascript/faye_spec.js +15 -0
  26. data/spec/javascript/grammar_spec.js +66 -0
  27. data/spec/javascript/node_adapter_spec.js +276 -0
  28. data/spec/javascript/server/connect_spec.js +168 -0
  29. data/spec/javascript/server/disconnect_spec.js +121 -0
  30. data/spec/javascript/server/extensions_spec.js +60 -0
  31. data/spec/javascript/server/handshake_spec.js +153 -0
  32. data/spec/javascript/server/subscribe_spec.js +245 -0
  33. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  34. data/spec/javascript/server_spec.js +146 -0
  35. data/spec/javascript/transport_spec.js +130 -0
  36. data/spec/node.js +34 -0
  37. data/spec/ruby/channel_spec.rb +17 -0
  38. data/spec/ruby/client_spec.rb +615 -0
  39. data/spec/ruby/engine_spec.rb +312 -0
  40. data/spec/ruby/faye_spec.rb +14 -0
  41. data/spec/ruby/grammar_spec.rb +68 -0
  42. data/spec/ruby/rack_adapter_spec.rb +209 -0
  43. data/spec/ruby/server/connect_spec.rb +170 -0
  44. data/spec/ruby/server/disconnect_spec.rb +120 -0
  45. data/spec/ruby/server/extensions_spec.rb +69 -0
  46. data/spec/ruby/server/handshake_spec.rb +151 -0
  47. data/spec/ruby/server/subscribe_spec.rb +247 -0
  48. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  49. data/spec/ruby/server_spec.rb +138 -0
  50. data/spec/ruby/transport_spec.rb +128 -0
  51. data/spec/spec_helper.rb +5 -0
  52. data/spec/testswarm.pl +200 -0
  53. data/spec/thin_proxy.rb +36 -0
  54. metadata +119 -84
  55. data/Manifest.txt +0 -27
  56. data/README.txt +0 -98
  57. data/lib/faye/protocol/connection.rb +0 -111
  58. data/test/scenario.rb +0 -172
  59. data/test/test_channel.rb +0 -54
  60. data/test/test_clients.rb +0 -381
  61. data/test/test_grammar.rb +0 -86
  62. data/test/test_server.rb +0 -488
@@ -0,0 +1,312 @@
1
+ require "spec_helper"
2
+
3
+ EngineSteps = EM::RSpec.async_steps do
4
+ def create_client(name, &resume)
5
+ @inboxes ||= {}
6
+ @clients ||= {}
7
+ engine.create_client do |client_id|
8
+ @clients[name] = client_id
9
+ @inboxes[name] ||= []
10
+ resume.call
11
+ end
12
+ end
13
+
14
+ def connect(name, engine, &resume)
15
+ engine.connect(@clients[name]) do |m|
16
+ m.each do |message|
17
+ message.delete("id")
18
+ @inboxes[name] << message
19
+ end
20
+ end
21
+ EM.add_timer(0.01, &resume)
22
+ end
23
+
24
+ def destroy_client(name, &resume)
25
+ engine.destroy_client(@clients[name], &resume)
26
+ end
27
+
28
+ def check_client_id(name, pattern, &resume)
29
+ @clients[name].should =~ pattern
30
+ resume.call
31
+ end
32
+
33
+ def check_num_clients(n, &resume)
34
+ ids = Set.new
35
+ @clients.each { |name,id| ids.add(id) }
36
+ ids.size.should == n
37
+ resume.call
38
+ end
39
+
40
+ def check_client_exists(name, exists, &resume)
41
+ engine.client_exists(@clients[name]) do |actual|
42
+ actual.should == exists
43
+ resume.call
44
+ end
45
+ end
46
+
47
+ def subscribe(name, channel, &resume)
48
+ engine.subscribe(@clients[name], channel, &resume)
49
+ end
50
+
51
+ def unsubscribe(name, channel, &resume)
52
+ engine.unsubscribe(@clients[name], channel, &resume)
53
+ end
54
+
55
+ def publish(messages, &resume)
56
+ messages = [messages].flatten
57
+ messages.each do |message|
58
+ message = {"id" => Faye.random}.merge(message)
59
+ engine.publish(message)
60
+ end
61
+ EM.add_timer(0.01, &resume)
62
+ end
63
+
64
+ def ping(name, &resume)
65
+ engine.ping(@clients[name])
66
+ resume.call
67
+ end
68
+
69
+ def clock_tick(time, &resume)
70
+ clock.tick(time)
71
+ resume.call
72
+ end
73
+
74
+ def expect_message(name, messages, &resume)
75
+ @inboxes[name].should == messages
76
+ resume.call
77
+ end
78
+
79
+ def expect_no_message(name, &resume)
80
+ @inboxes[name].should == []
81
+ resume.call
82
+ end
83
+
84
+ def clean_redis_db(&resume)
85
+ engine.disconnect
86
+ redis = EM::Hiredis::Client.connect
87
+ redis.flushall(&resume)
88
+ end
89
+ end
90
+
91
+ describe "Pub/sub engines" do
92
+ shared_examples_for "faye engine" do
93
+ include EngineSteps
94
+
95
+ let(:options) { {:timeout => 1} }
96
+ let(:engine) { engine_klass.new options }
97
+
98
+ before do
99
+ Faye.ensure_reactor_running!
100
+ create_client :alice
101
+ create_client :bob
102
+ create_client :carol
103
+ end
104
+
105
+ describe :create_client do
106
+ it "returns a client id" do
107
+ create_client :dave
108
+ check_client_id :dave, /^[a-z0-9]+$/
109
+ end
110
+
111
+ it "returns a different id every time" do
112
+ 1.upto(7) { |i| create_client "client#{i}" }
113
+ check_num_clients 10
114
+ end
115
+ end
116
+
117
+ describe :client_exists do
118
+ it "returns true if the client id exists" do
119
+ check_client_exists :alice, true
120
+ end
121
+
122
+ it "returns false if the client id does not exist" do
123
+ check_client_exists :anything, false
124
+ end
125
+ end
126
+ =begin
127
+ describe :ping do
128
+ it "removes a client if it does not ping often enough" do
129
+ clock_tick 2
130
+ check_client_exists :alice, false
131
+ end
132
+
133
+ it "prolongs the life of a client" do
134
+ clock_tick 1
135
+ ping :alice
136
+ clock_tick 1
137
+ check_client_exists :alice, true
138
+ clock_tick 1
139
+ check_client_exists :alice, false
140
+ end
141
+ end
142
+ =end
143
+ describe :destroy_client do
144
+ it "removes the given client" do
145
+ destroy_client :alice
146
+ check_client_exists :alice, false
147
+ end
148
+
149
+ describe "when the client has subscriptions" do
150
+ before do
151
+ @message = {"channel" => "/messages/foo", "data" => "ok"}
152
+ subscribe :alice, "/messages/foo"
153
+ end
154
+
155
+ it "stops the client receiving messages" do
156
+ connect :alice, engine
157
+ destroy_client :alice
158
+ publish @message
159
+ expect_no_message :alice
160
+ end
161
+ end
162
+ end
163
+
164
+ describe :publish do
165
+ before do
166
+ @message = {"channel" => "/messages/foo", "data" => "ok"}
167
+ connect :alice, engine
168
+ connect :bob, engine
169
+ connect :carol, engine
170
+ end
171
+
172
+ describe "with no subscriptions" do
173
+ it "delivers no messages" do
174
+ publish @message
175
+ expect_no_message :alice
176
+ expect_no_message :bob
177
+ expect_no_message :carol
178
+ end
179
+ end
180
+
181
+ describe "with a subscriber" do
182
+ before { subscribe :alice, "/messages/foo" }
183
+
184
+ it "delivers messages to the subscribed client" do
185
+ publish @message
186
+ expect_message :alice, [@message]
187
+ end
188
+ end
189
+
190
+ describe "with a subscriber that is removed" do
191
+ before do
192
+ subscribe :alice, "/messages/foo"
193
+ unsubscribe :alice, "/messages/foo"
194
+ end
195
+
196
+ it "does not deliver messages to unsubscribed clients" do
197
+ publish @message
198
+ expect_no_message :alice
199
+ expect_no_message :bob
200
+ expect_no_message :carol
201
+ end
202
+ end
203
+
204
+ describe "with multiple subscribers" do
205
+ before do
206
+ subscribe :alice, "/messages/foo"
207
+ subscribe :bob, "/messages/bar"
208
+ subscribe :carol, "/messages/foo"
209
+ end
210
+
211
+ it "delivers messages to the subscribed clients" do
212
+ publish @message
213
+ expect_message :alice, [@message]
214
+ expect_no_message :bob
215
+ expect_message :carol, [@message]
216
+ end
217
+ end
218
+
219
+ describe "with a single wildcard" do
220
+ before do
221
+ subscribe :alice, "/messages/*"
222
+ subscribe :bob, "/messages/bar"
223
+ subscribe :carol, "/*"
224
+ end
225
+
226
+ it "delivers messages to matching subscriptions" do
227
+ publish @message
228
+ expect_message :alice, [@message]
229
+ expect_no_message :bob
230
+ expect_no_message :carol
231
+ end
232
+ end
233
+
234
+ describe "with a double wildcard" do
235
+ before do
236
+ subscribe :alice, "/messages/**"
237
+ subscribe :bob, "/messages/bar"
238
+ subscribe :carol, "/**"
239
+ end
240
+
241
+ it "delivers messages to matching subscriptions" do
242
+ publish @message
243
+ expect_message :alice, [@message]
244
+ expect_no_message :bob
245
+ expect_message :carol, [@message]
246
+ end
247
+ end
248
+
249
+ describe "with multiple matching subscriptions for the same client" do
250
+ before do
251
+ subscribe :alice, "/messages/foo"
252
+ subscribe :alice, "/messages/*"
253
+ end
254
+
255
+ it "delivers each message once to each client" do
256
+ publish @message
257
+ expect_message :alice, [@message]
258
+ end
259
+
260
+ it "delivers the message as many times as it is published" do
261
+ publish [@message, @message]
262
+ expect_message :alice, [@message, @message]
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ shared_examples_for "distributed engine" do
269
+ include EngineSteps
270
+
271
+ let(:options) { {} }
272
+ let(:left) { engine_klass.new options }
273
+ let(:right) { engine_klass.new options }
274
+
275
+ alias :engine :left
276
+
277
+ before do
278
+ Faye.ensure_reactor_running!
279
+ create_client :alice
280
+ create_client :bob
281
+
282
+ connect :alice, left
283
+ end
284
+
285
+ describe :publish do
286
+ before do
287
+ subscribe :alice, "/foo"
288
+ publish "channel" => "/foo", "data" => "first"
289
+ end
290
+
291
+ it "only delivers each message once" do
292
+ expect_message :alice, ["channel" => "/foo", "data" => "first"]
293
+ publish "channel" => "/foo", "data" => "second"
294
+ connect :alice, right
295
+ expect_message :alice, [{"channel" => "/foo", "data" => "first"}, {"channel" => "/foo", "data" => "second"}]
296
+ end
297
+ end
298
+ end
299
+
300
+ describe Faye::Engine::Memory do
301
+ let(:engine_klass) { Faye::Engine::Memory }
302
+ it_should_behave_like "faye engine"
303
+ end
304
+
305
+ describe Faye::Engine::Redis do
306
+ let(:engine_klass) { Faye::Engine::Redis }
307
+ after { clean_redis_db }
308
+ it_should_behave_like "faye engine"
309
+ it_should_behave_like "distributed engine"
310
+ end
311
+ end
312
+
@@ -0,0 +1,14 @@
1
+ require "spec_helper"
2
+
3
+ describe Faye do
4
+ describe :random do
5
+ it "returns a 128-bit random number in base 36" do
6
+ Faye.random.should =~ /^[a-z0-9]+$/
7
+ end
8
+
9
+ it "always produces the same length of string" do
10
+ ids = (1..100).map { Faye.random }
11
+ ids.should be_all { |id| id.size == 25 }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ require "spec_helper"
2
+
3
+ describe Faye::Grammar do
4
+ describe :CHANNEL_NAME do
5
+ it "matches valid channel names" do
6
+ Faye::Grammar::CHANNEL_NAME.should =~ "/fo_o/$@()bar"
7
+ end
8
+
9
+ it "does not match channel patterns" do
10
+ Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/**"
11
+ end
12
+
13
+ it "does not match invalid channel names" do
14
+ Faye::Grammar::CHANNEL_NAME.should_not =~ "foo/$@()bar"
15
+ Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/$@()bar/"
16
+ Faye::Grammar::CHANNEL_NAME.should_not =~ "/fo o/$@()bar"
17
+ end
18
+ end
19
+
20
+ describe :CHANNEL_PATTERN do
21
+ it "does not match channel names" do
22
+ Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/fo_o/$@()bar"
23
+ end
24
+
25
+ it "matches valid channel patterns" do
26
+ Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/**"
27
+ Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/*"
28
+ end
29
+
30
+ it "does not match invalid channel patterns" do
31
+ Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/foo/**/*"
32
+ end
33
+ end
34
+
35
+ describe :ERROR do
36
+ it "matches an error with an argument" do
37
+ Faye::Grammar::ERROR.should =~ "402:xj3sjdsjdsjad:Unknown Client ID"
38
+ end
39
+
40
+ it "matches an error with many arguments" do
41
+ Faye::Grammar::ERROR.should =~ "403:xj3sjdsjdsjad,/foo/bar:Subscription denied"
42
+ end
43
+
44
+ it "matches an error with no arguments" do
45
+ Faye::Grammar::ERROR.should =~ "402::Unknown Client ID"
46
+ end
47
+
48
+ it "does not match an error with no code" do
49
+ Faye::Grammar::ERROR.should_not =~ ":xj3sjdsjdsjad:Unknown Client ID"
50
+ end
51
+
52
+ it "does not match an error with an invalid code" do
53
+ Faye::Grammar::ERROR.should_not =~ "40:xj3sjdsjdsjad:Unknown Client ID"
54
+ end
55
+ end
56
+
57
+ describe :VERSION do
58
+ it "matches a version number" do
59
+ Faye::Grammar::VERSION.should =~ "9"
60
+ Faye::Grammar::VERSION.should =~ "9.0.a-delta1"
61
+ end
62
+
63
+ it "does not match invalid version numbers" do
64
+ Faye::Grammar::VERSION.should_not =~ "9.0.a-delta1."
65
+ Faye::Grammar::VERSION.should_not =~ ""
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,209 @@
1
+ require "spec_helper"
2
+ require "thin_proxy"
3
+
4
+ describe Faye::RackAdapter do
5
+ include Rack::Test::Methods
6
+ let(:app) { ThinProxy.new(Faye::RackAdapter.new options) }
7
+ let(:options) { {:mount => "/bayeux", :timeout => 30} }
8
+ let(:server) { mock "server" }
9
+
10
+ after { app.stop }
11
+
12
+ let(:content_type) { last_response["Content-Type"] }
13
+ let(:access_control_origin) { last_response["Access-Control-Allow-Origin"] }
14
+ let(:json) { JSON.parse(body) }
15
+ let(:body) { last_response.body }
16
+ let(:status) { last_response.status.to_i }
17
+
18
+ before do
19
+ Faye::Server.should_receive(:new).with(options).and_return server
20
+ end
21
+
22
+ describe "POST requests" do
23
+ describe "with cross-origin access control" do
24
+ before do
25
+ header "Origin", "http://example.com"
26
+ end
27
+
28
+ it "returns a matching cross-origin access control header" do
29
+ server.stub(:process).and_yield []
30
+ post "/bayeux", :message => '[]'
31
+ access_control_origin.should == "http://example.com"
32
+ end
33
+
34
+ it "forwards the message param onto the server" do
35
+ server.should_receive(:process).with({"channel" => "/foo"}, false).and_yield []
36
+ post "/bayeux", :message => '{"channel":"/foo"}'
37
+ end
38
+
39
+ it "returns the server's response as JSON" do
40
+ server.stub(:process).and_yield ["channel" => "/meta/handshake"]
41
+ post "/bayeux", :message => '[]'
42
+ status.should == 200
43
+ content_type.should == "application/json"
44
+ json.should == ["channel" => "/meta/handshake"]
45
+ end
46
+
47
+ it "returns a 400 response if malformed JSON is given" do
48
+ server.should_not_receive(:process)
49
+ post "/bayeux", :message => "[}"
50
+ status.should == 400
51
+ content_type.should == "text/plain"
52
+ end
53
+
54
+ it "returns a 404 if the path is not matched" do
55
+ server.should_not_receive(:process)
56
+ post "/blaf", :message => "[]"
57
+ status.should == 404
58
+ content_type.should == "text/plain"
59
+ end
60
+ end
61
+
62
+ describe "with application/json" do
63
+ before do
64
+ header "Content-Type", "application/json"
65
+ end
66
+
67
+ it "does not return an access control header" do
68
+ server.stub(:process).and_yield []
69
+ post "/bayeux", :message => '[]'
70
+ access_control_origin.should be_nil
71
+ end
72
+
73
+ it "forwards the POST body onto the server" do
74
+ server.should_receive(:process).with({"channel" => "/foo"}, false).and_yield []
75
+ post "/bayeux", '{"channel":"/foo"}'
76
+ end
77
+
78
+ it "returns the server's response as JSON" do
79
+ server.stub(:process).and_yield ["channel" => "/meta/handshake"]
80
+ post "/bayeux", '[]'
81
+ status.should == 200
82
+ content_type.should == "application/json"
83
+ json.should == ["channel" => "/meta/handshake"]
84
+ end
85
+
86
+ it "returns a 400 response if malformed JSON is given" do
87
+ server.should_not_receive(:process)
88
+ post "/bayeux", "[}"
89
+ status.should == 400
90
+ content_type.should == "text/plain"
91
+ end
92
+
93
+ it "returns a 404 if the path is not matched" do
94
+ server.should_not_receive(:process)
95
+ post "/blaf", "[]"
96
+ status.should == 404
97
+ content_type.should == "text/plain"
98
+ end
99
+ end
100
+
101
+ describe "with no content type" do
102
+ it "forwards the message param onto the server" do
103
+ server.should_receive(:process).with({"channel" => "/foo"}, false).and_yield []
104
+ post "/bayeux", :message => '{"channel":"/foo"}'
105
+ end
106
+
107
+ it "returns the server's response as JSON" do
108
+ server.stub(:process).and_yield ["channel" => "/meta/handshake"]
109
+ post "/bayeux", :message => '[]'
110
+ status.should == 200
111
+ content_type.should == "application/json"
112
+ json.should == ["channel" => "/meta/handshake"]
113
+ end
114
+
115
+ it "returns a 400 response if malformed JSON is given" do
116
+ server.should_not_receive(:process)
117
+ post "/bayeux", :message => "[}"
118
+ status.should == 400
119
+ content_type.should == "text/plain"
120
+ end
121
+
122
+ it "returns a 404 if the path is not matched" do
123
+ server.should_not_receive(:process)
124
+ post "/blaf", :message => "[]"
125
+ status.should == 404
126
+ content_type.should == "text/plain"
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "GET requests" do
132
+ let(:params) {{:message => '{"channel":"/foo"}', :jsonp => "callback"}}
133
+
134
+ describe "with valid params" do
135
+ before do
136
+ server.should_receive(:flush_connection).with("channel" => "/foo")
137
+ end
138
+
139
+ it "forwards the message param onto the server" do
140
+ server.should_receive(:process).with({"channel" => "/foo"}, false).and_yield []
141
+ get "/bayeux", params
142
+ end
143
+
144
+ it "returns the server's response as JavaScript" do
145
+ server.stub(:process).and_yield ["channel" => "/meta/handshake"]
146
+ get "/bayeux", params
147
+ status.should == 200
148
+ content_type.should == "text/javascript"
149
+ body.should == 'callback([{"channel":"/meta/handshake"}]);'
150
+ end
151
+ end
152
+
153
+ describe "with an unknown path" do
154
+ it "returns a 404" do
155
+ server.should_not_receive(:process)
156
+ get "/blah", params
157
+ status.should == 404
158
+ content_type.should == "text/plain"
159
+ end
160
+ end
161
+
162
+ describe "missing jsonp" do
163
+ before do
164
+ params.delete(:jsonp)
165
+ server.should_receive(:flush_connection)
166
+ end
167
+
168
+ it "returns the server's response using the default callback" do
169
+ server.stub(:process).and_yield ["channel" => "/meta/handshake"]
170
+ get "/bayeux", params
171
+ status.should == 200
172
+ content_type.should == "text/javascript"
173
+ body.should == 'jsonpcallback([{"channel":"/meta/handshake"}]);'
174
+ end
175
+ end
176
+
177
+ shared_examples_for "bad GET request" do
178
+ it "does not call the server" do
179
+ server.should_not_receive(:process)
180
+ get "/bayeux", params
181
+ end
182
+
183
+ it "returns a 400 response" do
184
+ get "/bayeux", params
185
+ status.should == 400
186
+ content_type.should == "text/plain"
187
+ end
188
+ end
189
+
190
+ describe "with malformed JSON" do
191
+ before { params[:message] = "[}" }
192
+ it_should_behave_like "bad GET request"
193
+ end
194
+
195
+ describe "missing message" do
196
+ before { params.delete(:message) }
197
+ it_should_behave_like "bad GET request"
198
+ end
199
+
200
+ describe "for the client script" do
201
+ it "returns the client script" do
202
+ get "/bayeux.js"
203
+ status.should == 200
204
+ content_type.should == "text/javascript"
205
+ body.should =~ /function\(\)\{/
206
+ end
207
+ end
208
+ end
209
+ end