sinapse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +101 -0
- data/LICENSE +20 -0
- data/README.md +173 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/bin/sinapse +10 -0
- data/certs/ysbaddaden.pem +21 -0
- data/config/rainbows.rb +3 -0
- data/ext/mkrf_conf.rb +22 -0
- data/lib/sinapse.rb +42 -0
- data/lib/sinapse/authentication.rb +44 -0
- data/lib/sinapse/channels.rb +51 -0
- data/lib/sinapse/config.rb +31 -0
- data/lib/sinapse/cross_origin_resource_sharing.rb +79 -0
- data/lib/sinapse/keep_alive.rb +25 -0
- data/lib/sinapse/publishable.rb +26 -0
- data/lib/sinapse/server.rb +113 -0
- data/lib/sinapse/version.rb +10 -0
- data/sinapse.gemspec +33 -0
- data/test/authentication_test.rb +69 -0
- data/test/channels_test.rb +101 -0
- data/test/cross_origin_resource_sharing_test.rb +167 -0
- data/test/publishable_test.rb +32 -0
- data/test/server_test.rb +189 -0
- data/test/support/event_source.rb +70 -0
- data/test/support/goliath.rb +13 -0
- data/test/support/redis.rb +32 -0
- data/test/support/timeout.rb +8 -0
- data/test/test_helper.rb +31 -0
- metadata +262 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe "Sinapse::Channels" do
|
|
4
|
+
include RedisTestHelper
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
Sinapse.redis do |redis|
|
|
8
|
+
redis.sadd 'sinapse:channels:1', 'room:1'
|
|
9
|
+
redis.sadd 'sinapse:channels:1', 'room:83'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
Sinapse.redis { |redis| redis.del 'sinapse:channels:1' }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:user) { User.new(1) }
|
|
18
|
+
let(:room) { Room.new(1) }
|
|
19
|
+
|
|
20
|
+
it "key" do
|
|
21
|
+
assert_equal "sinapse:channels:1", User.new(1).sinapse.key
|
|
22
|
+
assert_equal "sinapse:channels:345", User.new(345).sinapse.key
|
|
23
|
+
assert_equal "sinapse:channels:345:add", User.new(345).sinapse.key(:add)
|
|
24
|
+
assert_equal "sinapse:channels:345:remove", User.new(345).sinapse.key(:remove)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "channel_for" do
|
|
28
|
+
it "accepts a string" do
|
|
29
|
+
assert_equal 'room:1', user.sinapse.channel_for('room:1')
|
|
30
|
+
assert_equal 'room:876', user.sinapse.channel_for('room:876')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "accepts a record" do
|
|
34
|
+
assert_equal 'room:1', user.sinapse.channel_for(Room.new(1))
|
|
35
|
+
assert_equal 'room:4321', user.sinapse.channel_for(Room.new(4321))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "channels" do
|
|
40
|
+
assert_equal ['room:1', 'room:83'], user.sinapse.channels.sort
|
|
41
|
+
assert_equal [], User.new(2).sinapse.channels
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "has_channel?" do
|
|
45
|
+
assert user.sinapse.has_channel?(room)
|
|
46
|
+
refute user.sinapse.has_channel?('room:2')
|
|
47
|
+
refute User.new(2).sinapse.has_channel?(room)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "clear" do
|
|
51
|
+
user.sinapse.clear
|
|
52
|
+
assert_empty user.sinapse.channels
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "destroy" do
|
|
56
|
+
user.sinapse.destroy
|
|
57
|
+
|
|
58
|
+
Sinapse.redis do |redis|
|
|
59
|
+
assert_nil redis.get(user.sinapse.auth.key)
|
|
60
|
+
assert_nil redis.get(user.sinapse.auth.token_key('a1b2c3d4e5f6'))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
assert_empty user.sinapse.channels
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe "add_channel" do
|
|
67
|
+
let(:room) { Room.new(12345) }
|
|
68
|
+
|
|
69
|
+
it "adds channel to the list" do
|
|
70
|
+
user.sinapse.add_channel(room)
|
|
71
|
+
assert user.sinapse.has_channel?(room)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "publishes a message" do
|
|
75
|
+
EM.run do
|
|
76
|
+
wait_for_message('sinapse:channels:*') do |channel, message|
|
|
77
|
+
assert_equal user.sinapse.key(:add), channel
|
|
78
|
+
assert_equal room.sinapse_channel, message
|
|
79
|
+
end
|
|
80
|
+
publish_until_received { user.sinapse.add_channel(room) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "remove_channel" do
|
|
86
|
+
it "removes channel from the list" do
|
|
87
|
+
user.sinapse.remove_channel(room)
|
|
88
|
+
refute user.sinapse.has_channel?(room)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "publishes a message" do
|
|
92
|
+
EM.run do
|
|
93
|
+
wait_for_message('sinapse:channels:*') do |channel, message|
|
|
94
|
+
assert_equal user.sinapse.key(:remove), channel
|
|
95
|
+
assert_equal room.sinapse_channel, message
|
|
96
|
+
end
|
|
97
|
+
publish_until_received { user.sinapse.remove_channel(room) }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'sinapse/cross_origin_resource_sharing'
|
|
3
|
+
|
|
4
|
+
describe "Sinapse::Rack::CrossOriginResourceSharing" do
|
|
5
|
+
let(:app) { Minitest::Mock.new }
|
|
6
|
+
|
|
7
|
+
describe "regular request" do
|
|
8
|
+
it "calls app" do
|
|
9
|
+
env = { 'REQUEST_METHOD' => 'POST' }
|
|
10
|
+
app.expect(:call, [200, {}, ''], [env])
|
|
11
|
+
assert_equal [200, {}, ''], cors(origin: '*').call(env)
|
|
12
|
+
app.verify
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "preflight check" do
|
|
17
|
+
let(:env) do
|
|
18
|
+
{ 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', 'REQUEST_METHOD' => 'OPTIONS' }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "when domain is *" do
|
|
22
|
+
let(:domain) { %w(test.host example.com somewhere.org)[rand(0..2)] }
|
|
23
|
+
|
|
24
|
+
it "allows any domain" do
|
|
25
|
+
status, headers, body = cors(origin: '*')
|
|
26
|
+
.call(env.merge('HTTP_ORIGIN' => "http://#{domain}"))
|
|
27
|
+
|
|
28
|
+
assert_equal 200, status
|
|
29
|
+
assert_equal 'text/plain', headers['Content-Type']
|
|
30
|
+
assert_equal "http://#{domain}", headers['Access-Control-Allow-Origin']
|
|
31
|
+
assert_nil headers['Access-Control-Allow-Headers']
|
|
32
|
+
assert_empty body
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "when origin is a domain" do
|
|
37
|
+
it "allows request for HTTP" do
|
|
38
|
+
status, headers, _ = cors(origin: 'example.com')
|
|
39
|
+
.call(env.merge('HTTP_ORIGIN' => 'http://example.com'))
|
|
40
|
+
assert_equal 200, status
|
|
41
|
+
assert_equal 'http://example.com', headers['Access-Control-Allow-Origin']
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "allows request for HTTPS" do
|
|
45
|
+
status, headers, _ = cors(origin: 'example.com')
|
|
46
|
+
.call(env.merge('HTTP_ORIGIN' => 'https://example.com'))
|
|
47
|
+
assert_equal 200, status
|
|
48
|
+
assert_equal 'https://example.com', headers['Access-Control-Allow-Origin']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "refuses another domain" do
|
|
52
|
+
status, headers, _ = cors(origin: 'example.com')
|
|
53
|
+
.call(env.merge('HTTP_ORIGIN' => 'http://test.host'))
|
|
54
|
+
assert_equal 400, status
|
|
55
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "when origin is a string" do
|
|
60
|
+
it "allows specific origin" do
|
|
61
|
+
status, headers, _ = cors(origin: 'http://example.com')
|
|
62
|
+
.call(env.merge('HTTP_ORIGIN' => 'http://example.com'))
|
|
63
|
+
assert_equal 200, status
|
|
64
|
+
assert_equal 'http://example.com', headers['Access-Control-Allow-Origin']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "refuses another origin" do
|
|
68
|
+
status, headers, _ = cors(origin: 'https://example.com')
|
|
69
|
+
.call(env.merge('HTTP_ORIGIN' => 'http://example.com'))
|
|
70
|
+
assert_equal 400, status
|
|
71
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "when origin is a regexp" do
|
|
76
|
+
let(:domain) { %w(example.com test.host)[rand(0..1)] }
|
|
77
|
+
|
|
78
|
+
it "allows matching origin" do
|
|
79
|
+
status, headers, _ = cors(origin: %r(^https?://(example\.com|test\.host)))
|
|
80
|
+
.call(env.merge('HTTP_ORIGIN' => "http://#{domain}"))
|
|
81
|
+
assert_equal 200, status
|
|
82
|
+
assert_equal "http://#{domain}", headers['Access-Control-Allow-Origin']
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "refuses an origin that doesn't match" do
|
|
86
|
+
status, headers, _ = cors(origin: %r(^http?://(example\.com|test\.host)))
|
|
87
|
+
.call(env.merge('HTTP_ORIGIN' => 'http://somewhere.org'))
|
|
88
|
+
assert_equal 400, status
|
|
89
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "methods" do
|
|
94
|
+
let(:methods) { %w(GET POST DELETE) }
|
|
95
|
+
let(:method) { methods[rand(0..2)] }
|
|
96
|
+
|
|
97
|
+
it "accepts method" do
|
|
98
|
+
status, headers, _ = cors(origin: '*', methods: methods).call(env.merge(
|
|
99
|
+
'HTTP_ORIGIN' => "http://test.host",
|
|
100
|
+
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => method
|
|
101
|
+
))
|
|
102
|
+
assert_equal 200, status
|
|
103
|
+
assert_equal "http://test.host", headers['Access-Control-Allow-Origin']
|
|
104
|
+
assert_equal 'GET, POST, DELETE', headers['Access-Control-Allow-Methods']
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "refuses method" do
|
|
108
|
+
status, headers, _ = cors(origin: '*', methods: methods).call(env.merge(
|
|
109
|
+
'HTTP_ORIGIN' => "http://test.host",
|
|
110
|
+
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'PATCH'
|
|
111
|
+
))
|
|
112
|
+
assert_equal 400, status
|
|
113
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
114
|
+
assert_nil headers['Access-Control-Allow-Methods']
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe "actual request" do
|
|
120
|
+
it "adds headers when origin header is present" do
|
|
121
|
+
env = {
|
|
122
|
+
'HTTP_ORIGIN' => 'http://example.com',
|
|
123
|
+
'REQUEST_METHOD' => 'POST'
|
|
124
|
+
}
|
|
125
|
+
app.expect(:call, [200, {}, ''], [env])
|
|
126
|
+
status, headers, body = cors(origin: '*', methods: %w(POST)).call(env)
|
|
127
|
+
app.verify
|
|
128
|
+
|
|
129
|
+
assert_equal 200, status
|
|
130
|
+
assert_equal '', body
|
|
131
|
+
assert_equal 'http://example.com', headers['Access-Control-Allow-Origin']
|
|
132
|
+
assert_equal 'POST', headers['Access-Control-Allow-Methods']
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "skips headers when origin is refused" do
|
|
136
|
+
env = {
|
|
137
|
+
'HTTP_ORIGIN' => 'http://test.com',
|
|
138
|
+
'REQUEST_METHOD' => 'POST'
|
|
139
|
+
}
|
|
140
|
+
app.expect(:call, [200, {}, ''], [env])
|
|
141
|
+
status, headers, _ = cors(origin: 'example.com', methods: %w(POST)).call(env)
|
|
142
|
+
app.verify
|
|
143
|
+
|
|
144
|
+
assert_equal 200, status
|
|
145
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
146
|
+
assert_nil headers['Access-Control-Allow-Methods']
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "skips headers when method is refused" do
|
|
150
|
+
env = {
|
|
151
|
+
'HTTP_ORIGIN' => 'http://test.host',
|
|
152
|
+
'REQUEST_METHOD' => 'GET'
|
|
153
|
+
}
|
|
154
|
+
app.expect(:call, [200, {}, ''], [env])
|
|
155
|
+
status, headers, _ = cors(origin: 'test.host', methods: %w(POST)).call(env)
|
|
156
|
+
app.verify
|
|
157
|
+
|
|
158
|
+
assert_equal 200, status
|
|
159
|
+
assert_nil headers['Access-Control-Allow-Origin']
|
|
160
|
+
assert_nil headers['Access-Control-Allow-Methods']
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cors(options = {})
|
|
165
|
+
Sinapse::Rack::CrossOriginResourceSharing.new(app, options)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe "Sinapse::Publishable" do
|
|
4
|
+
include RedisTestHelper
|
|
5
|
+
|
|
6
|
+
let(:room) { Room.new(1) }
|
|
7
|
+
|
|
8
|
+
it "sinapse_channel" do
|
|
9
|
+
assert_equal 'room:1', Room.new(1).sinapse_channel
|
|
10
|
+
assert_equal 'room:83', Room.new(83).sinapse_channel
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "publish" do
|
|
14
|
+
EM.run do
|
|
15
|
+
wait_for_message('room:*') do |channel, message|
|
|
16
|
+
assert_equal room.sinapse_channel, channel
|
|
17
|
+
assert_equal 'hello room 1', message
|
|
18
|
+
end
|
|
19
|
+
publish_until_received { room.publish('hello room 1') }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "publish with event type" do
|
|
24
|
+
EM.run do
|
|
25
|
+
wait_for_message('room:*') do |channel, message|
|
|
26
|
+
assert_equal room.sinapse_channel, channel
|
|
27
|
+
assert_equal ['hello', 'hello room 1'], message
|
|
28
|
+
end
|
|
29
|
+
publish_until_received { room.publish('hello room 1', event: 'hello') }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/test/server_test.rb
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'sinapse/server'
|
|
3
|
+
|
|
4
|
+
describe Sinapse::Server do
|
|
5
|
+
include Goliath::TestHelper
|
|
6
|
+
include RedisTestHelper
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
EM.synchrony do
|
|
10
|
+
redis.set('sinapse:tokens:valid', '1')
|
|
11
|
+
redis.set('sinapse:tokens:empty', '2')
|
|
12
|
+
redis.sadd('sinapse:channels:1', 'user:1')
|
|
13
|
+
redis.sadd('sinapse:channels:1', 'room:2')
|
|
14
|
+
redis.sadd('sinapse:channels:1', 'room:4')
|
|
15
|
+
EM.stop_event_loop
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after do
|
|
20
|
+
EM.synchrony do
|
|
21
|
+
redis.del('sinapse:tokens:valid')
|
|
22
|
+
redis.del('sinapse:tokens:empty')
|
|
23
|
+
redis.del('sinapse:channels:1')
|
|
24
|
+
EM.stop_event_loop
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "authentication" do
|
|
29
|
+
it "returns an event-stream on success" do
|
|
30
|
+
sse_connect do |client|
|
|
31
|
+
assert_equal 'close', client.headers['CONNECTION']
|
|
32
|
+
assert_equal 'text/event-stream', client.headers['CONTENT_TYPE']
|
|
33
|
+
assert_equal "retry: 5000\nevent: authentication\ndata: ok\n\n", client.receive
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "won't authenticate without token" do
|
|
38
|
+
connect(query: { access_token: '' }) do |client|
|
|
39
|
+
assert_equal 400, client.response_header.status
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "won't authenticate with unknown token" do
|
|
44
|
+
connect(query: { access_token: 'invalid' }) do |client|
|
|
45
|
+
assert_equal 401, client.response_header.status
|
|
46
|
+
end rescue LocalJumpError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "won't authenticate when user has no channels" do
|
|
50
|
+
connect(query: { access_token: 'empty' }) do |client|
|
|
51
|
+
assert_equal 401, client.response_header.status
|
|
52
|
+
end rescue LocalJumpError
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "cross origin resource sharing (when requested)" do
|
|
57
|
+
it "returns CORS headers on auth success" do
|
|
58
|
+
sse_connect(head: { origin: 'http://example.com' }) do |client|
|
|
59
|
+
assert_equal 'text/event-stream', client.headers['CONTENT_TYPE']
|
|
60
|
+
assert_equal 'http://example.com', client.headers['ACCESS_CONTROL_ALLOW_ORIGIN']
|
|
61
|
+
refute_nil client.headers['ACCESS_CONTROL_ALLOW_METHODS']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "skips CORS headers when configured origin doesn't match" do
|
|
66
|
+
stub_origin('test.host') do
|
|
67
|
+
sse_connect(head: { origin: "http://example.com" }) do |client|
|
|
68
|
+
assert_nil client.headers['ACCESS_CONTROL_ALLOW_ORIGIN']
|
|
69
|
+
assert_nil client.headers['ACCESS_CONTROL_ALLOW_METHODS']
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def stub_origin(forced)
|
|
75
|
+
Sinapse::Server.middlewares.each do |middleware, params, _|
|
|
76
|
+
next unless middleware == Sinapse::Rack::CrossOriginResourceSharing
|
|
77
|
+
options = params.first
|
|
78
|
+
original = options[:origin]
|
|
79
|
+
options[:origin] = forced
|
|
80
|
+
yield
|
|
81
|
+
options[:origin] = original
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "pub/sub" do
|
|
88
|
+
let(:channel_name) { 'user:1' }
|
|
89
|
+
|
|
90
|
+
# waits for server to be listening
|
|
91
|
+
def wait
|
|
92
|
+
sleep 0.001 until redis.publish('sinapse:channels:1:wait', nil) == 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "proxies published messages" do
|
|
96
|
+
sse_connect do |client|
|
|
97
|
+
client.receive # skips authentication message
|
|
98
|
+
wait
|
|
99
|
+
|
|
100
|
+
assert_equal 1, publish(channel_name, "payload message")
|
|
101
|
+
#assert_equal "event: #{channel_name}\ndata: payload message\n\n", client.receive
|
|
102
|
+
assert_equal "data: payload message\n\n", client.receive
|
|
103
|
+
|
|
104
|
+
assert_equal 1, publish(channel_name, "another message")
|
|
105
|
+
#assert_equal "event: #{channel_name}\ndata: another message\n\n", client.receive
|
|
106
|
+
assert_equal "data: another message\n\n", client.receive
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "disconnects from server on connection close" do
|
|
111
|
+
sse_connect do |client|
|
|
112
|
+
client.close
|
|
113
|
+
EM.synchrony { assert_equal 0, redis.publish(channel_name, "message") }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "updates subscriptions when the list changes" do
|
|
118
|
+
sse_connect do |client|
|
|
119
|
+
client.receive
|
|
120
|
+
|
|
121
|
+
redis.srem('sinapse:channels:1', 'room:2')
|
|
122
|
+
redis.publish('sinapse:channels:1:remove', 'room:2')
|
|
123
|
+
|
|
124
|
+
redis.sadd('sinapse:channels:1', 'room:5')
|
|
125
|
+
redis.publish('sinapse:channels:1:add', 'room:5')
|
|
126
|
+
|
|
127
|
+
assert_equal 1, publish('room:4', "message for room 4")
|
|
128
|
+
#assert_equal "event: room:4\ndata: message for room 4\n\n", client.receive
|
|
129
|
+
assert_equal "data: message for room 4\n\n", client.receive
|
|
130
|
+
|
|
131
|
+
assert_equal 1, publish('room:5', "message for room 5")
|
|
132
|
+
#assert_equal "event: room:5\ndata: message for room 5\n\n", client.receive
|
|
133
|
+
assert_equal "data: message for room 5\n\n", client.receive
|
|
134
|
+
|
|
135
|
+
assert_equal 0, publish('room:2', "message for room 2")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "sets channel name as event type" do
|
|
140
|
+
Sinapse::Config.stub(:channel_event, true) do
|
|
141
|
+
sse_connect do |client|
|
|
142
|
+
client.receive; wait
|
|
143
|
+
|
|
144
|
+
assert_equal 1, publish(channel_name, "payload message")
|
|
145
|
+
assert_equal "event: #{channel_name}\ndata: payload message\n\n", client.receive
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "publishes with event type" do
|
|
151
|
+
Sinapse::Config.stub(:channel_event, true) do
|
|
152
|
+
sse_connect do |client|
|
|
153
|
+
client.receive; wait
|
|
154
|
+
|
|
155
|
+
assert_equal 1, publish(channel_name, "payload message", "hello:world")
|
|
156
|
+
assert_equal "event: hello:world\ndata: payload message\n\n", client.receive
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "retry" do
|
|
163
|
+
it "uses configured value" do
|
|
164
|
+
Sinapse::Config.stub(:retry, 12000) do
|
|
165
|
+
sse_connect(head: { origin: 'http://example.com' }) do |client|
|
|
166
|
+
assert_match(/retry: 12000\n/, client.receive)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe "keep alive" do
|
|
173
|
+
it "periodically sends a comment" do
|
|
174
|
+
Sinapse::Config.stub(:keep_alive, 0.001) do
|
|
175
|
+
sse_connect do |client|
|
|
176
|
+
client.receive # skips authentication message
|
|
177
|
+
assert_equal ":\n", client.receive
|
|
178
|
+
assert_equal ":\n", client.receive
|
|
179
|
+
assert_equal ":\n", client.receive
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def publish(channel, message, event = nil)
|
|
186
|
+
data = MessagePack.pack(event ? [event, message] : message)
|
|
187
|
+
redis.publish(channel, data)
|
|
188
|
+
end
|
|
189
|
+
end
|