actioncable 7.0.8.7 → 7.2.2.1

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -180
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +4 -4
  5. data/app/assets/javascripts/action_cable.js +30 -9
  6. data/app/assets/javascripts/actioncable.esm.js +30 -9
  7. data/app/assets/javascripts/actioncable.js +30 -9
  8. data/lib/action_cable/channel/base.rb +114 -90
  9. data/lib/action_cable/channel/broadcasting.rb +25 -16
  10. data/lib/action_cable/channel/callbacks.rb +39 -0
  11. data/lib/action_cable/channel/naming.rb +10 -7
  12. data/lib/action_cable/channel/periodic_timers.rb +7 -7
  13. data/lib/action_cable/channel/streams.rb +77 -62
  14. data/lib/action_cable/channel/test_case.rb +117 -86
  15. data/lib/action_cable/connection/authorization.rb +4 -1
  16. data/lib/action_cable/connection/base.rb +70 -42
  17. data/lib/action_cable/connection/callbacks.rb +57 -0
  18. data/lib/action_cable/connection/client_socket.rb +3 -1
  19. data/lib/action_cable/connection/identification.rb +9 -5
  20. data/lib/action_cable/connection/internal_channel.rb +7 -2
  21. data/lib/action_cable/connection/message_buffer.rb +4 -1
  22. data/lib/action_cable/connection/stream.rb +2 -2
  23. data/lib/action_cable/connection/stream_event_loop.rb +4 -4
  24. data/lib/action_cable/connection/subscriptions.rb +7 -2
  25. data/lib/action_cable/connection/tagged_logger_proxy.rb +12 -7
  26. data/lib/action_cable/connection/test_case.rb +67 -55
  27. data/lib/action_cable/connection/web_socket.rb +11 -7
  28. data/lib/action_cable/deprecator.rb +9 -0
  29. data/lib/action_cable/engine.rb +18 -8
  30. data/lib/action_cable/gem_version.rb +6 -4
  31. data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
  32. data/lib/action_cable/remote_connections.rb +25 -13
  33. data/lib/action_cable/server/base.rb +29 -14
  34. data/lib/action_cable/server/broadcasting.rb +24 -16
  35. data/lib/action_cable/server/configuration.rb +27 -14
  36. data/lib/action_cable/server/connections.rb +13 -5
  37. data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
  38. data/lib/action_cable/server/worker.rb +4 -3
  39. data/lib/action_cable/subscription_adapter/async.rb +1 -1
  40. data/lib/action_cable/subscription_adapter/base.rb +2 -0
  41. data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
  42. data/lib/action_cable/subscription_adapter/inline.rb +2 -0
  43. data/lib/action_cable/subscription_adapter/postgresql.rb +4 -3
  44. data/lib/action_cable/subscription_adapter/redis.rb +7 -7
  45. data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
  46. data/lib/action_cable/subscription_adapter/test.rb +6 -5
  47. data/lib/action_cable/test_case.rb +2 -0
  48. data/lib/action_cable/test_helper.rb +89 -59
  49. data/lib/action_cable/version.rb +3 -1
  50. data/lib/action_cable.rb +30 -12
  51. data/lib/rails/generators/channel/USAGE +14 -8
  52. data/lib/rails/generators/channel/channel_generator.rb +23 -7
  53. data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
  54. metadata +27 -15
  55. data/lib/action_cable/channel.rb +0 -17
  56. data/lib/action_cable/connection.rb +0 -22
  57. data/lib/action_cable/server.rb +0 -16
  58. data/lib/action_cable/subscription_adapter.rb +0 -12
  59. /data/lib/rails/generators/channel/templates/application_cable/{channel.rb → channel.rb.tt} +0 -0
  60. /data/lib/rails/generators/channel/templates/application_cable/{connection.rb → connection.rb.tt} +0 -0
@@ -1,65 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Channel
5
- # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
6
- # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
7
- # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.
8
- #
9
- # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
10
- # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
11
- # comments on a given page:
12
- #
13
- # class CommentsChannel < ApplicationCable::Channel
14
- # def follow(data)
15
- # stream_from "comments_for_#{data['recording_id']}"
16
- # end
7
+ # # Action Cable Channel Streams
8
+ #
9
+ # Streams allow channels to route broadcastings to the subscriber. A
10
+ # broadcasting is, as discussed elsewhere, a pubsub queue where any data placed
11
+ # into it is automatically sent to the clients that are connected at that time.
12
+ # It's purely an online queue, though. If you're not streaming a broadcasting at
13
+ # the very moment it sends out an update, you will not get that update, even if
14
+ # you connect after it has been sent.
15
+ #
16
+ # Most commonly, the streamed broadcast is sent straight to the subscriber on
17
+ # the client-side. The channel just acts as a connector between the two parties
18
+ # (the broadcaster and the channel subscriber). Here's an example of a channel
19
+ # that allows subscribers to get all new comments on a given page:
20
+ #
21
+ # class CommentsChannel < ApplicationCable::Channel
22
+ # def follow(data)
23
+ # stream_from "comments_for_#{data['recording_id']}"
24
+ # end
17
25
  #
18
- # def unfollow
19
- # stop_all_streams
26
+ # def unfollow
27
+ # stop_all_streams
28
+ # end
20
29
  # end
21
- # end
22
30
  #
23
- # Based on the above example, the subscribers of this channel will get whatever data is put into the,
24
- # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
31
+ # Based on the above example, the subscribers of this channel will get whatever
32
+ # data is put into the, let's say, `comments_for_45` broadcasting as soon as
33
+ # it's put there.
25
34
  #
26
35
  # An example broadcasting for this channel looks like so:
27
36
  #
28
- # ActionCable.server.broadcast "comments_for_45", { author: 'DHH', content: 'Rails is just swell' }
37
+ # ActionCable.server.broadcast "comments_for_45", { author: 'DHH', content: 'Rails is just swell' }
29
38
  #
30
- # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
31
- # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
39
+ # If you have a stream that is related to a model, then the broadcasting used
40
+ # can be generated from the model and channel. The following example would
41
+ # subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`.
32
42
  #
33
- # class CommentsChannel < ApplicationCable::Channel
34
- # def subscribed
35
- # post = Post.find(params[:id])
36
- # stream_for post
43
+ # class CommentsChannel < ApplicationCable::Channel
44
+ # def subscribed
45
+ # post = Post.find(params[:id])
46
+ # stream_for post
47
+ # end
37
48
  # end
38
- # end
39
49
  #
40
50
  # You can then broadcast to this channel using:
41
51
  #
42
- # CommentsChannel.broadcast_to(@post, @comment)
52
+ # CommentsChannel.broadcast_to(@post, @comment)
43
53
  #
44
- # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out.
45
- # The below example shows how you can use this to provide performance introspection in the process:
54
+ # If you don't just want to parlay the broadcast unfiltered to the subscriber,
55
+ # you can also supply a callback that lets you alter what is sent out. The below
56
+ # example shows how you can use this to provide performance introspection in the
57
+ # process:
46
58
  #
47
- # class ChatChannel < ApplicationCable::Channel
48
- # def subscribed
49
- # @room = Chat::Room[params[:room_number]]
59
+ # class ChatChannel < ApplicationCable::Channel
60
+ # def subscribed
61
+ # @room = Chat::Room[params[:room_number]]
50
62
  #
51
- # stream_for @room, coder: ActiveSupport::JSON do |message|
52
- # if message['originated_at'].present?
53
- # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
63
+ # stream_for @room, coder: ActiveSupport::JSON do |message|
64
+ # if message['originated_at'].present?
65
+ # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
54
66
  #
55
- # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
56
- # logger.info "Message took #{elapsed_time}s to arrive"
57
- # end
67
+ # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
68
+ # logger.info "Message took #{elapsed_time}s to arrive"
69
+ # end
58
70
  #
59
- # transmit message
71
+ # transmit message
72
+ # end
60
73
  # end
61
74
  # end
62
- # end
63
75
  #
64
76
  # You can stop streaming from all broadcasts by calling #stop_all_streams.
65
77
  module Streams
@@ -69,18 +81,20 @@ module ActionCable
69
81
  on_unsubscribe :stop_all_streams
70
82
  end
71
83
 
72
- # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
73
- # instead of the default of just transmitting the updates straight to the subscriber.
74
- # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
75
- # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
84
+ # Start streaming from the named `broadcasting` pubsub queue. Optionally, you
85
+ # can pass a `callback` that'll be used instead of the default of just
86
+ # transmitting the updates straight to the subscriber. Pass `coder:
87
+ # ActiveSupport::JSON` to decode messages as JSON before passing to the
88
+ # callback. Defaults to `coder: nil` which does no decoding, passes raw
89
+ # messages.
76
90
  def stream_from(broadcasting, callback = nil, coder: nil, &block)
77
91
  broadcasting = String(broadcasting)
78
92
 
79
93
  # Don't send the confirmation until pubsub#subscribe is successful
80
94
  defer_subscription_confirmation!
81
95
 
82
- # Build a stream handler by wrapping the user-provided callback with
83
- # a decoder or defaulting to a JSON-decoding retransmitter.
96
+ # Build a stream handler by wrapping the user-provided callback with a decoder
97
+ # or defaulting to a JSON-decoding retransmitter.
84
98
  handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
85
99
  streams[broadcasting] = handler
86
100
 
@@ -92,17 +106,18 @@ module ActionCable
92
106
  end
93
107
  end
94
108
 
95
- # Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
96
- # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
97
- # to the subscriber.
109
+ # Start streaming the pubsub queue for the `model` in this channel. Optionally,
110
+ # you can pass a `callback` that'll be used instead of the default of just
111
+ # transmitting the updates straight to the subscriber.
98
112
  #
99
- # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
100
- # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
113
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to
114
+ # the callback. Defaults to `coder: nil` which does no decoding, passes raw
115
+ # messages.
101
116
  def stream_for(model, callback = nil, coder: nil, &block)
102
117
  stream_from(broadcasting_for(model), callback || block, coder: coder)
103
118
  end
104
119
 
105
- # Unsubscribes streams from the named <tt>broadcasting</tt>.
120
+ # Unsubscribes streams from the named `broadcasting`.
106
121
  def stop_stream_from(broadcasting)
107
122
  callback = streams.delete(broadcasting)
108
123
  if callback
@@ -111,7 +126,7 @@ module ActionCable
111
126
  end
112
127
  end
113
128
 
114
- # Unsubscribes streams for the <tt>model</tt>.
129
+ # Unsubscribes streams for the `model`.
115
130
  def stop_stream_for(model)
116
131
  stop_stream_from(broadcasting_for(model))
117
132
  end
@@ -124,7 +139,7 @@ module ActionCable
124
139
  end.clear
125
140
  end
126
141
 
127
- # Calls stream_for with the given <tt>model</tt> if it's present to start streaming,
142
+ # Calls stream_for with the given `model` if it's present to start streaming,
128
143
  # otherwise rejects the subscription.
129
144
  def stream_or_reject_for(model)
130
145
  if model
@@ -141,8 +156,8 @@ module ActionCable
141
156
  @_streams ||= {}
142
157
  end
143
158
 
144
- # Always wrap the outermost handler to invoke the user handler on the
145
- # worker pool rather than blocking the event loop.
159
+ # Always wrap the outermost handler to invoke the user handler on the worker
160
+ # pool rather than blocking the event loop.
146
161
  def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
147
162
  handler = stream_handler(broadcasting, user_handler, coder: coder)
148
163
 
@@ -151,8 +166,8 @@ module ActionCable
151
166
  end
152
167
  end
153
168
 
154
- # May be overridden to add instrumentation, logging, specialized error
155
- # handling, or other forms of handler decoration.
169
+ # May be overridden to add instrumentation, logging, specialized error handling,
170
+ # or other forms of handler decoration.
156
171
  #
157
172
  # TODO: Tests demonstrating this.
158
173
  def stream_handler(broadcasting, user_handler, coder: nil)
@@ -163,14 +178,14 @@ module ActionCable
163
178
  end
164
179
  end
165
180
 
166
- # May be overridden to change the default stream handling behavior
167
- # which decodes JSON and transmits to the client.
181
+ # May be overridden to change the default stream handling behavior which decodes
182
+ # JSON and transmits to the client.
168
183
  #
169
184
  # TODO: Tests demonstrating this.
170
185
  #
171
- # TODO: Room for optimization. Update transmit API to be coder-aware
172
- # so we can no-op when pubsub and connection are both JSON-encoded.
173
- # Then we can skip decode+encode if we're just proxying messages.
186
+ # TODO: Room for optimization. Update transmit API to be coder-aware so we can
187
+ # no-op when pubsub and connection are both JSON-encoded. Then we can skip
188
+ # decode+encode if we're just proxying messages.
174
189
  def default_stream_handler(broadcasting, coder:)
175
190
  coder ||= ActiveSupport::JSON
176
191
  stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support"
4
6
  require "active_support/test_case"
5
7
  require "active_support/core_ext/hash/indifferent_access"
@@ -15,9 +17,10 @@ module ActionCable
15
17
  end
16
18
  end
17
19
 
18
- # Stub +stream_from+ to track streams for the channel.
19
- # Add public aliases for +subscription_confirmation_sent?+ and
20
- # +subscription_rejected?+.
20
+ # # Action Cable Channel Stub
21
+ #
22
+ # Stub `stream_from` to track streams for the channel. Add public aliases for
23
+ # `subscription_confirmation_sent?` and `subscription_rejected?`.
21
24
  module ChannelStub
22
25
  def confirmed?
23
26
  subscription_confirmation_sent?
@@ -45,9 +48,12 @@ module ActionCable
45
48
  end
46
49
 
47
50
  class ConnectionStub
48
- attr_reader :transmissions, :identifiers, :subscriptions, :logger
51
+ attr_reader :server, :transmissions, :identifiers, :subscriptions, :logger
52
+
53
+ delegate :pubsub, :config, to: :server
49
54
 
50
55
  def initialize(identifiers = {})
56
+ @server = ActionCable.server
51
57
  @transmissions = []
52
58
 
53
59
  identifiers.each do |identifier, val|
@@ -81,103 +87,106 @@ module ActionCable
81
87
 
82
88
  # Superclass for Action Cable channel functional tests.
83
89
  #
84
- # == Basic example
90
+ # ## Basic example
85
91
  #
86
92
  # Functional tests are written as follows:
87
- # 1. First, one uses the +subscribe+ method to simulate subscription creation.
88
- # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
89
- # transmitted messages, subscribed streams, etc.
93
+ # 1. First, one uses the `subscribe` method to simulate subscription creation.
94
+ # 2. Then, one asserts whether the current state is as expected. "State" can be
95
+ # anything: transmitted messages, subscribed streams, etc.
96
+ #
90
97
  #
91
98
  # For example:
92
99
  #
93
- # class ChatChannelTest < ActionCable::Channel::TestCase
94
- # def test_subscribed_with_room_number
95
- # # Simulate a subscription creation
96
- # subscribe room_number: 1
100
+ # class ChatChannelTest < ActionCable::Channel::TestCase
101
+ # def test_subscribed_with_room_number
102
+ # # Simulate a subscription creation
103
+ # subscribe room_number: 1
97
104
  #
98
- # # Asserts that the subscription was successfully created
99
- # assert subscription.confirmed?
105
+ # # Asserts that the subscription was successfully created
106
+ # assert subscription.confirmed?
100
107
  #
101
- # # Asserts that the channel subscribes connection to a stream
102
- # assert_has_stream "chat_1"
108
+ # # Asserts that the channel subscribes connection to a stream
109
+ # assert_has_stream "chat_1"
103
110
  #
104
- # # Asserts that the channel subscribes connection to a specific
105
- # # stream created for a model
106
- # assert_has_stream_for Room.find(1)
107
- # end
111
+ # # Asserts that the channel subscribes connection to a specific
112
+ # # stream created for a model
113
+ # assert_has_stream_for Room.find(1)
114
+ # end
108
115
  #
109
- # def test_does_not_stream_with_incorrect_room_number
110
- # subscribe room_number: -1
116
+ # def test_does_not_stream_with_incorrect_room_number
117
+ # subscribe room_number: -1
111
118
  #
112
- # # Asserts that not streams was started
113
- # assert_no_streams
114
- # end
119
+ # # Asserts that not streams was started
120
+ # assert_no_streams
121
+ # end
115
122
  #
116
- # def test_does_not_subscribe_without_room_number
117
- # subscribe
123
+ # def test_does_not_subscribe_without_room_number
124
+ # subscribe
118
125
  #
119
- # # Asserts that the subscription was rejected
120
- # assert subscription.rejected?
126
+ # # Asserts that the subscription was rejected
127
+ # assert subscription.rejected?
128
+ # end
121
129
  # end
122
- # end
123
130
  #
124
131
  # You can also perform actions:
125
- # def test_perform_speak
126
- # subscribe room_number: 1
132
+ # def test_perform_speak
133
+ # subscribe room_number: 1
134
+ #
135
+ # perform :speak, message: "Hello, Rails!"
127
136
  #
128
- # perform :speak, message: "Hello, Rails!"
137
+ # assert_equal "Hello, Rails!", transmissions.last["text"]
138
+ # end
139
+ #
140
+ # ## Special methods
129
141
  #
130
- # assert_equal "Hello, Rails!", transmissions.last["text"]
131
- # end
142
+ # ActionCable::Channel::TestCase will also automatically provide the following
143
+ # instance methods for use in the tests:
132
144
  #
133
- # == Special methods
145
+ # connection
146
+ # : An ActionCable::Channel::ConnectionStub, representing the current HTTP
147
+ # connection.
134
148
  #
135
- # ActionCable::Channel::TestCase will also automatically provide the following instance
136
- # methods for use in the tests:
149
+ # subscription
150
+ # : An instance of the current channel, created when you call `subscribe`.
137
151
  #
138
- # <b>connection</b>::
139
- # An ActionCable::Channel::ConnectionStub, representing the current HTTP connection.
140
- # <b>subscription</b>::
141
- # An instance of the current channel, created when you call +subscribe+.
142
- # <b>transmissions</b>::
143
- # A list of all messages that have been transmitted into the channel.
152
+ # transmissions
153
+ # : A list of all messages that have been transmitted into the channel.
144
154
  #
145
155
  #
146
- # == Channel is automatically inferred
156
+ # ## Channel is automatically inferred
147
157
  #
148
158
  # ActionCable::Channel::TestCase will automatically infer the channel under test
149
159
  # from the test class name. If the channel cannot be inferred from the test
150
- # class name, you can explicitly set it with +tests+.
151
- #
152
- # class SpecialEdgeCaseChannelTest < ActionCable::Channel::TestCase
153
- # tests SpecialChannel
154
- # end
160
+ # class name, you can explicitly set it with `tests`.
155
161
  #
156
- # == Specifying connection identifiers
162
+ # class SpecialEdgeCaseChannelTest < ActionCable::Channel::TestCase
163
+ # tests SpecialChannel
164
+ # end
157
165
  #
158
- # You need to set up your connection manually to provide values for the identifiers.
159
- # To do this just use:
166
+ # ## Specifying connection identifiers
160
167
  #
161
- # stub_connection(user: users(:john))
168
+ # You need to set up your connection manually to provide values for the
169
+ # identifiers. To do this just use:
162
170
  #
163
- # == Testing broadcasting
171
+ # stub_connection(user: users(:john))
164
172
  #
165
- # ActionCable::Channel::TestCase enhances ActionCable::TestHelper assertions (e.g.
166
- # +assert_broadcasts+) to handle broadcasting to models:
173
+ # ## Testing broadcasting
167
174
  #
175
+ # ActionCable::Channel::TestCase enhances ActionCable::TestHelper assertions
176
+ # (e.g. `assert_broadcasts`) to handle broadcasting to models:
168
177
  #
169
- # # in your channel
170
- # def speak(data)
171
- # broadcast_to room, text: data["message"]
172
- # end
178
+ # # in your channel
179
+ # def speak(data)
180
+ # broadcast_to room, text: data["message"]
181
+ # end
173
182
  #
174
- # def test_speak
175
- # subscribe room_id: rooms(:chat).id
183
+ # def test_speak
184
+ # subscribe room_id: rooms(:chat).id
176
185
  #
177
- # assert_broadcast_on(rooms(:chat), text: "Hello, Rails!") do
178
- # perform :speak, message: "Hello, Rails!"
179
- # end
180
- # end
186
+ # assert_broadcast_on(rooms(:chat), text: "Hello, Rails!") do
187
+ # perform :speak, message: "Hello, Rails!"
188
+ # end
189
+ # end
181
190
  class TestCase < ActiveSupport::TestCase
182
191
  module Behavior
183
192
  extend ActiveSupport::Concern
@@ -226,16 +235,17 @@ module ActionCable
226
235
 
227
236
  # Set up test connection with the specified identifiers:
228
237
  #
229
- # class ApplicationCable < ActionCable::Connection::Base
230
- # identified_by :user, :token
231
- # end
238
+ # class ApplicationCable < ActionCable::Connection::Base
239
+ # identified_by :user, :token
240
+ # end
232
241
  #
233
- # stub_connection(user: users[:john], token: 'my-secret-token')
242
+ # stub_connection(user: users[:john], token: 'my-secret-token')
234
243
  def stub_connection(identifiers = {})
235
244
  @connection = ConnectionStub.new(identifiers)
236
245
  end
237
246
 
238
- # Subscribe to the channel under test. Optionally pass subscription parameters as a Hash.
247
+ # Subscribe to the channel under test. Optionally pass subscription parameters
248
+ # as a Hash.
239
249
  def subscribe(params = {})
240
250
  @connection ||= stub_connection
241
251
  @subscription = self.class.channel_class.new(connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
@@ -264,8 +274,7 @@ module ActionCable
264
274
  connection.transmissions.filter_map { |data| data["message"] }
265
275
  end
266
276
 
267
- # Enhance TestHelper assertions to handle non-String
268
- # broadcastings
277
+ # Enhance TestHelper assertions to handle non-String broadcastings
269
278
  def assert_broadcasts(stream_or_object, *args)
270
279
  super(broadcasting_for(stream_or_object), *args)
271
280
  end
@@ -276,10 +285,10 @@ module ActionCable
276
285
 
277
286
  # Asserts that no streams have been started.
278
287
  #
279
- # def test_assert_no_started_stream
280
- # subscribe
281
- # assert_no_streams
282
- # end
288
+ # def test_assert_no_started_stream
289
+ # subscribe
290
+ # assert_no_streams
291
+ # end
283
292
  #
284
293
  def assert_no_streams
285
294
  assert subscription.streams.empty?, "No streams started was expected, but #{subscription.streams.count} found"
@@ -287,10 +296,10 @@ module ActionCable
287
296
 
288
297
  # Asserts that the specified stream has been started.
289
298
  #
290
- # def test_assert_started_stream
291
- # subscribe
292
- # assert_has_stream 'messages'
293
- # end
299
+ # def test_assert_started_stream
300
+ # subscribe
301
+ # assert_has_stream 'messages'
302
+ # end
294
303
  #
295
304
  def assert_has_stream(stream)
296
305
  assert subscription.streams.include?(stream), "Stream #{stream} has not been started"
@@ -298,15 +307,37 @@ module ActionCable
298
307
 
299
308
  # Asserts that the specified stream for a model has started.
300
309
  #
301
- # def test_assert_started_stream_for
302
- # subscribe id: 42
303
- # assert_has_stream_for User.find(42)
304
- # end
310
+ # def test_assert_started_stream_for
311
+ # subscribe id: 42
312
+ # assert_has_stream_for User.find(42)
313
+ # end
305
314
  #
306
315
  def assert_has_stream_for(object)
307
316
  assert_has_stream(broadcasting_for(object))
308
317
  end
309
318
 
319
+ # Asserts that the specified stream has not been started.
320
+ #
321
+ # def test_assert_no_started_stream
322
+ # subscribe
323
+ # assert_has_no_stream 'messages'
324
+ # end
325
+ #
326
+ def assert_has_no_stream(stream)
327
+ assert subscription.streams.exclude?(stream), "Stream #{stream} has been started"
328
+ end
329
+
330
+ # Asserts that the specified stream for a model has not started.
331
+ #
332
+ # def test_assert_no_started_stream_for
333
+ # subscribe id: 41
334
+ # assert_has_no_stream_for User.find(42)
335
+ # end
336
+ #
337
+ def assert_has_no_stream_for(object)
338
+ assert_has_no_stream(broadcasting_for(object))
339
+ end
340
+
310
341
  private
311
342
  def check_subscribed!
312
343
  raise "Must be subscribed!" if subscription.nil? || subscription.rejected?
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Connection
5
7
  module Authorization
6
8
  class UnauthorizedError < StandardError; end
7
9
 
8
- # Closes the WebSocket connection if it is open and returns a 404 "File not Found" response.
10
+ # Closes the WebSocket connection if it is open and returns an "unauthorized"
11
+ # reason.
9
12
  def reject_unauthorized_connection
10
13
  logger.error "An unauthorized connection attempt was rejected"
11
14
  raise UnauthorizedError