websocket-rails 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -2
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +7 -0
- data/lib/websocket_rails/configuration.rb +8 -0
- data/lib/websocket_rails/connection_adapters.rb +34 -29
- data/lib/websocket_rails/connection_manager.rb +35 -9
- data/lib/websocket_rails/event.rb +6 -2
- data/lib/websocket_rails/synchronization.rb +48 -3
- data/lib/websocket_rails/user_manager.rb +210 -19
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/config/environments/test.rb +1 -2
- data/spec/dummy/config/{initializers/events.rb → events.rb} +0 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/integration/connection_manager_spec.rb +2 -2
- data/spec/unit/connection_adapters_spec.rb +77 -41
- data/spec/unit/connection_manager_spec.rb +45 -9
- data/spec/unit/synchronization_spec.rb +47 -18
- data/spec/unit/user_manager_spec.rb +115 -11
- metadata +12 -7
@@ -7,8 +7,7 @@ Dummy::Application.configure do
|
|
7
7
|
# and recreated between test runs. Don't rely on the data there!
|
8
8
|
config.cache_classes = true
|
9
9
|
|
10
|
-
|
11
|
-
config.whiny_nils = true
|
10
|
+
config.eager_load = true
|
12
11
|
|
13
12
|
# Show full error reports and disable caching
|
14
13
|
config.consider_all_requests_local = true
|
File without changes
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
5
|
+
#
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
7
|
+
# database schema. If you need to create the application database on another
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
|
+
#
|
12
|
+
# It's strongly recommended that you check this file into your version control system.
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define(version: 20130902222552) do
|
15
|
+
|
16
|
+
create_table "users", force: true do |t|
|
17
|
+
t.string "name"
|
18
|
+
t.string "email"
|
19
|
+
t.datetime "created_at"
|
20
|
+
t.datetime "updated_at"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/dummy/db/test.sqlite3
CHANGED
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[1m[36m (1.2ms)[0m [1mCREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL) [0m
|
2
|
+
[1m[35m (0.7ms)[0m CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
|
3
|
+
[1m[36mActiveRecord::SchemaMigration Load (0.1ms)[0m [1mSELECT "schema_migrations".* FROM "schema_migrations"[0m
|
4
|
+
Migrating to CreateUsers (20130902222552)
|
5
|
+
[1m[35m (0.0ms)[0m begin transaction
|
6
|
+
[1m[36m (0.3ms)[0m [1mCREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255), "created_at" datetime, "updated_at" datetime) [0m
|
7
|
+
[1m[35mSQL (1.4ms)[0m INSERT INTO "schema_migrations" ("version") VALUES (?) [["version", "20130902222552"]]
|
8
|
+
[1m[36m (0.9ms)[0m [1mcommit transaction[0m
|
9
|
+
[1m[35mActiveRecord::SchemaMigration Load (0.1ms)[0m SELECT "schema_migrations".* FROM "schema_migrations"
|
10
|
+
[1m[36mActiveRecord::SchemaMigration Load (0.1ms)[0m [1mSELECT "schema_migrations".* FROM "schema_migrations"[0m
|
11
|
+
[1m[35mActiveRecord::SchemaMigration Load (0.1ms)[0m SELECT "schema_migrations".* FROM "schema_migrations"
|
12
|
+
[1m[36m (1.0ms)[0m [1mCREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255), "created_at" datetime, "updated_at" datetime) [0m
|
13
|
+
[1m[35m (1.0ms)[0m CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL)
|
14
|
+
[1m[36m (0.8ms)[0m [1mCREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")[0m
|
15
|
+
[1m[35m (0.1ms)[0m SELECT version FROM "schema_migrations"
|
16
|
+
[1m[36m (0.8ms)[0m [1mINSERT INTO "schema_migrations" (version) VALUES ('20130902222552')[0m
|
17
|
+
[1m[35mActiveRecord::SchemaMigration Load (0.1ms)[0m SELECT "schema_migrations".* FROM "schema_migrations"
|
@@ -111,7 +111,7 @@ module WebsocketRails
|
|
111
111
|
end
|
112
112
|
|
113
113
|
context "WebSocket Adapter" do
|
114
|
-
let(:socket) { @server.connections.first }
|
114
|
+
let(:socket) { @server.connections.first[1] }
|
115
115
|
|
116
116
|
before do
|
117
117
|
::Faye::WebSocket.stub(:websocket?).and_return(true)
|
@@ -122,7 +122,7 @@ module WebsocketRails
|
|
122
122
|
end
|
123
123
|
|
124
124
|
describe "HTTP Adapter" do
|
125
|
-
let(:socket) { @server.connections.first }
|
125
|
+
let(:socket) { @server.connections.first[1] }
|
126
126
|
|
127
127
|
before do
|
128
128
|
@server = ConnectionManager.new
|
@@ -18,8 +18,15 @@ module WebsocketRails
|
|
18
18
|
end
|
19
19
|
|
20
20
|
context ".establish_connection" do
|
21
|
+
before do
|
22
|
+
connection_manager = double(ConnectionManager)
|
23
|
+
connection_manager.stub(:connections).and_return({})
|
24
|
+
@dispatcher = double(Dispatcher).as_null_object
|
25
|
+
@dispatcher.stub(:connection_manager).and_return(connection_manager)
|
26
|
+
end
|
27
|
+
|
21
28
|
it "should return the correct connection adapter instance" do
|
22
|
-
adapter = ConnectionAdapters.establish_connection(
|
29
|
+
adapter = ConnectionAdapters.establish_connection(mock_request, @dispatcher)
|
23
30
|
adapter.class.should == ConnectionAdapters::Test
|
24
31
|
end
|
25
32
|
end
|
@@ -28,11 +35,18 @@ module WebsocketRails
|
|
28
35
|
|
29
36
|
module ConnectionAdapters
|
30
37
|
describe Base do
|
31
|
-
let(:
|
32
|
-
let(:
|
33
|
-
let(:
|
34
|
-
|
35
|
-
|
38
|
+
let(:connection_manager) { double(ConnectionManager).as_null_object }
|
39
|
+
let(:dispatcher) { double(Dispatcher).as_null_object }
|
40
|
+
let(:channel_manager) { double(ChannelManager).as_null_object }
|
41
|
+
let(:event) { double(Event).as_null_object }
|
42
|
+
|
43
|
+
before do
|
44
|
+
connection_manager.stub(:connections).and_return({})
|
45
|
+
dispatcher.stub(:connection_manager).and_return(connection_manager)
|
46
|
+
Event.stub(:new_from_json).and_return(event)
|
47
|
+
end
|
48
|
+
|
49
|
+
subject { Base.new(mock_request, dispatcher) }
|
36
50
|
|
37
51
|
context "new adapter" do
|
38
52
|
it "should register itself in the adapters array when inherited" do
|
@@ -44,17 +58,17 @@ module WebsocketRails
|
|
44
58
|
subject.data_store.should be_a DataStore::Connection
|
45
59
|
end
|
46
60
|
|
47
|
-
before do
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
61
|
+
#before do
|
62
|
+
# WebsocketRails.config.stub(:user_identifier).and_return(:name)
|
63
|
+
# WebsocketRails::DelegationController.any_instance
|
64
|
+
# .stub_chain(:current_user, :name)
|
65
|
+
# .and_return('Frank')
|
66
|
+
# subject
|
67
|
+
#end
|
54
68
|
|
55
|
-
it "adds itself to the UserManager Hash" do
|
56
|
-
|
57
|
-
end
|
69
|
+
#it "adds itself to the UserManager Hash" do
|
70
|
+
# WebsocketRails.users['Frank'].should == subject
|
71
|
+
#end
|
58
72
|
end
|
59
73
|
|
60
74
|
describe "#on_open" do
|
@@ -81,11 +95,6 @@ module WebsocketRails
|
|
81
95
|
dispatcher.should_receive(:dispatch).with(on_close_event)
|
82
96
|
subject.on_close("data")
|
83
97
|
end
|
84
|
-
|
85
|
-
it "removes itself from the global UserMnaager" do
|
86
|
-
subject.on_close
|
87
|
-
WebsocketRails.users[subject.id].nil?.should == true
|
88
|
-
end
|
89
98
|
end
|
90
99
|
|
91
100
|
describe "#on_error" do
|
@@ -137,45 +146,72 @@ module WebsocketRails
|
|
137
146
|
end
|
138
147
|
end
|
139
148
|
|
140
|
-
describe "#
|
141
|
-
|
142
|
-
|
143
|
-
|
149
|
+
describe "#user_connection?" do
|
150
|
+
context "when a user is signed in" do
|
151
|
+
before do
|
152
|
+
subject.stub(:user_identifier).and_return("Jimbo Jones")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "returns true" do
|
156
|
+
subject.user_connection?.should == true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when a user is signed out" do
|
161
|
+
before do
|
162
|
+
subject.stub(:user_identifier).and_return(nil)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "returns true" do
|
166
|
+
subject.user_connection?.should == false
|
167
|
+
end
|
144
168
|
end
|
145
169
|
end
|
146
170
|
|
147
|
-
describe "#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
subject.
|
152
|
-
subject.
|
153
|
-
subject.trigger 'event'
|
171
|
+
describe "#user" do
|
172
|
+
it "provides access to the current_user object" do
|
173
|
+
user = double('User')
|
174
|
+
subject.stub(:user_identifier).and_return true
|
175
|
+
subject.stub_chain(:controller_delegate, :current_user).and_return user
|
176
|
+
subject.user.should == user
|
154
177
|
end
|
178
|
+
end
|
155
179
|
|
156
|
-
|
157
|
-
|
158
|
-
subject.
|
180
|
+
describe "#enqueue" do
|
181
|
+
it "should add the event to the queue" do
|
182
|
+
subject.enqueue 'event'
|
183
|
+
subject.queue.queue.should == ['event']
|
159
184
|
end
|
160
|
-
=end
|
161
185
|
end
|
162
186
|
|
163
187
|
describe "#flush" do
|
164
188
|
before do
|
165
|
-
event =
|
166
|
-
|
167
|
-
3.times { subject.enqueue event }
|
189
|
+
event = Event.new(:queued_event, data: 'test')
|
190
|
+
2.times { subject.enqueue event }
|
168
191
|
end
|
169
192
|
|
170
193
|
it "should serialize all events into one array" do
|
171
|
-
serialized_array =
|
172
|
-
|
194
|
+
serialized_array = <<-EOF.strip_heredoc
|
195
|
+
[["queued_event",{"id":null,"channel":null,"user_id":null,"data":"test","success":null,"result":null,"server_token":null}],
|
196
|
+
["queued_event",{"id":null,"channel":null,"user_id":null,"data":"test","success":null,"result":null,"server_token":null}]]
|
197
|
+
EOF
|
198
|
+
|
199
|
+
subject.should_receive(:send).with(serialized_array.gsub(/\n/,'').strip)
|
173
200
|
subject.flush
|
174
201
|
end
|
175
202
|
end
|
176
203
|
|
204
|
+
describe "#trigger" do
|
205
|
+
it "passes a serialized event to the connections #send method" do
|
206
|
+
event.stub(:serialize).and_return('test')
|
207
|
+
subject.should_receive(:send).with "[test]"
|
208
|
+
subject.trigger event
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
177
212
|
describe "#close_connection" do
|
178
213
|
before do
|
214
|
+
subject.stub(:user_identifier).and_return(1)
|
179
215
|
@connection_manager = double('connection_manager').as_null_object
|
180
216
|
subject.stub_chain(:dispatcher, :connection_manager).and_return(@connection_manager)
|
181
217
|
end
|
@@ -17,13 +17,13 @@ module WebsocketRails
|
|
17
17
|
|
18
18
|
before(:each) do
|
19
19
|
ConnectionAdapters::Base.any_instance.stub(:send)
|
20
|
-
@mock_socket = ConnectionAdapters::Base.new(mock_request,dispatcher)
|
20
|
+
@mock_socket = ConnectionAdapters::Base.new(mock_request, dispatcher)
|
21
21
|
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket)
|
22
22
|
end
|
23
23
|
|
24
24
|
describe "#initialize" do
|
25
|
-
it "should create an empty connections
|
26
|
-
subject.connections.should be_a
|
25
|
+
it "should create an empty connections hash" do
|
26
|
+
subject.connections.should be_a Hash
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should create a new dispatcher instance" do
|
@@ -36,20 +36,44 @@ module WebsocketRails
|
|
36
36
|
expect { open_connection }.to change { connections.count }.by(1)
|
37
37
|
end
|
38
38
|
|
39
|
-
it "should store the new connection in the @connections
|
39
|
+
it "should store the new connection in the @connections Hash" do
|
40
40
|
open_connection
|
41
|
-
connections
|
41
|
+
connections[@mock_socket.id].should == @mock_socket
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should return an Async Rack response" do
|
45
45
|
open_connection.should == [ -1, {}, [] ]
|
46
46
|
end
|
47
|
+
|
48
|
+
before do
|
49
|
+
SecureRandom.stub(:hex).and_return(1, 2)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "gives the new connection a unique ID" do
|
53
|
+
@mock_socket.should_receive(:id=).with(1)
|
54
|
+
open_connection
|
55
|
+
@mock_socket.should_receive(:id=).with(2)
|
56
|
+
open_connection
|
57
|
+
end
|
58
|
+
|
59
|
+
context "user connections" do
|
60
|
+
before do
|
61
|
+
@mock_socket.stub(:user_connection?).and_return true
|
62
|
+
@mock_socket.stub(:user_identifier).and_return "El Jefe"
|
63
|
+
open_connection
|
64
|
+
end
|
65
|
+
|
66
|
+
it "stores the connection in the UserManager" do
|
67
|
+
WebsocketRails.users["El Jefe"].connections.first.should == @mock_socket
|
68
|
+
end
|
69
|
+
end
|
47
70
|
end
|
48
71
|
|
49
72
|
context "new POST event" do
|
50
73
|
before(:each) do
|
51
|
-
@mock_http = ConnectionAdapters::Http.new(mock_request,dispatcher)
|
52
|
-
|
74
|
+
@mock_http = ConnectionAdapters::Http.new(mock_request, dispatcher)
|
75
|
+
@mock_http.id = 1
|
76
|
+
app.connections[@mock_http.id] = @mock_http
|
53
77
|
end
|
54
78
|
|
55
79
|
it "should receive the new event for the correct connection" do
|
@@ -60,7 +84,7 @@ module WebsocketRails
|
|
60
84
|
|
61
85
|
context "open connections" do
|
62
86
|
before(:each) do
|
63
|
-
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket,ConnectionAdapters::Base.new(mock_request,dispatcher))
|
87
|
+
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket, ConnectionAdapters::Base.new(mock_request, dispatcher))
|
64
88
|
4.times { open_connection }
|
65
89
|
end
|
66
90
|
|
@@ -81,7 +105,7 @@ module WebsocketRails
|
|
81
105
|
context "when closing" do
|
82
106
|
it "should remove the connection object from the @connections array" do
|
83
107
|
@mock_socket.on_close
|
84
|
-
connections.
|
108
|
+
connections.has_key?(@mock_socket.id).should be_false
|
85
109
|
end
|
86
110
|
|
87
111
|
it "should decrement the connection count by one" do
|
@@ -95,6 +119,18 @@ module WebsocketRails
|
|
95
119
|
end
|
96
120
|
@mock_socket.on_close
|
97
121
|
end
|
122
|
+
|
123
|
+
context "user connections" do
|
124
|
+
before do
|
125
|
+
@mock_socket.stub(:user_connection?).and_return true
|
126
|
+
@mock_socket.stub(:user_identifier).and_return "El Jefe"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "deletes the connection from the UserManager" do
|
130
|
+
@mock_socket.on_close
|
131
|
+
WebsocketRails.users["El Jefe"].class.should == UserManager::MissingConnection
|
132
|
+
end
|
133
|
+
end
|
98
134
|
end
|
99
135
|
|
100
136
|
end
|
@@ -1,18 +1,8 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "eventmachine"
|
3
|
+
require "ostruct"
|
3
4
|
|
4
5
|
module WebsocketRails
|
5
|
-
#class Synchronization
|
6
|
-
# def test_block(channel, &block)
|
7
|
-
# # do nothing beyatch
|
8
|
-
# block.call
|
9
|
-
# end
|
10
|
-
|
11
|
-
# def synchronize!
|
12
|
-
# test_block("something") { raise "FTW!" }
|
13
|
-
# end
|
14
|
-
#end
|
15
|
-
|
16
6
|
describe Synchronization do
|
17
7
|
|
18
8
|
around(:each) do |example|
|
@@ -59,7 +49,7 @@ module WebsocketRails
|
|
59
49
|
|
60
50
|
context "when dispatching user events" do
|
61
51
|
before do
|
62
|
-
@event = Event.new(:channel_event, :user_id =>
|
52
|
+
@event = Event.new(:channel_event, :user_id => "username", :data => 'hello channel one')
|
63
53
|
end
|
64
54
|
|
65
55
|
context "and the user is not connected to this server" do
|
@@ -71,18 +61,18 @@ module WebsocketRails
|
|
71
61
|
context "and the user is connected to this server" do
|
72
62
|
before do
|
73
63
|
@connection = double('Connection')
|
74
|
-
WebsocketRails.users[
|
64
|
+
WebsocketRails.users["username"] = @connection
|
75
65
|
end
|
76
66
|
|
77
67
|
it "triggers the event on the correct user" do
|
78
|
-
WebsocketRails.users[
|
68
|
+
WebsocketRails.users["username"].should_receive(:trigger).with @event
|
79
69
|
subject.trigger_incoming @event
|
80
70
|
end
|
81
71
|
end
|
82
72
|
end
|
83
73
|
end
|
84
74
|
|
85
|
-
describe "#
|
75
|
+
describe "#generate_server_token" do
|
86
76
|
before do
|
87
77
|
SecureRandom.stub(:urlsafe_base64).and_return(1, 2, 3)
|
88
78
|
end
|
@@ -91,14 +81,14 @@ module WebsocketRails
|
|
91
81
|
@redis.del "websocket_rails.active_servers"
|
92
82
|
end
|
93
83
|
|
94
|
-
it "should generate a unique token" do
|
84
|
+
it "should generate a unique server token" do
|
95
85
|
SecureRandom.should_receive(:urlsafe_base64).at_least(1).times
|
96
|
-
subject.
|
86
|
+
subject.generate_server_token
|
97
87
|
end
|
98
88
|
|
99
89
|
it "should generate another id if the current id is already registered" do
|
100
90
|
@redis.sadd "websocket_rails.active_servers", 1
|
101
|
-
token = subject.
|
91
|
+
token = subject.generate_server_token
|
102
92
|
token.should == 2
|
103
93
|
end
|
104
94
|
end
|
@@ -117,5 +107,44 @@ module WebsocketRails
|
|
117
107
|
end
|
118
108
|
end
|
119
109
|
|
110
|
+
describe "#register_user" do
|
111
|
+
before do
|
112
|
+
@connection = double('Connection')
|
113
|
+
@user = User.new
|
114
|
+
@user.attributes.update(name: 'Frank The Tank', email: 'frank@tank.com')
|
115
|
+
@user.instance_variable_set(:@new_record, false)
|
116
|
+
@user.instance_variable_set(:@destroyed, false)
|
117
|
+
@connection.stub(:user_identifier).and_return 'Frank The Tank'
|
118
|
+
@connection.stub(:user).and_return @user
|
119
|
+
end
|
120
|
+
|
121
|
+
it "stores the serialized user object in redis" do
|
122
|
+
@user.persisted?.should == true
|
123
|
+
Redis.any_instance.should_receive(:hset).with("websocket_rails.users", @connection.user_identifier, @user.as_json.to_json)
|
124
|
+
Synchronization.register_user(@connection)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#destroy_user" do
|
129
|
+
it "stores the serialized user object in redis" do
|
130
|
+
Redis.any_instance.should_receive(:hdel).with("websocket_rails.users", 'user_id')
|
131
|
+
Synchronization.destroy_user('user_id')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#find_user" do
|
136
|
+
it "retrieves the serialized user object in redis" do
|
137
|
+
Redis.any_instance.should_receive(:hget).with("websocket_rails.users", 'test')
|
138
|
+
Synchronization.find_user('test')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#all_users" do
|
143
|
+
it "retrieves the entire serialized users hash redis" do
|
144
|
+
Redis.any_instance.should_receive(:hgetall).with("websocket_rails.users")
|
145
|
+
Synchronization.all_users
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
120
149
|
end
|
121
150
|
end
|