aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/test/connection
diff options
context:
space:
mode:
Diffstat (limited to 'actioncable/test/connection')
-rw-r--r--actioncable/test/connection/authorization_test.rb33
-rw-r--r--actioncable/test/connection/base_test.rb141
-rw-r--r--actioncable/test/connection/client_socket_test.rb84
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb92
-rw-r--r--actioncable/test/connection/identifier_test.rb77
-rw-r--r--actioncable/test/connection/multiple_identifiers_test.rb43
-rw-r--r--actioncable/test/connection/stream_test.rb66
-rw-r--r--actioncable/test/connection/string_identifier_test.rb45
-rw-r--r--actioncable/test/connection/subscriptions_test.rb116
9 files changed, 697 insertions, 0 deletions
diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb
new file mode 100644
index 0000000000..7d039336b8
--- /dev/null
+++ b/actioncable/test/connection/authorization_test.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def connect
+ reject_unauthorized_connection
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ test "unauthorized connection" do
+ run_in_eventmachine do
+ server = TestServer.new
+ server.config.allowed_request_origins = %w( http://rubyonrails.com )
+
+ env = Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
+ "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
+
+ connection = Connection.new(server, env)
+ connection.websocket.expects(:close)
+
+ connection.process
+ end
+ end
+end
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
new file mode 100644
index 0000000000..99488e38c8
--- /dev/null
+++ b/actioncable/test/connection/base_test.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+require "active_support/core_ext/object/json"
+
+class ActionCable::Connection::BaseTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket, :subscriptions, :message_buffer, :connected
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "making a connection with invalid headers" do
+ run_in_eventmachine do
+ connection = ActionCable::Connection::Base.new(@server, Rack::MockRequest.env_for("/test"))
+ response = connection.process
+ assert_equal 404, response[0]
+ end
+ end
+
+ test "websocket connection" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ assert connection.websocket.possible?
+
+ wait_for_async
+ assert connection.websocket.alive?
+ end
+ end
+
+ test "rack response" do
+ run_in_eventmachine do
+ connection = open_connection
+ response = connection.process
+
+ assert_equal [ -1, {}, [] ], response
+ end
+ end
+
+ test "on connection open" do
+ run_in_eventmachine do
+ connection = open_connection
+
+ connection.websocket.expects(:transmit).with({ type: "welcome" }.to_json)
+ connection.message_buffer.expects(:process!)
+
+ connection.process
+ wait_for_async
+
+ assert_equal [ connection ], @server.connections
+ assert connection.connected
+ end
+ end
+
+ test "on connection close" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ # Setup the connection
+ connection.server.stubs(:timer).returns(true)
+ connection.send :handle_open
+ assert connection.connected
+
+ connection.subscriptions.expects(:unsubscribe_from_all)
+ connection.send :handle_close
+
+ assert ! connection.connected
+ assert_equal [], @server.connections
+ end
+ end
+
+ test "connection statistics" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ statistics = connection.statistics
+
+ assert statistics[:identifier].blank?
+ assert_kind_of Time, statistics[:started_at]
+ assert_equal [], statistics[:subscriptions]
+ end
+ end
+
+ test "explicitly closing a connection" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ connection.websocket.expects(:close)
+ connection.close
+ end
+ end
+
+ test "rejecting a connection causes a 404" do
+ run_in_eventmachine do
+ class CallMeMaybe
+ def call(*)
+ raise "Do not call me!"
+ end
+ end
+
+ env = Rack::MockRequest.env_for(
+ "/test",
+ "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
+ "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.org", "rack.hijack" => CallMeMaybe.new
+ )
+
+ connection = ActionCable::Connection::Base.new(@server, env)
+ response = connection.process
+ assert_equal 404, response[0]
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
+ "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
+
+ Connection.new(@server, env)
+ end
+end
diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb
new file mode 100644
index 0000000000..2051216010
--- /dev/null
+++ b/actioncable/test/connection/client_socket_test.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :connected, :websocket, :errors
+
+ def initialize(*)
+ super
+ @errors = []
+ end
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+
+ def on_error(message)
+ @errors << message
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "delegate socket errors to on_error handler" do
+ run_in_eventmachine do
+ connection = open_connection
+
+ # Internal hax = :(
+ client = connection.websocket.send(:websocket)
+ client.instance_variable_get("@stream").expects(:write).raises("foo")
+ client.expects(:client_gone).never
+
+ client.write("boo")
+ assert_equal %w[ foo ], connection.errors
+ end
+ end
+
+ test "closes hijacked i/o socket at shutdown" do
+ run_in_eventmachine do
+ connection = open_connection
+
+ client = connection.websocket.send(:websocket)
+ event = Concurrent::Event.new
+ client.instance_variable_get("@stream")
+ .instance_variable_get("@rack_hijack_io")
+ .define_singleton_method(:close) { event.set }
+ connection.close
+ event.wait
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for "/test",
+ "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
+ "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
+ io = \
+ begin
+ Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).first
+ rescue
+ StringIO.new
+ end
+ env["rack.hijack"] = -> { env["rack.hijack_io"] = io }
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ connection.send :handle_open
+ assert connection.connected
+ end
+ end
+end
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
new file mode 100644
index 0000000000..3e21138ffc
--- /dev/null
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
+ HOST = "rubyonrails.com"
+
+ class Connection < ActionCable::Connection::Base
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ @server.config.allow_same_origin_as_host = false
+ end
+
+ teardown do
+ @server.config.disable_request_forgery_protection = false
+ @server.config.allowed_request_origins = []
+ @server.config.allow_same_origin_as_host = true
+ end
+
+ test "disable forgery protection" do
+ @server.config.disable_request_forgery_protection = true
+ assert_origin_allowed "http://rubyonrails.com"
+ assert_origin_allowed "http://hax.com"
+ end
+
+ test "explicitly specified a single allowed origin" do
+ @server.config.allowed_request_origins = "http://hax.com"
+ assert_origin_not_allowed "http://rubyonrails.com"
+ assert_origin_allowed "http://hax.com"
+ end
+
+ test "explicitly specified multiple allowed origins" do
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com http://www.rubyonrails.com )
+ assert_origin_allowed "http://rubyonrails.com"
+ assert_origin_allowed "http://www.rubyonrails.com"
+ assert_origin_not_allowed "http://hax.com"
+ end
+
+ test "explicitly specified a single regexp allowed origin" do
+ @server.config.allowed_request_origins = /.*ha.*/
+ assert_origin_not_allowed "http://rubyonrails.com"
+ assert_origin_allowed "http://hax.com"
+ end
+
+ test "explicitly specified multiple regexp allowed origins" do
+ @server.config.allowed_request_origins = [/http:\/\/ruby.*/, /.*rai.s.*com/, "string" ]
+ assert_origin_allowed "http://rubyonrails.com"
+ assert_origin_allowed "http://www.rubyonrails.com"
+ assert_origin_not_allowed "http://hax.com"
+ assert_origin_not_allowed "http://rails.co.uk"
+ end
+
+ test "allow same origin as host" do
+ @server.config.allow_same_origin_as_host = true
+ assert_origin_allowed "http://#{HOST}"
+ assert_origin_not_allowed "http://hax.com"
+ assert_origin_not_allowed "http://rails.co.uk"
+ end
+
+ private
+ def assert_origin_allowed(origin)
+ response = connect_with_origin origin
+ assert_equal(-1, response[0])
+ end
+
+ def assert_origin_not_allowed(origin)
+ response = connect_with_origin origin
+ assert_equal 404, response[0]
+ end
+
+ def connect_with_origin(origin)
+ response = nil
+
+ run_in_eventmachine do
+ response = Connection.new(@server, env_for_origin(origin)).process
+ end
+
+ response
+ end
+
+ def env_for_origin(origin)
+ Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", "SERVER_NAME" => HOST,
+ "HTTP_HOST" => HOST, "HTTP_ORIGIN" => origin
+ end
+end
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
new file mode 100644
index 0000000000..6b6c8cd31a
--- /dev/null
+++ b/actioncable/test/connection/identifier_test.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+require "stubs/user"
+
+class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user
+ attr_reader :websocket
+
+ public :process_internal_message
+
+ def connect
+ self.current_user = User.new "lifo"
+ end
+ end
+
+ test "connection identifier" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "User#lifo", @connection.connection_identifier
+ end
+ end
+
+ test "should subscribe to internal channel on open and unsubscribe on close" do
+ run_in_eventmachine do
+ pubsub = mock("pubsub_adapter")
+ pubsub.expects(:subscribe).with("action_cable/User#lifo", kind_of(Proc))
+ pubsub.expects(:unsubscribe).with("action_cable/User#lifo", kind_of(Proc))
+
+ server = TestServer.new
+ server.stubs(:pubsub).returns(pubsub)
+
+ open_connection server: server
+ close_connection
+ end
+ end
+
+ test "processing disconnect message" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+
+ @connection.websocket.expects(:close)
+ @connection.process_internal_message "type" => "disconnect"
+ end
+ end
+
+ test "processing invalid message" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+
+ @connection.websocket.expects(:close).never
+ @connection.process_internal_message "type" => "unknown"
+ end
+ end
+
+ private
+ def open_connection_with_stubbed_pubsub
+ server = TestServer.new
+ server.stubs(:adapter).returns(stub_everything("adapter"))
+
+ open_connection server: server
+ end
+
+ def open_connection(server:)
+ env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket"
+ @connection = Connection.new(server, env)
+
+ @connection.process
+ @connection.send :handle_open
+ end
+
+ def close_connection
+ @connection.send :handle_close
+ end
+end
diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb
new file mode 100644
index 0000000000..230433a110
--- /dev/null
+++ b/actioncable/test/connection/multiple_identifiers_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+require "stubs/user"
+
+class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user, :current_room
+
+ def connect
+ self.current_user = User.new "lifo"
+ self.current_room = Room.new "my", "room"
+ end
+ end
+
+ test "multiple connection identifiers" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "Room#my-room:User#lifo", @connection.connection_identifier
+ end
+ end
+
+ private
+ def open_connection_with_stubbed_pubsub
+ server = TestServer.new
+ server.stubs(:pubsub).returns(stub_everything("pubsub"))
+
+ open_connection server: server
+ end
+
+ def open_connection(server:)
+ env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket"
+ @connection = Connection.new(server, env)
+
+ @connection.process
+ @connection.send :handle_open
+ end
+
+ def close_connection
+ @connection.send :handle_close
+ end
+end
diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb
new file mode 100644
index 0000000000..b0419b0994
--- /dev/null
+++ b/actioncable/test/connection/stream_test.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::Connection::StreamTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :connected, :websocket, :errors
+
+ def initialize(*)
+ super
+ @errors = []
+ end
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+
+ def on_error(message)
+ @errors << message
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ [ EOFError, Errno::ECONNRESET ].each do |closed_exception|
+ test "closes socket on #{closed_exception}" do
+ run_in_eventmachine do
+ connection = open_connection
+
+ # Internal hax = :(
+ client = connection.websocket.send(:websocket)
+ client.instance_variable_get("@stream").instance_variable_get("@rack_hijack_io").expects(:write).raises(closed_exception, "foo")
+ client.expects(:client_gone)
+
+ client.write("boo")
+ assert_equal [], connection.errors
+ end
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for "/test",
+ "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
+ "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
+ env["rack.hijack"] = -> { env["rack.hijack_io"] = StringIO.new }
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ connection.send :handle_open
+ assert connection.connected
+ end
+ end
+end
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
new file mode 100644
index 0000000000..6387fb792c
--- /dev/null
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "stubs/test_server"
+
+class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_token
+
+ def connect
+ self.current_token = "random-string"
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ test "connection identifier" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "random-string", @connection.connection_identifier
+ end
+ end
+
+ private
+ def open_connection_with_stubbed_pubsub
+ @server = TestServer.new
+ @server.stubs(:pubsub).returns(stub_everything("pubsub"))
+
+ open_connection
+ end
+
+ def open_connection
+ env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket"
+ @connection = Connection.new(@server, env)
+
+ @connection.process
+ @connection.send :on_open
+ end
+
+ def close_connection
+ @connection.send :on_close
+ end
+end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
new file mode 100644
index 0000000000..149a40604a
--- /dev/null
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ class ChatChannel < ActionCable::Channel::Base
+ attr_reader :room, :lines
+
+ def subscribed
+ @room = Room.new params[:id]
+ @lines = []
+ end
+
+ def speak(data)
+ @lines << data
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+
+ @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: "ActionCable::Connection::SubscriptionsTest::ChatChannel")
+ end
+
+ test "subscribe command" do
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
+
+ assert_kind_of ChatChannel, channel
+ assert_equal 1, channel.room.id
+ end
+ end
+
+ test "subscribe command without an identifier" do
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command "command" => "subscribe"
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "unsubscribe command" do
+ run_in_eventmachine do
+ setup_connection
+ subscribe_to_chat_channel
+
+ channel = subscribe_to_chat_channel
+ channel.expects(:unsubscribe_from_channel)
+
+ @subscriptions.execute_command "command" => "unsubscribe", "identifier" => @chat_identifier
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "unsubscribe command without an identifier" do
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command "command" => "unsubscribe"
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "message command" do
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
+
+ data = { "content" => "Hello World!", "action" => "speak" }
+ @subscriptions.execute_command "command" => "message", "identifier" => @chat_identifier, "data" => ActiveSupport::JSON.encode(data)
+
+ assert_equal [ data ], channel.lines
+ end
+ end
+
+ test "unsubscribe from all" do
+ run_in_eventmachine do
+ setup_connection
+
+ channel1 = subscribe_to_chat_channel
+
+ channel2_id = ActiveSupport::JSON.encode(id: 2, channel: "ActionCable::Connection::SubscriptionsTest::ChatChannel")
+ channel2 = subscribe_to_chat_channel(channel2_id)
+
+ channel1.expects(:unsubscribe_from_channel)
+ channel2.expects(:unsubscribe_from_channel)
+
+ @subscriptions.unsubscribe_from_all
+ end
+ end
+
+ private
+ def subscribe_to_chat_channel(identifier = @chat_identifier)
+ @subscriptions.execute_command "command" => "subscribe", "identifier" => identifier
+ assert_equal identifier, @subscriptions.identifiers.last
+
+ @subscriptions.send :find, "identifier" => identifier
+ end
+
+ def setup_connection
+ env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket"
+ @connection = Connection.new(@server, env)
+
+ @subscriptions = ActionCable::Connection::Subscriptions.new(@connection)
+ end
+end