aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/test/channel/base_test.rb
blob: c2968226c77ca1a2fc076a8b1903bea35f0c3fd1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# frozen_string_literal: true

require "test_helper"
require "minitest/mock"
require "stubs/test_connection"
require "stubs/room"

class ActionCable::Channel::BaseTest < ActionCable::TestCase
  class ActionCable::Channel::Base
    def kick
      @last_action = [ :kick ]
    end

    def topic
    end
  end

  class BasicChannel < ActionCable::Channel::Base
    def chatters
      @last_action = [ :chatters ]
    end
  end

  class ChatChannel < BasicChannel
    attr_reader :room, :last_action
    after_subscribe :toggle_subscribed
    after_unsubscribe :toggle_subscribed

    class SomeCustomError < StandardError; end
    rescue_from SomeCustomError, with: :error_handler

    def initialize(*)
      @subscribed = false
      super
    end

    def subscribed
      @room = Room.new params[:id]
      @actions = []
    end

    def unsubscribed
      @room = nil
    end

    def toggle_subscribed
      @subscribed = !@subscribed
    end

    def leave
      @last_action = [ :leave ]
    end

    def speak(data)
      @last_action = [ :speak, data ]
    end

    def topic(data)
      @last_action = [ :topic, data ]
    end

    def subscribed?
      @subscribed
    end

    def get_latest
      transmit data: "latest"
    end

    def receive
      @last_action = [ :receive ]
    end

    def error_action
      raise SomeCustomError
    end

    private
      def rm_rf
        @last_action = [ :rm_rf ]
      end

      def error_handler
        @last_action = [ :error_action ]
      end
  end

  setup do
    @user = User.new "lifo"
    @connection = TestConnection.new(@user)
    @channel = ChatChannel.new @connection, "{id: 1}", id: 1
  end

  test "should subscribe to a channel" do
    @channel.subscribe_to_channel
    assert_equal 1, @channel.room.id
  end

  test "on subscribe callbacks" do
    @channel.subscribe_to_channel
    assert @channel.subscribed
  end

  test "channel params" do
    assert_equal({ id: 1 }, @channel.params)
  end

  test "unsubscribing from a channel" do
    @channel.subscribe_to_channel

    assert @channel.room
    assert_predicate @channel, :subscribed?

    @channel.unsubscribe_from_channel

    assert_not @channel.room
    assert_not_predicate @channel, :subscribed?
  end

  test "connection identifiers" do
    assert_equal @user.name, @channel.current_user.name
  end

  test "callable action without any argument" do
    @channel.perform_action "action" => :leave
    assert_equal [ :leave ], @channel.last_action
  end

  test "callable action with arguments" do
    data = { "action" => :speak, "content" => "Hello World" }

    @channel.perform_action data
    assert_equal [ :speak, data ], @channel.last_action
  end

  test "should not dispatch a private method" do
    @channel.perform_action "action" => :rm_rf
    assert_nil @channel.last_action
  end

  test "should not dispatch a public method defined on Base" do
    @channel.perform_action "action" => :kick
    assert_nil @channel.last_action
  end

  test "should dispatch a public method defined on Base and redefined on channel" do
    data = { "action" => :topic, "content" => "This is Sparta!" }

    @channel.perform_action data
    assert_equal [ :topic, data ], @channel.last_action
  end

  test "should dispatch calling a public method defined in an ancestor" do
    @channel.perform_action "action" => :chatters
    assert_equal [ :chatters ], @channel.last_action
  end

  test "should dispatch receive action when perform_action is called with empty action" do
    data = { "content" => "hello" }
    @channel.perform_action data
    assert_equal [ :receive ], @channel.last_action
  end

  test "transmitting data" do
    @channel.perform_action "action" => :get_latest

    expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" } }
    assert_equal expected, @connection.last_transmission
  end

  test "do not send subscription confirmation on initialize" do
    assert_nil @connection.last_transmission
  end

  test "subscription confirmation on subscribe_to_channel" do
    expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
    @channel.subscribe_to_channel
    assert_equal expected, @connection.last_transmission
  end

  test "actions available on Channel" do
    available_actions = %w(room last_action subscribed unsubscribed toggle_subscribed leave speak subscribed? get_latest receive chatters topic error_action).to_set
    assert_equal available_actions, ChatChannel.action_methods
  end

  test "invalid action on Channel" do
    assert_logged("Unable to process ActionCable::Channel::BaseTest::ChatChannel#invalid_action") do
      @channel.perform_action "action" => :invalid_action
    end
  end

  test "notification for perform_action" do
    begin
      events = []
      ActiveSupport::Notifications.subscribe "perform_action.action_cable" do |*args|
        events << ActiveSupport::Notifications::Event.new(*args)
      end

      data = { "action" => :speak, "content" => "hello" }
      @channel.perform_action data

      assert_equal 1, events.length
      assert_equal "perform_action.action_cable", events[0].name
      assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class]
      assert_equal :speak, events[0].payload[:action]
      assert_equal data, events[0].payload[:data]
    ensure
      ActiveSupport::Notifications.unsubscribe "perform_action.action_cable"
    end
  end

  test "notification for transmit" do
    begin
      events = []
      ActiveSupport::Notifications.subscribe "transmit.action_cable" do |*args|
        events << ActiveSupport::Notifications::Event.new(*args)
      end

      @channel.perform_action "action" => :get_latest
      expected_data = { data: "latest" }

      assert_equal 1, events.length
      assert_equal "transmit.action_cable", events[0].name
      assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class]
      assert_equal expected_data, events[0].payload[:data]
      assert_nil events[0].payload[:via]
    ensure
      ActiveSupport::Notifications.unsubscribe "transmit.action_cable"
    end
  end

  test "notification for transmit_subscription_confirmation" do
    begin
      @channel.subscribe_to_channel

      events = []
      ActiveSupport::Notifications.subscribe "transmit_subscription_confirmation.action_cable" do |*args|
        events << ActiveSupport::Notifications::Event.new(*args)
      end

      @channel.stub(:subscription_confirmation_sent?, false) do
        @channel.send(:transmit_subscription_confirmation)

        assert_equal 1, events.length
        assert_equal "transmit_subscription_confirmation.action_cable", events[0].name
        assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class]
      end
    ensure
      ActiveSupport::Notifications.unsubscribe "transmit_subscription_confirmation.action_cable"
    end
  end

  test "notification for transmit_subscription_rejection" do
    begin
      events = []
      ActiveSupport::Notifications.subscribe "transmit_subscription_rejection.action_cable" do |*args|
        events << ActiveSupport::Notifications::Event.new(*args)
      end

      @channel.send(:transmit_subscription_rejection)

      assert_equal 1, events.length
      assert_equal "transmit_subscription_rejection.action_cable", events[0].name
      assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class]
    ensure
      ActiveSupport::Notifications.unsubscribe "transmit_subscription_rejection.action_cable"
    end
  end

  test "behaves like rescuable" do
    @channel.perform_action "action" => :error_action
    assert_equal [ :error_action ], @channel.last_action
  end

  private
    def assert_logged(message)
      old_logger = @connection.logger
      log = StringIO.new
      @connection.instance_variable_set(:@logger, Logger.new(log))

      begin
        yield

        log.rewind
        assert_match message, log.read
      ensure
        @connection.instance_variable_set(:@logger, old_logger)
      end
    end
end