diff options
Diffstat (limited to 'actioncable/test/connection')
-rw-r--r-- | actioncable/test/connection/authorization_test.rb | 33 | ||||
-rw-r--r-- | actioncable/test/connection/base_test.rb | 141 | ||||
-rw-r--r-- | actioncable/test/connection/client_socket_test.rb | 84 | ||||
-rw-r--r-- | actioncable/test/connection/cross_site_forgery_test.rb | 92 | ||||
-rw-r--r-- | actioncable/test/connection/identifier_test.rb | 77 | ||||
-rw-r--r-- | actioncable/test/connection/multiple_identifiers_test.rb | 43 | ||||
-rw-r--r-- | actioncable/test/connection/stream_test.rb | 66 | ||||
-rw-r--r-- | actioncable/test/connection/string_identifier_test.rb | 45 | ||||
-rw-r--r-- | actioncable/test/connection/subscriptions_test.rb | 116 |
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 |