bushido-faye 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/History.txt +247 -0
  2. data/README.rdoc +92 -0
  3. data/lib/faye-browser-min.js +1 -0
  4. data/lib/faye.rb +121 -0
  5. data/lib/faye/adapters/rack_adapter.rb +209 -0
  6. data/lib/faye/engines/connection.rb +60 -0
  7. data/lib/faye/engines/memory.rb +112 -0
  8. data/lib/faye/engines/proxy.rb +111 -0
  9. data/lib/faye/error.rb +49 -0
  10. data/lib/faye/mixins/logging.rb +47 -0
  11. data/lib/faye/mixins/publisher.rb +30 -0
  12. data/lib/faye/mixins/timeouts.rb +22 -0
  13. data/lib/faye/protocol/channel.rb +124 -0
  14. data/lib/faye/protocol/client.rb +378 -0
  15. data/lib/faye/protocol/extensible.rb +43 -0
  16. data/lib/faye/protocol/grammar.rb +58 -0
  17. data/lib/faye/protocol/publication.rb +5 -0
  18. data/lib/faye/protocol/server.rb +282 -0
  19. data/lib/faye/protocol/subscription.rb +24 -0
  20. data/lib/faye/transport/http.rb +76 -0
  21. data/lib/faye/transport/local.rb +22 -0
  22. data/lib/faye/transport/transport.rb +115 -0
  23. data/lib/faye/transport/web_socket.rb +99 -0
  24. data/lib/faye/util/namespace.rb +20 -0
  25. data/spec/browser.html +45 -0
  26. data/spec/encoding_helper.rb +7 -0
  27. data/spec/install.sh +78 -0
  28. data/spec/javascript/channel_spec.js +15 -0
  29. data/spec/javascript/client_spec.js +714 -0
  30. data/spec/javascript/engine/memory_spec.js +7 -0
  31. data/spec/javascript/engine_spec.js +417 -0
  32. data/spec/javascript/faye_spec.js +15 -0
  33. data/spec/javascript/grammar_spec.js +66 -0
  34. data/spec/javascript/node_adapter_spec.js +307 -0
  35. data/spec/javascript/publisher_spec.js +27 -0
  36. data/spec/javascript/server/connect_spec.js +168 -0
  37. data/spec/javascript/server/disconnect_spec.js +121 -0
  38. data/spec/javascript/server/extensions_spec.js +60 -0
  39. data/spec/javascript/server/handshake_spec.js +145 -0
  40. data/spec/javascript/server/integration_spec.js +124 -0
  41. data/spec/javascript/server/publish_spec.js +85 -0
  42. data/spec/javascript/server/subscribe_spec.js +247 -0
  43. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  44. data/spec/javascript/server_spec.js +110 -0
  45. data/spec/javascript/transport_spec.js +130 -0
  46. data/spec/node.js +55 -0
  47. data/spec/phantom.js +17 -0
  48. data/spec/ruby/channel_spec.rb +17 -0
  49. data/spec/ruby/client_spec.rb +724 -0
  50. data/spec/ruby/engine/memory_spec.rb +7 -0
  51. data/spec/ruby/engine_examples.rb +427 -0
  52. data/spec/ruby/faye_spec.rb +14 -0
  53. data/spec/ruby/grammar_spec.rb +68 -0
  54. data/spec/ruby/publisher_spec.rb +27 -0
  55. data/spec/ruby/rack_adapter_spec.rb +236 -0
  56. data/spec/ruby/server/connect_spec.rb +170 -0
  57. data/spec/ruby/server/disconnect_spec.rb +120 -0
  58. data/spec/ruby/server/extensions_spec.rb +68 -0
  59. data/spec/ruby/server/handshake_spec.rb +143 -0
  60. data/spec/ruby/server/integration_spec.rb +126 -0
  61. data/spec/ruby/server/publish_spec.rb +81 -0
  62. data/spec/ruby/server/subscribe_spec.rb +247 -0
  63. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  64. data/spec/ruby/server_spec.rb +110 -0
  65. data/spec/ruby/transport_spec.rb +134 -0
  66. data/spec/spec_helper.rb +11 -0
  67. data/spec/testswarm +29 -0
  68. data/spec/thin_proxy.rb +37 -0
  69. metadata +302 -0
@@ -0,0 +1,307 @@
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, {}, resume)
9
+ },
10
+
11
+ stop_server: function(resume) {
12
+ this._app.stop(resume)
13
+ },
14
+
15
+ header: function(key, value, resume) {
16
+ this._headers = this._headers || {}
17
+ this._headers[key] = value
18
+ resume()
19
+ },
20
+
21
+ get: function(path, params, resume) {
22
+ var client = http.createClient(this._port, "localhost"),
23
+ body = querystring.stringify(params),
24
+ request = client.request("GET", path + (body ? "?" + body : "")),
25
+ self = this
26
+
27
+ request.addListener("response", function(response) {
28
+ self._response = response
29
+ var data = ""
30
+ response.addListener("data", function(c) { data += c })
31
+ response.addListener("end", function() {
32
+ self._responseBody = data
33
+ resume()
34
+ })
35
+ })
36
+ request.end()
37
+ },
38
+
39
+ post: function(path, body, resume) {
40
+ var client = http.createClient(this._port, "localhost"),
41
+
42
+ body = (typeof body === "string")
43
+ ? body
44
+ : querystring.stringify(body),
45
+
46
+ headers = Faye.extend({
47
+ "Host": "localhost",
48
+ "Content-Length": body.length
49
+ }, this._headers || {}),
50
+
51
+ request = client.request("POST", path, headers),
52
+ self = this
53
+
54
+ request.addListener("response", function(response) {
55
+ self._response = response
56
+ var data = ""
57
+ response.addListener("data", function(c) { data += c })
58
+ response.addListener("end", function() {
59
+ self._responseBody = data
60
+ resume()
61
+ })
62
+ })
63
+ request.write(body)
64
+ request.end()
65
+ },
66
+
67
+ check_status: function(code, resume) {
68
+ this.assertEqual(code, this._response.statusCode)
69
+ resume()
70
+ },
71
+
72
+ check_access_control_origin: function(origin, resume) {
73
+ this.assertEqual(origin, this._response.headers["access-control-allow-origin"])
74
+ resume()
75
+ },
76
+
77
+ check_cache_control: function(value, resume) {
78
+ this.assertEqual(value, this._response.headers["cache-control"])
79
+ resume()
80
+ },
81
+
82
+ check_content_type: function(type, resume) {
83
+ this.assertEqual(type, this._response.headers["content-type"])
84
+ resume()
85
+ },
86
+
87
+ check_content_length: function(length, resume) {
88
+ this.assertEqual(length, this._response.headers["content-length"])
89
+ resume()
90
+ },
91
+
92
+ check_body: function(body, resume) {
93
+ if (typeof body === "string")
94
+ this.assertEqual(body, this._responseBody)
95
+ else
96
+ this.assertMatch(body, this._responseBody)
97
+ resume()
98
+ },
99
+
100
+ check_json: function(object, resume) {
101
+ this.assertEqual(object, JSON.parse(this._responseBody))
102
+ resume()
103
+ }
104
+ })
105
+
106
+ JS.ENV.NodeAdapterSpec = JS.Test.describe("NodeAdapter", function() { with(this) {
107
+ include(NodeAdapterSteps)
108
+
109
+ define("options", function() {
110
+ return {mount: "/bayeux", timeout: 30}
111
+ })
112
+
113
+ before(function() { with(this) {
114
+ this.server = {}
115
+ expect(Faye, "Server").given(options()).returning(server)
116
+ start_server(8282)
117
+ }})
118
+
119
+ after(function() { this.stop_server() })
120
+
121
+ describe("POST requests", function() { with(this) {
122
+ describe("with cross-origin access control", function() { with(this) {
123
+ sharedBehavior("cross-origin request", function() { with(this) {
124
+ before(function() { with(this) {
125
+ header("Origin", "http://example.com")
126
+ }})
127
+
128
+ it("returns a matching cross-origin access control header", function() { with(this) {
129
+ stub(server, "process").yields([[]])
130
+ post("/bayeux", {message: "[]"})
131
+ check_access_control_origin("http://example.com")
132
+ }})
133
+
134
+ it("forwards the message param onto the server", function() { with(this) {
135
+ expect(server, "process").given({channel: "/plain"}, false).yielding([[]])
136
+ post("/bayeux", "message=%7B%22channel%22%3A%22%2Fplain%22%7D")
137
+ }})
138
+
139
+ it("returns the server's response as JSON", function() { with(this) {
140
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
141
+ post("/bayeux", "message=%5B%5D")
142
+ check_status(200)
143
+ check_content_type("application/json")
144
+ check_content_length("31")
145
+ check_json([{channel: "/meta/handshake"}])
146
+ }})
147
+
148
+ it("returns a 400 response if malformed JSON is given", function() { with(this) {
149
+ expect(server, "process").exactly(0)
150
+ post("/bayeux", "message=%7B%5B")
151
+ check_status(400)
152
+ check_content_type("text/plain")
153
+ }})
154
+ }})
155
+
156
+ describe("with text/plain", function() { with(this) {
157
+ before(function() { this.header("Content-Type", "text/plain") })
158
+ behavesLike("cross-origin request")
159
+ }})
160
+
161
+ describe("with application/xml", function() { with(this) {
162
+ before(function() { this.header("Content-Type", "application/xml") })
163
+ behavesLike("cross-origin request")
164
+ }})
165
+ }})
166
+
167
+ describe("with application/json", function() { with(this) {
168
+ before(function() { with(this) {
169
+ header("Content-Type", "application/json")
170
+ }})
171
+
172
+ it("does not return an access control header", function() { with(this) {
173
+ stub(server, "process").yields([[]])
174
+ post("/bayeux", "[]")
175
+ check_access_control_origin(undefined)
176
+ }})
177
+
178
+ it("forwards the POST body onto the server", function() { with(this) {
179
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
180
+ post("/bayeux", '{"channel":"/foo"}')
181
+ }})
182
+
183
+ it("returns the server's response as JSON", function() { with(this) {
184
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
185
+ post("/bayeux", "[]")
186
+ check_status(200)
187
+ check_content_type("application/json")
188
+ check_content_length("31")
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", "[}")
195
+ check_status(400)
196
+ check_content_type("text/plain")
197
+ }})
198
+ }})
199
+
200
+ describe("with no content type", function() { with(this) {
201
+ it("forwards the message param onto the server", function() { with(this) {
202
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
203
+ post("/bayeux", {message: '{"channel":"/foo"}'})
204
+ }})
205
+
206
+ it("returns the server's response as JSON", function() { with(this) {
207
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
208
+ post("/bayeux", {message: "[]"})
209
+ check_status(200)
210
+ check_content_type("application/json")
211
+ check_content_length("31")
212
+ check_json([{channel: "/meta/handshake"}])
213
+ }})
214
+
215
+ it("returns a 400 response if malformed JSON is given", function() { with(this) {
216
+ expect(server, "process").exactly(0)
217
+ post("/bayeux", {message: "[}"})
218
+ check_status(400)
219
+ check_content_type("text/plain")
220
+ }})
221
+ }})
222
+ }})
223
+
224
+ describe("GET requests", function() { with(this) {
225
+ before(function() { with(this) {
226
+ this.params = {message: '{"channel":"/foo"}', jsonp: "callback"}
227
+ }})
228
+
229
+ describe("with valid params", function() { with(this) {
230
+ before(function() { with(this) {
231
+ expect(server, "flushConnection").given({channel: "/foo"})
232
+ }})
233
+
234
+ it("forwards the message param onto the server", function() { with(this) {
235
+ expect(server, "process").given({channel: "/foo"}, false).yielding([[]])
236
+ get("/bayeux", params)
237
+ }})
238
+
239
+ it("returns the server's response as JavaScript", function() { with(this) {
240
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
241
+ get("/bayeux", params)
242
+ check_status(200)
243
+ check_content_type("text/javascript")
244
+ check_content_length("42")
245
+ check_body('callback([{"channel":"/meta/handshake"}]);')
246
+ }})
247
+
248
+ it("does not let the client cache the response", function() { with(this) {
249
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
250
+ get("/bayeux", params)
251
+ check_cache_control("no-cache, no-store")
252
+ }})
253
+ }})
254
+
255
+ describe("missing jsonp", function() { with(this) {
256
+ before(function() { with(this) {
257
+ delete params.jsonp
258
+ expect(server, "flushConnection")
259
+ }})
260
+
261
+ it("returns the server's response using the default callback", function() { with(this) {
262
+ stub(server, "process").yields([[{channel: "/meta/handshake"}]])
263
+ get("/bayeux", params)
264
+ check_status(200)
265
+ check_content_type("text/javascript")
266
+ check_content_length("47")
267
+ check_body('jsonpcallback([{"channel":"/meta/handshake"}]);')
268
+ }})
269
+ }})
270
+
271
+ sharedBehavior("bad GET request", function() { with(this) {
272
+ it("does not call the server", function() { with(this) {
273
+ expect(server, "process").exactly(0)
274
+ get("/bayeux", params)
275
+ }})
276
+
277
+ it("returns a 400 response", function() { with(this) {
278
+ get("/bayeux", params)
279
+ check_status(400)
280
+ check_content_type("text/plain")
281
+ }})
282
+ }})
283
+
284
+ describe("with malformed JSON", function() { with(this) {
285
+ before(function() { with(this) {
286
+ params.message = "[}"
287
+ }})
288
+ behavesLike("bad GET request")
289
+ }})
290
+
291
+ describe("missing message", function() { with(this) {
292
+ before(function() { with(this) {
293
+ delete params.message
294
+ }})
295
+ behavesLike("bad GET request")
296
+ }})
297
+
298
+ describe("for the client script", function() { with(this) {
299
+ it("returns the client script", function() { with(this) {
300
+ get("/bayeux.js", {})
301
+ check_status(200)
302
+ check_content_type("text/javascript")
303
+ check_body(/function\(\)\{/)
304
+ }})
305
+ }})
306
+ }})
307
+ }})
@@ -0,0 +1,27 @@
1
+ JS.ENV.PublisherSpec = JS.Test.describe("Publisher", function() { with(this) {
2
+ before(function() { with(this) {
3
+ this.publisher = Faye.extend({}, Faye.Publisher)
4
+ }})
5
+
6
+ describe("with subscribers that remove themselves", function() { with(this) {
7
+ before(function() { with(this) {
8
+ this.calledA = false
9
+ this.calledB = false
10
+
11
+ this.handler = function() {
12
+ calledA = true
13
+ publisher.unbind("event", handler)
14
+ }
15
+
16
+ publisher.bind("event", handler)
17
+ publisher.bind("event", function() { calledB = true })
18
+ }})
19
+
20
+ it("successfully calls all the callbacks", function() { with(this) {
21
+ publisher.trigger("event")
22
+ assert( calledA )
23
+ assert( calledB )
24
+ }})
25
+ }})
26
+ }})
27
+
@@ -0,0 +1,168 @@
1
+ JS.ENV.Server.ConnectSpec = JS.Test.describe("Server connect", function() { with(this) {
2
+ before(function() { with(this) {
3
+ this.engine = {}
4
+ stub(Faye.Engine, "get").returns(engine)
5
+ this.server = new Faye.Server()
6
+ }})
7
+
8
+ describe("#connect", function() { with(this) {
9
+ before(function() { with(this) {
10
+ this.clientId = "fakeclientid"
11
+ this.message = {channel: "/meta/connect",
12
+ clientId: "fakeclientid",
13
+ connectionType: "long-polling"}
14
+ }})
15
+
16
+ describe("with valid parameters", function() { with(this) {
17
+ before(function() { with(this) {
18
+ message.advice = {timeout: 60}
19
+ expect(engine, "clientExists").given(clientId).yielding([true])
20
+ }})
21
+
22
+ it("connects to the engine to wait for new messages", function() { with(this) {
23
+ expect(engine, "connect").given(clientId, {timeout: 60}).yielding([[]])
24
+ server.connect(message, false, function() {})
25
+ }})
26
+
27
+ it("returns a successful response and any queued messages", function() { with(this) {
28
+ stub(engine, "connect").yields([{channel: "/x", data: "hello"}])
29
+ server.connect(message, false, function(response) {
30
+ assertEqual([
31
+ { channel: "/meta/connect",
32
+ successful: true,
33
+ clientId: clientId
34
+ },
35
+ { channel: "/x",
36
+ data: "hello"
37
+ }
38
+ ], response)
39
+ })
40
+ }})
41
+
42
+ describe("with a message id", function() { with(this) {
43
+ before(function() { this.message.id = "foo" })
44
+
45
+ it("returns the same id", function() { with(this) {
46
+ stub(engine, "connect")
47
+ server.connect(message, false, function(response) {
48
+ assertEqual({
49
+ channel: "/meta/connect",
50
+ successful: true,
51
+ clientId: clientId,
52
+ id: "foo"
53
+ }, response)
54
+ })
55
+ }})
56
+ }})
57
+ }})
58
+
59
+ describe("with an unknown client", function() { with(this) {
60
+ before(function() { with(this) {
61
+ expect(engine, "clientExists").given(clientId).yielding([false])
62
+ }})
63
+
64
+ it("does not connect to the engine", function() { with(this) {
65
+ expect(engine, "connect").exactly(0)
66
+ server.connect(message, false, function() {})
67
+ }})
68
+
69
+ it("returns an unsuccessful response", function() { with(this) {
70
+ server.connect(message, false, function(response) {
71
+ assertEqual({
72
+ channel: "/meta/connect",
73
+ successful: false,
74
+ error: "401:fakeclientid:Unknown client"
75
+ }, response)
76
+ })
77
+ }})
78
+ }})
79
+
80
+ describe("missing clientId", function() { with(this) {
81
+ before(function() { with(this) {
82
+ delete message.clientId
83
+ expect(engine, "clientExists").given(undefined).yielding([false])
84
+ }})
85
+
86
+ it("does not connect to the engine", function() { with(this) {
87
+ expect(engine, "connect").exactly(0)
88
+ server.connect(message, false, function() {})
89
+ }})
90
+
91
+ it("returns an unsuccessful response", function() { with(this) {
92
+ server.connect(message, false, function(response) {
93
+ assertEqual({
94
+ channel: "/meta/connect",
95
+ successful: false,
96
+ error: "402:clientId:Missing required parameter"
97
+ }, response)
98
+ })
99
+ }})
100
+ }})
101
+
102
+ describe("missing connectionType", function() { with(this) {
103
+ before(function() { with(this) {
104
+ delete message.connectionType
105
+ expect(engine, "clientExists").given(clientId).yielding([true])
106
+ }})
107
+
108
+ it("does not connect to the engine", function() { with(this) {
109
+ expect(engine, "connect").exactly(0)
110
+ server.connect(message, false, function() {})
111
+ }})
112
+
113
+ it("returns an unsuccessful response", function() { with(this) {
114
+ server.connect(message, false, function(response) {
115
+ assertEqual({
116
+ channel: "/meta/connect",
117
+ successful: false,
118
+ error: "402:connectionType:Missing required parameter"
119
+ }, response)
120
+ })
121
+ }})
122
+ }})
123
+
124
+ describe("with an unknown connectionType", function() { with(this) {
125
+ before(function() { with(this) {
126
+ message.connectionType = "flash"
127
+ expect(engine, "clientExists").given(clientId).yielding([true])
128
+ }})
129
+
130
+ it("does not connect to the engine", function() { with(this) {
131
+ expect(engine, "connect").exactly(0)
132
+ server.connect(message, false, function() {})
133
+ }})
134
+
135
+ it("returns an unsuccessful response", function() { with(this) {
136
+ server.connect(message, false, function(response) {
137
+ assertEqual({
138
+ channel: "/meta/connect",
139
+ successful: false,
140
+ error: "301:flash:Connection types not supported"
141
+ }, response)
142
+ })
143
+ }})
144
+ }})
145
+
146
+ describe("with an error", function() { with(this) {
147
+ before(function() { with(this) {
148
+ message.error = "invalid"
149
+ expect(engine, "clientExists").given(clientId).yielding([true])
150
+ }})
151
+
152
+ it("does not connect to the engine", function() { with(this) {
153
+ expect(engine, "connect").exactly(0)
154
+ server.connect(message, false, function() {})
155
+ }})
156
+
157
+ it("returns an unsuccessful response", function() { with(this) {
158
+ server.connect(message, false, function(response) {
159
+ assertEqual({
160
+ channel: "/meta/connect",
161
+ successful: false,
162
+ error: "invalid"
163
+ }, response)
164
+ })
165
+ }})
166
+ }})
167
+ }})
168
+ }})