mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
b168eb5819
* Introduce a connection coder responsible for encoding Cable messages as WebSocket messages, defaulting to `ActiveSupport::JSON` and duck- typing to any object responding to `#encode` and `#decode`. * Consolidate encoding responsibility to the connection. No longer explicitly JSON-encode from channels or other sources. Pass Cable messages as Hashes to `#transmit` and rely on it to encode. * Introduce stream encoders responsible for decoding pubsub messages. Preserve the currently raw encoding, but make it easy to use JSON. Same duck type as the connection encoder. * Revert recent data normalization/quoting (#23649) which treated `identifier` and `data` values as nested JSON objects rather than as opaque JSON-encoded strings. That dealt us an awkward hand where we'd decode JSON strings… or not, but always encode as JSON. Embedding JSON object values directly is preferably, no extra JSON encoding, but that should be a purposeful protocol version change rather than ambiguously, inadvertently supporting multiple message formats.
259 lines
7.2 KiB
Ruby
259 lines
7.2 KiB
Ruby
require 'test_helper'
|
|
require 'stubs/test_connection'
|
|
require 'stubs/room'
|
|
|
|
class ActionCable::Channel::BaseTest < ActiveSupport::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
|
|
|
|
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
|
|
|
|
private
|
|
def rm_rf
|
|
@last_action = [ :rm_rf ]
|
|
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 on initialize" do
|
|
assert_equal 1, @channel.room.id
|
|
end
|
|
|
|
test "on subscribe callbacks" do
|
|
assert @channel.subscribed
|
|
end
|
|
|
|
test "channel params" do
|
|
assert_equal({ id: 1 }, @channel.params)
|
|
end
|
|
|
|
test "unsubscribing from a channel" do
|
|
assert @channel.room
|
|
assert @channel.subscribed?
|
|
|
|
@channel.unsubscribe_from_channel
|
|
|
|
assert ! @channel.room
|
|
assert ! @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 "subscription confirmation" do
|
|
expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
|
|
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).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
|
|
events = []
|
|
ActiveSupport::Notifications.subscribe 'transmit_subscription_confirmation.action_cable' do |*args|
|
|
events << ActiveSupport::Notifications::Event.new(*args)
|
|
end
|
|
|
|
@channel.stubs(:subscription_confirmation_sent?).returns(false)
|
|
@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]
|
|
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
|
|
|
|
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
|