aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/test/subscription_adapter
diff options
context:
space:
mode:
Diffstat (limited to 'actioncable/test/subscription_adapter')
-rw-r--r--actioncable/test/subscription_adapter/async_test.rb19
-rw-r--r--actioncable/test/subscription_adapter/base_test.rb65
-rw-r--r--actioncable/test/subscription_adapter/channel_prefix.rb38
-rw-r--r--actioncable/test/subscription_adapter/common.rb131
-rw-r--r--actioncable/test/subscription_adapter/inline_test.rb19
-rw-r--r--actioncable/test/subscription_adapter/postgresql_test.rb65
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb52
-rw-r--r--actioncable/test/subscription_adapter/subscriber_map_test.rb19
-rw-r--r--actioncable/test/subscription_adapter/test_adapter_test.rb47
9 files changed, 455 insertions, 0 deletions
diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb
new file mode 100644
index 0000000000..6e038259b5
--- /dev/null
+++ b/actioncable/test/subscription_adapter/async_test.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "common"
+
+class AsyncAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ @tx_adapter.shutdown
+ @tx_adapter = @rx_adapter
+ end
+
+ def cable_config
+ { adapter: "async" }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb
new file mode 100644
index 0000000000..999dc0cba1
--- /dev/null
+++ b/actioncable/test/subscription_adapter/base_test.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
+ ## TEST THAT ERRORS ARE RETURNED FOR INHERITORS THAT DON'T OVERRIDE METHODS
+
+ class BrokenAdapter < ActionCable::SubscriptionAdapter::Base
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.subscription_adapter = BrokenAdapter
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "#broadcast returns NotImplementedError by default" do
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).broadcast("channel", "payload")
+ end
+ end
+
+ test "#subscribe returns NotImplementedError by default" do
+ callback = lambda { puts "callback" }
+ success_callback = lambda { puts "success" }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).subscribe("channel", callback, success_callback)
+ end
+ end
+
+ test "#unsubscribe returns NotImplementedError by default" do
+ callback = lambda { puts "callback" }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).unsubscribe("channel", callback)
+ end
+ end
+
+ # TEST METHODS THAT ARE REQUIRED OF THE ADAPTER'S BACKEND STORAGE OBJECT
+
+ test "#broadcast is implemented" do
+ assert_nothing_raised do
+ SuccessAdapter.new(@server).broadcast("channel", "payload")
+ end
+ end
+
+ test "#subscribe is implemented" do
+ callback = lambda { puts "callback" }
+ success_callback = lambda { puts "success" }
+
+ assert_nothing_raised do
+ SuccessAdapter.new(@server).subscribe("channel", callback, success_callback)
+ end
+ end
+
+ test "#unsubscribe is implemented" do
+ callback = lambda { puts "callback" }
+
+ assert_nothing_raised do
+ SuccessAdapter.new(@server).unsubscribe("channel", callback)
+ end
+ end
+end
diff --git a/actioncable/test/subscription_adapter/channel_prefix.rb b/actioncable/test/subscription_adapter/channel_prefix.rb
new file mode 100644
index 0000000000..3071facd9d
--- /dev/null
+++ b/actioncable/test/subscription_adapter/channel_prefix.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionCable::Server::WithIndependentConfig < ActionCable::Server::Base
+ # ActionCable::Server::Base defines config as a class variable.
+ # Need config to be an instance variable here as we're testing 2 separate configs
+ def config
+ @config ||= ActionCable::Server::Configuration.new
+ end
+end
+
+module ChannelPrefixTest
+ def test_channel_prefix
+ server2 = ActionCable::Server::WithIndependentConfig.new
+ server2.config.cable = alt_cable_config
+ server2.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+
+ adapter_klass = server2.config.pubsub_adapter
+
+ rx_adapter2 = adapter_klass.new(server2)
+ tx_adapter2 = adapter_klass.new(server2)
+
+ subscribe_as_queue("channel") do |queue|
+ subscribe_as_queue("channel", rx_adapter2) do |queue2|
+ @tx_adapter.broadcast("channel", "hello world")
+ tx_adapter2.broadcast("channel", "hello world 2")
+
+ assert_equal "hello world", queue.pop
+ assert_equal "hello world 2", queue2.pop
+ end
+ end
+ end
+
+ def alt_cable_config
+ cable_config.merge(channel_prefix: "foo")
+ end
+end
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
new file mode 100644
index 0000000000..b3e9ae9d5c
--- /dev/null
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "concurrent"
+
+require "active_support/core_ext/hash/indifferent_access"
+require "pathname"
+
+module CommonSubscriptionAdapterTest
+ WAIT_WHEN_EXPECTING_EVENT = 3
+ WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
+
+ def setup
+ server = ActionCable::Server::Base.new
+ server.config.cable = cable_config.with_indifferent_access
+ server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+
+ adapter_klass = server.config.pubsub_adapter
+
+ @rx_adapter = adapter_klass.new(server)
+ @tx_adapter = adapter_klass.new(server)
+ end
+
+ def teardown
+ [@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown)
+ end
+
+ def subscribe_as_queue(channel, adapter = @rx_adapter)
+ queue = Queue.new
+
+ callback = -> data { queue << data }
+ subscribed = Concurrent::Event.new
+ adapter.subscribe(channel, callback, Proc.new { subscribed.set })
+ subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)
+ assert_predicate subscribed, :set?
+
+ yield queue
+
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT
+ assert_empty queue
+ ensure
+ adapter.unsubscribe(channel, callback) if subscribed.set?
+ end
+
+ def test_subscribe_and_unsubscribe
+ subscribe_as_queue("channel") do |queue|
+ end
+ end
+
+ def test_basic_broadcast
+ subscribe_as_queue("channel") do |queue|
+ @tx_adapter.broadcast("channel", "hello world")
+
+ assert_equal "hello world", queue.pop
+ end
+ end
+
+ def test_broadcast_after_unsubscribe
+ keep_queue = nil
+ subscribe_as_queue("channel") do |queue|
+ keep_queue = queue
+
+ @tx_adapter.broadcast("channel", "hello world")
+
+ assert_equal "hello world", queue.pop
+ end
+
+ @tx_adapter.broadcast("channel", "hello void")
+
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT
+ assert_empty keep_queue
+ end
+
+ def test_multiple_broadcast
+ subscribe_as_queue("channel") do |queue|
+ @tx_adapter.broadcast("channel", "bananas")
+ @tx_adapter.broadcast("channel", "apples")
+
+ received = []
+ 2.times { received << queue.pop }
+ assert_equal ["apples", "bananas"], received.sort
+ end
+ end
+
+ def test_identical_subscriptions
+ subscribe_as_queue("channel") do |queue|
+ subscribe_as_queue("channel") do |queue_2|
+ @tx_adapter.broadcast("channel", "hello")
+
+ assert_equal "hello", queue_2.pop
+ end
+
+ assert_equal "hello", queue.pop
+ end
+ end
+
+ def test_simultaneous_subscriptions
+ subscribe_as_queue("channel") do |queue|
+ subscribe_as_queue("other channel") do |queue_2|
+ @tx_adapter.broadcast("channel", "apples")
+ @tx_adapter.broadcast("other channel", "oranges")
+
+ assert_equal "apples", queue.pop
+ assert_equal "oranges", queue_2.pop
+ end
+ end
+ end
+
+ def test_channel_filtered_broadcast
+ subscribe_as_queue("channel") do |queue|
+ @tx_adapter.broadcast("other channel", "one")
+ @tx_adapter.broadcast("channel", "two")
+
+ assert_equal "two", queue.pop
+ end
+ end
+
+ def test_long_identifiers
+ channel_1 = "a" * 100 + "1"
+ channel_2 = "a" * 100 + "2"
+ subscribe_as_queue(channel_1) do |queue|
+ subscribe_as_queue(channel_2) do |queue_2|
+ @tx_adapter.broadcast(channel_1, "apples")
+ @tx_adapter.broadcast(channel_2, "oranges")
+
+ assert_equal "apples", queue.pop
+ assert_equal "oranges", queue_2.pop
+ end
+ end
+ end
+end
diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb
new file mode 100644
index 0000000000..6305626b2b
--- /dev/null
+++ b/actioncable/test/subscription_adapter/inline_test.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "common"
+
+class InlineAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ @tx_adapter.shutdown
+ @tx_adapter = @rx_adapter
+ end
+
+ def cable_config
+ { adapter: "inline" }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb
new file mode 100644
index 0000000000..5fb26a8896
--- /dev/null
+++ b/actioncable/test/subscription_adapter/postgresql_test.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "common"
+
+require "active_record"
+
+class PostgresqlAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ database_config = { "adapter" => "postgresql", "database" => "activerecord_unittest" }
+ ar_tests = File.expand_path("../../../activerecord/test", __dir__)
+ if Dir.exist?(ar_tests)
+ require File.join(ar_tests, "config")
+ require File.join(ar_tests, "support/config")
+ local_config = ARTest.config["connections"]["postgresql"]["arunit"]
+ database_config.update local_config if local_config
+ end
+
+ ActiveRecord::Base.establish_connection database_config
+
+ begin
+ ActiveRecord::Base.connection
+ rescue
+ @rx_adapter = @tx_adapter = nil
+ skip "Couldn't connect to PostgreSQL: #{database_config.inspect}"
+ end
+
+ super
+ end
+
+ def teardown
+ super
+
+ ActiveRecord::Base.clear_all_connections!
+ end
+
+ def cable_config
+ { adapter: "postgresql" }
+ end
+
+ def test_clear_active_record_connections_adapter_still_works
+ server = ActionCable::Server::Base.new
+ server.config.cable = cable_config.with_indifferent_access
+ server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+
+ adapter_klass = Class.new(server.config.pubsub_adapter) do
+ def active?
+ !@listener.nil?
+ end
+ end
+
+ adapter = adapter_klass.new(server)
+
+ subscribe_as_queue("channel", adapter) do |queue|
+ adapter.broadcast("channel", "hello world")
+ assert_equal "hello world", queue.pop
+ end
+
+ ActiveRecord::Base.clear_reloadable_connections!
+
+ assert adapter.active?
+ end
+end
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
new file mode 100644
index 0000000000..ac2d8ef724
--- /dev/null
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "common"
+require_relative "channel_prefix"
+
+require "action_cable/subscription_adapter/redis"
+
+class RedisAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+ include ChannelPrefixTest
+
+ def cable_config
+ { adapter: "redis", driver: "ruby" }
+ end
+end
+
+class RedisAdapterTest::Hiredis < RedisAdapterTest
+ def cable_config
+ super.merge(driver: "hiredis")
+ end
+end
+
+class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest
+ def cable_config
+ alt_cable_config = super.dup
+ alt_cable_config.delete(:url)
+ alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12)
+ end
+end
+
+class RedisAdapterTest::Connector < ActionCable::TestCase
+ test "slices url, host, port, db, password and id from config" do
+ config = { url: 1, host: 2, port: 3, db: 4, password: 5, id: "Some custom ID" }
+
+ assert_called_with ::Redis, :new, [ config ] do
+ connect config.merge(other: "unrelated", stuff: "here")
+ end
+ end
+
+ test "adds default id if it is not specified" do
+ config = { url: 1, host: 2, port: 3, db: 4, password: 5, id: "ActionCable-PID-#{$$}" }
+
+ assert_called_with ::Redis, :new, [ config ] do
+ connect config.except(:id)
+ end
+ end
+
+ def connect(config)
+ ActionCable::SubscriptionAdapter::Redis.redis_connector.call(config)
+ end
+end
diff --git a/actioncable/test/subscription_adapter/subscriber_map_test.rb b/actioncable/test/subscription_adapter/subscriber_map_test.rb
new file mode 100644
index 0000000000..ed81099cbc
--- /dev/null
+++ b/actioncable/test/subscription_adapter/subscriber_map_test.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class SubscriberMapTest < ActionCable::TestCase
+ test "broadcast should not change subscribers" do
+ setup_subscription_map
+ origin = @subscription_map.instance_variable_get(:@subscribers).dup
+
+ @subscription_map.broadcast("not_exist_channel", "")
+
+ assert_equal origin, @subscription_map.instance_variable_get(:@subscribers)
+ end
+
+ private
+ def setup_subscription_map
+ @subscription_map = ActionCable::SubscriptionAdapter::SubscriberMap.new
+ end
+end
diff --git a/actioncable/test/subscription_adapter/test_adapter_test.rb b/actioncable/test/subscription_adapter/test_adapter_test.rb
new file mode 100644
index 0000000000..3fe07adb4a
--- /dev/null
+++ b/actioncable/test/subscription_adapter/test_adapter_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "common"
+
+class ActionCable::SubscriptionAdapter::TestTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ @tx_adapter.shutdown
+ @tx_adapter = @rx_adapter
+ end
+
+ def cable_config
+ { adapter: "test" }
+ end
+
+ test "#broadcast stores messages for streams" do
+ @tx_adapter.broadcast("channel", "payload")
+ @tx_adapter.broadcast("channel2", "payload2")
+
+ assert_equal ["payload"], @tx_adapter.broadcasts("channel")
+ assert_equal ["payload2"], @tx_adapter.broadcasts("channel2")
+ end
+
+ test "#clear_messages deletes recorded broadcasts for the channel" do
+ @tx_adapter.broadcast("channel", "payload")
+ @tx_adapter.broadcast("channel2", "payload2")
+
+ @tx_adapter.clear_messages("channel")
+
+ assert_equal [], @tx_adapter.broadcasts("channel")
+ assert_equal ["payload2"], @tx_adapter.broadcasts("channel2")
+ end
+
+ test "#clear deletes all recorded broadcasts" do
+ @tx_adapter.broadcast("channel", "payload")
+ @tx_adapter.broadcast("channel2", "payload2")
+
+ @tx_adapter.clear
+
+ assert_equal [], @tx_adapter.broadcasts("channel")
+ assert_equal [], @tx_adapter.broadcasts("channel2")
+ end
+end