action-cable-testing 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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