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,319 @@
1
+ JS.ENV.EngineSteps = JS.Test.asyncSteps({
2
+ create_client: function(name, resume) {
3
+ var inboxes = this._inboxes = this._inboxes || {}
4
+ var clients = this._clients = this._clients || {}
5
+ this.engine.createClient(function(clientId) {
6
+ clients[name] = clientId
7
+ inboxes[name] = inboxes[name] || []
8
+ resume()
9
+ })
10
+ },
11
+
12
+ connect: function(name, engine, resume) {
13
+ var clientId = this._clients[name]
14
+ var inboxes = this._inboxes
15
+ engine.connect(clientId, {}, function(m) {
16
+ Faye.each(m, function(message) {
17
+ delete message.id
18
+ inboxes[name].push(message)
19
+ })
20
+ })
21
+ setTimeout(resume, 10)
22
+ },
23
+
24
+ destroy_client: function(name, resume) {
25
+ this.engine.destroyClient(this._clients[name], resume)
26
+ },
27
+
28
+ check_client_id: function(name, pattern, resume) {
29
+ this.assertMatch(pattern, this._clients[name])
30
+ resume()
31
+ },
32
+
33
+ check_num_clients: function(n, resume) {
34
+ var ids = new JS.Set()
35
+ Faye.each(this._clients, function(name, id) { ids.add(id) })
36
+ this.assertEqual(n, ids.count())
37
+ resume()
38
+ },
39
+
40
+ check_client_exists: function(name, exists, resume) {
41
+ var tc = this
42
+ tc.engine.clientExists(tc._clients[name], function(actual) {
43
+ tc.assertEqual(exists, actual)
44
+ resume()
45
+ })
46
+ },
47
+
48
+ subscribe: function(name, channel, resume) {
49
+ this.engine.subscribe(this._clients[name], channel, resume)
50
+ },
51
+
52
+ unsubscribe: function(name, channel, resume) {
53
+ this.engine.unsubscribe(this._clients[name], channel, resume)
54
+ },
55
+
56
+ publish: function(messages, resume) {
57
+ messages = [].concat(messages)
58
+ Faye.each(messages, function(message) {
59
+ message = Faye.extend({id: Faye.random()}, message)
60
+ this.engine.publish(message)
61
+ }, this)
62
+ setTimeout(resume, 20)
63
+ },
64
+
65
+ ping: function(name, resume) {
66
+ this.engine.ping(this._clients[name])
67
+ resume()
68
+ },
69
+
70
+ clock_tick: function(time, resume) {
71
+ setTimeout(resume, time)
72
+ },
73
+
74
+ expect_message: function(name, messages, resume) {
75
+ this.assertEqual(messages, this._inboxes[name])
76
+ resume()
77
+ },
78
+
79
+ expect_no_message: function(name, resume) {
80
+ this.assertEqual([], this._inboxes[name])
81
+ resume()
82
+ },
83
+
84
+ clean_redis_db: function(resume) {
85
+ this.engine.disconnect()
86
+ var redis = require('redis').createClient()
87
+ redis.flushall(function() {
88
+ redis.end()
89
+ resume()
90
+ })
91
+ }
92
+ })
93
+
94
+ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this) {
95
+ include(JS.Test.Helpers)
96
+
97
+ sharedExamplesFor("faye engine", function() { with(this) {
98
+ include(EngineSteps)
99
+
100
+ define("options", function() { return {timeout: 1} })
101
+
102
+ before(function() { with(this) {
103
+ this.engine = new engineKlass(options())
104
+ create_client("alice")
105
+ create_client("bob")
106
+ create_client("carol")
107
+ }})
108
+
109
+ describe("createClient", function() { with(this) {
110
+ it("returns a client id", function() { with(this) {
111
+ create_client("dave")
112
+ check_client_id("dave", /^[a-z0-9]+$/)
113
+ }})
114
+
115
+ it("returns a different id every time", function() { with(this) {
116
+ $R(1,7).forEach(function(i) { create_client("client" + i) })
117
+ check_num_clients(10)
118
+ }})
119
+ }})
120
+
121
+ describe("clientExists", function() { with(this) {
122
+ it("returns true if the client id exists", function() { with(this) {
123
+ check_client_exists("alice", true)
124
+ }})
125
+
126
+ it("returns false if the client id does not exist", function() { with(this) {
127
+ check_client_exists("anything", false)
128
+ }})
129
+ }})
130
+
131
+ describe("ping", function() { with(this) {
132
+ define("options", function() { return {timeout: 0.3} })
133
+
134
+ it("removes a client if it does not ping often enough", function() { with(this) {
135
+ clock_tick(700)
136
+ check_client_exists("alice", false)
137
+ }})
138
+
139
+ it("prolongs the life of a client", function() { with(this) {
140
+ clock_tick(330)
141
+ ping("alice")
142
+ clock_tick(330)
143
+ check_client_exists("alice", true)
144
+ clock_tick(330)
145
+ check_client_exists("alice", false)
146
+ }})
147
+ }})
148
+
149
+ describe("destroyClient", function() { with(this) {
150
+ it("removes the given client", function() { with(this) {
151
+ destroy_client("alice")
152
+ check_client_exists("alice", false)
153
+ }})
154
+
155
+ describe("when the client has subscriptions", function() { with(this) {
156
+ before(function() { with(this) {
157
+ this.message = {"channel": "/messages/foo", "data": "ok"}
158
+ subscribe("alice", "/messages/foo")
159
+ }})
160
+
161
+ it("stops the client receiving messages", function() { with(this) {
162
+ connect("alice", engine)
163
+ destroy_client("alice")
164
+ publish(message)
165
+ expect_no_message("alice")
166
+ }})
167
+ }})
168
+ }})
169
+
170
+ describe("publish", function() { with(this) {
171
+ before(function() { with(this) {
172
+ this.message = {"channel": "/messages/foo", "data": "ok"}
173
+ connect("alice", engine)
174
+ connect("bob", engine)
175
+ connect("carol", engine)
176
+ }})
177
+
178
+ describe("with no subscriptions", function() { with(this) {
179
+ it("delivers no messages", function() { with(this) {
180
+ publish(message)
181
+ expect_no_message("alice")
182
+ expect_no_message("bob")
183
+ expect_no_message("carol")
184
+ }})
185
+ }})
186
+
187
+ describe("with a subscriber", function() { with(this) {
188
+ before(function() { with(this) {
189
+ subscribe("alice", "/messages/foo")
190
+ }})
191
+
192
+ it("delivers messages to the subscribed client", function() { with(this) {
193
+ publish(message)
194
+ expect_message("alice", [message])
195
+ }})
196
+ }})
197
+
198
+ describe("with a subscriber that is removed", function() { with(this) {
199
+ before(function() { with(this) {
200
+ subscribe("alice", "/messages/foo")
201
+ unsubscribe("alice", "/messages/foo")
202
+ }})
203
+
204
+ it("does not deliver messages to unsubscribed clients", function() { with(this) {
205
+ publish(message)
206
+ expect_no_message("alice")
207
+ expect_no_message("bob")
208
+ expect_no_message("carol")
209
+ }})
210
+ }})
211
+
212
+ describe("with multiple subscribers", function() { with(this) {
213
+ before(function() { with(this) {
214
+ subscribe("alice", "/messages/foo")
215
+ subscribe("bob", "/messages/bar")
216
+ subscribe("carol", "/messages/foo")
217
+ }})
218
+
219
+ it("delivers messages to the subscribed clients", function() { with(this) {
220
+ publish(message)
221
+ expect_message("alice", [message])
222
+ expect_no_message("bob")
223
+ expect_message("carol", [message])
224
+ }})
225
+ }})
226
+
227
+ describe("with a single wildcard", function() { with(this) {
228
+ before(function() { with(this) {
229
+ subscribe("alice", "/messages/*")
230
+ subscribe("bob", "/messages/bar")
231
+ subscribe("carol", "/*")
232
+ }})
233
+
234
+ it("delivers messages to matching subscriptions", function() { with(this) {
235
+ publish(message)
236
+ expect_message("alice", [message])
237
+ expect_no_message("bob")
238
+ expect_no_message("carol")
239
+ }})
240
+ }})
241
+
242
+ describe("with a double wildcard", function() { with(this) {
243
+ before(function() { with(this) {
244
+ subscribe("alice", "/messages/**")
245
+ subscribe("bob", "/messages/bar")
246
+ subscribe("carol", "/**")
247
+ }})
248
+
249
+ it("delivers messages to matching subscriptions", function() { with(this) {
250
+ publish(message)
251
+ expect_message("alice", [message])
252
+ expect_no_message("bob")
253
+ expect_message("carol", [message])
254
+ }})
255
+ }})
256
+
257
+ describe("with multiple matching subscriptions for the same client", function() { with(this) {
258
+ before(function() { with(this) {
259
+ subscribe("alice", "/messages/*")
260
+ subscribe("alice", "/messages/foo")
261
+ }})
262
+
263
+ it("delivers each message once to each client", function() { with(this) {
264
+ publish(message)
265
+ expect_message("alice", [message])
266
+ }})
267
+
268
+ it("delivers the message as many times as it is published", function() { with(this) {
269
+ publish([message, message])
270
+ expect_message("alice", [message, message])
271
+ }})
272
+ }})
273
+ }})
274
+ }})
275
+
276
+ sharedBehavior("distributed engine", function() { with(this) {
277
+ include(EngineSteps)
278
+ define("options", function() { return {timeout: 1} })
279
+
280
+ before(function() { with(this) {
281
+ this.left = new engineKlass(options())
282
+ this.right = new engineKlass(options())
283
+ this.engine = left
284
+
285
+ create_client("alice")
286
+ create_client("bob")
287
+
288
+ connect("alice", left)
289
+ }})
290
+
291
+ describe("publish", function() { with(this) {
292
+ before(function() { with(this) {
293
+ subscribe("alice", "/foo")
294
+ publish({channel: "/foo", data: "first"})
295
+ }})
296
+
297
+ it("only delivers each message once", function() { with(this) {
298
+ expect_message("alice", [{channel: "/foo", data: "first"}])
299
+ publish({channel: "/foo", data: "second"})
300
+ connect("alice", right)
301
+ expect_message("alice", [{channel: "/foo", data: "first"}, {channel: "/foo", data: "second"}])
302
+ }})
303
+ }})
304
+ }})
305
+
306
+ describe("Faye.Engine.Memory", function() { with(this) {
307
+ before(function() { this.engineKlass = Faye.Engine.Memory })
308
+ itShouldBehaveLike("faye engine")
309
+ }})
310
+
311
+ describe("Faye.Engine.Redis", function() { with(this) {
312
+ before(function() { this.engineKlass = Faye.Engine.Redis })
313
+ after(function() { this.clean_redis_db() })
314
+ itShouldBehaveLike("faye engine")
315
+ describe("distribution", function() { with(this) {
316
+ itShouldBehaveLike("distributed engine")
317
+ }})
318
+ }})
319
+ }})
@@ -0,0 +1,15 @@
1
+ JS.ENV.FayeSpec = JS.Test.describe("Faye", function() { with(this) {
2
+ include(JS.Test.Helpers)
3
+
4
+ describe("random", function() { with(this) {
5
+ it("returns a 128-bit random number in base 36", function() { with(this) {
6
+ assertMatch( /^[a-z0-9]+$/, Faye.random() )
7
+ }})
8
+
9
+ it("always produces the same length of string", function() { with(this) {
10
+ var ids = $R(1,100).map(function() { return Faye.random().length })
11
+ var expected = $R(1,100).map(function() { return 28 })
12
+ assertEqual( expected, ids )
13
+ }})
14
+ }})
15
+ }})
@@ -0,0 +1,66 @@
1
+ JS.ENV.GrammarSpec = JS.Test.describe("Grammar", function() { with(this) {
2
+ describe("CHANNEL_NAME", function() { with(this) {
3
+ it("matches valid channel names", function() { with(this) {
4
+ assertMatch( Faye.Grammar.CHANNEL_NAME, "/fo_o/$@()bar" )
5
+ }})
6
+
7
+ it("does not match channel patterns", function() { with(this) {
8
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/foo/**" )
9
+ }})
10
+
11
+ it("does not match invalid channel names", function() { with(this) {
12
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "foo/$@()bar" )
13
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/foo/$@()bar/" )
14
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/fo o/$@()bar" )
15
+ }})
16
+ }})
17
+
18
+ describe("CHANNEL_PATTERN", function() { with(this) {
19
+ it("does not match channel names", function() { with(this) {
20
+ assertNoMatch( Faye.Grammar.CHANNEL_PATTERN, "/fo_o/$@()bar" )
21
+ }})
22
+
23
+ it("matches valid channel patterns", function() { with(this) {
24
+ assertMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/**" )
25
+ assertMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/*" )
26
+ }})
27
+
28
+ it("does not match invalid channel patterns", function() { with(this) {
29
+ assertNoMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/**/*" )
30
+ }})
31
+ }})
32
+
33
+ describe("ERROR", function() { with(this) {
34
+ it("matches an error with an argument", function() { with(this) {
35
+ assertMatch( Faye.Grammar.ERROR, "402:xj3sjdsjdsjad:Unknown Client ID" )
36
+ }})
37
+
38
+ it("matches an error with many arguments", function() { with(this) {
39
+ assertMatch( Faye.Grammar.ERROR, "403:xj3sjdsjdsjad,/foo/bar:Subscription denied" )
40
+ }})
41
+
42
+ it("matches an error with no arguments", function() { with(this) {
43
+ assertMatch( Faye.Grammar.ERROR, "402::Unknown Client ID" )
44
+ }})
45
+
46
+ it("does not match an error with no code", function() { with(this) {
47
+ assertNoMatch( Faye.Grammar.ERROR, ":xj3sjdsjdsjad:Unknown Client ID" )
48
+ }})
49
+
50
+ it("does not match an error with an invalid code", function() { with(this) {
51
+ assertNoMatch( Faye.Grammar.ERROR, "40:xj3sjdsjdsjad:Unknown Client ID" )
52
+ }})
53
+ }})
54
+
55
+ describe("VERSION", function() { with(this) {
56
+ it("matches a version number", function() { with(this) {
57
+ assertMatch( Faye.Grammar.VERSION, "9" )
58
+ assertMatch( Faye.Grammar.VERSION, "9.0.a-delta1" )
59
+ }})
60
+
61
+ it("does not match invalid version numbers", function() { with(this) {
62
+ assertNoMatch( Faye.Grammar.VERSION, "9.0.a-delta1." )
63
+ assertNoMatch( Faye.Grammar.VERSION, "" )
64
+ }})
65
+ }})
66
+ }})
@@ -0,0 +1,276 @@
1
+ var http = require("http"),
2
+ querystring = require("querystring")
3
+
4
+ JS.ENV.NodeAdapterSteps = JS.Test.asyncSteps({
5
+ start_server: function(port, resume) {
6
+ this._port = port
7
+ this._app = new Faye.NodeAdapter(this.options())
8
+ this._app.listen(port)
9
+ setTimeout(resume, 50)
10
+ },
11
+
12
+ stop_server: function(resume) {
13
+ this._app.stop()
14
+ resume()
15
+ },
16
+
17
+ header: function(key, value, resume) {
18
+ this._headers = this._headers || {}
19
+ this._headers[key] = value
20
+ resume()
21
+ },
22
+
23
+ get: function(path, params, resume) {
24
+ var client = http.createClient(this._port, "localhost"),
25
+ body = querystring.stringify(params),
26
+ request = client.request("GET", path + (body ? "?" + body : "")),
27
+ self = this
28
+
29
+ request.addListener("response", function(response) {
30
+ self._response = response
31
+ var data = ""
32
+ response.addListener("data", function(c) { data += c })
33
+ response.addListener("end", function() {
34
+ self._responseBody = data
35
+ resume()
36
+ })
37
+ })
38
+ request.end()
39
+ },
40
+
41
+ post: function(path, body, resume) {
42
+ var client = http.createClient(this._port, "localhost"),
43
+
44
+ body = (typeof body === "string")
45
+ ? body
46
+ : querystring.stringify(body),
47
+
48
+ headers = Faye.extend({
49
+ "Host": "localhost",
50
+ "Content-Length": body.length
51
+ }, this._headers || {}),
52
+
53
+ request = client.request("POST", path, headers),
54
+ self = this
55
+
56
+ request.addListener("response", function(response) {
57
+ self._response = response
58
+ var data = ""
59
+ response.addListener("data", function(c) { data += c })
60
+ response.addListener("end", function() {
61
+ self._responseBody = data
62
+ resume()
63
+ })
64
+ })
65
+ request.write(body)
66
+ request.end()
67
+ },
68
+
69
+ check_status: function(code, resume) {
70
+ this.assertEqual(code, this._response.statusCode)
71
+ resume()
72
+ },
73
+
74
+ check_access_control_origin: function(origin, resume) {
75
+ this.assertEqual(origin, this._response.headers["access-control-allow-origin"])
76
+ resume()
77
+ },
78
+
79
+ check_content_type: function(type, resume) {
80
+ this.assertEqual(type, this._response.headers["content-type"])
81
+ resume()
82
+ },
83
+
84
+ check_body: function(body, resume) {
85
+ if (typeof body === "string")
86
+ this.assertEqual(body, this._responseBody)
87
+ else
88
+ this.assertMatch(body, this._responseBody)
89
+ resume()
90
+ },
91
+
92
+ check_json: function(object, resume) {
93
+ this.assertEqual(object, JSON.parse(this._responseBody))
94
+ resume()
95
+ }
96
+ })
97
+
98
+ JS.ENV.NodeAdapterSpec = JS.Test.describe("NodeAdapter", function() { with(this) {
99
+ include(NodeAdapterSteps)
100
+
101
+ define("options", function() {
102
+ return {mount: "/bayeux", timeout: 30}
103
+ })
104
+
105
+ before(function() { with(this) {
106
+ this.server = {}
107
+ expect(Faye, "Server").given(options()).returning(server)
108
+ start_server(8282)
109
+ }})
110
+
111
+ after(function() { this.stop_server() })
112
+
113
+ describe("POST requests", function() { with(this) {
114
+ describe("with cross-origin access control", function() { with(this) {
115
+ before(function() { with(this) {
116
+ header("Origin", "http://example.com")
117
+ }})
118
+
119
+ it("returns a matching cross-origin access control header", function() { with(this) {
120
+ stub(server, "process").yields([[]])
121
+ post("/bayeux", {message: "[]"})
122
+ check_access_control_origin("http://example.com")
123
+ }})
124
+
125
+ it("forwards the message param onto the server", function() { with(this) {
126
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
127
+ post("/bayeux", {message: '{"channel":"/foo"}'})
128
+ }})
129
+
130
+ it("returns the server's response as JSON", function() { with(this) {
131
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
132
+ post("/bayeux", {message: "[]"})
133
+ check_status(200)
134
+ check_content_type("application/json")
135
+ check_json([{channel: "/meta/handshake"}])
136
+ }})
137
+
138
+ it("returns a 400 response if malformed JSON is given", function() { with(this) {
139
+ expect(server, "process").exactly(0)
140
+ post("/bayeux", {message: "[}"})
141
+ check_status(400)
142
+ check_content_type("text/plain")
143
+ }})
144
+ }})
145
+
146
+ describe("with application/json", function() { with(this) {
147
+ before(function() { with(this) {
148
+ header("Content-Type", "application/json")
149
+ }})
150
+
151
+ it("does not return an access control header", function() { with(this) {
152
+ stub(server, "process").yields([[]])
153
+ post("/bayeux", "[]")
154
+ check_access_control_origin(undefined)
155
+ }})
156
+
157
+ it("forwards the POST body onto the server", function() { with(this) {
158
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
159
+ post("/bayeux", '{"channel":"/foo"}')
160
+ }})
161
+
162
+ it("returns the server's response as JSON", function() { with(this) {
163
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
164
+ post("/bayeux", "[]")
165
+ check_status(200)
166
+ check_content_type("application/json")
167
+ check_json([{channel: "/meta/handshake"}])
168
+ }})
169
+
170
+ it("returns a 400 response if malformed JSON is given", function() { with(this) {
171
+ expect(server, "process").exactly(0)
172
+ post("/bayeux", "[}")
173
+ check_status(400)
174
+ check_content_type("text/plain")
175
+ }})
176
+ }})
177
+
178
+ describe("with no content type", function() { with(this) {
179
+ it("forwards the message param onto the server", function() { with(this) {
180
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
181
+ post("/bayeux", {message: '{"channel":"/foo"}'})
182
+ }})
183
+
184
+ it("returns the server's response as JSON", function() { with(this) {
185
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
186
+ post("/bayeux", {message: "[]"})
187
+ check_status(200)
188
+ check_content_type("application/json")
189
+ check_json([{channel: "/meta/handshake"}])
190
+ }})
191
+
192
+ it("returns a 400 response if malformed JSON is given", function() { with(this) {
193
+ expect(server, "process").exactly(0)
194
+ post("/bayeux", {message: "[}"})
195
+ check_status(400)
196
+ check_content_type("text/plain")
197
+ }})
198
+ }})
199
+ }})
200
+
201
+ describe("GET requests", function() { with(this) {
202
+ before(function() { with(this) {
203
+ this.params = {message: '{"channel":"/foo"}', jsonp: "callback"}
204
+ }})
205
+
206
+ describe("with valid params", function() { with(this) {
207
+ before(function() { with(this) {
208
+ expect(server, "flushConnection").given({channel: "/foo"})
209
+ }})
210
+
211
+ it("forwards the message param onto the server", function() { with(this) {
212
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
213
+ get("/bayeux", params)
214
+ }})
215
+
216
+ it("returns the server's response as JavaScript", function() { with(this) {
217
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
218
+ get("/bayeux", params)
219
+ check_status(200)
220
+ check_content_type("text/javascript")
221
+ check_body('callback([{"channel":"/meta/handshake"}]);')
222
+ }})
223
+ }})
224
+
225
+ describe("missing jsonp", function() { with(this) {
226
+ before(function() { with(this) {
227
+ delete params.jsonp
228
+ expect(server, "flushConnection")
229
+ }})
230
+
231
+ it("returns the server's response using the default callback", function() { with(this) {
232
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
233
+ get("/bayeux", params)
234
+ check_status(200)
235
+ check_content_type("text/javascript")
236
+ check_body('jsonpcallback([{"channel":"/meta/handshake"}]);')
237
+ }})
238
+ }})
239
+
240
+ sharedBehavior("bad GET request", function() { with(this) {
241
+ it("does not call the server", function() { with(this) {
242
+ expect(server, "process").exactly(0)
243
+ get("/bayeux", params)
244
+ }})
245
+
246
+ it("returns a 400 response", function() { with(this) {
247
+ get("/bayeux", params)
248
+ check_status(400)
249
+ check_content_type("text/plain")
250
+ }})
251
+ }})
252
+
253
+ describe("with malformed JSON", function() { with(this) {
254
+ before(function() { with(this) {
255
+ params.message = "[}"
256
+ }})
257
+ behavesLike("bad GET request")
258
+ }})
259
+
260
+ describe("missing message", function() { with(this) {
261
+ before(function() { with(this) {
262
+ delete params.message
263
+ }})
264
+ behavesLike("bad GET request")
265
+ }})
266
+
267
+ describe("for the client script", function() { with(this) {
268
+ it("returns the client script", function() { with(this) {
269
+ get("/bayeux.js", {})
270
+ check_status(200)
271
+ check_content_type("text/javascript")
272
+ check_body(/function\(\)\{/)
273
+ }})
274
+ }})
275
+ }})
276
+ }})