495 lines
23 KiB
Ruby
495 lines
23 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#
|
|
# Copyright (C) 2016 - present 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/>.
|
|
|
|
describe AddressBook::MessageableUser do
|
|
describe "known_users" do
|
|
it "restricts to provided users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student1 = student_in_course(active_all: true).user
|
|
student2 = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_users([student1])
|
|
expect(known_users.map(&:id)).to include(student1.id)
|
|
expect(known_users.map(&:id)).not_to include(student2.id)
|
|
end
|
|
|
|
it "includes only known users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student1 = student_in_course(active_all: true).user
|
|
student2 = student_in_course(course: course_factory(), active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_users([student1, student2])
|
|
expect(known_users.map(&:id)).to include(student1.id)
|
|
expect(known_users.map(&:id)).not_to include(student2.id)
|
|
end
|
|
|
|
it 'includes group members from different sections' do
|
|
course = course_factory(active_all: true)
|
|
section1 = course.course_sections.create!
|
|
section2 = course.course_sections.create!
|
|
|
|
student1 = student_in_course(user: @sender, course: course, active_all: true, section: section1, limit_privileges_to_course_section: true).user
|
|
student2 = student_in_course(course: course, active_all: true, section: section2, limit_privileges_to_course_section: true).user
|
|
student3 = student_in_course(course: course, active_all: true, section: section2, limit_privileges_to_course_section: true).user
|
|
|
|
group = group_with_user(user: student1, group_context: course).group
|
|
group.add_user(student2)
|
|
|
|
address_book = AddressBook::MessageableUser.new(student1)
|
|
known_users = address_book.known_users([student2, student3])
|
|
expect(known_users.map(&:id)).to include(student2.id)
|
|
expect(known_users.map(&:id)).not_to include(student3.id)
|
|
end
|
|
|
|
it 'works for a discussion topic' do
|
|
course = course_factory(active_all: true)
|
|
topic = course.discussion_topics.create!
|
|
|
|
student1 = student_in_course(user: @sender, course: course, active_all: true).user
|
|
student2 = student_in_course(course: course, active_all: true).user
|
|
|
|
address_book = AddressBook::MessageableUser.new(student1)
|
|
known_users = address_book.known_users([student2], context: topic)
|
|
expect(known_users.map(&:id)).to include(student2.id)
|
|
end
|
|
|
|
it 'works for a group discussion topic' do
|
|
course = course_factory(active_all: true)
|
|
|
|
student1 = student_in_course(user: @sender, course: course, active_all: true).user
|
|
student2 = student_in_course(course: course, active_all: true).user
|
|
student3 = student_in_course(course: course, active_all: true).user
|
|
group = group_with_user(user: student1, group_context: course).group
|
|
group.add_user(student2)
|
|
topic = group.discussion_topics.create!
|
|
|
|
address_book = AddressBook::MessageableUser.new(student1)
|
|
known_users = address_book.known_users([student2, student3], context: topic)
|
|
expect(known_users.map(&:id)).to include(student2.id)
|
|
expect(known_users.map(&:id)).not_to include(student3.id)
|
|
end
|
|
|
|
it "caches the results for known users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
expect(address_book.known_users([student])).to be_present
|
|
expect(address_book.cached?(student)).to be_truthy
|
|
end
|
|
|
|
it "caches the failure for unknown users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(course: course_factory(), active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
expect(address_book.known_users([student])).to be_empty
|
|
expect(address_book.cached?(student)).to be_truthy
|
|
end
|
|
|
|
it "doesn't refetch already cached users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student1 = student_in_course(active_all: true).user
|
|
student2 = student_in_course(active_all: true).user
|
|
student3 = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
address_book.known_users([student1, student2])
|
|
expect(teacher).to receive(:load_messageable_users)
|
|
.with([student3], anything)
|
|
.and_return(MessageableUser.where(id: student3).to_a)
|
|
known_users = address_book.known_users([student2, student3])
|
|
expect(known_users.map(&:id)).to include(student2.id)
|
|
expect(known_users.map(&:id)).to include(student3.id)
|
|
end
|
|
|
|
describe "with optional :context" do
|
|
before :each do
|
|
@recipient = user_model(workflow_state: 'registered')
|
|
@sender = user_model(workflow_state: 'registered')
|
|
@address_book = AddressBook::MessageableUser.new(@sender)
|
|
|
|
# recipient participates in three courses
|
|
@course1 = course_model(workflow_state: 'available')
|
|
@course2 = course_model(workflow_state: 'available')
|
|
@course3 = course_model(workflow_state: 'available')
|
|
student_in_course(user: @recipient, course: @course1, active_all: true)
|
|
student_in_course(user: @recipient, course: @course2, active_all: true)
|
|
student_in_course(user: @recipient, course: @course3, active_all: true)
|
|
|
|
# but only two are shared with sender (visible with the sender)
|
|
teacher_in_course(user: @sender, course: @course1, active_all: true)
|
|
teacher_in_course(user: @sender, course: @course2, active_all: true)
|
|
end
|
|
|
|
it "includes all known contexts when absent" do
|
|
expect(@address_book.known_users([@recipient])).not_to be_empty
|
|
expect(@address_book.common_courses(@recipient)).to include(@course1.id)
|
|
expect(@address_book.common_courses(@recipient)).to include(@course2.id)
|
|
end
|
|
|
|
it "excludes unknown contexts when absent, even if admin" do
|
|
account_admin_user(user: @sender, account: @course3.account)
|
|
expect(@address_book.known_users([@recipient])).not_to be_empty
|
|
expect(@address_book.common_courses(@recipient)).not_to include(@course3.id)
|
|
end
|
|
|
|
it "excludes other known contexts when specified" do
|
|
expect(@address_book.known_users([@recipient], context: @course1)).not_to be_empty
|
|
expect(@address_book.common_courses(@recipient)).to include(@course1.id)
|
|
expect(@address_book.common_courses(@recipient)).not_to include(@course2.id)
|
|
end
|
|
|
|
it "excludes specified unknown context when sender is non-admin" do
|
|
expect(@address_book.known_users([@recipient], context: @course3)).to be_empty
|
|
expect(@address_book.common_courses(@recipient)).not_to include(@course3.id)
|
|
end
|
|
|
|
it "excludes specified unknown course when sender is a participant admin" do
|
|
# i.e. the sender does partipate in the course, at a level that
|
|
# nominally gives them read_as_admin (e.g. teacher, usually), but still
|
|
# doesn't know of recipient's participation, likely because of section
|
|
# limited enrollment.
|
|
section = @course3.course_sections.create!
|
|
teacher_in_course(user: @sender, course: @course3, active_all: true, section: section, limit_privileges_to_course_section: true)
|
|
expect(@address_book.known_users([@recipient], context: @course3)).to be_empty
|
|
expect(@address_book.common_courses(@recipient)).not_to include(@course3.id)
|
|
end
|
|
|
|
it "includes specified unknown context when sender is non-participant admin" do
|
|
account_admin_user(user: @sender, account: @course3.account)
|
|
expect(@address_book.known_users([@recipient], context: @course3)).not_to be_empty
|
|
expect(@address_book.common_courses(@recipient)).to include(@course3.id)
|
|
end
|
|
end
|
|
|
|
describe "with optional :conversation_id" do
|
|
it "treats unknown users in that conversation as known" do
|
|
course1 = course_factory(active_all: true)
|
|
course2 = course_factory(active_all: true)
|
|
teacher = teacher_in_course(course: course1, active_all: true).user
|
|
student = student_in_course(course: course2, active_all: true).user
|
|
conversation = Conversation.initiate([teacher, student], true)
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_users([student], conversation_id: conversation.id)
|
|
expect(known_users.map(&:id)).to include(student.id)
|
|
end
|
|
|
|
it "ignores if sender is not a participant in the conversation" do
|
|
course1 = course_factory(active_all: true)
|
|
course2 = course_factory(active_all: true)
|
|
teacher = teacher_in_course(course: course1, active_all: true).user
|
|
student1 = student_in_course(course: course2, active_all: true).user
|
|
student2 = student_in_course(course: course2, active_all: true).user
|
|
conversation = Conversation.initiate([student1, student2], true)
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_users([student1], conversation_id: conversation.id)
|
|
expect(known_users.map(&:id)).not_to include(student1.id)
|
|
end
|
|
end
|
|
|
|
describe "sharding" do
|
|
specs_require_sharding
|
|
|
|
it "finds cross-shard known users" do
|
|
enrollment = @shard1.activate { teacher_in_course(active_all: true) }
|
|
teacher = enrollment.user
|
|
course = enrollment.course
|
|
student = @shard2.activate { user_factory(active_all: true) }
|
|
student_in_course(course: course, user: student, active_all: true)
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_users([student])
|
|
expect(known_users.map(&:id)).to include(student.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "known_user" do
|
|
it "returns the user if known" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_user = address_book.known_user(student)
|
|
expect(known_user).not_to be_nil
|
|
end
|
|
|
|
it "returns nil if not known" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
other = user_factory(active_all: true)
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_user = address_book.known_user(other)
|
|
expect(known_user).to be_nil
|
|
end
|
|
end
|
|
|
|
describe "common_courses" do
|
|
it "pulls the corresponding MessageableUser's common_courses" do
|
|
enrollment = teacher_in_course(active_all: true)
|
|
teacher = enrollment.user
|
|
course = enrollment.course
|
|
student = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
common_courses = address_book.common_courses(student)
|
|
expect(common_courses).to eql({ course.id => ['StudentEnrollment'] })
|
|
end
|
|
end
|
|
|
|
describe "common_groups" do
|
|
it "pulls the corresponding MessageableUser's common_groups" do
|
|
sender = user_factory(active_all: true)
|
|
recipient = user_factory(active_all: true)
|
|
group = group()
|
|
group.add_user(sender, 'accepted')
|
|
group.add_user(recipient, 'accepted')
|
|
address_book = AddressBook::MessageableUser.new(sender)
|
|
common_groups = address_book.common_groups(recipient)
|
|
expect(common_groups).to eql({ group.id => ['Member'] })
|
|
end
|
|
end
|
|
|
|
describe "known_in_context" do
|
|
it "limits to users in context" do
|
|
course1 = course_factory(active_all: true)
|
|
course2 = course_factory(active_all: true)
|
|
teacher = teacher_in_course(course: course1, active_all: true).user
|
|
teacher_in_course(user: teacher, course: course2, active_all: true)
|
|
student1 = student_in_course(course: course1, active_all: true).user
|
|
student2 = student_in_course(course: course2, active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.known_in_context(course1.asset_string)
|
|
expect(known_users.map(&:id)).to include(student1.id)
|
|
expect(known_users.map(&:id)).not_to include(student2.id)
|
|
end
|
|
|
|
it "caches the results for known users" do
|
|
enrollment = teacher_in_course(active_all: true)
|
|
teacher = enrollment.user
|
|
course = enrollment.course
|
|
student = student_in_course(active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
address_book.known_in_context(course.asset_string)
|
|
expect(address_book.cached?(student)).to be_truthy
|
|
end
|
|
|
|
it "does not cache unknown users" do
|
|
enrollment = teacher_in_course(active_all: true)
|
|
teacher = enrollment.user
|
|
course1 = enrollment.course
|
|
student = student_in_course(course: course_factory(), active_all: true).user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
address_book.known_in_context(course1.asset_string)
|
|
expect(address_book.cached?(student)).to be_falsey
|
|
end
|
|
|
|
describe "sharding" do
|
|
specs_require_sharding
|
|
|
|
before :each do
|
|
enrollment = @shard1.activate { teacher_in_course(active_all: true) }
|
|
@teacher = enrollment.user
|
|
@course = enrollment.course
|
|
@student = @shard2.activate { user_factory(active_all: true) }
|
|
student_in_course(course: @course, user: @student, active_all: true)
|
|
end
|
|
|
|
it "works for cross-shard courses" do
|
|
address_book = AddressBook::MessageableUser.new(@student)
|
|
known_users = address_book.known_in_context(@course.asset_string)
|
|
expect(known_users.map(&:id)).to include(@teacher.id)
|
|
end
|
|
|
|
it "finds known cross-shard users in course" do
|
|
address_book = AddressBook::MessageableUser.new(@teacher)
|
|
known_users = address_book.known_in_context(@course.asset_string)
|
|
expect(known_users.map(&:id)).to include(@student.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "count_in_contexts" do
|
|
it "limits to known users in contexts" do
|
|
enrollment = ta_in_course(active_all: true, limit_privileges_to_course_section: true)
|
|
ta = enrollment.user
|
|
course = enrollment.course
|
|
section1 = course.default_section
|
|
section2 = course.course_sections.create!
|
|
student_in_course(section: section1, active_all: true)
|
|
student_in_course(section: section2, active_all: true)
|
|
# includes teacher, ta, and student in section1, but excludes student in section2
|
|
address_book = AddressBook::MessageableUser.new(ta)
|
|
expect(address_book.count_in_contexts([course.asset_string])).to eql({
|
|
course.asset_string => 3
|
|
})
|
|
end
|
|
|
|
it "returns count in an unassociated :context when an admin" do
|
|
sender = account_admin_user(active_all: true)
|
|
enrollment = student_in_course(active_all: true)
|
|
course = enrollment.course
|
|
address_book = AddressBook::MessageableUser.new(sender)
|
|
expect(address_book.count_in_contexts([course.asset_string])).to eql({
|
|
course.asset_string => 2
|
|
})
|
|
end
|
|
end
|
|
|
|
describe "search_users" do
|
|
it "returns a paginatable collection" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student_in_course(active_all: true, name: 'Bob').user
|
|
student_in_course(active_all: true, name: 'Bobby').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob')
|
|
expect(known_users).to respond_to(:paginate)
|
|
expect(known_users.paginate(per_page: 1).size).to eql(1)
|
|
end
|
|
|
|
it "finds matching known users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student1 = student_in_course(active_all: true, name: 'Bob').user
|
|
student2 = student_in_course(active_all: true, name: 'Bobby').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob').paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).to include(student1.id)
|
|
expect(known_users.map(&:id)).to include(student2.id)
|
|
end
|
|
|
|
it "excludes matching known user in optional :exclude_ids" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true, name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob', exclude_ids: [student.id]).paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).not_to include(student.id)
|
|
end
|
|
|
|
it "restricts to matching known users in optional :context" do
|
|
course1 = course_factory(active_all: true)
|
|
course2 = course_factory(active_all: true)
|
|
teacher = teacher_in_course(course: course1, active_all: true).user
|
|
teacher_in_course(user: teacher, course: course2, active_all: true)
|
|
student1 = student_in_course(course: course1, active_all: true, name: 'Bob').user
|
|
student2 = student_in_course(course: course2, active_all: true, name: 'Bobby').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob', context: course1.asset_string).paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).to include(student1.id)
|
|
expect(known_users.map(&:id)).not_to include(student2.id)
|
|
end
|
|
|
|
it "finds users in an unassociated :context when an admin" do
|
|
admin = account_admin_user(active_all: true)
|
|
enrollment = student_in_course(active_all: true, name: 'Bob')
|
|
student = enrollment.user
|
|
course = enrollment.course
|
|
address_book = AddressBook::MessageableUser.new(admin)
|
|
known_users = address_book.search_users(search: 'Bob', context: course.asset_string).paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).to include(student.id)
|
|
end
|
|
|
|
it "excludes users in an admined :context when also participating" do
|
|
admin = account_admin_user(active_all: true)
|
|
enrollment = student_in_course(active_all: true, name: 'Bob')
|
|
student = enrollment.user
|
|
course = enrollment.course
|
|
section = course.course_sections.create!
|
|
teacher_in_course(user: admin, course: course, active_all: true, section: section, limit_privileges_to_course_section: true)
|
|
address_book = AddressBook::MessageableUser.new(admin)
|
|
known_users = address_book.search_users(search: 'Bob', context: course.asset_string).paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).not_to include(student.id)
|
|
end
|
|
|
|
it "excludes 'weak' users without :weak_checks" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(user_state: 'creation_pending', enrollment_state: 'invited', name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob').paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).not_to include(student.id)
|
|
end
|
|
|
|
it "excludes 'weak' enrollments without :weak_checks" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_user: true, enrollment_state: 'creation_pending', name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob').paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).not_to include(student.id)
|
|
end
|
|
|
|
it "expands to include 'weak' users and 'weak' enrollments when :weak_checks" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_user: true, enrollment_state: 'creation_pending', name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
known_users = address_book.search_users(search: 'Bob', weak_checks: true).paginate(per_page: 10)
|
|
expect(known_users.map(&:id)).to include(student.id)
|
|
end
|
|
|
|
it "caches the results for known users when a page is materialized" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true, name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
collection = address_book.search_users(search: 'Bob')
|
|
expect(address_book.cached?(student)).to be_falsey
|
|
collection.paginate(per_page: 10)
|
|
expect(address_book.cached?(student)).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe "preload_users" do
|
|
it "avoids db query with rails cache" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true, name: 'Bob').user
|
|
expect(Rails.cache).to receive(:fetch)
|
|
.with(match(/address_book_preload/))
|
|
.and_return(MessageableUser.where(id: student).to_a)
|
|
expect(teacher).to receive(:load_messageable_users).never
|
|
AddressBook::MessageableUser.new(teacher).preload_users([student])
|
|
end
|
|
|
|
it "caches all provided users" do
|
|
teacher = teacher_in_course(active_all: true).user
|
|
student = student_in_course(active_all: true, name: 'Bob').user
|
|
address_book = AddressBook::MessageableUser.new(teacher)
|
|
address_book.preload_users([student])
|
|
expect(address_book.cached?(student)).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe "sections" do
|
|
it "returns course sections known to sender" do
|
|
enrollment = ta_in_course(active_all: true)
|
|
ta = enrollment.user
|
|
course = enrollment.course
|
|
section1 = course.default_section
|
|
section2 = course.course_sections.create!
|
|
address_book = AddressBook::MessageableUser.new(ta)
|
|
sections = address_book.sections
|
|
expect(sections.map(&:id)).to include(section1.id)
|
|
expect(sections.map(&:id)).to include(section2.id)
|
|
end
|
|
end
|
|
|
|
describe "groups" do
|
|
it "returns groups known to sender" do
|
|
membership = group_with_user(active_all: true)
|
|
user = membership.user
|
|
group1 = membership.group
|
|
group2 = group(active_all: true)
|
|
address_book = AddressBook::MessageableUser.new(user)
|
|
groups = address_book.groups
|
|
expect(groups.map(&:id)).to include(group1.id)
|
|
expect(groups.map(&:id)).not_to include(group2.id)
|
|
end
|
|
end
|
|
end
|