require 'spec_helper' # these tests avoid the use of ActiveRecord and FactoryGirl to do blackbox, non test-instrumented tests describe ConnectionManager, no_transaction: true do TRACKS = [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "some_client_track_id"}] STALE_TIME = 40 EXPIRE_TIME = 60 STALE_BUT_NOT_EXPIRED = 50 DEFINITELY_EXPIRED = 70 GATEWAY = 'gateway1' REACHABLE = true let(:channel_id) { '1' } before do @conn = PG::Connection.new(:dbname => SpecDb::TEST_DB_NAME, :user => "postgres", :password => "postgres", :host => "localhost") @connman = ConnectionManager.new(:conn => @conn) @message_factory = MessageFactory.new end def create_user(first_name, last_name, email, options = {:musician => true}) @conn.exec("INSERT INTO users (first_name, last_name, email, musician, encrypted_password, city, state, country) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id", [first_name, last_name, email, options[:musician], '1', 'Apex', 'NC', 'US']) do |result| return result.getvalue(0, 0) end end def assert_num_connections(client_id, expected_num_connections) # make sure the connection is still there @conn.exec("SELECT count(*) FROM connections where client_id = $1", [client_id]) do |result| result.getvalue(0, 0).to_i.should == expected_num_connections end end def assert_session_exists(music_session_id, exists) @conn.exec("SELECT count(*) FROM active_music_sessions where id = $1", [music_session_id]) do |result| if exists result.getvalue(0, 0).should == "1" else result.getvalue(0, 0).should == "0" end end end describe "cleanup_dangling" do it "success" do @connman.cleanup_dangling client_id = "client_id9" user_id = create_user("test", "user9", "user9@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) Connection.where('music_session_id is not null').count.should == 1 @connman.cleanup_dangling Connection.where('music_session_id is not null').count.should == 1 # well in the past music_session.update_attribute("updated_at", '2020-01-01 00:00:00') @connman.cleanup_dangling Connection.where('music_session_id is not null').count.should == 0 end end it "can't create two client_ids of same value" do client_id = "client_id1" user_id = create_user("test", "user1", "user1@jamkazam.com") user = User.find(user_id) user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user) user.save! user = nil @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) expect { @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) }.to raise_error(PG::Error) end it "create connection then delete it" do client_id = "client_id2" #user_id = create_user("test", "user2", "user2@jamkazam.com") user = FactoryGirl.create(:user) count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) count.should == 1 # make sure the connection is seen @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 1 end cc = Connection.find_by_client_id!(client_id) cc.connected?.should be_true cc.ip_address.should eql("1.1.1.1") cc.addr.should == 0x01010101 count = @connman.delete_connection(client_id) count.should == 0 @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 0 end end it "create connection, reconnect, then delete it" do client_id = "client_id3" #user_id = create_user("test", "user2", "user2@jamkazam.com") user = FactoryGirl.create(:user) count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) count.should == 1 # make sure the connection is seen @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 1 end cc = Connection.find_by_client_id!(client_id) cc.connected?.should be_true cc.ip_address.should eql("1.1.1.1") cc.addr.should == 0x01010101 cc.udp_reachable.should == true @connman.reconnect(cc, channel_id, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME, false, GATEWAY) cc = Connection.find_by_client_id!(client_id) cc.connected?.should be_true cc.ip_address.should eql("33.1.2.3") cc.udp_reachable.should == false count = @connman.delete_connection(client_id) count.should == 0 @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 0 end end it "create connection, reconnect via heartbeat" do client_id = "client_id3" #user_id = create_user("test", "user2", "user2@jamkazam.com") user = FactoryGirl.create(:user) count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, false, GATEWAY, false) count.should == 1 # make sure the connection is seen @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 1 end cc = Connection.find_by_client_id!(client_id) cc.connected?.should be_true cc.ip_address.should eql("1.1.1.1") cc.udp_reachable.should == false @connman.reconnect(cc, channel_id, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME, nil, GATEWAY) # heartbeat passes nil in for udp_reachable cc = Connection.find_by_client_id!(client_id) cc.connected?.should be_true cc.ip_address.should eql("33.1.2.3") cc.udp_reachable.should == false count = @connman.delete_connection(client_id) count.should == 0 @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user.id]) do |result| result.getvalue(0, 0).to_i.should == 0 end end # it "create connection creates user joined message appropriately" do # client_id = "client_id3" # client_id2 = "client_id3_1" # user_id = create_user("test", "user3", "user3@jamkazam.com") # # we should get a message saying that this user is online # friend_update = @message_factory.friend_update(user_id, true) # @connman.mq_router.should_receive(:publish_to_friends).with([], friend_update, user_id) # @connman.create_connection(user_id, client_id, "1.1.1.1", 'client') # # but a second connection from the same user should cause no such message # @connman.should_receive(:publish_to_friends).exactly(0).times # @connman.create_connection(user_id, client_id2, "1.1.1.1", 'client') # end # it "deletes connection creates user left message appropriately" do # client_id = "client_id4" # client_id2 = "client_id4_1" # user_id = create_user("test", "user4", "user4@jamkazam.com") # # we should get a message saying that this user is online # @connman.create_connection(user_id, client_id, "1.1.1.1", 'client') # @connman.create_connection(user_id, client_id2, "1.1.1.1", 'client') # # deleting one of the two connections should cause no messages # @connman.should_receive(:publish_to_friends).exactly(0).times # @connman.delete_connection(client_id) # # but deleting the final connection should cause a left message # friend_update = @message_factory.friend_update(user_id, false) # @connman.mq_router.should_receive(:publish_to_friends).with([], friend_update, user_id) # @connman.delete_connection(client_id2) # end it "lookup of friends should find mutual friends only" do def create_friend(user_id, friend_id) @conn.exec("INSERT INTO friendships(user_id, friend_id) VALUES ($1, $2)", [user_id, friend_id]) end def delete_friend(user_id, friend_id) @conn.exec("DELETE FROM friendships WHERE user_id = $1 AND friend_id = $2", [user_id, friend_id]) end client_id = "client_id5" user_id1 = create_user("test", "user5", "user5@jamkazam.com") user_id2 = create_user("test", "user6", "user6@jamkazam.com") user_id3 = create_user("test", "user7", "user7@jamkazam.com") @connman.gather_friends(@conn, user_id1).should == [] @connman.gather_friends(@conn, user_id2).should == [] @connman.gather_friends(@conn, user_id3).should == [] # create one-way link create_friend(user_id1, user_id2) @connman.gather_friends(@conn, user_id1).should == [] @connman.gather_friends(@conn, user_id2).should == [] @connman.gather_friends(@conn, user_id3).should == [] # create one-way link back the other way create_friend(user_id2, user_id1) @connman.gather_friends(@conn, user_id1).should == [user_id2] @connman.gather_friends(@conn, user_id2).should == [user_id1] @connman.gather_friends(@conn, user_id3).should == [] # make sure a new link to user 1 > user 3 doesn't disrupt anything create_friend(user_id1, user_id3) @connman.gather_friends(@conn, user_id1).should == [user_id2] @connman.gather_friends(@conn, user_id2).should == [user_id1] @connman.gather_friends(@conn, user_id3).should == [] # make sure a new link to user 1 > user 3 doesn't disrupt anything create_friend(user_id3, user_id1) @connman.gather_friends(@conn, user_id1).should =~ [user_id2, user_id3] @connman.gather_friends(@conn, user_id2).should == [user_id1] @connman.gather_friends(@conn, user_id3).should == [user_id1] end it "flag stale connection" do client_id = "client_id8" user_id = create_user("test", "user8", "user8@jamkazam.com") @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) num = JamRuby::Connection.where('aasm_state = ?', 'connected').count num.should == 1 assert_num_connections(client_id, num) @connman.flag_stale_connections(GATEWAY) assert_num_connections(client_id, num) conn = Connection.find_by_client_id(client_id) set_updated_at(conn, Time.now - STALE_BUT_NOT_EXPIRED) num = Connection.where("updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'connected'").count num.should == 1 # this should change the aasm_state to stale @connman.flag_stale_connections(GATEWAY) num = Connection.where("updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'connected'").count num.should == 0 num = Connection.where("updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'stale'").count num.should == 1 assert_num_connections(client_id, 1) conn = Connection.find_by_client_id(client_id) set_updated_at(conn, Time.now - DEFINITELY_EXPIRED) cids = @connman.stale_connection_client_ids(GATEWAY) cids.size.should == 1 cids[0][:client_id].should == client_id cids[0][:client_type].should == Connection::TYPE_CLIENT cids[0][:music_session_id].should be_nil cids[0][:user_id].should == user_id cids.each { |cid| @connman.delete_connection(cid[:client_id]) } assert_num_connections(client_id, 0) end it "expires stale connection" do client_id = "client_id8" user_id = create_user("test", "user8", "user8@jamkazam.com") @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) conn = Connection.find_by_client_id(client_id) set_updated_at(conn, Time.now - STALE_BUT_NOT_EXPIRED) @connman.flag_stale_connections(GATEWAY) assert_num_connections(client_id, 1) # assert_num_connections(client_id, JamRuby::Connection.count(:conditions => ['aasm_state = ?','stale'])) @connman.expire_stale_connections(GATEWAY) assert_num_connections(client_id, 1) set_updated_at(conn, Time.now - DEFINITELY_EXPIRED) # this should delete the stale connection @connman.expire_stale_connections(GATEWAY) assert_num_connections(client_id, 0) end it "connections with music_sessions associated" do client_id = "client_id9" user_id = create_user("test", "user9", "user9@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) connection.errors.any?.should be_false assert_session_exists(music_session_id, true) @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result| result.getvalue(0, 0).should == music_session_id end @connman.delete_connection(client_id) assert_num_connections(client_id, 0) assert_session_exists(music_session_id, false) end it "join_music_session fails if no connection" do client_id = "client_id10" user_id = create_user("test", "user10", "user10@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(JamRuby::JamRecordNotFound) end it "join_music_session fails if user is a fan but wants to join as a musician" do client_id = "client_id10.11" client_id2 = "client_id10.12" user_id = create_user("test", "user10.11", "user10.11@jamkazam.com", :musician => true) user_id2 = create_user("test", "user10.12", "user10.12@jamkazam.com", :musician => false) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) @connman.create_connection(user_id2, client_id2, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) user = User.find(user_id2) connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS, 10) connection.errors.size.should == 1 connection.errors[:as_musician].should == [ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN] end it "as_musician is coerced to boolean" do client_id = "client_id10.2" user_id = create_user("test", "user10.2", "user10.2@jamkazam.com") @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) music_session = FactoryGirl.create(:active_music_session, user_id: user_id) user = User.find(user_id) connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS, 10) connection.errors.size.should == 0 connection.as_musician.should be_false end it "join_music_session fails if fan_access=false and the user is a fan" do musician_client_id = "client_id10.3" fan_client_id = "client_id10.4" musician_id = create_user("test", "user10.3", "user10.3@jamkazam.com") fan_id = create_user("test", "user10.4", "user10.4@jamkazam.com", :musician => false) @connman.create_connection(musician_id, musician_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) @connman.create_connection(fan_id, fan_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) music_session = FactoryGirl.create(:active_music_session, :fan_access => false, user_id: musician_id) music_session_id = music_session.id user = User.find(musician_id) @connman.join_music_session(user, musician_client_id, music_session, true, TRACKS, 10) # now join the session as a fan, bt fan_access = false user = User.find(fan_id) connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS, 10) connection.errors.size.should == 1 end it "join_music_session fails if incorrect user_id specified" do client_id = "client_id20" user_id = create_user("test", "user20", "user20@jamkazam.com") user_id2 = create_user("test", "user21", "user21@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id2) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) # specify real user id, but not associated with this session expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(JamRuby::JamPermissionError) end it "join_music_session fails if no music_session" do client_id = "client_id11" user_id = create_user("test", "user11", "user11@jamkazam.com") user = User.find(user_id) music_session = ActiveMusicSession.new @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) connection.errors.size.should == 1 connection.errors[:music_session].should == [ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED] end it "join_music_session fails if approval_required and no invitation, but generates join_request" do client_id = "client_id11.1" user_id = create_user("test", "user11.1", "user11.1@jamkazam.com") user_id2 = create_user("test", "user11.2", "user11.2@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, :approval_required => true, user_id: user_id) music_session_id = music_session.id user = User.find(user_id2) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) # specify real user id, but not associated with this session expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(JamRuby::JamPermissionError) end it "leave_music_session fails if no music_session" do client_id = "client_id12" user_id = create_user("test", "user12", "user12@jamkazam.com") user = User.find(user_id) dummy_music_session = ActiveMusicSession.new @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError) end it "leave_music_session fails if in different music_session" do client_id = "client_id13" user_id = create_user("test", "user13", "user13@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) dummy_music_session = ActiveMusicSession.new @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError) end it "leave_music_session works" do client_id = "client_id14" user_id = create_user("test", "user14", "user14@jamkazam.com") music_session = FactoryGirl.create(:active_music_session, user_id: user_id) music_session_id = music_session.id user = User.find(user_id) @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) assert_session_exists(music_session_id, true) @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result| result.getvalue(0, 0).should == music_session_id end @connman.leave_music_session(user, Connection.find_by_client_id(client_id), music_session) @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result| result.getvalue(0, 0).should == nil end assert_session_exists(music_session_id, false) @connman.delete_connection(client_id) assert_num_connections(client_id, 0) end it "join_music_session fails if user has music_session already active" do # there are two different problems: user can only be in one active music_session at a time, # and a connection can only point to one active music_session at a time. this is a test of # the former but we need a test of the latter, too. pending end it "join_music_session fails if connection has music_session already active" do # there are two different problems: user can only be in one active music_session at a time, # and a connection can only point to one active music_session at a time. this is a test of # the latter but we need a test of the former, too. user_id = create_user("test", "user11", "user11@jamkazam.com") user = User.find(user_id) client_id1 = Faker::Number.number(20) @connman.create_connection(user_id, client_id1, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) music_session1 = FactoryGirl.create(:active_music_session, :user_id => user_id) connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS, 10) connection1.errors.size.should == 0 music_session2 = FactoryGirl.create(:active_music_session, :user_id => user_id) connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS, 10) connection2.errors.size.should == 1 connection2.errors[:music_session].should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS] # client_id2 = Faker::Number.number(20) # @connman.create_connection(user_id, client_id2, "2.2.2.2", 'client') # music_session2 = MusicSession.find(create_music_session(user_id)) # connection2 = @connman.join_music_session(user, client_id2, music_session2, true, TRACKS) # # connection2.errors.size.should == 1 # connection2.errors[:music_session].should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS] # # user.update_attribute(:admin, true) # client_id = Faker::Number.number(20) # @connman.create_connection(user_id, client_id, "1.1.1.1", 'client') # music_session = MusicSession.find(create_music_session(user_id)) # connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS) # client_id = Faker::Number.number(20) # @connman.create_connection(user_id, client_id, Faker::Internet.ip_v4_address, 'client') # music_session = MusicSession.find(create_music_session(user_id)) # connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS) # connection.errors.size.should == 0 end end