483 lines
17 KiB
Ruby
483 lines
17 KiB
Ruby
#
|
|
# Copyright (C) 2011 Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper.rb')
|
|
|
|
describe ConversationParticipant do
|
|
it "should correctly set up conversations" do
|
|
sender = user
|
|
recipient = user
|
|
convo = sender.initiate_conversation([recipient])
|
|
convo.add_message('test')
|
|
|
|
expect(sender.conversations).to eq [convo]
|
|
expect(convo.participants.size).to eq 2
|
|
expect(convo.conversation.participants.size).to eq 2
|
|
expect(convo.messages.size).to eq 1
|
|
end
|
|
|
|
it "should not decrement unread_conversations_count to a negative number" do
|
|
sender = user
|
|
recipient = user
|
|
convo = sender.initiate_conversation([recipient])
|
|
convo.add_message('test')
|
|
|
|
User.where(:id => recipient).update_all(:unread_conversations_count => 0) # force into the wrong state
|
|
|
|
part = recipient.conversations.first
|
|
part.update_one(:event => 'mark_as_read')
|
|
|
|
recipient.reload
|
|
expect(recipient.unread_conversations_count).to eq 0
|
|
end
|
|
|
|
it "should correctly manage messages" do
|
|
sender = user
|
|
recipient = user
|
|
convo = sender.initiate_conversation([recipient])
|
|
convo.add_message('test')
|
|
convo.add_message('another')
|
|
rconvo = recipient.conversations.first
|
|
expect(convo.messages.size).to eq 2
|
|
expect(rconvo.messages.size).to eq 2
|
|
|
|
convo.remove_messages(convo.messages.last)
|
|
convo.messages.reload
|
|
expect(convo.messages.size).to eq 1
|
|
expect(convo.all_messages.size).to eq 2
|
|
# the recipient's messages are unaffected, since removing a message
|
|
# only sets workflow state on the join table.
|
|
expect(rconvo.messages.size).to eq 2
|
|
|
|
convo.remove_messages(:all)
|
|
expect(convo.messages.size).to eq 0
|
|
expect(convo.all_messages.size).to eq 2
|
|
rconvo.reload
|
|
expect(rconvo.messages.size).to eq 2
|
|
|
|
convo.delete_messages(:all)
|
|
expect(convo.all_messages.size).to eq 0
|
|
|
|
rconvo.delete_messages(rconvo.messages.last)
|
|
expect(rconvo.messages.size).to eq 1
|
|
expect(rconvo.all_messages.size).to eq 1
|
|
end
|
|
|
|
it "should update the updated_at stamp of its user on workflow_state change" do
|
|
sender = user
|
|
recipient = user
|
|
updated_at = sender.updated_at
|
|
conversation = sender.initiate_conversation([recipient])
|
|
conversation.update_attribute(:workflow_state, 'unread')
|
|
expect(sender.reload.updated_at).not_to eql updated_at
|
|
end
|
|
|
|
it "should support starred/starred=" do
|
|
sender = user
|
|
recipient = user
|
|
conversation = sender.initiate_conversation([recipient])
|
|
|
|
conversation.starred = true
|
|
conversation.save
|
|
conversation.reload
|
|
expect(conversation.starred).to be_truthy
|
|
|
|
conversation.starred = false
|
|
conversation.save
|
|
conversation.reload
|
|
expect(conversation.starred).to be_falsey
|
|
end
|
|
|
|
it "should support :starred in update_attributes" do
|
|
sender = user
|
|
recipient = user
|
|
conversation = sender.initiate_conversation([recipient])
|
|
|
|
conversation.update_attributes(:starred => true)
|
|
conversation.save
|
|
conversation.reload
|
|
expect(conversation.starred).to be_truthy
|
|
|
|
conversation.update_attributes(:starred => false)
|
|
conversation.save
|
|
conversation.reload
|
|
expect(conversation.starred).to be_falsey
|
|
end
|
|
|
|
context "tagged scope" do
|
|
def conversation_for(*tags_or_users)
|
|
users, tags = tags_or_users.partition{ |u| u.is_a?(User) }
|
|
users << user if users.empty?
|
|
c = @me.initiate_conversation(users)
|
|
c.add_message("test")
|
|
c.tags = tags
|
|
c.save!
|
|
c.reload
|
|
end
|
|
|
|
before :once do
|
|
@me = user
|
|
@c1 = conversation_for("course_1")
|
|
@c2 = conversation_for("course_1", "course_2")
|
|
@c3 = conversation_for("course_2")
|
|
@c4 = conversation_for("group_1")
|
|
@c5 = conversation_for(@u1 = user)
|
|
@c6 = conversation_for(@u2 = user)
|
|
@c7 = conversation_for(@u1, @u2)
|
|
@c8 = conversation_for("course_1", @u1, user)
|
|
end
|
|
|
|
it "should return conversations that match the given course" do
|
|
expect(@me.conversations.tagged("course_1").sort_by(&:id)).to eql [@c1, @c2, @c8]
|
|
end
|
|
|
|
it "should return conversations that match any of the given courses" do
|
|
expect(@me.conversations.tagged("course_1", "course_2").sort_by(&:id)).to eql [@c1, @c2, @c3, @c8]
|
|
end
|
|
|
|
it "should return conversations that match all of the given courses" do
|
|
expect(@me.conversations.tagged("course_1", "course_2", :mode => :and).sort_by(&:id)).to eql [@c2]
|
|
end
|
|
|
|
it "should return conversations that match the given group" do
|
|
expect(@me.conversations.tagged("group_1").sort_by(&:id)).to eql [@c4]
|
|
end
|
|
|
|
it "should return conversations that match the given user" do
|
|
expect(@me.conversations.tagged(@u1.asset_string).sort_by(&:id)).to eql [@c5, @c7, @c8]
|
|
end
|
|
|
|
it "should return conversations that match any of the given users" do
|
|
expect(@me.conversations.tagged(@u1.asset_string, @u2.asset_string).sort_by(&:id)).to eql [@c5, @c6, @c7, @c8]
|
|
end
|
|
|
|
it "should return conversations that match all of the given users" do
|
|
expect(@me.conversations.tagged(@u1.asset_string, @u2.asset_string, :mode => :and).sort_by(&:id)).to eql [@c7]
|
|
end
|
|
|
|
it "should return conversations that match either the given course or user" do
|
|
expect(@me.conversations.tagged(@u1.asset_string, "course_1").sort_by(&:id)).to eql [@c1, @c2, @c5, @c7, @c8]
|
|
end
|
|
|
|
it "should return conversations that match both the given course and user" do
|
|
expect(@me.conversations.tagged(@u1.asset_string, "course_1", :mode => :and).sort_by(&:id)).to eql [@c8]
|
|
end
|
|
|
|
context "sharding" do
|
|
specs_require_sharding
|
|
|
|
it "should find conversations for users on different shards" do
|
|
@shard1.activate do
|
|
@u3 = user
|
|
@c9 = conversation_for(@u3)
|
|
end
|
|
expect(@me.conversations.tagged(@u3.asset_string).map(&:conversation)).to eq [@c9.conversation]
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for_masquerading_user scope" do
|
|
before :once do
|
|
@a1 = Account.create
|
|
@a2 = Account.create
|
|
@a3 = Account.create
|
|
@admin_user = user
|
|
@a1.account_users.create!(user: @admin_user)
|
|
@a2.account_users.create!(user: @admin_user)
|
|
@a3.pseudonyms.create!(:user => @admin_user, :unique_id => 'a3') # in the account, but not an admin
|
|
|
|
@target_user = user
|
|
# visible to @user
|
|
@c1 = @target_user.initiate_conversation([user])
|
|
@c1.add_message("hey man", :root_account_id => @a1.id)
|
|
@c2 = @target_user.initiate_conversation([user])
|
|
@c2.add_message("foo", :root_account_id => @a1.id)
|
|
@c2.add_message("bar", :root_account_id => @a2.id)
|
|
# invisible to @user, unless @user is a site admin
|
|
@c3 = @target_user.initiate_conversation([user])
|
|
@c3.add_message("secret", :root_account_id => @a3.id)
|
|
@c4 = @target_user.initiate_conversation([user])
|
|
@c4.add_message("super", :root_account_id => @a1.id)
|
|
@c4.add_message("sekrit", :root_account_id => @a3.id)
|
|
end
|
|
|
|
it "should let site admins see everything" do
|
|
Account.site_admin.account_users.create!(user: @admin_user)
|
|
Account.site_admin.stubs(:grants_right?).with(@admin_user, :become_user).returns(false)
|
|
convos = @target_user.conversations.for_masquerading_user(@admin_user)
|
|
expect(convos.size).to eql 4
|
|
expect(convos).to eq @target_user.conversations.to_a
|
|
end
|
|
|
|
it "should limit others to their associated root accounts" do
|
|
convos = @target_user.conversations.for_masquerading_user(@admin_user)
|
|
expect(convos.size).to eql 2
|
|
expect(convos.sort_by(&:id)).to eql [@c1, @c2]
|
|
end
|
|
end
|
|
|
|
context "participants" do
|
|
before :once do
|
|
@me = course_with_student(:active_all => true).user
|
|
@u1 = student_in_course(:active_all => true).user
|
|
@u2 = student_in_course(:active_all => true).user
|
|
@u3 = student_in_course(:active_all => true).user
|
|
@convo = @me.initiate_conversation([@u1, @u2, @u3])
|
|
@convo.add_message "ohai"
|
|
@u3.destroy
|
|
@u4 = student_in_course(:active_all => true).user
|
|
|
|
other_convo = @u4.initiate_conversation([@me])
|
|
message = other_convo.add_message "just between you and me"
|
|
@convo.add_message("haha i forwarded it", :forwarded_message_ids => [message.id])
|
|
end
|
|
|
|
it "should not include shared contexts by default" do
|
|
users = @convo.reload.participants
|
|
users.each do |user|
|
|
expect(user.common_groups).to be_empty
|
|
expect(user.common_courses).to be_empty
|
|
end
|
|
end
|
|
|
|
it "should not include forwarded participants by default" do
|
|
users = @convo.reload.participants
|
|
expect(users.map(&:id).sort).to eql [@me.id, @u1.id, @u2.id, @u3.id]
|
|
end
|
|
|
|
it "should include shared contexts if requested" do
|
|
users = @convo.reload.participants(:include_participant_contexts => true)
|
|
users.each do |user|
|
|
expect(user.common_groups).to eq({})
|
|
if [@me.id, @u3.id].include? user.id
|
|
expect(user.common_courses).to eq({})
|
|
else
|
|
expect(user.common_courses).to eq({@course.id => ["StudentEnrollment"]})
|
|
end
|
|
end
|
|
end
|
|
|
|
it "should include include forwarded participants if requested" do
|
|
users = @convo.reload.participants(:include_indirect_participants => true)
|
|
expect(users.map(&:id).sort).to eql [@me.id, @u1.id, @u2.id, @u3.id, @u4.id]
|
|
end
|
|
end
|
|
|
|
context "move_to_user" do
|
|
before :once do
|
|
@user1 = user_model
|
|
@user2 = user_model
|
|
end
|
|
|
|
it "should move a group conversation to the new user" do
|
|
c = @user1.initiate_conversation([user, user])
|
|
c.add_message("hello")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
|
|
c.move_to_user @user2
|
|
|
|
expect(c.reload.user_id).to eql @user2.id
|
|
expect(c.conversation.participants.map(&:id)).not_to include(@user1.id)
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should clean up group conversations having both users" do
|
|
c = @user1.initiate_conversation([@user2, user, user])
|
|
c.add_message("hello")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
rconvo = c.conversation
|
|
expect(rconvo.participants.size).to eql 4
|
|
|
|
c.move_to_user @user2
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
|
|
rconvo.reload
|
|
expect(rconvo.participants.size).to eql 3
|
|
expect(rconvo.participants.map(&:id)).not_to include(@user1.id)
|
|
expect(rconvo.participants.map(&:id)).to include(@user2.id)
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should move a private conversation to the new user" do
|
|
c = @user1.initiate_conversation([user])
|
|
c.add_message("hello")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
rconvo = c.conversation
|
|
old_hash = rconvo.private_hash
|
|
|
|
c.reload.move_to_user @user2
|
|
|
|
expect(c.reload.user_id).to eql @user2.id
|
|
rconvo.reload
|
|
expect(rconvo.participants.size).to eql 2
|
|
expect(rconvo.private_hash).not_to eql old_hash
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should merge a private conversation into the existing private conversation" do
|
|
other_guy = user
|
|
c = @user1.initiate_conversation([other_guy])
|
|
c.add_message("hello")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
c2 = @user2.initiate_conversation([other_guy])
|
|
c2.add_message("hola")
|
|
|
|
c.reload.move_to_user @user2
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
expect{ Conversation.find(c.conversation_id) }.to raise_error # deleted
|
|
|
|
expect(c2.reload.messages.size).to eql 2
|
|
expect(c2.messages.map(&:author_id)).to eql [@user2.id, @user2.id]
|
|
expect(c2.message_count).to eql 2
|
|
expect(c2.user_id).to eql @user2.id
|
|
expect(c2.conversation.participants.size).to eql 2
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
expect(other_guy.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should change a private conversation between the two users into a monologue" do
|
|
c = @user1.initiate_conversation([@user2])
|
|
c.add_message("hello self")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
@user2.mark_all_conversations_as_read!
|
|
rconvo = c.conversation
|
|
old_hash = rconvo.private_hash
|
|
|
|
c.reload.move_to_user @user2
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
rconvo.reload
|
|
expect(rconvo.participants.size).to eql 1
|
|
expect(rconvo.private_hash).not_to eql old_hash
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should merge a private conversations between the two users into the existing monologue" do
|
|
c = @user1.initiate_conversation([@user2])
|
|
c.add_message("hello self")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
c2 = @user2.initiate_conversation([@user2])
|
|
c2.add_message("monologue!")
|
|
@user2.mark_all_conversations_as_read!
|
|
|
|
c.reload.move_to_user @user2
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
expect{ Conversation.find(c.conversation_id) }.to raise_error # deleted
|
|
|
|
expect(c2.reload.messages.size).to eql 2
|
|
expect(c2.messages.map(&:author_id)).to eql [@user2.id, @user2.id]
|
|
expect(c2.message_count).to eql 2
|
|
expect(c2.user_id).to eql @user2.id
|
|
expect(c2.conversation.participants.size).to eql 1
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should merge a monologue into the existing monologue" do
|
|
c = @user1.initiate_conversation([@user1])
|
|
c.add_message("monologue 1")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
c2 = @user2.initiate_conversation([@user2])
|
|
c2.add_message("monologue 2")
|
|
|
|
c.reload.move_to_user @user2
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
expect{ Conversation.find(c.conversation_id) }.to raise_error # deleted
|
|
|
|
expect(c2.reload.messages.size).to eql 2
|
|
expect(c2.messages.map(&:author_id)).to eql [@user2.id, @user2.id]
|
|
expect(c2.message_count).to eql 2
|
|
expect(c2.user_id).to eql @user2.id
|
|
expect(c2.conversation.participants.size).to eql 1
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
it "should not be adversely affected by an outer scope" do
|
|
other_guy = user
|
|
c = @user1.initiate_conversation([other_guy])
|
|
c.add_message("hello")
|
|
c.update_attribute(:workflow_state, 'unread')
|
|
c2 = @user2.initiate_conversation([other_guy])
|
|
c2.add_message("hola")
|
|
|
|
c.reload
|
|
ConversationParticipant.where(:user_id => @user1.id).scoping do
|
|
c.move_to_user @user2
|
|
end
|
|
|
|
expect{ c.reload }.to raise_error # deleted
|
|
expect{ Conversation.find(c.conversation_id) }.to raise_error # deleted
|
|
|
|
expect(c2.reload.messages.size).to eql 2
|
|
expect(c2.messages.map(&:author_id)).to eql [@user2.id, @user2.id]
|
|
expect(c2.message_count).to eql 2
|
|
expect(c2.user_id).to eql @user2.id
|
|
expect(c2.conversation.participants.size).to eql 2
|
|
expect(@user1.reload.unread_conversations_count).to eql 0
|
|
expect(@user2.reload.unread_conversations_count).to eql 1
|
|
expect(other_guy.reload.unread_conversations_count).to eql 1
|
|
end
|
|
|
|
context "sharding" do
|
|
specs_require_sharding
|
|
|
|
it "should be able to move to a user on a different shard" do
|
|
u1 = User.create!
|
|
cp = u1.initiate_conversation([u1])
|
|
@shard1.activate do
|
|
u2 = User.create!
|
|
cp.move_to_user(u2)
|
|
cp.reload
|
|
expect(cp.user).to eq u2
|
|
cp2 = u2.all_conversations.first
|
|
expect(cp2).not_to eq cp
|
|
expect(cp2.shard).to eq @shard1
|
|
end
|
|
end
|
|
|
|
describe "for_masquerading_user scope" do
|
|
it "finds participants with global ids in root_account_ids" do
|
|
@a1 = Account.create
|
|
@admin_user = user
|
|
@a1.account_users.create!(user: @admin_user)
|
|
@target_user = user
|
|
@c1 = @target_user.initiate_conversation([user])
|
|
@c1.add_message("foo", :root_account_id => @a1.id)
|
|
|
|
# not sure how this happens in prod, but it does
|
|
@c1.update_attribute(:root_account_ids, [@a1.id, @a1.global_id].sort.join(","))
|
|
|
|
convos = @target_user.conversations.for_masquerading_user(@admin_user)
|
|
expect(convos.size).to eql 1
|
|
expect(convos.sort_by(&:id)).to eql [@c1]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|