cable_room 0.5.3 → 0.5.5
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 +4 -4
- data/lib/cable_room/room/input_handling.rb +1 -1
- data/lib/cable_room/room/port_management.rb +9 -2
- data/lib/cable_room/version.rb +1 -1
- data/spec/cable_room/e2e_room_spec.rb +481 -0
- data/spec/internal/config/cable.yml +1 -1
- data/spec/internal/log/test.log +5225 -3613
- data/spec/spec_helper.rb +5 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3adda4804c009f6778b8c629dd3ee94393f8f9329e3472fd155795f4cf6f0ad
|
|
4
|
+
data.tar.gz: 586c39696ce02f18fe83e975e256023151d4e0af8ad6ae9e9a5bb5fdcc6ca829
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36485ec4fce091883958e09767761cb27cc83c3888c86220c182742a9c319bf9ac21a16f4e0878691bed0071537e7050d01351f7ff42d1401248e21df8788514
|
|
7
|
+
data.tar.gz: aae807731576843f3bcaf0f5f2b7b8da19229f1aac178c687b5191fc7b0852870e4704216ec37dcf8b7323232ff1c432ad3532cb2e19a836f028668106e9801b
|
|
@@ -57,7 +57,7 @@ module CableRoom
|
|
|
57
57
|
ports[self.class::ROOM_IN_CHANNEL].stream do |message|
|
|
58
58
|
begin
|
|
59
59
|
Room.current_message = message
|
|
60
|
-
ActiveSupport::Notifications.instrument("message_received.cable_room", { room: self, message:
|
|
60
|
+
ActiveSupport::Notifications.instrument("message_received.cable_room", { room: self, message: message }) do
|
|
61
61
|
run_callbacks :receive_message do
|
|
62
62
|
handle_received_message(message)
|
|
63
63
|
end
|
|
@@ -104,7 +104,7 @@ module CableRoom
|
|
|
104
104
|
def handle_received_message(message)
|
|
105
105
|
case message['type']
|
|
106
106
|
when 'port_connected'
|
|
107
|
-
mo = @_port_clients[@current_message_origin] ||= PortClient.new(@current_message_origin)
|
|
107
|
+
mo = @_port_clients[@current_message_origin] ||= PortClient.new(self, @current_message_origin)
|
|
108
108
|
|
|
109
109
|
mo.touch_activity!
|
|
110
110
|
mo.tag!(message['tags'])
|
|
@@ -155,7 +155,8 @@ module CableRoom
|
|
|
155
155
|
class PortClient
|
|
156
156
|
attr_reader :token
|
|
157
157
|
|
|
158
|
-
def initialize(token)
|
|
158
|
+
def initialize(room, token)
|
|
159
|
+
@room = room
|
|
159
160
|
@token = token
|
|
160
161
|
@metadata = HashWithIndifferentAccess.new
|
|
161
162
|
@metadata[:tags] = Set.new
|
|
@@ -164,6 +165,12 @@ module CableRoom
|
|
|
164
165
|
|
|
165
166
|
delegate :[], :[]=, to: :@metadata
|
|
166
167
|
|
|
168
|
+
def <<(data)
|
|
169
|
+
@room.with_port_scope(client_port: @token) do
|
|
170
|
+
@room.broadcast(data)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
167
174
|
def merge!(value)
|
|
168
175
|
@metadata.merge!(value || {})
|
|
169
176
|
end
|
data/lib/cable_room/version.rb
CHANGED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
# E2E Integration Tests for CableRoom
|
|
4
|
+
#
|
|
5
|
+
# These tests use the async ActionCable adapter (configured in spec_helper.rb)
|
|
6
|
+
# instead of the test adapter to enable real message delivery during tests.
|
|
7
|
+
# This allows us to test actual end-to-end communication flow between rooms
|
|
8
|
+
# and room members, rather than just verifying that broadcasts were sent.
|
|
9
|
+
|
|
10
|
+
RSpec.describe "CableRoom E2E Integration", type: :channel do
|
|
11
|
+
# Setup a test room class for the e2e tests
|
|
12
|
+
class E2ETestRoom < CableRoom::Room::Base
|
|
13
|
+
cattr_accessor :events, default: []
|
|
14
|
+
cattr_accessor :latest_instance
|
|
15
|
+
|
|
16
|
+
after_startup do
|
|
17
|
+
self.class.latest_instance = self
|
|
18
|
+
self.class.events << { type: :room_started, key: key, timestamp: Time.current }
|
|
19
|
+
self << { type: "room_ready", message: "Room is now active" }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
on_user_joined do
|
|
23
|
+
user_id = message_origin&.user
|
|
24
|
+
self.class.events << { type: :user_joined, user: user_id, key: key, timestamp: Time.current }
|
|
25
|
+
broadcast({ type: "user_joined_broadcast", user: user_id })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
on_user_left do
|
|
29
|
+
user_id = message_origin&.user
|
|
30
|
+
self.class.events << { type: :user_left, user: user_id, key: key, timestamp: Time.current }
|
|
31
|
+
broadcast({ type: "user_left_broadcast", user: user_id })
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
before_shutdown do
|
|
35
|
+
self.class.events << { type: :room_shutting_down, key: key, timestamp: Time.current }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
after_shutdown do
|
|
39
|
+
self.class.events << { type: :room_shutdown, key: key, timestamp: Time.current }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Custom message handlers
|
|
43
|
+
def on_custom_message(msg)
|
|
44
|
+
self.class.events << { type: :custom_message_received, data: msg['data'], key: key }
|
|
45
|
+
broadcast({ type: "custom_response", original: msg['data'], processed: true })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def on_request_info(msg)
|
|
49
|
+
message_origin << { type: "room_info", key: key, users: connected_users.count }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Setup a test channel for users to join rooms
|
|
54
|
+
class E2ETestChannel < ApplicationCable::Channel
|
|
55
|
+
include CableRoom::RoomMember
|
|
56
|
+
|
|
57
|
+
attr_reader :room, :received_messages
|
|
58
|
+
|
|
59
|
+
def initialize(*)
|
|
60
|
+
super
|
|
61
|
+
@received_messages = []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def current_user
|
|
65
|
+
params[:user_id]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def subscribed
|
|
69
|
+
key = params[:room_key] || SecureRandom.hex(8)
|
|
70
|
+
|
|
71
|
+
@room = join_room(
|
|
72
|
+
E2ETestRoom,
|
|
73
|
+
key,
|
|
74
|
+
create: params[:create] || false,
|
|
75
|
+
tags: params[:tags] || [],
|
|
76
|
+
as: current_user,
|
|
77
|
+
on_joined: ->(membership) {
|
|
78
|
+
@received_messages << { type: :on_joined_callback, timestamp: Time.current }
|
|
79
|
+
},
|
|
80
|
+
on_room_opened: ->(membership) {
|
|
81
|
+
@received_messages << { type: :on_room_opened_callback, timestamp: Time.current }
|
|
82
|
+
},
|
|
83
|
+
on_room_closed: ->(membership) {
|
|
84
|
+
@received_messages << { type: :on_room_closed_callback, timestamp: Time.current }
|
|
85
|
+
},
|
|
86
|
+
on_message: ->(message) {
|
|
87
|
+
@received_messages << { type: :on_message_callback, message: message, timestamp: Time.current }
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def unsubscribed
|
|
93
|
+
@room&.leave!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def send_custom_message(data)
|
|
97
|
+
@room << { type: 'custom_message', data: data }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def request_room_info
|
|
101
|
+
@room << { type: 'request_info' }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class ConnStub < ::ActionCable::Channel::ConnectionStub
|
|
106
|
+
delegate :worker_pool, :logger, to: :server
|
|
107
|
+
def initialize(...)
|
|
108
|
+
super
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module ChannelStub
|
|
113
|
+
def confirmed?
|
|
114
|
+
subscription_confirmation_sent?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def rejected?
|
|
118
|
+
subscription_rejected?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def streams; super; end
|
|
122
|
+
|
|
123
|
+
# def stream_from(broadcasting, *)
|
|
124
|
+
# super
|
|
125
|
+
# # streams << broadcasting
|
|
126
|
+
# end
|
|
127
|
+
|
|
128
|
+
# def stream_from(broadcasting, callback = nil, coder: nil, &block)
|
|
129
|
+
# broadcasting = String(broadcasting)
|
|
130
|
+
|
|
131
|
+
# # Don't send the confirmation until pubsub#subscribe is successful
|
|
132
|
+
# defer_subscription_confirmation!
|
|
133
|
+
|
|
134
|
+
# # Build a stream handler by wrapping the user-provided callback with a decoder
|
|
135
|
+
# # or defaulting to a JSON-decoding retransmitter.
|
|
136
|
+
# handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
|
|
137
|
+
# streams << broadcasting
|
|
138
|
+
|
|
139
|
+
# connection.server.event_loop.post do
|
|
140
|
+
# pubsub.subscribe(broadcasting, handler, lambda do
|
|
141
|
+
# ensure_confirmation_sent
|
|
142
|
+
# logger.info "#{self.class.name} is streaming from #{broadcasting}"
|
|
143
|
+
# end)
|
|
144
|
+
# end
|
|
145
|
+
# end
|
|
146
|
+
|
|
147
|
+
# def stop_all_streams
|
|
148
|
+
# @_streams = []
|
|
149
|
+
# end
|
|
150
|
+
|
|
151
|
+
# def streams
|
|
152
|
+
# @_streams ||= []
|
|
153
|
+
# end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def subscribe(params={})
|
|
157
|
+
@connection ||= stub_connection
|
|
158
|
+
@subscription = self.class.channel_class.new(connection, "test_stub", params.with_indifferent_access)
|
|
159
|
+
@subscription.singleton_class.include(ChannelStub)
|
|
160
|
+
@subscription.subscribe_to_channel
|
|
161
|
+
@subscription
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
before(:each) do
|
|
165
|
+
@connection = ConnStub.new
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe E2ETestChannel, type: :channel do
|
|
169
|
+
let(:room_key) { "e2e-test-#{SecureRandom.hex(4)}" }
|
|
170
|
+
|
|
171
|
+
before(:each) do
|
|
172
|
+
E2ETestRoom.events.clear
|
|
173
|
+
E2ETestRoom.latest_instance = nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe "Room Creation and Lifecycle" do
|
|
177
|
+
it "creates a room when a user connects with create: true" do
|
|
178
|
+
expect {
|
|
179
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
180
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(1)
|
|
181
|
+
|
|
182
|
+
expect(subscription).to be_confirmed
|
|
183
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_started, key: room_key))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "does not create a room when create: false" do
|
|
187
|
+
expect {
|
|
188
|
+
subscribe(room_key: room_key, create: false, user_id: "user1")
|
|
189
|
+
}.not_to change { CableRoom::ChannelTracker.instance.room_channels.count }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "properly shuts down a room when sent KILL message" do
|
|
193
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
194
|
+
|
|
195
|
+
initial_count = CableRoom::ChannelTracker.instance.room_channels.count
|
|
196
|
+
|
|
197
|
+
E2ETestRoom.send_message(room_key, "KILL", port: :to_room)
|
|
198
|
+
sleep 0.2 # Give time for shutdown to complete
|
|
199
|
+
|
|
200
|
+
expect(CableRoom::ChannelTracker.instance.room_channels.count).to eq(initial_count - 1)
|
|
201
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_shutdown, key: room_key))
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe "Port Connections" do
|
|
206
|
+
it "establishes proper port connections for room communication" do
|
|
207
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
208
|
+
|
|
209
|
+
# Verify the expected streams are opened
|
|
210
|
+
expect(subscription.streams.keys).to include(/E2ETestRoom:.*:from_room/)
|
|
211
|
+
expect(subscription.streams.keys).to include(/E2ETestRoom:.*:[0-9a-f]{32}/) # Token-based port
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "creates separate port streams for different users" do
|
|
215
|
+
# First user subscribes
|
|
216
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
217
|
+
first_subscription = subscription
|
|
218
|
+
|
|
219
|
+
# Second user subscribes to same room
|
|
220
|
+
subscribe(room_key: room_key, create: false, user_id: "user2")
|
|
221
|
+
second_subscription = subscription
|
|
222
|
+
|
|
223
|
+
# Should have the shared broadcast stream
|
|
224
|
+
expect(first_subscription.streams.keys).to include(/E2ETestRoom:.*:from_room/)
|
|
225
|
+
expect(second_subscription.streams.keys).to include(/E2ETestRoom:.*:from_room/)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "supports tagged ports for selective broadcasting" do
|
|
229
|
+
subscribe(room_key: room_key, create: true, user_id: "user1", tags: ["admin", "moderator"])
|
|
230
|
+
|
|
231
|
+
# Give time for connection to establish
|
|
232
|
+
sleep 0.1
|
|
233
|
+
|
|
234
|
+
# User should be listening to tagged ports
|
|
235
|
+
room = E2ETestRoom.latest_instance
|
|
236
|
+
expect(room).not_to be_nil
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
describe "User Joining and Leaving" do
|
|
241
|
+
it "properly handles a user joining the room" do
|
|
242
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
243
|
+
|
|
244
|
+
# Give time for join callbacks to fire
|
|
245
|
+
sleep 0.1
|
|
246
|
+
|
|
247
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_joined, user: "user1"))
|
|
248
|
+
expect(subscription.received_messages).to include(hash_including(type: :on_joined_callback))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "handles multiple users joining the same room" do
|
|
252
|
+
# First user joins and creates room
|
|
253
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
254
|
+
sleep 0.1
|
|
255
|
+
|
|
256
|
+
first_user_count = E2ETestRoom.events.count { |e| e[:type] == :user_joined }
|
|
257
|
+
|
|
258
|
+
# Second user joins existing room
|
|
259
|
+
subscribe(room_key: room_key, create: false, user_id: "user2")
|
|
260
|
+
sleep 0.1
|
|
261
|
+
|
|
262
|
+
# Should have two user_joined events
|
|
263
|
+
expect(E2ETestRoom.events.count { |e| e[:type] == :user_joined }).to eq(first_user_count + 1)
|
|
264
|
+
|
|
265
|
+
# Verify both users are tracked
|
|
266
|
+
expect(E2ETestRoom.events.map { |e| e[:user] if e[:type] == :user_joined }.compact).to include("user1", "user2")
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it "properly handles a user leaving the room" do
|
|
270
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
271
|
+
sleep 0.1
|
|
272
|
+
|
|
273
|
+
# User leaves
|
|
274
|
+
unsubscribe
|
|
275
|
+
sleep 0.1
|
|
276
|
+
|
|
277
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_left, user: "user1"))
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "handles same user connecting from multiple ports" do
|
|
281
|
+
# Same user subscribes twice (e.g., multiple browser tabs)
|
|
282
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
283
|
+
first_subscription = subscription
|
|
284
|
+
sleep 0.1
|
|
285
|
+
|
|
286
|
+
initial_join_count = E2ETestRoom.events.count { |e| e[:type] == :user_joined }
|
|
287
|
+
|
|
288
|
+
subscribe(room_key: room_key, create: false, user_id: "user1")
|
|
289
|
+
sleep 0.1
|
|
290
|
+
|
|
291
|
+
# Should only trigger one user_joined event (same user)
|
|
292
|
+
expect(E2ETestRoom.events.count { |e| e[:type] == :user_joined }).to eq(initial_join_count)
|
|
293
|
+
|
|
294
|
+
# Close one connection
|
|
295
|
+
first_subscription.unsubscribe_from_channel
|
|
296
|
+
sleep 0.1
|
|
297
|
+
|
|
298
|
+
# User should not have left yet (still has one connection)
|
|
299
|
+
final_left_count = E2ETestRoom.events.count { |e| e[:type] == :user_left && e[:user] == "user1" }
|
|
300
|
+
expect(final_left_count).to eq(0)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
describe "Message Broadcasting and Communication" do
|
|
305
|
+
it "broadcasts messages to all connected users" do
|
|
306
|
+
# Two users join
|
|
307
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
308
|
+
user1 = subscription
|
|
309
|
+
sleep 0.1
|
|
310
|
+
|
|
311
|
+
subscribe(room_key: room_key, create: false, user_id: "user2")
|
|
312
|
+
user2 = subscription
|
|
313
|
+
sleep 0.1
|
|
314
|
+
|
|
315
|
+
# User 1 sends a custom message
|
|
316
|
+
user1.send_custom_message({ text: "Hello everyone!" })
|
|
317
|
+
sleep 0.1
|
|
318
|
+
|
|
319
|
+
# Both users should receive the broadcast response
|
|
320
|
+
expect(user1.received_messages).to include(
|
|
321
|
+
hash_including(
|
|
322
|
+
type: :on_message_callback,
|
|
323
|
+
message: hash_including("type" => "custom_response", "processed" => true)
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
expect(user2.received_messages).to include(
|
|
327
|
+
hash_including(
|
|
328
|
+
type: :on_message_callback,
|
|
329
|
+
message: hash_including("type" => "custom_response", "processed" => true)
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Room should have recorded the custom message
|
|
334
|
+
expect(E2ETestRoom.events).to include(
|
|
335
|
+
hash_including(type: :custom_message_received, data: { "text" => "Hello everyone!" })
|
|
336
|
+
)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
it "supports direct messaging to specific users via their port" do
|
|
340
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
341
|
+
user1 = subscription
|
|
342
|
+
sleep 0.1
|
|
343
|
+
|
|
344
|
+
subscribe(room_key: room_key, create: false, user_id: "user2")
|
|
345
|
+
user2 = subscription
|
|
346
|
+
sleep 0.1
|
|
347
|
+
|
|
348
|
+
# User 2 requests room info (should get private response)
|
|
349
|
+
user2.request_room_info
|
|
350
|
+
sleep 0.1
|
|
351
|
+
|
|
352
|
+
# User 2 should receive room info
|
|
353
|
+
expect(user2.received_messages).to include(
|
|
354
|
+
hash_including(
|
|
355
|
+
type: :on_message_callback,
|
|
356
|
+
message: hash_including("type" => "room_info")
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
describe "Complete E2E Flow" do
|
|
363
|
+
it "handles full lifecycle: create, multiple users join/leave, messages, shutdown" do
|
|
364
|
+
# Step 1: First user creates room
|
|
365
|
+
subscribe(room_key: room_key, create: true, user_id: "alice")
|
|
366
|
+
alice = subscription
|
|
367
|
+
sleep 0.1
|
|
368
|
+
|
|
369
|
+
expect(CableRoom::ChannelTracker.instance.room_channels.count).to be > 0
|
|
370
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_started))
|
|
371
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_joined, user: "alice"))
|
|
372
|
+
|
|
373
|
+
# Step 2: Second user joins
|
|
374
|
+
subscribe(room_key: room_key, create: false, user_id: "bob")
|
|
375
|
+
bob = subscription
|
|
376
|
+
sleep 0.1
|
|
377
|
+
|
|
378
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_joined, user: "bob"))
|
|
379
|
+
|
|
380
|
+
# Step 3: Third user joins
|
|
381
|
+
subscribe(room_key: room_key, create: false, user_id: "charlie")
|
|
382
|
+
charlie = subscription
|
|
383
|
+
sleep 0.1
|
|
384
|
+
|
|
385
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_joined, user: "charlie"))
|
|
386
|
+
|
|
387
|
+
# Step 4: Users exchange messages
|
|
388
|
+
alice.send_custom_message({ from: "alice", text: "Hello room!" })
|
|
389
|
+
sleep 0.1
|
|
390
|
+
|
|
391
|
+
bob.send_custom_message({ from: "bob", text: "Hi alice!" })
|
|
392
|
+
sleep 0.1
|
|
393
|
+
|
|
394
|
+
# All users should have received broadcasts
|
|
395
|
+
expect(alice.received_messages.count { |m| m[:type] == :on_message_callback && m[:message]["type"] == "custom_response" }).to eq(2)
|
|
396
|
+
expect(bob.received_messages.count { |m| m[:type] == :on_message_callback && m[:message]["type"] == "custom_response" }).to eq(2)
|
|
397
|
+
expect(charlie.received_messages.count { |m| m[:type] == :on_message_callback && m[:message]["type"] == "custom_response" }).to eq(2)
|
|
398
|
+
|
|
399
|
+
# Step 5: One user leaves
|
|
400
|
+
bob.unsubscribe_from_channel
|
|
401
|
+
sleep 0.1
|
|
402
|
+
|
|
403
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :user_left, user: "bob"))
|
|
404
|
+
|
|
405
|
+
# Step 6: Another message is sent (bob shouldn't receive it)
|
|
406
|
+
bob_message_count_before = bob.received_messages.count
|
|
407
|
+
alice.send_custom_message({ from: "alice", text: "Bob left!" })
|
|
408
|
+
sleep 0.1
|
|
409
|
+
|
|
410
|
+
expect(bob.received_messages.count).to eq(bob_message_count_before) # No new messages for bob
|
|
411
|
+
expect(charlie.received_messages.count { |m| m[:type] == :on_message_callback && m[:message]["type"] == "custom_response" }).to eq(3)
|
|
412
|
+
|
|
413
|
+
# Step 7: Shutdown the room
|
|
414
|
+
room_count_before = CableRoom::ChannelTracker.instance.room_channels.count
|
|
415
|
+
E2ETestRoom.send_message(room_key, "KILL", port: :to_room)
|
|
416
|
+
sleep 0.2
|
|
417
|
+
|
|
418
|
+
# Room should be shut down
|
|
419
|
+
expect(CableRoom::ChannelTracker.instance.room_channels.count).to eq(room_count_before - 1)
|
|
420
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_shutting_down))
|
|
421
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_shutdown))
|
|
422
|
+
|
|
423
|
+
# Users should receive room_closed notifications
|
|
424
|
+
expect(alice.received_messages).to include(hash_including(type: :on_room_closed_callback))
|
|
425
|
+
expect(charlie.received_messages).to include(hash_including(type: :on_room_closed_callback))
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
describe "Room Reaping and Idle Timeout" do
|
|
430
|
+
it "automatically shuts down idle rooms" do
|
|
431
|
+
subscribe(room_key: room_key, create: true, user_id: "user1")
|
|
432
|
+
room = E2ETestRoom.latest_instance
|
|
433
|
+
vchan = room.instance_variable_get(:@cable_channel)
|
|
434
|
+
|
|
435
|
+
unsubscribe
|
|
436
|
+
sleep 0.1
|
|
437
|
+
|
|
438
|
+
# Room should still exist (hasn't timed out yet)
|
|
439
|
+
expect(CableRoom::ChannelTracker.instance.room_channels).to include(vchan)
|
|
440
|
+
|
|
441
|
+
# Simulate watchdog timeout
|
|
442
|
+
vchan.instance_variable_set(:@last_watchdog_ping_at, 2.hours.ago)
|
|
443
|
+
|
|
444
|
+
room_count_before = CableRoom::ChannelTracker.instance.room_channels.count
|
|
445
|
+
vchan.send(:check_room_watchdog)
|
|
446
|
+
sleep 0.2
|
|
447
|
+
|
|
448
|
+
# Room should be reaped
|
|
449
|
+
expect(CableRoom::ChannelTracker.instance.room_channels.count).to eq(room_count_before - 1)
|
|
450
|
+
expect(E2ETestRoom.events).to include(hash_including(type: :room_shutdown))
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
describe "Concurrent Room Operations" do
|
|
455
|
+
it "handles multiple rooms simultaneously" do
|
|
456
|
+
room1_key = "concurrent-room-1-#{SecureRandom.hex(4)}"
|
|
457
|
+
room2_key = "concurrent-room-2-#{SecureRandom.hex(4)}"
|
|
458
|
+
|
|
459
|
+
# Create two separate rooms
|
|
460
|
+
subscribe(room_key: room1_key, create: true, user_id: "user1")
|
|
461
|
+
room1_sub = subscription
|
|
462
|
+
sleep 0.1
|
|
463
|
+
|
|
464
|
+
subscribe(room_key: room2_key, create: true, user_id: "user2")
|
|
465
|
+
room2_sub = subscription
|
|
466
|
+
sleep 0.1
|
|
467
|
+
|
|
468
|
+
# Both rooms should exist
|
|
469
|
+
expect(E2ETestRoom.events.count { |e| e[:type] == :room_started }).to be >= 2
|
|
470
|
+
|
|
471
|
+
# Send messages in both rooms
|
|
472
|
+
room1_sub.send_custom_message({ room: "room1" })
|
|
473
|
+
room2_sub.send_custom_message({ room: "room2" })
|
|
474
|
+
sleep 0.1
|
|
475
|
+
|
|
476
|
+
# Each room should have processed its own message
|
|
477
|
+
expect(E2ETestRoom.events.count { |e| e[:type] == :custom_message_received }).to eq(2)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
test:
|
|
2
|
-
adapter:
|
|
2
|
+
adapter: async
|