action-cable-testing 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e9a6464f3ec1cc6ee48f51d97f0a8e0bebd34c3fee8f9523f01a6e0b6f19382
4
- data.tar.gz: 3db543599b78e0944fa89679d47ab02275c09714b59b134430662a74163cddd8
3
+ metadata.gz: e9193234672a859c68a8e8d2a68dab7a7ed89b8038f77f3ffd91cd9fc77f4fb8
4
+ data.tar.gz: e314c6633804c930a8963f9c8b0540214b4e070f9d0c77c29199efc164c1f55f
5
5
  SHA512:
6
- metadata.gz: 787512840b4dda952c365f770fcfb5858d6cbfb0ce261ca9fcbeb3384fd32a0102f490e4a1e1840a4fa1a3f68b9d4a24a4840322104a33af1a3164036b02bf5b
7
- data.tar.gz: 2558049b61e39c42f57bd7a5a98f85522129a7d27001c5721e6525c56ba24136d9de913d579c02fd95205a8653eefed99006da3e722602b3fa7815eae69c9eda
6
+ metadata.gz: cbe3764f6edf4b3d894b59440c1591a50c6677270102f408314c4fb329e124ae88f903d0ba202125bc59fed96df22e5971734a568a5b84f3a87d59df19d2ecba
7
+ data.tar.gz: bd252a3a5c11a65606c0da763e53b1a80a88a038e21f231ec902ae2f210d997589414e5b53f77e30ab655cb644a93184e12b3e211a3082160e6ddc6f57c461a5
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.5.0 (2019-02-24)
6
+
7
+ - Make compatible with Rails 6. ([@palkan][])
8
+
9
+ That allows using RSpec part of the gem with Rails 6
10
+ (since Action Cable testing will included only in the upcoming RSpec 4).
11
+
12
+ - Backport Rails 6.0 API changes. ([@palkan][], [@sponomarev][])
13
+
14
+ Some APIs have been deprecated (to be removed in 1.0).
15
+
5
16
  ## 0.4.0 (2019-01-10)
6
17
 
7
18
  - Add stream assert methods and matchers. ([@sponomarev][])
data/README.md CHANGED
@@ -4,7 +4,11 @@
4
4
 
5
5
  This gem provides missing testing utils for [Action Cable][].
6
6
 
7
- **NOTE:** this gem is just a combination of two PRs to Rails itself ([#23211](https://github.com/rails/rails/pull/23211) and [#27191](https://github.com/rails/rails/pull/27191)) and (hopefully) will be merged into Rails eventually.
7
+ **NOTE:** this gem [has](https://github.com/rails/rails/pull/33659) [been](https://github.com/rails/rails/pull/33969) [merged](https://github.com/rails/rails/pull/34845) into Rails 6.0.
8
+
9
+ If you're using Minitest – you don't need this gem anymore.
10
+
11
+ If you're using RSpec < 4, you still can use this gem to write Action Cable specs even for Rails 6.
8
12
 
9
13
 
10
14
  ## Installation
@@ -60,6 +64,41 @@ class ExampleTest < ActionDispatch::IntegrationTest
60
64
  end
61
65
  ```
62
66
 
67
+ If you want to test the broadcasting made with `Channel.broadcast_to`, you should use
68
+ `Channel.broadcasting_for`\* to generate an underlying stream name and **use Rails 6 compatibility refinement**:
69
+
70
+ ```ruby
71
+ # app/jobs/chat_relay_job.rb
72
+ class ChatRelayJob < ApplicationJob
73
+ def perform_later(room, message)
74
+ ChatChannel.broadcast_to room, text: message
75
+ end
76
+ end
77
+
78
+
79
+ # test/jobs/chat_relay_job_test.rb
80
+ require "test_helper"
81
+
82
+ # Activate Rails 6 compatible API (for `broadcasting_for`)
83
+ using ActionCable::Testing::Rails6
84
+
85
+ class ChatRelayJobTest < ActiveJob::TestCase
86
+ include ActionCable::TestHelper
87
+
88
+ test "broadcast message to room" do
89
+ room = rooms(:all)
90
+
91
+ assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
92
+ ChatRelayJob.perform_now(room, "Hi!")
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ \* **NOTE:** in Rails 6.0 you should use `.broadcasting_for`, but it's not backward compatible
99
+ and we cannot use it in Rails 5.x. See https://github.com/rails/rails/pull/35021.
100
+ Note also, that this feature hasn't been released in Rails 6.0.0.beta1, so you still need the refinement.
101
+
63
102
  ### Channels Testing
64
103
 
65
104
  Channels tests are written as follows:
@@ -166,8 +205,10 @@ For example:
166
205
  module ApplicationCable
167
206
  class ConnectionTest < ActionCable::Connection::TestCase
168
207
  def test_connects_with_cookies
208
+ cookies.signed[:user_id] = users[:john].id
209
+
169
210
  # Simulate a connection
170
- connect cookies: { user_id: users[:john].id }
211
+ connect
171
212
 
172
213
  # Asserts that the connection identifier is correct
173
214
  assert_equal "John", connection.user.name
@@ -3,24 +3,19 @@
3
3
  require "action_cable/testing/version"
4
4
 
5
5
  require "action_cable"
6
+ require "action_cable/testing/rails_six"
6
7
 
7
- module ActionCable
8
- autoload :TestCase
9
- autoload :TestHelper
8
+ # These has been merged into Rails 6
9
+ unless ActionCable::VERSION::MAJOR >= 6
10
+ require "action_cable/testing/test_helper"
11
+ require "action_cable/testing/test_case"
10
12
 
11
- module Channel
12
- eager_autoload do
13
- autoload :TestCase
14
- end
15
- end
13
+ require "action_cable/testing/channel/test_case"
16
14
 
17
- module Connection
18
- eager_autoload do
19
- autoload :TestCase
20
- end
21
- end
15
+ require "action_cable/testing/connection/test_case"
22
16
 
23
- module SubscriptionAdapter
24
- autoload :Test
25
- end
17
+ # We cannot move subsription adapter under 'testing/' path,
18
+ # 'cause Action Cable uses this path when resolving an
19
+ # adapter from its name (in the config.yml)
20
+ require "action_cable/subscription_adapter/test"
26
21
  end
@@ -10,7 +10,7 @@ module ActionCable
10
10
  class NonInferrableChannelError < ::StandardError
11
11
  def initialize(name)
12
12
  super "Unable to determine the channel to test from #{name}. " +
13
- "You'll need to specify it using tests YourChannel in your " +
13
+ "You'll need to specify it using `tests YourChannel` in your " +
14
14
  "test case definition."
15
15
  end
16
16
  end
@@ -150,14 +150,13 @@ module ActionCable
150
150
 
151
151
  include ActiveSupport::Testing::ConstantLookup
152
152
  include ActionCable::TestHelper
153
- include ActionCable::Connection::TestCase::Behavior
154
153
 
155
154
  CHANNEL_IDENTIFIER = "test_stub"
156
155
 
157
156
  included do
158
157
  class_attribute :_channel_class
159
158
 
160
- attr_reader :subscription
159
+ attr_reader :connection, :subscription
161
160
 
162
161
  def streams
163
162
  ActiveSupport::Deprecation.warn "Use appropriate `assert_has_stream`, `assert_has_stream_for`, `assert_no_streams` " +
@@ -242,6 +241,16 @@ module ActionCable
242
241
  connection.transmissions.map { |data| data["message"] }.compact
243
242
  end
244
243
 
244
+ # Enhance TestHelper assertions to handle non-String
245
+ # broadcastings
246
+ def assert_broadcasts(stream_or_object, *args)
247
+ super(broadcasting_for(stream_or_object), *args)
248
+ end
249
+
250
+ def assert_broadcast_on(stream_or_object, *args)
251
+ super(broadcasting_for(stream_or_object), *args)
252
+ end
253
+
245
254
  # Asserts that no streams have been started.
246
255
  #
247
256
  # def test_assert_no_started_stream
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/test_case"
5
+ require "active_support/core_ext/hash/indifferent_access"
6
+ require "action_dispatch"
7
+ require "action_dispatch/http/headers"
8
+ require "action_dispatch/testing/test_request"
9
+
10
+ module ActionCable
11
+ module Connection
12
+ class NonInferrableConnectionError < ::StandardError
13
+ def initialize(name)
14
+ super "Unable to determine the connection to test from #{name}. " +
15
+ "You'll need to specify it using `tests YourConnection` in your " +
16
+ "test case definition."
17
+ end
18
+ end
19
+
20
+ module Assertions
21
+ # Asserts that the connection is rejected (via +reject_unauthorized_connection+).
22
+ #
23
+ # # Asserts that connection without user_id fails
24
+ # assert_reject_connection { connect cookies: { user_id: '' } }
25
+ def assert_reject_connection(&block)
26
+ assert_raises(Authorization::UnauthorizedError, "Expected to reject connection but no rejection was made", &block)
27
+ end
28
+ end
29
+
30
+ # We don't want to use the whole "encryption stack" for connection
31
+ # unit-tests, but we want to make sure that users test against the correct types
32
+ # of cookies (i.e. signed or encrypted or plain)
33
+ class TestCookieJar < ActiveSupport::HashWithIndifferentAccess
34
+ def signed
35
+ self[:signed] ||= {}.with_indifferent_access
36
+ end
37
+
38
+ def encrypted
39
+ self[:encrypted] ||= {}.with_indifferent_access
40
+ end
41
+ end
42
+
43
+ class TestRequest < ActionDispatch::TestRequest
44
+ attr_accessor :session, :cookie_jar
45
+ end
46
+
47
+ module TestConnection
48
+ attr_reader :logger, :request
49
+
50
+ def initialize(request)
51
+ inner_logger = ActiveSupport::Logger.new(StringIO.new)
52
+ tagged_logging = ActiveSupport::TaggedLogging.new(inner_logger)
53
+ @logger = ActionCable::Connection::TaggedLoggerProxy.new(tagged_logging, tags: [])
54
+ @request = request
55
+ @env = request.env
56
+ end
57
+ end
58
+
59
+ # Unit test Action Cable connections.
60
+ #
61
+ # Useful to check whether a connection's +identified_by+ gets assigned properly
62
+ # and that any improper connection requests are rejected.
63
+ #
64
+ # == Basic example
65
+ #
66
+ # Unit tests are written as follows:
67
+ #
68
+ # 1. Simulate a connection attempt by calling +connect+.
69
+ # 2. Assert state, e.g. identifiers, has been assigned.
70
+ #
71
+ #
72
+ # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
73
+ # def test_connects_with_proper_cookie
74
+ # # Simulate the connection request with a cookie.
75
+ # cookies["user_id"] = users(:john).id
76
+ #
77
+ # connect
78
+ #
79
+ # # Assert the connection identifier matches the fixture.
80
+ # assert_equal users(:john).id, connection.user.id
81
+ # end
82
+ #
83
+ # def test_rejects_connection_without_proper_cookie
84
+ # assert_reject_connection { connect }
85
+ # end
86
+ # end
87
+ #
88
+ # +connect+ accepts additional information the HTTP request with the
89
+ # +params+, +headers+, +session+ and Rack +env+ options.
90
+ #
91
+ # def test_connect_with_headers_and_query_string
92
+ # connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
93
+ #
94
+ # assert_equal "1", connection.user.id
95
+ # assert_equal "secret-my", connection.token
96
+ # end
97
+ #
98
+ # def test_connect_with_params
99
+ # connect params: { user_id: 1 }
100
+ #
101
+ # assert_equal "1", connection.user.id
102
+ # end
103
+ #
104
+ # You can also setup the correct cookies before the connection request:
105
+ #
106
+ # def test_connect_with_cookies
107
+ # # Plain cookies:
108
+ # cookies["user_id"] = 1
109
+ #
110
+ # # Or signed/encrypted:
111
+ # # cookies.signed["user_id"] = 1
112
+ # # cookies.encrypted["user_id"] = 1
113
+ #
114
+ # connect
115
+ #
116
+ # assert_equal "1", connection.user_id
117
+ # end
118
+ #
119
+ # == Connection is automatically inferred
120
+ #
121
+ # ActionCable::Connection::TestCase will automatically infer the connection under test
122
+ # from the test class name. If the channel cannot be inferred from the test
123
+ # class name, you can explicitly set it with +tests+.
124
+ #
125
+ # class ConnectionTest < ActionCable::Connection::TestCase
126
+ # tests ApplicationCable::Connection
127
+ # end
128
+ #
129
+ class TestCase < ActiveSupport::TestCase
130
+ module Behavior
131
+ extend ActiveSupport::Concern
132
+
133
+ DEFAULT_PATH = "/cable"
134
+
135
+ include ActiveSupport::Testing::ConstantLookup
136
+ include Assertions
137
+
138
+ included do
139
+ class_attribute :_connection_class
140
+
141
+ attr_reader :connection
142
+
143
+ ActiveSupport.run_load_hooks(:action_cable_connection_test_case, self)
144
+ end
145
+
146
+ module ClassMethods
147
+ def tests(connection)
148
+ case connection
149
+ when String, Symbol
150
+ self._connection_class = connection.to_s.camelize.constantize
151
+ when Module
152
+ self._connection_class = connection
153
+ else
154
+ raise NonInferrableConnectionError.new(connection)
155
+ end
156
+ end
157
+
158
+ def connection_class
159
+ if connection = self._connection_class
160
+ connection
161
+ else
162
+ tests determine_default_connection(name)
163
+ end
164
+ end
165
+
166
+ def determine_default_connection(name)
167
+ connection = determine_constant_from_test_name(name) do |constant|
168
+ Class === constant && constant < ActionCable::Connection::Base
169
+ end
170
+ raise NonInferrableConnectionError.new(name) if connection.nil?
171
+ connection
172
+ end
173
+ end
174
+
175
+ # Performs connection attempt to exert #connect on the connection under test.
176
+ #
177
+ # Accepts request path as the first argument and the following request options:
178
+ #
179
+ # - params – url parameters (Hash)
180
+ # - headers – request headers (Hash)
181
+ # - session – session data (Hash)
182
+ # - env – additional Rack env configuration (Hash)
183
+ def connect(path = ActionCable.server.config.mount_path, cookies: nil, **request_params)
184
+ path ||= DEFAULT_PATH
185
+
186
+ unless cookies.nil?
187
+ ActiveSupport::Deprecation.warn(
188
+ "Use `cookies[:param] = value` (or `cookies.signed[:param] = value`). " \
189
+ "Passing `cookies` as the `connect` option is deprecated and is going to be removed in version 1.0"
190
+ )
191
+ cookies.each { |k, v| self.cookies[k] = v }
192
+ end
193
+
194
+ connection = self.class.connection_class.allocate
195
+ connection.singleton_class.include(TestConnection)
196
+ connection.send(:initialize, build_test_request(path, request_params))
197
+ connection.connect if connection.respond_to?(:connect)
198
+
199
+ # Only set instance variable if connected successfully
200
+ @connection = connection
201
+ end
202
+
203
+ # Exert #disconnect on the connection under test.
204
+ def disconnect
205
+ raise "Must be connected!" if connection.nil?
206
+
207
+ connection.disconnect if connection.respond_to?(:disconnect)
208
+ @connection = nil
209
+ end
210
+
211
+ def cookies
212
+ @cookie_jar ||= TestCookieJar.new
213
+ end
214
+
215
+ private
216
+ def build_test_request(path, params: nil, headers: {}, session: {}, env: {})
217
+ wrapped_headers = ActionDispatch::Http::Headers.from_hash(headers)
218
+
219
+ uri = URI.parse(path)
220
+
221
+ query_string = params.nil? ? uri.query : params.to_query
222
+
223
+ request_env = {
224
+ "QUERY_STRING" => query_string,
225
+ "PATH_INFO" => uri.path
226
+ }.merge(env)
227
+
228
+ if wrapped_headers.present?
229
+ ActionDispatch::Http::Headers.from_hash(request_env).merge!(wrapped_headers)
230
+ end
231
+
232
+ TestRequest.create(request_env).tap do |request|
233
+ request.session = session.with_indifferent_access
234
+ request.cookie_jar = cookies
235
+ end
236
+ end
237
+ end
238
+
239
+ include Behavior
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionCable
4
+ module Testing
5
+ # Enables Rails 6 compatible API via Refinements.
6
+ #
7
+ # Use this to write Rails 6 compatible tests (not every Rails 6 API
8
+ # could be backported).
9
+ #
10
+ # Usage:
11
+ # # my_test.rb
12
+ # require "test_helper"
13
+ #
14
+ # using ActionCable::Testing::Syntax
15
+ module Rails6
16
+ begin
17
+ # Has been added only after 6.0.0.beta1
18
+ unless ActionCable::Channel.respond_to?(:serialize_broadcasting)
19
+ refine ActionCable::Channel::Broadcasting::ClassMethods do
20
+ def broadcasting_for(model)
21
+ super([channel_name, model])
22
+ end
23
+ end
24
+ end
25
+
26
+ SUPPORTED = true
27
+ rescue TypeError
28
+ warn "Your Ruby version doesn't suppport Module refinements. " \
29
+ "Rails 6 compatibility refinement could not be applied"
30
+
31
+ SUPPORTED = false
32
+ end
33
+ end
34
+ end
35
+ end
@@ -3,7 +3,7 @@
3
3
  module ActionCable
4
4
  # Provides helper methods for testing Action Cable broadcasting
5
5
  module TestHelper
6
- CHANNEL_NOT_FOUND = ArgumentError.new("Broadcastnig channel can't be infered. Please, specify it with `:channel`")
6
+ CHANNEL_NOT_FOUND = ArgumentError.new("Broadcasting channel can't be infered. Please, specify it with `:channel`")
7
7
 
8
8
  def before_setup # :nodoc:
9
9
  server = ActionCable.server
@@ -45,6 +45,8 @@ module ActionCable
45
45
  # end
46
46
  #
47
47
  def assert_broadcasts(target, number, channel: nil)
48
+ warn_deprecated_channel! unless channel.nil?
49
+
48
50
  stream = stream(target, channel)
49
51
 
50
52
  if block_given?
@@ -98,6 +100,8 @@ module ActionCable
98
100
  # end
99
101
  #
100
102
  def assert_broadcast_on(target, data, channel: nil)
103
+ warn_deprecated_channel! unless channel.nil?
104
+
101
105
  # Encode to JSON and back–we want to use this value to compare
102
106
  # with decoded JSON.
103
107
  # Comparing JSON strings doesn't work due to the order if the keys.
@@ -138,9 +142,17 @@ module ActionCable
138
142
  return target if target.is_a?(String)
139
143
 
140
144
  channel ||= @subscription
141
- raise CHANNEL_NOT_FOUND unless channel && channel.respond_to?(:channel_name)
145
+ return target unless channel && channel.respond_to?(:channel_name)
142
146
 
143
147
  channel.broadcasting_for([channel.channel_name, target])
144
148
  end
149
+
150
+ def warn_deprecated_channel!
151
+ ActiveSupport::Deprecation.warn(
152
+ "Passing channel class is deprecated and will be removed in version 1.0. " \
153
+ "Use `Channel.broadcasting_for(object) to build a stream name instead and " \
154
+ "add `using ActionCable::Testing::Rails6` to your test file."
155
+ )
156
+ end
145
157
  end
146
158
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActionCable
4
4
  module Testing
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -21,6 +21,7 @@ if defined?(ActionCable)
21
21
  module ChannelExampleGroup
22
22
  extend ActiveSupport::Concern
23
23
  include RSpec::Rails::RailsExampleGroup
24
+ include ActionCable::Connection::TestCase::Behavior
24
25
  include ActionCable::Channel::TestCase::Behavior
25
26
 
26
27
  # Class-level DSL for channel specs.
@@ -160,7 +160,7 @@ module RSpec
160
160
  def check_channel_presence
161
161
  return if @channel.present? && @channel.respond_to?(:channel_name)
162
162
 
163
- error_msg = "Broadcastnig channel can't be infered. Please, specify it with `from_channel`"
163
+ error_msg = "Broadcasting channel can't be infered. Please, specify it with `from_channel`"
164
164
  raise ArgumentError, error_msg
165
165
  end
166
166
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action-cable-testing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-10 00:00:00.000000000 Z
11
+ date: 2019-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actioncable
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
@@ -147,14 +147,15 @@ files:
147
147
  - LICENSE.txt
148
148
  - README.md
149
149
  - lib/action-cable-testing.rb
150
- - lib/action_cable/channel/test_case.rb
151
- - lib/action_cable/connection/test_case.rb
152
150
  - lib/action_cable/subscription_adapter/test.rb
153
- - lib/action_cable/test_case.rb
154
- - lib/action_cable/test_helper.rb
155
151
  - lib/action_cable/testing.rb
152
+ - lib/action_cable/testing/channel/test_case.rb
153
+ - lib/action_cable/testing/connection/test_case.rb
154
+ - lib/action_cable/testing/rails_six.rb
156
155
  - lib/action_cable/testing/rspec.rb
157
156
  - lib/action_cable/testing/rspec/features.rb
157
+ - lib/action_cable/testing/test_case.rb
158
+ - lib/action_cable/testing/test_helper.rb
158
159
  - lib/action_cable/testing/version.rb
159
160
  - lib/generators/rspec/channel/channel_generator.rb
160
161
  - lib/generators/rspec/channel/templates/channel_spec.rb.erb
@@ -177,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
178
  requirements:
178
179
  - - ">="
179
180
  - !ruby/object:Gem::Version
180
- version: '0'
181
+ version: 2.3.0
181
182
  required_rubygems_version: !ruby/object:Gem::Requirement
182
183
  requirements:
183
184
  - - ">="
@@ -1,201 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support"
4
- require "active_support/test_case"
5
- require "active_support/core_ext/hash/indifferent_access"
6
- require "action_dispatch"
7
- require "action_dispatch/testing/test_request"
8
-
9
- module ActionCable
10
- module Connection
11
- class NonInferrableConnectionError < ::StandardError
12
- def initialize(name)
13
- super "Unable to determine the connection to test from #{name}. " +
14
- "You'll need to specify it using tests YourConnection in your " +
15
- "test case definition."
16
- end
17
- end
18
-
19
- module Assertions
20
- # Asserts that the connection is rejected (via +reject_unauthorized_connection+).
21
- #
22
- # # Asserts that connection without user_id fails
23
- # assert_reject_connection { connect cookies: { user_id: '' } }
24
- def assert_reject_connection(&block)
25
- res =
26
- begin
27
- block.call
28
- false
29
- rescue ActionCable::Connection::Authorization::UnauthorizedError
30
- true
31
- end
32
-
33
- assert res, "Expected to reject connection but no rejection were made"
34
- end
35
- end
36
-
37
- class TestRequest < ActionDispatch::TestRequest
38
- attr_reader :cookie_jar
39
- attr_accessor :session
40
-
41
- module CookiesStub
42
- # Stub signed cookies
43
- def signed
44
- self
45
- end
46
-
47
- # Stub encrypted cookies
48
- def encrypted
49
- self
50
- end
51
- end
52
-
53
- def cookie_jar=(val)
54
- @cookie_jar = val.tap do |h|
55
- h.singleton_class.include(CookiesStub)
56
- end
57
- end
58
- end
59
-
60
- module TestConnection
61
- attr_reader :logger, :request
62
-
63
- def initialize(path, cookies, headers, session)
64
- inner_logger = ActiveSupport::Logger.new(StringIO.new)
65
- tagged_logging = ActiveSupport::TaggedLogging.new(inner_logger)
66
- @logger = ActionCable::Connection::TaggedLoggerProxy.new(tagged_logging, tags: [])
67
-
68
- uri = URI.parse(path)
69
- env = {
70
- "QUERY_STRING" => uri.query,
71
- "PATH_INFO" => uri.path
72
- }.merge(build_headers(headers))
73
-
74
- @request = TestRequest.create(env)
75
- @request.cookie_jar = cookies.with_indifferent_access
76
- @request.session = session.with_indifferent_access
77
- end
78
-
79
- def build_headers(headers)
80
- headers.each_with_object({}) do |(k, v), obj|
81
- k = k.upcase
82
- k.tr!("-", "_")
83
- obj["HTTP_#{k}"] = v
84
- end
85
- end
86
- end
87
-
88
- # Superclass for Action Cable connection unit tests.
89
- #
90
- # == Basic example
91
- #
92
- # Unit tests are written as follows:
93
- # 1. First, one uses the +connect+ method to simulate connection.
94
- # 2. Then, one asserts whether the current state is as expected (e.g. identifiers).
95
- #
96
- # For example:
97
- #
98
- # module ApplicationCable
99
- # class ConnectionTest < ActionCable::Connection::TestCase
100
- # def test_connects_with_cookies
101
- # # Simulate a connection
102
- # connect cookies: { user_id: users[:john].id }
103
- #
104
- # # Asserts that the connection identifier is correct
105
- # assert_equal "John", connection.user.name
106
- # end
107
- #
108
- # def test_does_not_connect_without_user
109
- # assert_reject_connection do
110
- # connect
111
- # end
112
- # end
113
- # end
114
- #
115
- # You can also provide additional information about underlying HTTP request:
116
- # def test_connect_with_headers_and_query_string
117
- # connect "/cable?user_id=1", headers: { "X-API-TOKEN" => 'secret-my' }
118
- #
119
- # assert_equal connection.user_id, "1"
120
- # end
121
- #
122
- # == Connection is automatically inferred
123
- #
124
- # ActionCable::Connection::TestCase will automatically infer the connection under test
125
- # from the test class name. If the channel cannot be inferred from the test
126
- # class name, you can explicitly set it with +tests+.
127
- #
128
- # class ConnectionTest < ActionCable::Connection::TestCase
129
- # tests ApplicationCable::Connection
130
- # end
131
- #
132
- class TestCase < ActiveSupport::TestCase
133
- module Behavior
134
- extend ActiveSupport::Concern
135
-
136
- include ActiveSupport::Testing::ConstantLookup
137
- include Assertions
138
-
139
- included do
140
- class_attribute :_connection_class
141
-
142
- attr_reader :connection
143
-
144
- ActiveSupport.run_load_hooks(:action_cable_connection_test_case, self)
145
- end
146
-
147
- module ClassMethods
148
- def tests(connection)
149
- case connection
150
- when String, Symbol
151
- self._connection_class = connection.to_s.camelize.constantize
152
- when Module
153
- self._connection_class = connection
154
- else
155
- raise NonInferrableConnectionError.new(connection)
156
- end
157
- end
158
-
159
- def connection_class
160
- if connection = self._connection_class
161
- connection
162
- else
163
- tests determine_default_connection(name)
164
- end
165
- end
166
-
167
- def determine_default_connection(name)
168
- connection = determine_constant_from_test_name(name) do |constant|
169
- Class === constant && constant < ActionCable::Connection::Base
170
- end
171
- raise NonInferrableConnectionError.new(name) if connection.nil?
172
- connection
173
- end
174
- end
175
-
176
- # Performs connection attempt (i.e. calls #connect method).
177
- #
178
- # Accepts request path as the first argument and cookies and headers as options.
179
- def connect(path = "/cable", cookies: {}, headers: {}, session: {})
180
- connection = self.class.connection_class.allocate
181
- connection.singleton_class.include(TestConnection)
182
- connection.send(:initialize, path, cookies, headers, session)
183
- connection.connect if connection.respond_to?(:connect)
184
-
185
- # Only set instance variable if connected successfully
186
- @connection = connection
187
- end
188
-
189
- # Disconnect the connection under test (i.e. calls #disconnect)
190
- def disconnect
191
- raise "Must be connected!" if connection.nil?
192
-
193
- connection.disconnect if connection.respond_to?(:disconnect)
194
- @connection = nil
195
- end
196
- end
197
-
198
- include Behavior
199
- end
200
- end
201
- end