_bushido-faye 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +247 -0
- data/README.rdoc +92 -0
- data/lib/faye-browser-min.js +1 -0
- data/lib/faye.rb +121 -0
- data/lib/faye/adapters/rack_adapter.rb +210 -0
- data/lib/faye/engines/connection.rb +60 -0
- data/lib/faye/engines/memory.rb +112 -0
- data/lib/faye/engines/proxy.rb +111 -0
- data/lib/faye/error.rb +49 -0
- data/lib/faye/mixins/logging.rb +47 -0
- data/lib/faye/mixins/publisher.rb +30 -0
- data/lib/faye/mixins/timeouts.rb +22 -0
- data/lib/faye/protocol/channel.rb +124 -0
- data/lib/faye/protocol/client.rb +378 -0
- data/lib/faye/protocol/extensible.rb +43 -0
- data/lib/faye/protocol/grammar.rb +58 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +282 -0
- data/lib/faye/protocol/subscription.rb +24 -0
- data/lib/faye/transport/http.rb +76 -0
- data/lib/faye/transport/local.rb +22 -0
- data/lib/faye/transport/transport.rb +115 -0
- data/lib/faye/transport/web_socket.rb +99 -0
- data/lib/faye/util/namespace.rb +20 -0
- data/spec/browser.html +45 -0
- data/spec/encoding_helper.rb +7 -0
- data/spec/install.sh +78 -0
- data/spec/javascript/channel_spec.js +15 -0
- data/spec/javascript/client_spec.js +714 -0
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +417 -0
- data/spec/javascript/faye_spec.js +15 -0
- data/spec/javascript/grammar_spec.js +66 -0
- data/spec/javascript/node_adapter_spec.js +307 -0
- data/spec/javascript/publisher_spec.js +27 -0
- data/spec/javascript/server/connect_spec.js +168 -0
- data/spec/javascript/server/disconnect_spec.js +121 -0
- data/spec/javascript/server/extensions_spec.js +60 -0
- data/spec/javascript/server/handshake_spec.js +145 -0
- data/spec/javascript/server/integration_spec.js +124 -0
- data/spec/javascript/server/publish_spec.js +85 -0
- data/spec/javascript/server/subscribe_spec.js +247 -0
- data/spec/javascript/server/unsubscribe_spec.js +245 -0
- data/spec/javascript/server_spec.js +110 -0
- data/spec/javascript/transport_spec.js +130 -0
- data/spec/node.js +55 -0
- data/spec/phantom.js +17 -0
- data/spec/ruby/channel_spec.rb +17 -0
- data/spec/ruby/client_spec.rb +724 -0
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/engine_examples.rb +427 -0
- data/spec/ruby/faye_spec.rb +14 -0
- data/spec/ruby/grammar_spec.rb +68 -0
- data/spec/ruby/publisher_spec.rb +27 -0
- data/spec/ruby/rack_adapter_spec.rb +236 -0
- data/spec/ruby/server/connect_spec.rb +170 -0
- data/spec/ruby/server/disconnect_spec.rb +120 -0
- data/spec/ruby/server/extensions_spec.rb +68 -0
- data/spec/ruby/server/handshake_spec.rb +143 -0
- data/spec/ruby/server/integration_spec.rb +126 -0
- data/spec/ruby/server/publish_spec.rb +81 -0
- data/spec/ruby/server/subscribe_spec.rb +247 -0
- data/spec/ruby/server/unsubscribe_spec.rb +247 -0
- data/spec/ruby/server_spec.rb +110 -0
- data/spec/ruby/transport_spec.rb +134 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/testswarm +29 -0
- data/spec/thin_proxy.rb +37 -0
- metadata +302 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
JS.ENV.ServerSpec = JS.Test.describe("Server", 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("#process", function() { with(this) {
|
9
|
+
before(function() { with(this) {
|
10
|
+
this.handshake = {channel: "/meta/handshake", data: "handshake"}
|
11
|
+
this.connect = {channel: "/meta/connect", data: "connect"}
|
12
|
+
this.disconnect = {channel: "/meta/disconnect", data: "disconnect"}
|
13
|
+
this.subscribe = {channel: "/meta/subscribe", data: "subscribe"}
|
14
|
+
this.unsubscribe = {channel: "/meta/unsubscribe", data: "unsubscribe"}
|
15
|
+
this.publish = {channel: "/some/channel", data: "publish"}
|
16
|
+
|
17
|
+
stub(engine, "interval", 0)
|
18
|
+
stub(engine, "timeout", 60)
|
19
|
+
}})
|
20
|
+
|
21
|
+
it("returns an empty response for no messages", function() { with(this) {
|
22
|
+
var response = null
|
23
|
+
server.process([], false, function(r) { response = r})
|
24
|
+
assertEqual( [], response )
|
25
|
+
}})
|
26
|
+
|
27
|
+
it("ignores invalid messages", function() { with(this) {
|
28
|
+
var response = null
|
29
|
+
server.process([{}, {channel: "invalid"}], false, function(r) { response = r})
|
30
|
+
assertEqual([
|
31
|
+
{ successful: false,
|
32
|
+
error: "405::Invalid channel"
|
33
|
+
},
|
34
|
+
{ channel: "invalid",
|
35
|
+
successful: false,
|
36
|
+
error: "405:invalid:Invalid channel"
|
37
|
+
}
|
38
|
+
], response)
|
39
|
+
}})
|
40
|
+
|
41
|
+
it("routes single messages to appropriate handlers", function() { with(this) {
|
42
|
+
expect(server, "handshake").given(handshake, false).yielding([{}])
|
43
|
+
server.process(handshake, false, function() {})
|
44
|
+
}})
|
45
|
+
|
46
|
+
it("routes a list of messages to appropriate handlers", function() { with(this) {
|
47
|
+
expect(server, "handshake").given(handshake, false).yielding([{}])
|
48
|
+
expect(server, "connect").given(connect, false).yielding([{}])
|
49
|
+
expect(server, "disconnect").given(disconnect, false).yielding([{}])
|
50
|
+
expect(server, "subscribe").given(subscribe, false).yielding([{}])
|
51
|
+
expect(server, "unsubscribe").given(unsubscribe, false).yielding([{}])
|
52
|
+
|
53
|
+
expect(engine, "publish").given(handshake).exactly(0)
|
54
|
+
expect(engine, "publish").given(connect).exactly(0)
|
55
|
+
expect(engine, "publish").given(disconnect).exactly(0)
|
56
|
+
expect(engine, "publish").given(subscribe).exactly(0)
|
57
|
+
expect(engine, "publish").given(unsubscribe).exactly(0)
|
58
|
+
|
59
|
+
expect(engine, "publish").given(publish)
|
60
|
+
|
61
|
+
server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false, function() {})
|
62
|
+
}})
|
63
|
+
|
64
|
+
describe("handshaking", function() { with(this) {
|
65
|
+
before(function() { with(this) {
|
66
|
+
expect(server, "handshake").given(handshake, false).yielding([{channel: "/meta/handshake", successful: true}])
|
67
|
+
}})
|
68
|
+
|
69
|
+
it("returns the handshake response with advice", function() { with(this) {
|
70
|
+
server.process(handshake, false, function(response) {
|
71
|
+
assertEqual([
|
72
|
+
{ channel: "/meta/handshake",
|
73
|
+
successful: true,
|
74
|
+
advice: {reconnect: "retry", interval: 0, timeout: 60000}
|
75
|
+
}
|
76
|
+
], response)
|
77
|
+
})
|
78
|
+
}})
|
79
|
+
}})
|
80
|
+
|
81
|
+
describe("connecting for messages", function() { with(this) {
|
82
|
+
before(function() { with(this) {
|
83
|
+
this.messages = [{channel: "/a"}, {channel: "/b"}]
|
84
|
+
expect(server, "connect").given(connect, false).yielding([messages])
|
85
|
+
}})
|
86
|
+
|
87
|
+
it("returns the new messages", function() { with(this) {
|
88
|
+
server.process(connect, false, function(response) {
|
89
|
+
assertEqual( messages, response )
|
90
|
+
})
|
91
|
+
}})
|
92
|
+
}})
|
93
|
+
}})
|
94
|
+
|
95
|
+
describe("#flushConnection", function() { with(this) {
|
96
|
+
before(function() { with(this) {
|
97
|
+
this.message = {clientId: "fakeclientid"}
|
98
|
+
}})
|
99
|
+
|
100
|
+
it("flushes the connection when given one message", function() { with(this) {
|
101
|
+
expect(engine, "flush").given("fakeclientid")
|
102
|
+
server.flushConnection(message)
|
103
|
+
}})
|
104
|
+
|
105
|
+
it("flushes the connection when given a list of messages", function() { with(this) {
|
106
|
+
expect(engine, "flush").given("fakeclientid")
|
107
|
+
server.flushConnection([message])
|
108
|
+
}})
|
109
|
+
}})
|
110
|
+
}})
|
@@ -0,0 +1,130 @@
|
|
1
|
+
JS.ENV.TransportSpec = JS.Test.describe("Transport", function() { with(this) {
|
2
|
+
before(function() { with(this) {
|
3
|
+
this.client = {endpoint: "http://example.com/"}
|
4
|
+
|
5
|
+
if (Faye.Transport.NodeLocal) {
|
6
|
+
this.LocalTransport = Faye.Transport.NodeLocal
|
7
|
+
this.HttpTransport = Faye.Transport.NodeHttp
|
8
|
+
this.inProcess = "in-process"
|
9
|
+
this.longPolling = "long-polling"
|
10
|
+
} else {
|
11
|
+
this.LocalTransport = Faye.Transport.WebSocket
|
12
|
+
this.HttpTransport = Faye.Transport.XHR
|
13
|
+
this.inProcess = "websocket"
|
14
|
+
this.longPolling = "long-polling"
|
15
|
+
}
|
16
|
+
}})
|
17
|
+
|
18
|
+
describe("get", function() { with(this) {
|
19
|
+
before(function() { with(this) {
|
20
|
+
stub(HttpTransport, "isUsable").yields([false])
|
21
|
+
stub(LocalTransport, "isUsable").yields([false])
|
22
|
+
}})
|
23
|
+
|
24
|
+
describe("when no transport is usable", function() { with(this) {
|
25
|
+
it("raises an exception", function() { with(this) {
|
26
|
+
assertThrows(Error, function() { Faye.Transport.get(client, [longPolling, inProcess]) })
|
27
|
+
}})
|
28
|
+
}})
|
29
|
+
|
30
|
+
describe("when a less preferred transport is usable", function() { with(this) {
|
31
|
+
before(function() { with(this) {
|
32
|
+
stub(HttpTransport, "isUsable").yields([true])
|
33
|
+
}})
|
34
|
+
|
35
|
+
it("returns a transport of the usable type", function() { with(this) {
|
36
|
+
Faye.Transport.get(client, [longPolling, inProcess], function(transport) {
|
37
|
+
assertKindOf( HttpTransport, transport )
|
38
|
+
})
|
39
|
+
}})
|
40
|
+
|
41
|
+
it("raises an exception if the usable type is not requested", function() { with(this) {
|
42
|
+
assertThrows(Error, function() { Faye.Transport.get(client, [inProcess]) })
|
43
|
+
}})
|
44
|
+
|
45
|
+
it("allows the usable type to be specifically selected", function() { with(this) {
|
46
|
+
Faye.Transport.get(client, [longPolling], function(transport) {
|
47
|
+
assertKindOf( HttpTransport, transport )
|
48
|
+
})
|
49
|
+
}})
|
50
|
+
}})
|
51
|
+
|
52
|
+
describe("when all transports are usable", function() { with(this) {
|
53
|
+
before(function() { with(this) {
|
54
|
+
stub(LocalTransport, "isUsable").yields([true])
|
55
|
+
stub(HttpTransport, "isUsable").yields([true])
|
56
|
+
}})
|
57
|
+
|
58
|
+
it("returns the most preferred type", function() { with(this) {
|
59
|
+
Faye.Transport.get(client, [longPolling, inProcess], function(transport) {
|
60
|
+
assertKindOf( LocalTransport, transport )
|
61
|
+
})
|
62
|
+
}})
|
63
|
+
|
64
|
+
it("allows types to be specifically selected", function() { with(this) {
|
65
|
+
Faye.Transport.get(client, [inProcess], function(transport) {
|
66
|
+
assertKindOf( LocalTransport, transport )
|
67
|
+
})
|
68
|
+
Faye.Transport.get(client, [longPolling], function(transport) {
|
69
|
+
assertKindOf( HttpTransport, transport )
|
70
|
+
})
|
71
|
+
}})
|
72
|
+
}})
|
73
|
+
}})
|
74
|
+
|
75
|
+
describe("send", function() { with(this) {
|
76
|
+
include(JS.Test.FakeClock)
|
77
|
+
before(function() { this.clock.stub() })
|
78
|
+
after(function() { this.clock.reset() })
|
79
|
+
|
80
|
+
describe("for batching transports", function() { with(this) {
|
81
|
+
before(function() { with(this) {
|
82
|
+
this.Transport = Faye.Class(Faye.Transport, {batching: true})
|
83
|
+
this.transport = new Transport(client)
|
84
|
+
}})
|
85
|
+
|
86
|
+
it("does not make an immediate request", function() { with(this) {
|
87
|
+
expect(transport, "request").exactly(0)
|
88
|
+
transport.send({batch: "me"}, 60)
|
89
|
+
}})
|
90
|
+
|
91
|
+
it("queues the message to be sent after a timeout", function() { with(this) {
|
92
|
+
expect(transport, "request").given([{batch: "me"}], 60)
|
93
|
+
transport.send({batch: "me"}, 60)
|
94
|
+
clock.tick(10)
|
95
|
+
}})
|
96
|
+
|
97
|
+
it("allows multiple messages to be batched together", function() { with(this) {
|
98
|
+
expect(transport, "request").given([{id: 1}, {id: 2}], 60)
|
99
|
+
transport.send({id: 1}, 60)
|
100
|
+
transport.send({id: 2}, 60)
|
101
|
+
clock.tick(10)
|
102
|
+
}})
|
103
|
+
|
104
|
+
it("adds advice to connect messages sent with others", function() { with(this) {
|
105
|
+
expect(transport, "request").given([{channel: "/meta/connect", advice: {timeout: 0}}, {}], 60)
|
106
|
+
transport.send({channel: "/meta/connect"}, 60)
|
107
|
+
transport.send({}, 60)
|
108
|
+
clock.tick(10)
|
109
|
+
}})
|
110
|
+
|
111
|
+
it("adds no advice to connect messages sent alone", function() { with(this) {
|
112
|
+
expect(transport, "request").given([{channel: "/meta/connect"}], 60)
|
113
|
+
transport.send({channel: "/meta/connect"}, 60)
|
114
|
+
clock.tick(10)
|
115
|
+
}})
|
116
|
+
}})
|
117
|
+
|
118
|
+
describe("for non-batching transports", function() { with(this) {
|
119
|
+
before(function() { with(this) {
|
120
|
+
this.Transport = Faye.Class(Faye.Transport, {batching: false})
|
121
|
+
this.transport = new Transport(client)
|
122
|
+
}})
|
123
|
+
|
124
|
+
it("makes a request immediately", function() { with(this) {
|
125
|
+
expect(transport, "request").given([{no: "batch"}], 60)
|
126
|
+
transport.send({no: "batch"}, 60)
|
127
|
+
}})
|
128
|
+
}})
|
129
|
+
}})
|
130
|
+
}})
|
data/spec/node.js
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require('jsclass')
|
2
|
+
Faye = require('../build/faye-node')
|
3
|
+
Faye.logger = function() {}
|
4
|
+
|
5
|
+
JS.Packages(function() { with(this) {
|
6
|
+
autoload(/.*Spec/, {from: 'spec/javascript'})
|
7
|
+
}})
|
8
|
+
|
9
|
+
FakeSocket = function() {
|
10
|
+
this._fragments = []
|
11
|
+
}
|
12
|
+
FakeSocket.prototype.write = function(buffer, encoding) {
|
13
|
+
this._fragments.push([buffer, encoding])
|
14
|
+
}
|
15
|
+
FakeSocket.prototype.read = function() {
|
16
|
+
var output = []
|
17
|
+
this._fragments.forEach(function(buffer, i) {
|
18
|
+
for (var j = 0, n = buffer[0].length; j < n; j++)
|
19
|
+
output.push(buffer[0][j])
|
20
|
+
})
|
21
|
+
return output
|
22
|
+
}
|
23
|
+
FakeSocket.prototype.addListener = function() {}
|
24
|
+
|
25
|
+
JS.require('Faye', 'JS.Test', 'JS.Range', function() {
|
26
|
+
JS.Test.Unit.Assertions.include({
|
27
|
+
assertYield: function(expected) {
|
28
|
+
var testcase = this
|
29
|
+
return function(actual) { testcase.assertEqual(expected, actual) }
|
30
|
+
}
|
31
|
+
})
|
32
|
+
|
33
|
+
JS.ENV.Engine = {}
|
34
|
+
JS.ENV.Server = {}
|
35
|
+
|
36
|
+
JS.require( 'FayeSpec',
|
37
|
+
'GrammarSpec',
|
38
|
+
'PublisherSpec',
|
39
|
+
'ChannelSpec',
|
40
|
+
'EngineSpec',
|
41
|
+
'Engine.MemorySpec',
|
42
|
+
'ServerSpec',
|
43
|
+
'Server.HandshakeSpec',
|
44
|
+
'Server.ConnectSpec',
|
45
|
+
'Server.DisconnectSpec',
|
46
|
+
'Server.SubscribeSpec',
|
47
|
+
'Server.UnsubscribeSpec',
|
48
|
+
'Server.PublishSpec',
|
49
|
+
'Server.ExtensionsSpec',
|
50
|
+
'Server.IntegrationSpec',
|
51
|
+
'NodeAdapterSpec',
|
52
|
+
'ClientSpec',
|
53
|
+
'TransportSpec',
|
54
|
+
JS.Test.method('autorun'))
|
55
|
+
})
|
data/spec/phantom.js
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
// This script should be run with PhantomJS
|
2
|
+
// http://www.phantomjs.org/
|
3
|
+
|
4
|
+
var page = new WebPage()
|
5
|
+
|
6
|
+
page.onConsoleMessage = function(message) {
|
7
|
+
try {
|
8
|
+
var result = JSON.parse(message)
|
9
|
+
if ('total' in result && 'fail' in result) {
|
10
|
+
console.log(message)
|
11
|
+
var status = (!result.fail && !result.error) ? 0 : 1
|
12
|
+
phantom.exit(status)
|
13
|
+
}
|
14
|
+
} catch (e) {}
|
15
|
+
}
|
16
|
+
|
17
|
+
page.open('spec/browser.html')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Faye::Channel do
|
4
|
+
describe :expand do
|
5
|
+
it "returns all patterns that match a channel" do
|
6
|
+
Faye::Channel.expand("/foo").should == [
|
7
|
+
"/**", "/foo", "/*"]
|
8
|
+
|
9
|
+
Faye::Channel.expand("/foo/bar").should == [
|
10
|
+
"/**", "/foo/bar", "/foo/*", "/foo/**"]
|
11
|
+
|
12
|
+
Faye::Channel.expand("/foo/bar/qux").should == [
|
13
|
+
"/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,724 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Faye::Client do
|
4
|
+
let :transport do
|
5
|
+
transport = mock("transport", :cookies= => nil, :headers= => nil)
|
6
|
+
transport.stub(:connection_type).and_return "fake"
|
7
|
+
transport.stub(:send)
|
8
|
+
transport.extend(Faye::Publisher)
|
9
|
+
transport
|
10
|
+
end
|
11
|
+
|
12
|
+
before { EM.stub(:add_timer) }
|
13
|
+
|
14
|
+
def stub_response(response)
|
15
|
+
transport.stub(:send) do |message, *args|
|
16
|
+
response["id"] = message["id"]
|
17
|
+
@client.receive_message(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_client
|
22
|
+
Faye::Transport.stub(:get).and_yield(transport)
|
23
|
+
@client = Faye::Client.new("http://localhost/")
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_connected_client
|
27
|
+
create_client
|
28
|
+
stub_response "channel" => "/meta/handshake",
|
29
|
+
"successful" => true,
|
30
|
+
"version" => "1.0",
|
31
|
+
"supportedConnectionTypes" => ["websocket"],
|
32
|
+
"clientId" => "fakeid"
|
33
|
+
|
34
|
+
@client.handshake
|
35
|
+
end
|
36
|
+
|
37
|
+
def subscribe(client, channel, callback = nil)
|
38
|
+
stub_response "channel" => "/meta/subscribe",
|
39
|
+
"successful" => true,
|
40
|
+
"clientId" => "fakeid",
|
41
|
+
"subscription" => channel
|
42
|
+
|
43
|
+
@subs_called = 0
|
44
|
+
callback ||= lambda { |m| @subs_called = 1 }
|
45
|
+
@client.subscribe(channel, &callback)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe :initialize do
|
49
|
+
it "creates a transport the server must support" do
|
50
|
+
Faye::Transport.should_receive(:get).with(instance_of(Faye::Client),
|
51
|
+
["long-polling", "callback-polling", "in-process"]).
|
52
|
+
and_return(transport)
|
53
|
+
Faye::Client.new("http://localhost/")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "puts the client in the UNCONNECTED state" do
|
57
|
+
Faye::Transport.stub(:get)
|
58
|
+
client = Faye::Client.new("http://localhost/")
|
59
|
+
client.state.should == :UNCONNECTED
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe :handshake do
|
64
|
+
before { create_client }
|
65
|
+
|
66
|
+
it "sends a handshake message to the server" do
|
67
|
+
transport.should_receive(:send).with({
|
68
|
+
"channel" => "/meta/handshake",
|
69
|
+
"version" => "1.0",
|
70
|
+
"supportedConnectionTypes" => ["fake"],
|
71
|
+
"id" => instance_of(String)
|
72
|
+
}, 60)
|
73
|
+
@client.handshake
|
74
|
+
end
|
75
|
+
|
76
|
+
it "puts the client in the CONNECTING state" do
|
77
|
+
transport.stub(:send)
|
78
|
+
@client.handshake
|
79
|
+
@client.state.should == :CONNECTING
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "with an outgoing extension installed" do
|
83
|
+
before do
|
84
|
+
extension = Class.new do
|
85
|
+
def outgoing(message, callback)
|
86
|
+
message["ext"] = {"auth" => "password"}
|
87
|
+
callback.call(message)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
@client.add_extension(extension.new)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "passes the handshake message through the extension" do
|
94
|
+
transport.should_receive(:send).with({
|
95
|
+
"channel" => "/meta/handshake",
|
96
|
+
"version" => "1.0",
|
97
|
+
"supportedConnectionTypes" => ["fake"],
|
98
|
+
"id" => instance_of(String),
|
99
|
+
"ext" => {"auth" => "password"}
|
100
|
+
}, 60)
|
101
|
+
@client.handshake
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "on successful response" do
|
106
|
+
before do
|
107
|
+
stub_response "channel" => "/meta/handshake",
|
108
|
+
"successful" => true,
|
109
|
+
"version" => "1.0",
|
110
|
+
"supportedConnectionTypes" => ["long-polling", "websocket"],
|
111
|
+
"clientId" => "fakeid"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "stores the clientId" do
|
115
|
+
@client.handshake
|
116
|
+
@client.client_id.should == "fakeid"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "puts the client in the CONNECTED state" do
|
120
|
+
@client.handshake
|
121
|
+
@client.state.should == :CONNECTED
|
122
|
+
end
|
123
|
+
|
124
|
+
it "registers any pre-existing subscriptions" do
|
125
|
+
@client.should_receive(:subscribe).with([], true)
|
126
|
+
@client.handshake
|
127
|
+
end
|
128
|
+
|
129
|
+
it "selects a new transport based on what the server supports" do
|
130
|
+
Faye::Transport.should_receive(:get).with(instance_of(Faye::Client), ["long-polling", "websocket"]).
|
131
|
+
and_return(transport)
|
132
|
+
@client.handshake
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "with websocket disabled" do
|
136
|
+
before { @client.disable("websocket") }
|
137
|
+
|
138
|
+
it "selects a new transport, excluding websocket" do
|
139
|
+
Faye::Transport.should_receive(:get).with(instance_of(Faye::Client), ["long-polling"]).
|
140
|
+
and_return(transport)
|
141
|
+
@client.handshake
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "on unsuccessful response" do
|
147
|
+
before do
|
148
|
+
stub_response "channel" => "/meta/handshake",
|
149
|
+
"successful" => false,
|
150
|
+
"version" => "1.0",
|
151
|
+
"supportedConnectionTypes" => ["websocket"]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "schedules a retry" do
|
155
|
+
EM.should_receive(:add_timer)
|
156
|
+
@client.handshake
|
157
|
+
end
|
158
|
+
|
159
|
+
it "puts the client in the UNCONNECTED state" do
|
160
|
+
EM.stub(:add_timer)
|
161
|
+
@client.handshake
|
162
|
+
@client.state.should == :UNCONNECTED
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "with existing subscriptions after a server restart" do
|
167
|
+
before do
|
168
|
+
create_connected_client
|
169
|
+
|
170
|
+
@message = nil
|
171
|
+
subscribe @client, "/messages/foo", lambda { |m| @message = m }
|
172
|
+
|
173
|
+
@client.receive_message "advice" => {"reconnect" => "handshake"}
|
174
|
+
|
175
|
+
stub_response "channel" => "/meta/handshake",
|
176
|
+
"successful" => true,
|
177
|
+
"version" => "1.0",
|
178
|
+
"supportedConnectionTypes" => ["websocket"],
|
179
|
+
"clientId" => "reconnectid"
|
180
|
+
end
|
181
|
+
|
182
|
+
it "resends the subscriptions to the server" do
|
183
|
+
transport.should_receive(:send).with(hash_including("channel" => "/meta/handshake"), 60)
|
184
|
+
transport.should_receive(:send).with({
|
185
|
+
"channel" => "/meta/subscribe",
|
186
|
+
"clientId" => "reconnectid",
|
187
|
+
"subscription" => "/messages/foo",
|
188
|
+
"id" => instance_of(String)
|
189
|
+
}, 60)
|
190
|
+
@client.handshake
|
191
|
+
end
|
192
|
+
|
193
|
+
it "retains the listeners for the subscriptions" do
|
194
|
+
@client.handshake
|
195
|
+
@client.receive_message("channel" => "/messages/foo", "data" => "ok")
|
196
|
+
@message.should == "ok"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "with a connected client" do
|
201
|
+
before { create_connected_client }
|
202
|
+
|
203
|
+
it "does not send a handshake message to the server" do
|
204
|
+
transport.should_not_receive(:send).with({
|
205
|
+
"channel" => "/meta/handshake",
|
206
|
+
"version" => "1.0",
|
207
|
+
"supportedConnectionTypes" => ["fake"],
|
208
|
+
"id" => instance_of(String)
|
209
|
+
}, 60)
|
210
|
+
@client.handshake
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe :connect do
|
216
|
+
describe "with an unconnected client" do
|
217
|
+
before do
|
218
|
+
stub_response "channel" => "/meta/handshake",
|
219
|
+
"successful" => true,
|
220
|
+
"version" => "1.0",
|
221
|
+
"supportedConnectionTypes" => ["websocket"],
|
222
|
+
"clientId" => "handshakeid"
|
223
|
+
|
224
|
+
create_client
|
225
|
+
end
|
226
|
+
|
227
|
+
it "handshakes before connecting" do
|
228
|
+
transport.should_receive(:send).with({
|
229
|
+
"channel" => "/meta/connect",
|
230
|
+
"clientId" => "handshakeid",
|
231
|
+
"connectionType" => "fake",
|
232
|
+
"id" => instance_of(String)
|
233
|
+
}, 60)
|
234
|
+
@client.connect
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe "with a connected client" do
|
239
|
+
before { create_connected_client }
|
240
|
+
|
241
|
+
it "sends a connect message to the server" do
|
242
|
+
transport.should_receive(:send).with({
|
243
|
+
"channel" => "/meta/connect",
|
244
|
+
"clientId" => "fakeid",
|
245
|
+
"connectionType" => "fake",
|
246
|
+
"id" => instance_of(String)
|
247
|
+
}, 60)
|
248
|
+
@client.connect
|
249
|
+
end
|
250
|
+
|
251
|
+
it "only opens one connect request at a time" do
|
252
|
+
transport.should_receive(:send).with({
|
253
|
+
"channel" => "/meta/connect",
|
254
|
+
"clientId" => "fakeid",
|
255
|
+
"connectionType" => "fake",
|
256
|
+
"id" => instance_of(String)
|
257
|
+
}, 60).
|
258
|
+
exactly(1).
|
259
|
+
and_return # override stub implementation
|
260
|
+
|
261
|
+
@client.connect
|
262
|
+
@client.connect
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe :disconnect do
|
268
|
+
before { create_connected_client }
|
269
|
+
|
270
|
+
it "sends a disconnect message to the server" do
|
271
|
+
transport.stub(:close)
|
272
|
+
transport.should_receive(:send).with({
|
273
|
+
"channel" => "/meta/disconnect",
|
274
|
+
"clientId" => "fakeid",
|
275
|
+
"id" => instance_of(String)
|
276
|
+
}, 60)
|
277
|
+
@client.disconnect
|
278
|
+
end
|
279
|
+
|
280
|
+
it "puts the client in the DISCONNECTED state" do
|
281
|
+
transport.stub(:close)
|
282
|
+
@client.disconnect
|
283
|
+
@client.state.should == :DISCONNECTED
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "on successful response" do
|
287
|
+
before do
|
288
|
+
stub_response "channel" => "/meta/disconnect",
|
289
|
+
"successful" => true,
|
290
|
+
"clientId" => "fakeid"
|
291
|
+
end
|
292
|
+
|
293
|
+
it "closes the transport" do
|
294
|
+
transport.should_receive(:close)
|
295
|
+
@client.disconnect
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe :subscribe do
|
301
|
+
before do
|
302
|
+
create_connected_client
|
303
|
+
@subscribe_message = {
|
304
|
+
"channel" => "/meta/subscribe",
|
305
|
+
"clientId" => "fakeid",
|
306
|
+
"subscription" => "/foo/*",
|
307
|
+
"id" => instance_of(String)
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
describe "with no prior subscriptions" do
|
312
|
+
it "sends a subscribe message to the server" do
|
313
|
+
transport.should_receive(:send).with(@subscribe_message, 60)
|
314
|
+
@client.subscribe("/foo/*")
|
315
|
+
end
|
316
|
+
|
317
|
+
# The Bayeux spec says the server should accept a list of subscriptions
|
318
|
+
# in one message but the cometD server doesn't actually support this
|
319
|
+
it "sends multiple subscribe messages if given an array" do
|
320
|
+
transport.should_receive(:send).with({
|
321
|
+
"channel" => "/meta/subscribe",
|
322
|
+
"clientId" => "fakeid",
|
323
|
+
"subscription" => "/foo",
|
324
|
+
"id" => instance_of(String)
|
325
|
+
}, 60)
|
326
|
+
transport.should_receive(:send).with({
|
327
|
+
"channel" => "/meta/subscribe",
|
328
|
+
"clientId" => "fakeid",
|
329
|
+
"subscription" => "/bar",
|
330
|
+
"id" => instance_of(String)
|
331
|
+
}, 60)
|
332
|
+
@client.subscribe(["/foo", "/bar"])
|
333
|
+
end
|
334
|
+
|
335
|
+
describe "on successful response" do
|
336
|
+
before do
|
337
|
+
stub_response "channel" => "/meta/subscribe",
|
338
|
+
"successful" => true,
|
339
|
+
"clientId" => "fakeid",
|
340
|
+
"subscription" => "/foo/*"
|
341
|
+
end
|
342
|
+
|
343
|
+
it "sets up a listener for the subscribed channel" do
|
344
|
+
@message = nil
|
345
|
+
@client.subscribe("/foo/*") { |m| @message = m }
|
346
|
+
@client.receive_message("channel" => "/foo/bar", "data" => "hi")
|
347
|
+
@message.should == "hi"
|
348
|
+
end
|
349
|
+
|
350
|
+
it "does not call the listener for non-matching channels" do
|
351
|
+
@message = nil
|
352
|
+
@client.subscribe("/foo/*") { |m| @message = m }
|
353
|
+
@client.receive_message("channel" => "/bar", "data" => "hi")
|
354
|
+
@message.should be_nil
|
355
|
+
end
|
356
|
+
|
357
|
+
it "activates the subscription" do
|
358
|
+
active = false
|
359
|
+
@client.subscribe("/foo/*").callback { active = true }
|
360
|
+
active.should be_true
|
361
|
+
end
|
362
|
+
|
363
|
+
describe "with an incoming extension installed" do
|
364
|
+
before do
|
365
|
+
extension = Class.new do
|
366
|
+
def incoming(message, callback)
|
367
|
+
message["data"]["changed"] = true if message["data"]
|
368
|
+
callback.call(message)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
@client.add_extension(extension.new)
|
372
|
+
@message = nil
|
373
|
+
@client.subscribe("/foo/*") { |m| @message = m }
|
374
|
+
end
|
375
|
+
|
376
|
+
it "passes delivered messages through the extension" do
|
377
|
+
@client.receive_message("channel" => "/foo/bar", "data" => {"hello" => "there"})
|
378
|
+
@message.should == {"hello" => "there", "changed" => true}
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "with an outgoing extension installed" do
|
383
|
+
before do
|
384
|
+
extension = Class.new do
|
385
|
+
def outgoing(message, callback)
|
386
|
+
message["data"]["changed"] = true if message["data"]
|
387
|
+
callback.call(message)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
@client.add_extension(extension.new)
|
391
|
+
@message = nil
|
392
|
+
@client.subscribe("/foo/*") { |m| @message = m }
|
393
|
+
end
|
394
|
+
|
395
|
+
it "leaves messages unchanged" do
|
396
|
+
@client.receive_message("channel" => "/foo/bar", "data" => {"hello" => "there"})
|
397
|
+
@message.should == {"hello" => "there"}
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
describe "with an incoming extension that invalidates the response" do
|
402
|
+
before do
|
403
|
+
extension = Class.new do
|
404
|
+
def incoming(message, callback)
|
405
|
+
message["successful"] = false if message["channel"] == "/meta/subscribe"
|
406
|
+
callback.call(message)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
@client.add_extension(extension.new)
|
410
|
+
end
|
411
|
+
|
412
|
+
it "does not set up a listener for the subscribed channel" do
|
413
|
+
@message = nil
|
414
|
+
@client.subscribe("/foo/*") { |m| @message = m }
|
415
|
+
@client.receive_message("channel" => "/foo/bar", "data" => "hi")
|
416
|
+
@message.should be_nil
|
417
|
+
end
|
418
|
+
|
419
|
+
it "does not activate the subscription" do
|
420
|
+
active = false
|
421
|
+
@client.subscribe("/foo/*").callback { active = true }
|
422
|
+
active.should be_false
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
describe "on unsuccessful response" do
|
428
|
+
before do
|
429
|
+
stub_response "channel" => "/meta/subscribe",
|
430
|
+
"error" => "403:/meta/foo:Forbidden channel",
|
431
|
+
"successful" => false,
|
432
|
+
"clientId" => "fakeid",
|
433
|
+
"subscription" => "/meta/foo"
|
434
|
+
end
|
435
|
+
|
436
|
+
it "does not set up a listener for the subscribed channel" do
|
437
|
+
@message = nil
|
438
|
+
@client.subscribe("/meta/foo") { |m| @message = m }
|
439
|
+
@client.receive_message("channel" => "/meta/foo", "data" => "hi")
|
440
|
+
@message.should be_nil
|
441
|
+
end
|
442
|
+
|
443
|
+
it "does not activate the subscription" do
|
444
|
+
active = false
|
445
|
+
@client.subscribe("/meta/foo").callback { active = true }
|
446
|
+
active.should be_false
|
447
|
+
end
|
448
|
+
|
449
|
+
it "reports the error through an errback" do
|
450
|
+
error = nil
|
451
|
+
@client.subscribe("/meta/foo").errback { |e| error = e }
|
452
|
+
error.code.should == 403
|
453
|
+
error.params.should == ["/meta/foo"]
|
454
|
+
error.message.should == "Forbidden channel"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
describe "with an existing subscription" do
|
460
|
+
before do
|
461
|
+
subscribe @client, "/foo/*"
|
462
|
+
end
|
463
|
+
|
464
|
+
it "does not send another subscribe message to the server" do
|
465
|
+
transport.should_not_receive(:send).with(@subscribe_message, 60)
|
466
|
+
@client.subscribe("/foo/*")
|
467
|
+
end
|
468
|
+
|
469
|
+
it "sets up another listener on the channel" do
|
470
|
+
@client.subscribe("/foo/*") { @subs_called += 1 }
|
471
|
+
@client.receive_message("channel" => "/foo/bar", "data" => "hi")
|
472
|
+
@subs_called.should == 2
|
473
|
+
end
|
474
|
+
|
475
|
+
it "activates the subscription" do
|
476
|
+
active = false
|
477
|
+
@client.subscribe("/foo/*").callback { active = true }
|
478
|
+
active.should be_true
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
describe :unsubscribe do
|
484
|
+
before do
|
485
|
+
create_connected_client
|
486
|
+
@unsubscribe_message = {
|
487
|
+
"channel" => "/meta/unsubscribe",
|
488
|
+
"clientId" => "fakeid",
|
489
|
+
"subscription" => "/foo/*",
|
490
|
+
"id" => instance_of(String)
|
491
|
+
}
|
492
|
+
end
|
493
|
+
|
494
|
+
describe "with no subscriptions" do
|
495
|
+
it "does not send an unsubscribe message to the server" do
|
496
|
+
transport.should_not_receive(:send).with(@unsubscribe_message, 60)
|
497
|
+
@client.unsubscribe("/foo/*")
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
describe "with a single subscription" do
|
502
|
+
before do
|
503
|
+
@message = nil
|
504
|
+
@listener = lambda { |m| @message = m }
|
505
|
+
subscribe @client, "/foo/*", @listener
|
506
|
+
end
|
507
|
+
|
508
|
+
it "sends an unsubscribe message to the server" do
|
509
|
+
transport.should_receive(:send).with(@unsubscribe_message, 60)
|
510
|
+
@client.unsubscribe("/foo/*")
|
511
|
+
end
|
512
|
+
|
513
|
+
it "removes the listener from the channel" do
|
514
|
+
@client.receive_message("channel" => "/foo/bar", "data" => "first")
|
515
|
+
@client.unsubscribe("/foo/*", &@listener)
|
516
|
+
@client.receive_message("channel" => "/foo/bar", "data" => "second")
|
517
|
+
@message.should == "first"
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
describe "with multiple subscriptions to the same channel" do
|
522
|
+
before do
|
523
|
+
@messages = []
|
524
|
+
@hey = lambda { |m| @messages << ("hey " + m["text"]) }
|
525
|
+
@bye = lambda { |m| @messages << ("bye " + m["text"]) }
|
526
|
+
subscribe @client, "/foo/*", @hey
|
527
|
+
subscribe @client, "/foo/*", @bye
|
528
|
+
end
|
529
|
+
|
530
|
+
it "removes one of the listeners from the channel" do
|
531
|
+
@client.receive_message("channel" => "/foo/bar", "data" => {"text" => "you"})
|
532
|
+
@client.unsubscribe("/foo/*", &@hey)
|
533
|
+
@client.receive_message("channel" => "/foo/bar", "data" => {"text" => "you"})
|
534
|
+
@messages.should == ["hey you", "bye you", "bye you"]
|
535
|
+
end
|
536
|
+
|
537
|
+
it "does not send an unsubscribe message if one listener is removed" do
|
538
|
+
transport.should_not_receive(:send).with(@unsubscribe_message, 60)
|
539
|
+
@client.unsubscribe("/foo/*", &@bye)
|
540
|
+
end
|
541
|
+
|
542
|
+
it "sends an unsubscribe message if each listener is removed" do
|
543
|
+
transport.should_receive(:send).with(@unsubscribe_message, 60)
|
544
|
+
@client.unsubscribe("/foo/*", &@bye)
|
545
|
+
@client.unsubscribe("/foo/*", &@hey)
|
546
|
+
end
|
547
|
+
|
548
|
+
it "sends an unsubscribe message if all listeners are removed" do
|
549
|
+
transport.should_receive(:send).with(@unsubscribe_message, 60)
|
550
|
+
@client.unsubscribe("/foo/*")
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
describe "with multiple subscriptions to different channels" do
|
555
|
+
before do
|
556
|
+
subscribe @client, "/foo"
|
557
|
+
subscribe @client, "/bar"
|
558
|
+
end
|
559
|
+
|
560
|
+
it "sends multiple unsubscribe messages if given an array" do
|
561
|
+
transport.should_receive(:send).with({
|
562
|
+
"channel" => "/meta/unsubscribe",
|
563
|
+
"clientId" => "fakeid",
|
564
|
+
"subscription" => "/foo",
|
565
|
+
"id" => instance_of(String)
|
566
|
+
}, 60)
|
567
|
+
transport.should_receive(:send).with({
|
568
|
+
"channel" => "/meta/unsubscribe",
|
569
|
+
"clientId" => "fakeid",
|
570
|
+
"subscription" => "/bar",
|
571
|
+
"id" => instance_of(String)
|
572
|
+
}, 60)
|
573
|
+
@client.unsubscribe(["/foo", "/bar"])
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
describe :publish do
|
579
|
+
before { create_connected_client }
|
580
|
+
|
581
|
+
it "sends the message to the server with an ID" do
|
582
|
+
transport.should_receive(:send).with({
|
583
|
+
"channel" => "/messages/foo",
|
584
|
+
"clientId" => "fakeid",
|
585
|
+
"data" => {"hello" => "world"},
|
586
|
+
"id" => instance_of(String)
|
587
|
+
}, 60)
|
588
|
+
@client.publish("/messages/foo", "hello" => "world")
|
589
|
+
end
|
590
|
+
|
591
|
+
it "throws an error when publishing to an invalid channel" do
|
592
|
+
transport.should_not_receive(:send).with(hash_including("channel" => "/messages/*"), 60)
|
593
|
+
lambda { @client.publish("/messages/*", "hello" => "world") }.should raise_error
|
594
|
+
end
|
595
|
+
|
596
|
+
describe "on publish failure" do
|
597
|
+
before do
|
598
|
+
stub_response "channel" => "/messages/foo",
|
599
|
+
"error" => "407:/messages/foo:Failed to publish",
|
600
|
+
"successful" => false,
|
601
|
+
"clientId" => "fakeid"
|
602
|
+
end
|
603
|
+
|
604
|
+
it "should not be published" do
|
605
|
+
published = false
|
606
|
+
@client.publish("/messages/foo", "text" => "hi").callback { published = true }
|
607
|
+
published.should be_false
|
608
|
+
end
|
609
|
+
|
610
|
+
it "reports the error through an errback" do
|
611
|
+
error = nil
|
612
|
+
@client.publish("/messages/foo", "text" => "hi").errback { |e| error = e }
|
613
|
+
error.code.should == 407
|
614
|
+
error.params.should == ["/messages/foo"]
|
615
|
+
error.message.should == "Failed to publish"
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
describe "on receipt of the published message" do
|
620
|
+
before do
|
621
|
+
stub_response "channel" => "/messages/foo",
|
622
|
+
"data" => {"text" => "hi"},
|
623
|
+
"clientId" => "fakeid"
|
624
|
+
end
|
625
|
+
|
626
|
+
it "does not trigger the callbacks" do
|
627
|
+
published = false
|
628
|
+
publication = @client.publish("/messages/foo", "text" => "hi")
|
629
|
+
publication.callback { published = true }
|
630
|
+
publication.errback { published = true }
|
631
|
+
published.should be_false
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
describe "with an outgoing extension installed" do
|
636
|
+
before do
|
637
|
+
extension = Class.new do
|
638
|
+
def outgoing(message, callback)
|
639
|
+
message["ext"] = {"auth" => "password"}
|
640
|
+
callback.call(message)
|
641
|
+
end
|
642
|
+
end
|
643
|
+
@client.add_extension(extension.new)
|
644
|
+
end
|
645
|
+
|
646
|
+
it "passes messages through the extension" do
|
647
|
+
transport.should_receive(:send).with({
|
648
|
+
"channel" => "/messages/foo",
|
649
|
+
"clientId" => "fakeid",
|
650
|
+
"data" => {"hello" => "world"},
|
651
|
+
"id" => instance_of(String),
|
652
|
+
"ext" => {"auth" => "password"}
|
653
|
+
}, 60)
|
654
|
+
@client.publish("/messages/foo", "hello" => "world")
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
describe "with an incoming extension installed" do
|
659
|
+
before do
|
660
|
+
extension = Class.new do
|
661
|
+
def incoming(message, callback)
|
662
|
+
message["ext"] = {"auth" => "password"}
|
663
|
+
callback.call(message)
|
664
|
+
end
|
665
|
+
end
|
666
|
+
@client.add_extension(extension.new)
|
667
|
+
end
|
668
|
+
|
669
|
+
it "leaves the message unchanged" do
|
670
|
+
transport.should_receive(:send).with({
|
671
|
+
"channel" => "/messages/foo",
|
672
|
+
"clientId" => "fakeid",
|
673
|
+
"data" => {"hello" => "world"},
|
674
|
+
"id" => instance_of(String)
|
675
|
+
}, 60)
|
676
|
+
@client.publish("/messages/foo", "hello" => "world")
|
677
|
+
end
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
describe "network notifications" do
|
682
|
+
before { create_client }
|
683
|
+
|
684
|
+
describe "in the default state" do
|
685
|
+
it "broadcasts a down notification" do
|
686
|
+
@client.should_receive(:trigger).with("transport:down")
|
687
|
+
transport.trigger(:down)
|
688
|
+
end
|
689
|
+
|
690
|
+
it "broadcasts an up notification" do
|
691
|
+
@client.should_receive(:trigger).with("transport:up")
|
692
|
+
transport.trigger(:up)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
describe "when the transport is up" do
|
697
|
+
before { transport.trigger(:up) }
|
698
|
+
|
699
|
+
it "broadcasts a down notification" do
|
700
|
+
@client.should_receive(:trigger).with("transport:down")
|
701
|
+
transport.trigger(:down)
|
702
|
+
end
|
703
|
+
|
704
|
+
it "does not broadcast an up notification" do
|
705
|
+
@client.should_not_receive(:trigger)
|
706
|
+
transport.trigger(:up)
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
describe "when the transport is down" do
|
711
|
+
before { transport.trigger(:down) }
|
712
|
+
|
713
|
+
it "does not broadcast a down notification" do
|
714
|
+
@client.should_not_receive(:trigger)
|
715
|
+
transport.trigger(:down)
|
716
|
+
end
|
717
|
+
|
718
|
+
it "broadcasts an up notification" do
|
719
|
+
@client.should_receive(:trigger).with("transport:up")
|
720
|
+
transport.trigger(:up)
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
end
|