canvas-lms/spec/models/user_spec.rb

3236 lines
130 KiB
Ruby

#
# Copyright (C) 2011 - 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/>.
#
require 'rotp'
require_relative '../sharding_spec_helper'
describe User do
context "validation" do
it "should create a new instance given valid attributes" do
expect(user_model).to be_valid
end
context 'on update' do
let(:user) { user_model }
it 'fails validation if lti_id changes' do
user.short_name = "chewie"
user.lti_id = "changedToThis"
expect(user).to_not be_valid
end
it 'passes validation if lti_id is not changed' do
user
user.short_name = "chewie"
expect(user).to be_valid
end
end
end
it 'adds an lti_id on creation' do
user = User.new
expect(user.lti_id).to be_blank
user.save!
expect(user.lti_id).to_not be_blank
end
it "should get the first email from communication_channel" do
@user = User.create
@cc1 = double('CommunicationChannel')
allow(@cc1).to receive(:path).and_return('cc1')
@cc2 = double('CommunicationChannel')
allow(@cc2).to receive(:path).and_return('cc2')
allow(@user).to receive(:communication_channels).and_return([@cc1, @cc2])
allow(@user).to receive(:communication_channel).and_return(@cc1)
expect(@user.communication_channel).to eql(@cc1)
end
it "should be able to assert a name" do
@user = User.create
@user.assert_name(nil)
expect(@user.name).to eql('User')
@user.assert_name('david')
expect(@user.name).to eql('david')
@user.assert_name('bill')
expect(@user.name).to eql('bill')
@user.assert_name(nil)
expect(@user.name).to eql('bill')
@user = User.find(@user.id)
expect(@user.name).to eql('bill')
end
it "should correctly identify active courses when there are no active groups" do
user = User.create(:name => "longname1", :short_name => "shortname1")
expect(user.current_active_groups?).to eql(false)
end
it "should correctly identify active courses when there are active groups" do
account1 = account_model
course_with_student(:account => account1)
group_model(:group_category => @communities, :is_public => true, :context => @course)
group.add_user(@student)
expect(@student.current_active_groups?).to eql(true)
end
it "should update account associations when a course account changes" do
account1 = account_model
account2 = account_model
course_with_student
expect(@user.associated_accounts.length).to eql(1)
expect(@user.associated_accounts.first).to eql(Account.default)
@course.account = account1
@course.save!
@course.reload
@user.reload
expect(@user.associated_accounts.length).to eql(1)
expect(@user.associated_accounts.first).to eql(account1)
@course.account = account2
@course.save!
@user.reload
expect(@user.associated_accounts.length).to eql(1)
expect(@user.associated_accounts.first).to eql(account2)
end
it "should update account associations when a course account moves in the hierachy" do
account1 = account_model
@enrollment = course_with_student(:account => account1)
@course.account = account1
@course.save!
@course.reload
@user.reload
expect(@user.associated_accounts.length).to eql(1)
expect(@user.associated_accounts.first).to eql(account1)
account2 = account_model
account1.parent_account = account2
account1.save!
@course.reload
@user.reload
expect(@user.associated_accounts.length).to eql(2)
expect(@user.associated_accounts[0]).to eql(account1)
expect(@user.associated_accounts[1]).to eql(account2)
end
it "should update account associations when a user is associated to an account just by pseudonym" do
account1 = account_model
account2 = account_model
user = user_with_pseudonym
pseudonym = user.pseudonyms.first
pseudonym.account = account1
pseudonym.save
user.reload
expect(user.associated_accounts.length).to eql(1)
expect(user.associated_accounts.first).to eql(account1)
# Make sure that multiple sequential updates also work
pseudonym.account = account2
pseudonym.save
pseudonym.account = account1
pseudonym.save
user.reload
expect(user.associated_accounts.length).to eql(1)
expect(user.associated_accounts.first).to eql(account1)
account1.parent_account = account2
account1.save!
user.reload
expect(user.associated_accounts.length).to eql(2)
expect(user.associated_accounts[0]).to eql(account1)
expect(user.associated_accounts[1]).to eql(account2)
end
it "should update account associations when a user is associated to an account just by account_users" do
account = account_model
@user = User.create
account.account_users.create!(user: @user)
@user.reload
expect(@user.associated_accounts.length).to eql(1)
expect(@user.associated_accounts.first).to eql(account)
end
it "should exclude deleted enrollments from all courses list" do
account1 = account_model
enrollment1 = course_with_student(:account => account1)
enrollment2 = course_with_student(:account => account1)
enrollment1.user = @user
enrollment2.user = @user
enrollment1.save!
enrollment2.save!
@user.reload
expect(@user.all_courses_for_active_enrollments.length).to be(2)
expect { enrollment1.destroy! }.
to change {
@user.reload.all_courses_for_active_enrollments.size
}.from(2).to(1)
end
it "should populate dashboard_messages" do
Notification.create(:name => "Assignment Created")
course_with_teacher(:active_all => true)
expect(@user.stream_item_instances).to be_empty
@a = @course.assignments.new(:title => "some assignment")
@a.workflow_state = "available"
@a.save
expect(@user.stream_item_instances.reload).not_to be_empty
end
it "should ignore orphaned stream item instances" do
course_with_student(:active_all => true)
google_docs_collaboration_model(:user_id => @user.id)
expect(@user.recent_stream_items.size).to eq 1
StreamItem.delete_all
expect(@user.recent_stream_items.size).to eq 0
end
it "should ignore stream item instances from concluded courses" do
course_with_teacher(:active_all => true)
google_docs_collaboration_model(:user_id => @user.id)
expect(@user.recent_stream_items.size).to eq 1
@course.soft_conclude!
@course.save
expect(@user.recent_stream_items.size).to eq 0
end
it "should ignore stream item instances from courses the user is no longer participating in" do
course_with_student(:active_all => true)
google_docs_collaboration_model(:user_id => @user.id)
expect(@user.recent_stream_items.size).to eq 1
@enrollment.end_at = @enrollment.start_at = Time.now - 1.day
@enrollment.save!
@user = User.find(@user.id)
expect(@user.recent_stream_items.size).to eq 0
end
describe "#recent_stream_items" do
it "should skip submission stream items" do
course_with_teacher(:active_all => true)
course_with_student(:active_all => true, :course => @course)
assignment = @course.assignments.create!(:title => "some assignment", :submission_types => ['online_text_entry'])
sub = assignment.submit_homework @student, body: "submission"
sub.add_comment :author => @teacher, :comment => "lol"
item = StreamItem.last
expect(item.asset).to eq sub
expect(@student.visible_stream_item_instances.map(&:stream_item)).to include item
expect(@student.recent_stream_items).not_to include item
end
end
describe "#cached_recent_stream_items" do
before(:once) do
@contexts = []
# create stream item 1
course_with_teacher(:active_all => true)
@contexts << @course
discussion_topic_model(:context => @course)
# create stream item 2
course_with_teacher(:active_all => true, :user => @teacher)
@contexts << @course
discussion_topic_model(:context => @course)
@dashboard_key = StreamItemCache.recent_stream_items_key(@teacher)
end
let(:context_keys) do
@contexts.map { |context|
StreamItemCache.recent_stream_items_key(@teacher, context.class.base_class.name, context.id)
}
end
it "creates cache keys for each context" do
enable_cache do
@teacher.cached_recent_stream_items(:contexts => @contexts)
expect(Rails.cache.read(@dashboard_key)).to be_blank
context_keys.each do |context_key|
expect(Rails.cache.read(context_key)).not_to be_blank
end
end
end
it "creates one cache key when there are no contexts" do
enable_cache do
@teacher.cached_recent_stream_items # cache the dashboard items
expect(Rails.cache.read(@dashboard_key)).not_to be_blank
context_keys.each do |context_key|
expect(Rails.cache.read(context_key)).to be_blank
end
end
end
end
it "should be able to remove itself from a root account" do
account1 = Account.create
account2 = Account.create
sub = account2.sub_accounts.create!
user = User.create
user.register!
p1 = user.pseudonyms.create(:unique_id => "user1")
p2 = user.pseudonyms.create(:unique_id => "user2")
p1.account = account1
p2.account = account2
p1.save!
p2.save!
account1.account_users.create!(user: user)
account2.account_users.create!(user: user)
sub.account_users.create!(user: user)
course1 = account1.courses.create
course2 = account2.courses.create
course1.offer!
course2.offer!
enrollment1 = course1.enroll_student(user)
enrollment2 = course2.enroll_student(user)
enrollment1.workflow_state = 'active'
enrollment2.workflow_state = 'active'
enrollment1.save!
enrollment2.save!
expect(user.associated_account_ids.include?(account1.id)).to be_truthy
expect(user.associated_account_ids.include?(account2.id)).to be_truthy
user.remove_from_root_account(account2)
user.reload
expect(user.associated_account_ids.include?(account1.id)).to be_truthy
expect(user.associated_account_ids.include?(account2.id)).to be_falsey
expect(user.account_users.active.where(account_id: [account2, sub])).to be_empty
end
it "should search by multiple fields" do
@account = Account.create!
user1 = User.create! :name => "longname1", :short_name => "shortname1"
user1.register!
user2 = User.create! :name => "longname2", :short_name => "shortname2"
user2.register!
expect(User.name_like("longname1").map(&:id)).to eq [user1.id]
expect(User.name_like("shortname2").map(&:id)).to eq [user2.id]
expect(User.name_like("sisid1").map(&:id)).to eq []
expect(User.name_like("uniqueid2").map(&:id)).to eq []
p1 = user1.pseudonyms.new :unique_id => "uniqueid1", :account => @account
p1.sis_user_id = "sisid1"
p1.save!
p2 = user2.pseudonyms.new :unique_id => "uniqueid2", :account => @account
p2.sis_user_id = "sisid2"
p2.save!
expect(User.name_like("longname1").map(&:id)).to eq [user1.id]
expect(User.name_like("shortname2").map(&:id)).to eq [user2.id]
expect(User.name_like("sisid1").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid2").map(&:id)).to eq [user2.id]
p3 = user1.pseudonyms.new :unique_id => "uniqueid3", :account => @account
p3.sis_user_id = "sisid3"
p3.save!
expect(User.name_like("longname1").map(&:id)).to eq [user1.id]
expect(User.name_like("shortname2").map(&:id)).to eq [user2.id]
expect(User.name_like("sisid1").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid2").map(&:id)).to eq [user2.id]
expect(User.name_like("uniqueid3").map(&:id)).to eq [user1.id]
p4 = user1.pseudonyms.new :unique_id => "uniqueid4", :account => @account
p4.sis_user_id = "sisid3 2"
p4.save!
expect(User.name_like("longname1").map(&:id)).to eq [user1.id]
expect(User.name_like("shortname2").map(&:id)).to eq [user2.id]
expect(User.name_like("sisid1").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid2").map(&:id)).to eq [user2.id]
expect(User.name_like("uniqueid3").map(&:id)).to eq [user1.id]
expect(User.name_like("sisid3").map(&:id)).to eq [user1.id]
user3 = User.create! :name => "longname1", :short_name => "shortname3"
user3.register!
expect(User.name_like("longname1").map(&:id).sort).to eq [user1.id, user3.id].sort
expect(User.name_like("shortname2").map(&:id)).to eq [user2.id]
expect(User.name_like("sisid1").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid2").map(&:id)).to eq [user2.id]
expect(User.name_like("uniqueid3").map(&:id)).to eq [user1.id]
expect(User.name_like("sisid3").map(&:id)).to eq [user1.id]
expect(User.name_like("sisid3").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid4").map(&:id)).to eq [user1.id]
p4.destroy
expect(User.name_like("sisid3").map(&:id)).to eq [user1.id]
expect(User.name_like("uniqueid4").map(&:id)).to eq []
end
it "should be able to be removed from a root account with non-Canvas auth" do
account1 = account_with_cas
account2 = Account.create!
user = User.create!
user.register!
p1 = user.pseudonyms.new :unique_id => "id1", :account => account1
p1.sis_user_id = 'sis_id1'
p1.save!
user.pseudonyms.create! :unique_id => "id2", :account => account2
user.remove_from_root_account account1
expect(user.associated_root_accounts.to_a).to eql [account2]
end
describe "update_account_associations" do
it "should support incrementally adding to account associations" do
user = User.create!
expect(user.user_account_associations).to eq []
account1, account2, account3 = Account.create!, Account.create!, Account.create!
sort_account_associations = lambda { |a, b| a.keys.first <=> b.keys.first }
User.update_account_associations([user], :incremental => true, :precalculated_associations => {account1.id => 0})
expect(user.user_account_associations.reload.map { |aa| {aa.account_id => aa.depth} }).to eq [{account1.id => 0}]
User.update_account_associations([user], :incremental => true, :precalculated_associations => {account2.id => 1})
expect(user.user_account_associations.reload.map { |aa| {aa.account_id => aa.depth} }.sort(&sort_account_associations)).to eq [{account1.id => 0}, {account2.id => 1}].sort(&sort_account_associations)
User.update_account_associations([user], :incremental => true, :precalculated_associations => {account3.id => 1, account1.id => 2, account2.id => 0})
expect(user.user_account_associations.reload.map { |aa| {aa.account_id => aa.depth} }.sort(&sort_account_associations)).to eq [{account1.id => 0}, {account2.id => 0}, {account3.id => 1}].sort(&sort_account_associations)
end
it "should not have account associations for creation_pending or deleted" do
user = User.create! { |u| u.workflow_state = 'creation_pending' }
expect(user).to be_creation_pending
course = Course.create!
course.offer!
enrollment = course.enroll_student(user)
expect(enrollment).to be_invited
expect(user.user_account_associations).to eq []
Account.default.account_users.create!(user: user)
expect(user.user_account_associations.reload).to eq []
user.pseudonyms.create!(:unique_id => 'test@example.com')
expect(user.user_account_associations.reload).to eq []
user.update_account_associations
expect(user.user_account_associations.reload).to eq []
user.register!
expect(user.user_account_associations.reload.map(&:account)).to eq [Account.default]
user.destroy
expect(user.user_account_associations.reload).to eq []
end
it "should not create/update account associations for student view student" do
account1 = account_model
account2 = account_model
course_with_teacher(:active_all => true)
@fake_student = @course.student_view_student
expect(@fake_student.reload.user_account_associations).to be_empty
@course.account_id = account1.id
@course.save!
expect(@fake_student.reload.user_account_associations).to be_empty
account1.parent_account = account2
account1.save!
expect(@fake_student.reload.user_account_associations).to be_empty
@course.complete!
expect(@fake_student.reload.user_account_associations).to be_empty
@fake_student = @course.reload.student_view_student
expect(@fake_student.reload.user_account_associations).to be_empty
@section2 = @course.course_sections.create!(:name => "Other Section")
@fake_student = @course.reload.student_view_student
expect(@fake_student.reload.user_account_associations).to be_empty
end
context "sharding" do
specs_require_sharding
it "should create associations for a user in multiple shards" do
user_factory
Account.site_admin.account_users.create!(user: @user)
expect(@user.user_account_associations.map(&:account)).to eq [Account.site_admin]
@shard1.activate do
@account = Account.create!
au = @account.account_users.create!(user: @user)
expect(@user.user_account_associations.shard(@user).map(&:account).sort_by(&:id)).to eq(
[Account.site_admin, @account].sort_by(&:id)
)
expect(@account.user_account_associations.map(&:user)).to eq [@user]
au.destroy
expect(@user.user_account_associations.shard(@user).map(&:account)).to eq [Account.site_admin]
expect(@account.reload.user_account_associations.map(&:user)).to eq []
@account.account_users.create!(user: @user)
expect(@user.user_account_associations.shard(@user).map(&:account).sort_by(&:id)).to eq(
[Account.site_admin, @account].sort_by(&:id)
)
expect(@account.reload.user_account_associations.map(&:user)).to eq [@user]
UserAccountAssociation.delete_all
end
UserAccountAssociation.delete_all
@shard2.activate do
@user.update_account_associations
expect(@user.user_account_associations.shard(@user).map(&:account).sort_by(&:id)).to eq(
[Account.site_admin, @account].sort_by(&:id)
)
expect(@account.reload.user_account_associations.map(&:user)).to eq [@user]
end
UserAccountAssociation.delete_all
@shard1.activate do
# check sharding for when we pass user IDs into update_account_associations, rather than user objects themselves
User.update_account_associations([@user.id], :all_shards => true)
expect(@account.reload.all_users).to eq [@user]
end
@shard2.activate { expect(@account.reload.all_users).to eq [@user] }
end
end
end
def create_course_with_student_and_assignment
@course = course_model
@course.offer!
@student = user_model
@course.enroll_student @student
@assignment = @course.assignments.create :title => "Test Assignment", :points_possible => 10
end
describe "#recent_feedback" do
let_once(:post_policies_course) { Course.create!(workflow_state: :available) }
let_once(:auto_posted_assignment) { post_policies_course.assignments.create!(points_possible: 10) }
let_once(:manual_posted_assignment) do
assignment = post_policies_course.assignments.create!(points_possible: 10)
assignment.post_policy.update!(post_manually: true)
assignment
end
let_once(:student) { User.create! }
let_once(:teacher) { User.create! }
before(:once) do
post_policies_course.enroll_student(student, enrollment_state: :active)
post_policies_course.enroll_teacher(teacher, enrollment_state: :active)
end
context "for a course with Post Policies enabled" do
it "does not include assignments for which there is no feedback" do
expect(student.recent_feedback).to be_empty
end
it "includes recent posted feedback" do
auto_posted_assignment.grade_student(student, grader: teacher, score: 10)
expect(student.recent_feedback).to contain_exactly(auto_posted_assignment.submission_for_student(student))
end
it "includes feedback that was posted after being initially hidden" do
manual_posted_assignment.grade_student(student, grader: teacher, score: 10)
manual_posted_assignment.post_submissions
expect(student.recent_feedback).to contain_exactly(manual_posted_assignment.submission_for_student(student))
end
it "does not include recent unposted feedback" do
manual_posted_assignment.grade_student(student, grader: teacher, score: 10)
expect(student.recent_feedback).to be_empty
end
it "does not include recent feedback that was posted but subsequently hidden" do
auto_posted_assignment.grade_student(student, grader: teacher, score: 10)
auto_posted_assignment.hide_submissions
expect(student.recent_feedback).to be_empty
end
end
it "only returns feedback for posted submissions" do
auto_posted_assignment.grade_student(student, grader: teacher, score: 10)
manual_posted_assignment.grade_student(student, grader: teacher, score: 10)
expect(student.recent_feedback).to contain_exactly(
auto_posted_assignment.submission_for_student(student)
)
end
it "only returns feedback for specific courses if specified" do
other_course = Course.create!(workflow_state: :available)
other_course.enroll_student(student, enrollment_state: :active)
other_course.enroll_teacher(teacher, enrollment_state: :active)
auto_assignment = other_course.assignments.create!(points_possible: 10)
manual_assignment = other_course.assignments.create!(points_possible: 10)
manual_assignment.post_policy.update!(post_manually: true)
auto_assignment.grade_student(student, grader: teacher, score: 10)
expect(student.recent_feedback(contexts: [other_course])).to contain_exactly(
auto_assignment.submission_for_student(student)
)
end
it "includes recent feedback for student view users" do
test_student = post_policies_course.student_view_student
auto_posted_assignment.grade_student(test_student, grade: 9, grader: teacher)
expect(test_student.recent_feedback).not_to be_empty
end
it "does not include recent feedback for unpublished assignments" do
auto_posted_assignment.grade_student(student, grade: 9, grader: teacher)
auto_posted_assignment.unpublish
expect(student.recent_feedback(contexts: [post_policies_course])).to be_empty
end
it "does not include recent feedback for other students in admin feedback" do
other_teacher = post_policies_course.enroll_teacher(User.create!, enrollment_state: :active).user
submission = auto_posted_assignment.grade_student(student, grade: 9, grader: teacher).first
submission.add_comment(author: other_teacher, comment: "hi :)")
expect(teacher.recent_feedback(contexts: [post_policies_course])).to be_empty
end
it "does not include non-recent feedback via old submission comments" do
submission = auto_posted_assignment.grade_student(student, grade: 9, grader: teacher).first
submission.add_comment(author: teacher, comment: "hooray")
Timecop.travel(1.year.from_now) do
expect(student.recent_feedback(contexts: [post_policies_course])).not_to include submission
end
end
end
describe '#courses_with_primary_enrollment' do
it "should return appropriate courses with primary enrollment" do
user_factory
@course1 = course_factory(:course_name => "course_factory", :active_course => true)
@course1.enroll_user(@user, 'StudentEnrollment', :enrollment_state => 'active')
@course2 = course_factory(:course_name => "other course_factory", :active_course => true)
@course2.enroll_user(@user, 'TeacherEnrollment', :enrollment_state => 'active')
@course3 = course_factory(:course_name => "yet another course", :active_course => true)
@course3.enroll_user(@user, 'StudentEnrollment', :enrollment_state => 'active')
@course3.enroll_user(@user, 'TeacherEnrollment', :enrollment_state => 'active')
@course4 = course_factory(:course_name => "not yet active")
@course4.enroll_user(@user, 'StudentEnrollment')
@course5 = course_factory(:course_name => "invited")
@course5.enroll_user(@user, 'TeacherEnrollment')
@course6 = course_factory(:course_name => "active but date restricted", :active_course => true)
@course6.restrict_student_future_view = true
@course6.save!
e = @course6.enroll_user(@user, 'StudentEnrollment')
e.accept!
e.start_at = 1.day.from_now
e.end_at = 2.days.from_now
e.save!
@course7 = course_factory(:course_name => "soft concluded", :active_course => true)
e = @course7.enroll_user(@user, 'StudentEnrollment')
e.accept!
e.start_at = 2.days.ago
e.end_at = 1.day.ago
e.save!
# only four, in the right order (type, then name), and with the top type per course
expect(@user.courses_with_primary_enrollment.map{|c| [c.id, c.primary_enrollment_type]}).to eql [
[@course5.id, 'TeacherEnrollment'],
[@course2.id, 'TeacherEnrollment'],
[@course3.id, 'TeacherEnrollment'],
[@course1.id, 'StudentEnrollment']
]
end
it "includes invitations to temporary users" do
user1 = user_factory
user2 = user_factory
c1 = course_factory(name: 'a', active_course: true)
e = c1.enroll_teacher(user1)
allow(user2).to receive(:temporary_invitations).and_return([e])
c2 = course_factory(name: 'b', active_course: true)
c2.enroll_user(user2)
expect(user2.courses_with_primary_enrollment.map(&:id)).to eq [c1.id, c2.id]
end
it 'filters out enrollments for deleted courses' do
student_in_course(active_course: true)
expect(@user.current_and_invited_courses.count).to eq 1
Course.where(id: @course).update_all(workflow_state: 'deleted')
expect(@user.current_and_invited_courses.count).to eq 0
end
it 'excludes deleted courses in cached_invitations' do
student_in_course(active_course: true)
expect(@user.cached_invitations.count).to eq 1
Course.where(id: @course).update_all(workflow_state: 'deleted')
expect(@user.cached_invitations.count).to eq 0
end
describe 'with cross sharding' do
specs_require_sharding
it 'pulls the enrollments that are completed with global ids' do
alice = bob = bobs_enrollment = alices_enrollment = nil
duped_enrollment_id = 0
@shard1.activate do
alice = User.create!(:name => 'alice')
bob = User.create!(:name => 'bob')
account = Account.create!
courseX = account.courses.build
courseX.workflow_state = 'available'
courseX.save!
bobs_enrollment = StudentEnrollment.create!(:course => courseX, :user => bob, :workflow_state => 'completed')
duped_enrollment_id = bobs_enrollment.id
end
@shard2.activate do
account = Account.create!
courseY = account.courses.build
courseY.workflow_state = 'available'
courseY.save!
alices_enrollment = StudentEnrollment.new(:course => courseY, :user => alice, :workflow_state => 'active')
alices_enrollment.id = duped_enrollment_id
alices_enrollment.save!
end
expect(alice.courses_with_primary_enrollment.size).to eq 1
end
it 'still filters out completed enrollments for the correct user' do
alice = nil
@shard1.activate do
alice = User.create!(:name => 'alice')
account = Account.create!
courseX = account.courses.build
courseX.workflow_state = 'available'
courseX.save!
StudentEnrollment.create!(:course => courseX, :user => alice, :workflow_state => 'completed')
end
expect(alice.courses_with_primary_enrollment.size).to eq 0
end
it 'filters out completed-by-date enrollments for the correct user' do
@shard1.activate do
@user = User.create!(:name => 'user')
account = Account.create!
courseX = account.courses.build
courseX.workflow_state = 'available'
courseX.start_at = 7.days.ago
courseX.conclude_at = 2.days.ago
courseX.restrict_enrollments_to_course_dates = true
courseX.save!
StudentEnrollment.create!(:course => courseX, :user => @user, :workflow_state => 'active')
end
expect(@user.courses_with_primary_enrollment.count).to eq 0
expect(@user.courses_with_primary_enrollment(:current_and_invited_courses, nil, :include_completed_courses => true).count).to eq 1
end
it 'works with favorite_courses' do
@user = User.create!(:name => 'user')
@shard1.activate do
account = Account.create!
@course = account.courses.build
@course.workflow_state = 'available'
@course.save!
StudentEnrollment.create!(:course => @course, :user => @user, :workflow_state => 'active')
end
@user.favorites.create!(:context => @course)
expect(@user.courses_with_primary_enrollment(:favorite_courses)).to eq [@course]
end
end
end
it "should delete system generated pseudonyms on delete" do
user_with_managed_pseudonym
expect(@pseudonym).to be_managed_password
expect(@user.workflow_state).to eq "pre_registered"
@user.destroy
expect(@user.workflow_state).to eq "deleted"
@user.reload
expect(@user.workflow_state).to eq "deleted"
end
it "destroys associated active eportfolios upon soft-deletion" do
user = User.create
user.eportfolios.create!
expect { user.destroy }.to change {
user.reload.eportfolios.active.count
}.from(1).to(0)
end
it "destroys associated active eportfolios when removed from root account" do
user = User.create
user.eportfolios.create!
expect { user.remove_from_root_account(Account.default) }.to change {
user.reload.eportfolios.active.count
}.from(1).to(0)
end
it "should record deleted_at" do
user = User.create
user.destroy
expect(user.deleted_at).not_to be_nil
end
describe "can_masquerade?" do
it "should allow self" do
@user = user_with_pseudonym(:username => 'nobody1@example.com')
expect(@user.can_masquerade?(@user, Account.default)).to be_truthy
end
it "should not allow other users" do
@user1 = user_with_pseudonym(:username => 'nobody1@example.com')
@user2 = user_with_pseudonym(:username => 'nobody2@example.com')
expect(@user1.can_masquerade?(@user2, Account.default)).to be_falsey
expect(@user2.can_masquerade?(@user1, Account.default)).to be_falsey
end
it "should allow site and account admins" do
user = user_with_pseudonym(:username => 'nobody1@example.com')
@admin = user_with_pseudonym(:username => 'nobody2@example.com')
@site_admin = user_with_pseudonym(:username => 'nobody3@example.com', :account => Account.site_admin)
Account.site_admin.account_users.create!(user: @site_admin)
Account.default.account_users.create!(user: @admin)
expect(user.can_masquerade?(@site_admin, Account.default)).to be_truthy
expect(@admin.can_masquerade?(@site_admin, Account.default)).to be_truthy
expect(user.can_masquerade?(@admin, Account.default)).to be_truthy
expect(@admin.can_masquerade?(@admin, Account.default)).to be_truthy
expect(@admin.can_masquerade?(user, Account.default)).to be_falsey
expect(@site_admin.can_masquerade?(@site_admin, Account.default)).to be_truthy
expect(@site_admin.can_masquerade?(user, Account.default)).to be_falsey
expect(@site_admin.can_masquerade?(@admin, Account.default)).to be_falsey
end
it "should not allow restricted admins to become full admins" do
user = user_with_pseudonym(:username => 'nobody1@example.com')
@restricted_admin = user_with_pseudonym(:username => 'nobody3@example.com')
role = custom_account_role('Restricted', :account => Account.default)
account_admin_user_with_role_changes(:user => @restricted_admin, :role => role, :role_changes => { :become_user => true })
@admin = user_with_pseudonym(:username => 'nobody2@example.com')
Account.default.account_users.create!(user: @admin)
expect(user.can_masquerade?(@restricted_admin, Account.default)).to be_truthy
expect(@admin.can_masquerade?(@restricted_admin, Account.default)).to be_falsey
expect(@restricted_admin.can_masquerade?(@admin, Account.default)).to be_truthy
end
it "should allow to admin even if user is in multiple accounts" do
user = user_with_pseudonym(:username => 'nobody1@example.com')
@account2 = Account.create!
user.pseudonyms.create!(:unique_id => 'nobodyelse@example.com', :account => @account2)
@admin = user_with_pseudonym(:username => 'nobody2@example.com')
@site_admin = user_with_pseudonym(:username => 'nobody3@example.com')
Account.default.account_users.create!(user: @admin)
Account.site_admin.account_users.create!(user: @site_admin)
expect(user.can_masquerade?(@admin, Account.default)).to be_truthy
expect(user.can_masquerade?(@admin, @account2)).to be_falsey
expect(user.can_masquerade?(@site_admin, Account.default)).to be_truthy
expect(user.can_masquerade?(@site_admin, @account2)).to be_truthy
@account2.account_users.create!(user: @admin)
end
it "should allow site admin when they don't otherwise qualify for :create_courses" do
user = user_with_pseudonym(:username => 'nobody1@example.com')
@admin = user_with_pseudonym(:username => 'nobody2@example.com')
@site_admin = user_with_pseudonym(:username => 'nobody3@example.com', :account => Account.site_admin)
Account.default.account_users.create!(user: @admin)
Account.site_admin.account_users.create!(user: @site_admin)
course_factory
@course.enroll_teacher(@admin)
Account.default.update_attribute(:settings, {:teachers_can_create_courses => true})
expect(@admin.can_masquerade?(@site_admin, Account.default)).to be_truthy
end
it "should allow teacher to become student view student" do
course_with_teacher(:active_all => true)
@fake_student = @course.student_view_student
expect(@fake_student.can_masquerade?(@teacher, Account.default)).to be_truthy
end
end
describe '#has_subset_of_account_permissions?' do
let(:user) { User.new }
let(:other_user) { User.new }
it 'returns true for self' do
expect(user.has_subset_of_account_permissions?(user, nil)).to be_truthy
end
it 'is false if the account is not a root account' do
expect(user.has_subset_of_account_permissions?(other_user, double(:root_account? => false))).to be_falsey
end
it 'is true if there are no account users for this root account' do
account = double(:root_account? => true, :cached_all_account_users_for => [])
expect(user.has_subset_of_account_permissions?(other_user, account)).to be_truthy
end
it 'is true when all account_users for current user are subsets of target user' do
account = double(:root_account? => true, :cached_all_account_users_for => [double(:is_subset_of? => true)])
expect(user.has_subset_of_account_permissions?(other_user, account)).to be_truthy
end
it 'is false when any account_user for current user is not a subset of target user' do
account = double(:root_account? => true, :cached_all_account_users_for => [double(:is_subset_of? => false)])
expect(user.has_subset_of_account_permissions?(other_user, account)).to be_falsey
end
end
context "check_courses_right?" do
before :once do
course_with_teacher(active_all: true)
course_with_student(course: @course, active_all: true)
@teacher1 = @teacher
@student1 = @student
@active_course = @course
course_with_teacher(active_all: true)
course_with_student(course: @course, active_all: true)
@teacher2 = @teacher
@student2 = @student
@concluded_course = @course
@concluded_course.complete!
end
it "should require parameters" do
expect(@student1.check_courses_right?(nil, :some_right)).to be_falsey
expect(@student1.check_courses_right?(@teacher1, nil)).to be_falsey
end
it "should check both active and concluded courses" do
expect(@student1.check_courses_right?(@teacher1, :manage_wiki)).to be_truthy
expect(@student2.check_courses_right?(@teacher2, :read_forum)).to be_truthy
@concluded_course.grants_right?(@teacher2, :manage_wiki)
end
it "allows for narrowing courses by enrollments" do
expect(@student2.check_courses_right?(@teacher2, :manage_account_memberships, @student2.enrollments.concluded)).to be_falsey
end
end
context "search_messageable_users" do
before(:once) do
@admin = user_model
@student = user_model
tie_user_to_account(@admin, :role => admin_role)
role = custom_account_role('CustomStudent', :account => Account.default)
tie_user_to_account(@student, :role => role)
set_up_course_with_users
end
def set_up_course_with_users
@course = course_model(:name => 'the course')
@this_section_teacher = @teacher
@course.offer!
@this_section_user = user_model
@this_section_user_enrollment = @course.enroll_user(@this_section_user, 'StudentEnrollment', :enrollment_state => 'active')
@other_section_user = user_model
@other_section = @course.course_sections.create
@course.enroll_user(@other_section_user, 'StudentEnrollment', :enrollment_state => 'active', :section => @other_section)
@other_section_teacher = user_model
@course.enroll_user(@other_section_teacher, 'TeacherEnrollment', :enrollment_state => 'active', :section => @other_section)
@group = @course.groups.create(:name => 'the group')
@group.users = [@this_section_user]
@unrelated_user = user_model
@deleted_user = user_model(:name => 'deleted')
@course.enroll_user(@deleted_user, 'StudentEnrollment', :enrollment_state => 'active')
@deleted_user.destroy
end
# convenience to search and then get the first page. none of these specs
# should be putting more than a handful of users into the search results...
# right?
def search_messageable_users(viewing_user, *args)
viewing_user.address_book.search_users(*args).paginate(:page => 1, :per_page => 20)
end
it "should include yourself even when not enrolled in courses" do
@student = user_model
expect(search_messageable_users(@student).map(&:id)).to include(@student.id)
end
it "should only return users from the specified context and type" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
expect(search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id).sort).
to eql [@student, @this_section_user, @this_section_teacher, @other_section_user, @other_section_teacher].map(&:id).sort
expect(@student.count_messageable_users_in_course(@course)).to eql 5
expect(search_messageable_users(@student, :context => "course_#{@course.id}_students").map(&:id).sort).
to eql [@student, @this_section_user, @other_section_user].map(&:id).sort
expect(search_messageable_users(@student, :context => "group_#{@group.id}").map(&:id).sort).
to eql [@this_section_user].map(&:id).sort
expect(@student.count_messageable_users_in_group(@group)).to eql 1
expect(search_messageable_users(@student, :context => "section_#{@other_section.id}").map(&:id).sort).
to eql [@other_section_user, @other_section_teacher].map(&:id).sort
expect(search_messageable_users(@student, :context => "section_#{@other_section.id}_teachers").map(&:id).sort).
to eql [@other_section_teacher].map(&:id).sort
end
it "should not include users from other sections if visibility is limited to sections" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active', :limit_privileges_to_course_section => true)
messageable_users = search_messageable_users(@student).map(&:id)
expect(messageable_users).to include @this_section_user.id
expect(messageable_users).not_to include @other_section_user.id
messageable_users = search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id)
expect(messageable_users).to include @this_section_user.id
expect(messageable_users).not_to include @other_section_user.id
messageable_users = search_messageable_users(@student, :context => "section_#{@other_section.id}").map(&:id)
expect(messageable_users).to be_empty
end
it "should let students message the entire class by default" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
expect(search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id).sort).
to eql [@student, @this_section_user, @this_section_teacher, @other_section_user, @other_section_teacher].map(&:id).sort
end
it "should not let users message the entire class if they cannot send_messages" do
RoleOverride.create!(:context => @course.account, :permission => 'send_messages',
:role => student_role, :enabled => false)
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
# can only message self or the admins
expect(search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id).sort).
to eql [@student, @this_section_teacher, @other_section_teacher].map(&:id).sort
end
it "should not include deleted users" do
expect(search_messageable_users(@student).map(&:id)).not_to include(@deleted_user.id)
expect(search_messageable_users(@student, :search => @deleted_user.name).map(&:id)).to be_empty
expect(search_messageable_users(@student, :strict_checks => false).map(&:id)).not_to include(@deleted_user.id)
expect(search_messageable_users(@student, :strict_checks => false, :search => @deleted_user.name).map(&:id)).to be_empty
end
it "should include deleted iff strict_checks=false" do
expect(@student.load_messageable_user(@deleted_user.id, :strict_checks => false)).not_to be_nil
expect(@student.load_messageable_user(@deleted_user.id)).to be_nil
end
it "should only include users from the specified section" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
messageable_users = search_messageable_users(@student, :context => "section_#{@course.default_section.id}").map(&:id)
expect(messageable_users).to include @this_section_user.id
expect(messageable_users).not_to include @other_section_user.id
messageable_users = search_messageable_users(@student, :context => "section_#{@other_section.id}").map(&:id)
expect(messageable_users).not_to include @this_section_user.id
expect(messageable_users).to include @other_section_user.id
end
it "should include users from all sections if visibility is not limited to sections" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
messageable_users = search_messageable_users(@student).map(&:id)
expect(messageable_users).to include @this_section_user.id
expect(messageable_users).to include @other_section_user.id
end
it "should return users for a specified group if the receiver can access the group" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
expect(search_messageable_users(@this_section_user, :context => "group_#{@group.id}").map(&:id)).to eql [@this_section_user.id]
# student can see it too, even though he's not in the group (since he can view the roster)
expect(search_messageable_users(@student, :context => "group_#{@group.id}").map(&:id)).to eql [@this_section_user.id]
end
it "should respect section visibility when returning users for a specified group" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active', :limit_privileges_to_course_section => true)
@group.users << @other_section_user
expect(search_messageable_users(@this_section_user, :context => "group_#{@group.id}").map(&:id).sort).to eql [@this_section_user.id, @other_section_user.id]
expect(@this_section_user.count_messageable_users_in_group(@group)).to eql 2
# student can only see people in his section
expect(search_messageable_users(@student, :context => "group_#{@group.id}").map(&:id)).to eql [@this_section_user.id]
expect(@student.count_messageable_users_in_group(@group)).to eql 1
end
it "should only show admins and the observed if the receiver is an observer" do
@course.enroll_user(@admin, 'TeacherEnrollment', :enrollment_state => 'active')
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
observer = user_model
enrollment = @course.enroll_user(observer, 'ObserverEnrollment', :enrollment_state => 'active')
enrollment.associated_user_id = @student.id
enrollment.save
messageable_users = search_messageable_users(observer).map(&:id)
expect(messageable_users).to include @admin.id
expect(messageable_users).to include @student.id
expect(messageable_users).not_to include @this_section_user.id
expect(messageable_users).not_to include @other_section_user.id
end
it "should not show non-linked observers to students" do
@course.enroll_user(@admin, 'TeacherEnrollment', :enrollment_state => 'active')
student1, student2 = user_model, user_model
@course.enroll_user(student1, 'StudentEnrollment', :enrollment_state => 'active')
@course.enroll_user(student2, 'StudentEnrollment', :enrollment_state => 'active')
observer = user_model
enrollment = @course.enroll_user(observer, 'ObserverEnrollment', :enrollment_state => 'active')
enrollment.associated_user_id = student1.id
enrollment.save
expect(search_messageable_users(student1).map(&:id)).to include observer.id
expect(student1.count_messageable_users_in_course(@course)).to eql 8
expect(search_messageable_users(student2).map(&:id)).not_to include observer.id
expect(student2.count_messageable_users_in_course(@course)).to eql 7
end
it "should include all shared contexts and enrollment information" do
@first_course = @course
@first_course.enroll_user(@this_section_user, 'TaEnrollment', :enrollment_state => 'active')
@first_course.enroll_user(@admin, 'TeacherEnrollment', :enrollment_state => 'active')
@other_course = course_model
@other_course.offer!
@other_course.enroll_user(@admin, 'TeacherEnrollment', :enrollment_state => 'active')
# other_section_user is a teacher in one course, student in another
@other_course.enroll_user(@other_section_user, 'TeacherEnrollment', :enrollment_state => 'active')
address_book = @admin.address_book
search_messageable_users(@admin)
common_courses = address_book.common_courses(@this_section_user)
expect(common_courses.keys).to include @first_course.id
expect(common_courses[@first_course.id].sort).to eql ['StudentEnrollment', 'TaEnrollment']
common_courses = address_book.common_courses(@other_section_user)
expect(common_courses.keys).to include @first_course.id
expect(common_courses[@first_course.id].sort).to eql ['StudentEnrollment']
expect(common_courses.keys).to include @other_course.id
expect(common_courses[@other_course.id].sort).to eql ['TeacherEnrollment']
end
it "should include users with no shared contexts iff admin" do
expect(search_messageable_users(@admin).map(&:id)).to include(@student.id)
expect(search_messageable_users(@student).map(&:id)).not_to include(@admin.id)
end
it "should not do admin catch-all if specific contexts requested" do
course1 = course_model
course2 = course_model
course2.offer!
enrollment = course2.enroll_teacher(@admin)
enrollment.workflow_state = 'active'
enrollment.save
@admin.reload
enrollment = course2.enroll_student(@student)
enrollment.workflow_state = 'active'
enrollment.save
expect(search_messageable_users(@admin, :context => "course_#{course1.id}").map(&:id)).not_to include(@student.id)
expect(search_messageable_users(@admin, :context => "course_#{course2.id}").map(&:id)).to include(@student.id)
expect(search_messageable_users(@student, :context => "course_#{course2.id}").map(&:id)).to include(@admin.id)
end
it "should not rank results by default" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
# ordered by name (all the same), then id
expect(search_messageable_users(@student).map(&:id)).
to eql [@student.id, @this_section_teacher.id, @this_section_user.id, @other_section_user.id, @other_section_teacher.id]
end
context "concluded enrollments" do
it "should return concluded enrollments" do # i.e. you can do a bare search for people who used to be in your class
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
@this_section_user_enrollment.conclude
expect(search_messageable_users(@this_section_user).map(&:id)).to include @this_section_user.id
expect(search_messageable_users(@student).map(&:id)).to include @this_section_user.id
end
it "should not return concluded student enrollments in the course" do # when browsing a course you should not see concluded enrollments
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
@course.complete!
expect(search_messageable_users(@this_section_user, :context => "course_#{@course.id}").map(&:id)).not_to include @this_section_user.id
# if the course was a concluded, a student should be able to browse it and message an admin (if if the admin's enrollment concluded too)
expect(search_messageable_users(@this_section_user, :context => "course_#{@course.id}").map(&:id)).to include @this_section_teacher.id
expect(@this_section_user.count_messageable_users_in_course(@course)).to eql 2 # just the admins
expect(search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id)).not_to include @this_section_user.id
expect(search_messageable_users(@student, :context => "course_#{@course.id}").map(&:id)).to include @this_section_teacher.id
expect(@student.count_messageable_users_in_course(@course)).to eql 2
end
it "users with concluded enrollments should not be messageable" do
@course.enroll_user(@student, 'StudentEnrollment', :enrollment_state => 'active')
expect(search_messageable_users(@student, :context => "group_#{@group.id}").map(&:id)).to eql [@this_section_user.id]
expect(@student.count_messageable_users_in_group(@group)).to eql 1
@this_section_user_enrollment.conclude
expect(search_messageable_users(@this_section_user, :context => "group_#{@group.id}").map(&:id)).to eql []
expect(@this_section_user.count_messageable_users_in_group(@group)).to eql 0
expect(search_messageable_users(@student, :context => "group_#{@group.id}").map(&:id)).to eql []
expect(@student.count_messageable_users_in_group(@group)).to eql 0
end
end
context "weak_checks" do
it "should optionally show invited enrollments" do
course_factory(active_all: true)
student_in_course(:user_state => 'creation_pending')
expect(search_messageable_users(@teacher, weak_checks: true).map(&:id)).to include @student.id
end
it "should optionally show pending enrollments in unpublished courses" do
course_factory()
teacher_in_course(:active_all => true)
student_in_course()
expect(search_messageable_users(@teacher, weak_checks: true, context: @course.asset_string).map(&:id)).to include @student.id
end
end
end
context "tabs_available" do
before(:once) { Account.default }
it "should not include unconfigured external tools" do
tool = Account.default.context_external_tools.new(:consumer_key => 'bob', :shared_secret => 'bob', :name => 'bob', :domain => "example.com")
tool.course_navigation = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
expect(tool.has_placement?(:user_navigation)).to eq false
user_model
tabs = @user.profile.tabs_available(@user, :root_account => Account.default)
expect(tabs.map{|t| t[:id] }).not_to be_include(tool.asset_string)
end
it "should include configured external tools" do
tool = Account.default.context_external_tools.new(:consumer_key => 'bob', :shared_secret => 'bob', :name => 'bob', :domain => "example.com")
tool.user_navigation = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
expect(tool.has_placement?(:user_navigation)).to eq true
user_model
tabs = @user.profile.tabs_available(@user, :root_account => Account.default)
expect(tabs.map{|t| t[:id] }).to be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
expect(tab[:href]).to eq :user_external_tool_path
expect(tab[:args]).to eq [@user.id, tool.id]
expect(tab[:label]).to eq "Example URL"
end
end
context "avatars" do
before :once do
user_model
end
it "should find only users with avatars set" do
@user.avatar_state = 'submitted'
@user.save!
expect(User.with_avatar_state('submitted').count).to eq 0
expect(User.with_avatar_state('any').count).to eq 0
@user.avatar_image_url = 'http://www.example.com'
@user.save!
expect(User.with_avatar_state('submitted').count).to eq 1
expect(User.with_avatar_state('any').count).to eq 1
end
it "should clear avatar state when assigning by service that no longer exists" do
@user.avatar_image_url = 'http://www.example.com'
@user.avatar_image = { 'type' => 'twitter' }
expect(@user.avatar_image_url).to be_nil
end
it "should not allow external urls to be assigned" do
@user.avatar_image = { 'type' => 'external', 'url' => 'http://www.example.com/image.jpg' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should allow external urls that match avatar_external_url_patterns to be assigned" do
@user.avatar_image = { 'type' => 'external', 'url' => 'https://www.instructure.com/image.jpg' }
@user.save!
expect(@user.reload.avatar_image_url).to eq "https://www.instructure.com/image.jpg"
end
it "should not allow external urls that do not match avatar_external_url_patterns to be assigned (apple.com)" do
@user.avatar_image = { 'type' => 'external', 'url' => 'https://apple.com/image.jpg' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should not allow external urls that do not match avatar_external_url_patterns to be assigned (ddinstructure.com)" do
@user.avatar_image = { 'type' => 'external', 'url' => 'https://ddinstructure.com/image' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should not allow external urls that do not match avatar_external_url_patterns to be assigned (3510111291#instructure.com)" do
@user.avatar_image = { 'type' => 'external', 'url' => 'https://3510111291#sdf.instructure.com/image' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should allow gravatar urls to be assigned" do
@user.avatar_image = { 'type' => 'gravatar', 'url' => 'http://www.gravatar.com/image.jpg' }
@user.save!
expect(@user.reload.avatar_image_url).to eq 'http://www.gravatar.com/image.jpg'
end
it "should not allow non gravatar urls to be assigned (ddgravatar.com)" do
@user.avatar_image = { 'type' => 'external', 'url' => 'http://ddgravatar.com/@google.com' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should not allow non gravatar external urls to be assigned (3510111291#secure.gravatar.com)" do
@user.avatar_image = { 'type' => 'external', 'url' => 'http://3510111291#secure.gravatar.com/@google.com' }
@user.save!
expect(@user.reload.avatar_image_url).to eq nil
end
it "should return a useful avatar_fallback_url" do
allow(HostUrl).to receive(:protocol).and_return('https')
expect(User.avatar_fallback_url).to eq(
"https://#{HostUrl.default_host}/images/messages/avatar-50.png"
)
expect(User.avatar_fallback_url("/somepath")).to eq(
"https://#{HostUrl.default_host}/somepath"
)
expect(HostUrl).to receive(:default_host).and_return('somedomain:3000')
expect(User.avatar_fallback_url("/path")).to eq(
"https://somedomain:3000/path"
)
expect(User.avatar_fallback_url("//somedomain/path")).to eq(
"https://somedomain/path"
)
expect(User.avatar_fallback_url("http://somedomain/path")).to eq(
"http://somedomain/path"
)
expect(User.avatar_fallback_url("http://somedomain:3000/path")).to eq(
"http://somedomain:3000/path"
)
expect(User.avatar_fallback_url(nil, OpenObject.new(:host => "foo", :protocol => "http://"))).to eq(
"http://foo/images/messages/avatar-50.png"
)
expect(User.avatar_fallback_url("/somepath", OpenObject.new(:host => "bar", :protocol => "https://"))).to eq(
"https://bar/somepath"
)
expect(User.avatar_fallback_url("//somedomain/path", OpenObject.new(:host => "bar", :protocol => "https://"))).to eq(
"https://somedomain/path"
)
expect(User.avatar_fallback_url("http://somedomain/path", OpenObject.new(:host => "bar", :protocol => "https://"))).to eq(
"http://somedomain/path"
)
expect(User.avatar_fallback_url('%{fallback}')).to eq(
'%{fallback}'
)
end
describe "#clear_avatar_image_url_with_uuid" do
before :once do
@user.avatar_image_url = '1234567890ABCDEF'
@user.save!
end
it "should raise ArgumentError when uuid nil or blank" do
expect { @user.clear_avatar_image_url_with_uuid(nil) }.to raise_error(ArgumentError, "'uuid' is required and cannot be blank")
expect { @user.clear_avatar_image_url_with_uuid('') }.to raise_error(ArgumentError, "'uuid' is required and cannot be blank")
expect { @user.clear_avatar_image_url_with_uuid(' ') }.to raise_error(ArgumentError, "'uuid' is required and cannot be blank")
end
it "should clear avatar_image_url when uuid matches" do
@user.clear_avatar_image_url_with_uuid('1234567890ABCDEF')
expect(@user.avatar_image_url).to be_nil
expect(@user.changed?).to eq false # should be saved
end
it "should not clear avatar_image_url when no match" do
@user.clear_avatar_image_url_with_uuid('NonMatchingText')
expect(@user.avatar_image_url).to eq '1234567890ABCDEF'
end
it "should not error when avatar_image_url is nil" do
@user.avatar_image_url = nil
@user.save!
#
expect { @user.clear_avatar_image_url_with_uuid('something') }.not_to raise_error
expect(@user.avatar_image_url).to be_nil
end
end
end
it "should find sections for course" do
course_with_student
expect(@student.sections_for_course(@course)).to include @course.default_section
end
describe "name_parts" do
it "should infer name parts" do
expect(User.name_parts('Cody Cutrer')).to eq ['Cody', 'Cutrer', nil]
expect(User.name_parts(' Cody Cutrer ')).to eq ['Cody', 'Cutrer', nil]
expect(User.name_parts('Cutrer, Cody')).to eq ['Cody', 'Cutrer', nil]
expect(User.name_parts('Cutrer, Cody',
likely_already_surname_first: true)).to eq ['Cody', 'Cutrer', nil]
expect(User.name_parts('Cutrer, Cody Houston')).to eq ['Cody Houston', 'Cutrer', nil]
expect(User.name_parts('Cutrer, Cody Houston',
likely_already_surname_first: true)).to eq ['Cody Houston', 'Cutrer', nil]
expect(User.name_parts('St. Clair, John')).to eq ['John', 'St. Clair', nil]
expect(User.name_parts('St. Clair, John',
likely_already_surname_first: true)).to eq ['John', 'St. Clair', nil]
# sorry, can't figure this out
expect(User.name_parts('John St. Clair')).to eq ['John St.', 'Clair', nil]
expect(User.name_parts('Jefferson Thomas Cutrer IV')).to eq ['Jefferson Thomas', 'Cutrer', 'IV']
expect(User.name_parts('Jefferson Thomas Cutrer, IV')).to eq ['Jefferson Thomas', 'Cutrer', 'IV']
expect(User.name_parts('Cutrer, Jefferson, IV')).to eq ['Jefferson', 'Cutrer', 'IV']
expect(User.name_parts('Cutrer, Jefferson, IV',
likely_already_surname_first: true)).to eq ['Jefferson', 'Cutrer', 'IV']
expect(User.name_parts('Cutrer, Jefferson IV')).to eq ['Jefferson', 'Cutrer', 'IV']
expect(User.name_parts('Cutrer, Jefferson IV',
likely_already_surname_first: true)).to eq ['Jefferson', 'Cutrer', 'IV']
expect(User.name_parts(nil)).to eq [nil, nil, nil]
expect(User.name_parts('Bob')).to eq ['Bob', nil, nil]
expect(User.name_parts('Ho, Chi, Min')).to eq ['Chi Min', 'Ho', nil]
expect(User.name_parts('Ho, Chi, Min')).to eq ['Chi Min', 'Ho', nil]
# sorry, don't understand cultures that put the surname first
# they should just manually specify their sort name
expect(User.name_parts('Ho Chi Min')).to eq ['Ho Chi', 'Min', nil]
expect(User.name_parts('')).to eq [nil, nil, nil]
expect(User.name_parts('John Doe')).to eq ['John', 'Doe', nil]
expect(User.name_parts('Junior')).to eq ['Junior', nil, nil]
expect(User.name_parts('John St. Clair', prior_surname: 'St. Clair')).to eq ['John', 'St. Clair', nil]
expect(User.name_parts('John St. Clair', prior_surname: 'Cutrer')).to eq ['John St.', 'Clair', nil]
expect(User.name_parts('St. Clair', prior_surname: 'St. Clair')).to eq [nil, 'St. Clair', nil]
expect(User.name_parts('St. Clair,')).to eq [nil, 'St. Clair', nil]
# don't get confused by given names that look like suffixes
expect(User.name_parts('Duing, Vi')).to eq ['Vi', 'Duing', nil]
# we can't be perfect. don't know what to do with this
expect(User.name_parts('Duing Chi Min, Vi')).to eq ['Duing Chi', 'Min', 'Vi']
# unless we thought it was already last name first
expect(User.name_parts('Duing Chi Min, Vi',
likely_already_surname_first: true)).to eq ['Vi', 'Duing Chi Min', nil]
end
it "should keep the sortable_name up to date if all that changed is the name" do
u = User.new
u.name = 'Cody Cutrer'
u.save!
expect(u.sortable_name).to eq 'Cutrer, Cody'
u.name = 'Bracken Mosbacker'
u.save!
expect(u.sortable_name).to eq 'Mosbacker, Bracken'
u.name = 'John St. Clair'
u.sortable_name = 'St. Clair, John'
u.save!
expect(u.sortable_name).to eq 'St. Clair, John'
u.name = 'Matthew St. Clair'
u.save!
expect(u.sortable_name).to eq "St. Clair, Matthew"
u.name = 'St. Clair'
u.save!
expect(u.sortable_name).to eq "St. Clair,"
end
end
context "group_member_json" do
before :once do
@account = Account.default
@enrollment = course_with_student(:active_all => true)
@section = @enrollment.course_section
@student.sortable_name = 'Doe, John'
@student.short_name = 'Johnny'
@student.save
end
it "should include user_id, name, and display_name" do
expect(@student.group_member_json(@account)).to eq({
:user_id => @student.id,
:name => 'Doe, John',
:display_name => 'Johnny'
})
end
it "should include course section (section_id and section_code) if appropriate" do
expect(@student.group_member_json(@account)).to eq({
:user_id => @student.id,
:name => 'Doe, John',
:display_name => 'Johnny'
})
expect(@student.group_member_json(@course)).to eq({
:user_id => @student.id,
:name => 'Doe, John',
:display_name => 'Johnny',
:sections => [ {
:section_id => @section.id,
:section_code => @section.section_code
} ]
})
end
end
describe "menu_courses" do
it "should include temporary invitations" do
user_with_pseudonym(:active_all => 1)
@user1 = @user
user_factory
@user2 = @user
@user2.update_attribute(:workflow_state, 'creation_pending')
@user2.communication_channels.create!(:path => @cc.path)
course_factory(active_all: true)
@course.enroll_user(@user2)
expect(@user1.menu_courses).to eq [@course]
end
end
describe "favorites" do
before :once do
@user = User.create!
@courses = []
(1..3).each do |x|
course = course_with_student(:course_name => "Course #{x}", :user => @user, :active_all => true).course
@courses << course
@user.favorites.first_or_create!(:context_type => "Course", :context_id => course)
end
@user.save!
end
it "should default favorites to enrolled courses when favorite courses do not exist" do
@user.favorites.by("Course").destroy_all
expect(@user.menu_courses.to_set).to eq @courses.to_set
end
it "should only include favorite courses when set" do
course = @courses.shift
@user.favorites.where(context_type: "Course", context_id: course).first.destroy
expect(@user.menu_courses.to_set).to eq @courses.to_set
end
context "sharding" do
specs_require_sharding
before :each do
account2 = @shard1.activate { account_model }
(4..6).each do |x|
course = course_with_student(:course_name => "Course #{x}", :user => @user, :active_all => true, :account => account2).course
@courses << course
@user.favorites.first_or_create!(:context_type => "Course", :context_id => course)
end
end
it "should include cross shard favorite courses" do
expect(@user.menu_courses).to match_array(@courses)
end
it 'works for shadow records' do
@shard1.activate do
@shadow = User.create!(:id => @user.global_id)
end
expect(@shadow.favorites.exists?).to be_truthy
end
end
end
describe "adding to favorites on enrollment" do
it "doesn't add a favorite if no course favorites already exist" do
course_with_student(:active_all => true)
expect(@student.favorites.count).to eq 0
end
it "adds a favorite if any course favorites already exist" do
u = User.create!
c1 = course_with_student(:active_all => true, :user => u).course
u.favorites.create!(:context_type => "Course", :context_id => c1)
c2 = course_with_student(:active_all => true, :user => u).course
expect(u.favorites.where(:context_type => "Course", :context_id => c2).exists?).to eq true
end
end
describe "cached_currentish_enrollments" do
it "should include temporary invitations" do
user_with_pseudonym(:active_all => 1)
@user1 = @user
user_factory
@user2 = @user
@user2.update_attribute(:workflow_state, 'creation_pending')
@user2.communication_channels.create!(:path => @cc.path)
course_factory(active_all: true)
@enrollment = @course.enroll_user(@user2)
expect(@user1.cached_currentish_enrollments).to eq [@enrollment]
end
context "sharding" do
specs_require_sharding
it "should include enrollments from all shards" do
user = User.create!
course1 = Account.default.courses.create!
course1.offer!
e1 = course1.enroll_student(user)
e2 = @shard1.activate do
account2 = Account.create!
course2 = account2.courses.create!
course2.offer!
course2.enroll_student(user)
end
expect(user.cached_currentish_enrollments).to eq [e1, e2]
end
it "should properly update when using new redis cache keys" do
skip("requires redis") unless Canvas.redis_enabled?
enable_cache(:redis_cache_store) do
user = User.create!
course1 = Account.default.courses.create!(:workflow_state => "available")
e1 = course1.enroll_student(user, :enrollment_state => "active")
expect(user.cached_currentish_enrollments).to eq [e1]
e2 = @shard1.activate do
account2 = Account.create!
course2 = account2.courses.create!(:workflow_state => "available")
course2.enroll_student(user, :enrollment_state => "active")
end
expect(user.cached_currentish_enrollments).to eq [e1, e2]
end
end
end
end
describe "#find_or_initialize_pseudonym_for_account" do
before :once do
@account1 = Account.create!
@account2 = Account.create!
@account3 = Account.create!
end
it "should create a copy of an existing pseudonym" do
# from unrelated account
user_with_pseudonym(:active_all => 1, :account => @account2, :username => 'unrelated@example.com', :password => 'abcdefgh')
new_pseudonym = @user.find_or_initialize_pseudonym_for_account(@account1)
expect(new_pseudonym).not_to be_nil
expect(new_pseudonym).to be_new_record
expect(new_pseudonym.unique_id).to eq 'unrelated@example.com'
# from default account
@user.pseudonyms.create!(:unique_id => 'default@example.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
@user.pseudonyms.create!(:account => @account3, :unique_id => 'preferred@example.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
new_pseudonym = @user.find_or_initialize_pseudonym_for_account(@account1)
expect(new_pseudonym).not_to be_nil
expect(new_pseudonym).to be_new_record
expect(new_pseudonym.unique_id).to eq 'default@example.com'
# from site admin account
site_admin_pseudo = @user.pseudonyms.create!(:account => Account.site_admin, :unique_id => 'siteadmin@example.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
new_pseudonym = @user.find_or_initialize_pseudonym_for_account(@account1)
expect(new_pseudonym).not_to be_nil
expect(new_pseudonym).to be_new_record
expect(new_pseudonym.unique_id).to eq 'siteadmin@example.com'
site_admin_pseudo.destroy
@user.reload
# from preferred account
new_pseudonym = @user.find_or_initialize_pseudonym_for_account(@account1, @account3)
expect(new_pseudonym).not_to be_nil
expect(new_pseudonym).to be_new_record
expect(new_pseudonym.unique_id).to eq 'preferred@example.com'
# from unrelated account, if other options are not viable
user2 = User.create!
@account1.pseudonyms.create!(:user => user2, :unique_id => 'preferred@example.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
@user.pseudonyms.detect { |p| p.account == Account.site_admin }.update_attribute(:password_auto_generated, true)
Account.default.authentication_providers.create!(:auth_type => 'cas')
Account.default.authentication_providers.first.move_to_bottom
new_pseudonym = @user.find_or_initialize_pseudonym_for_account(@account1, @account3)
expect(new_pseudonym).not_to be_nil
expect(new_pseudonym).to be_new_record
expect(new_pseudonym.unique_id).to eq 'unrelated@example.com'
new_pseudonym.save!
expect(new_pseudonym.valid_password?('abcdefgh')).to be_truthy
end
it "should not create a new one when there are no viable candidates" do
# no pseudonyms
user_factory
expect(@user.find_or_initialize_pseudonym_for_account(@account1)).to be_nil
# auto-generated password
@user.pseudonyms.create!(:account => @account2, :unique_id => 'bracken@instructure.com')
expect(@user.find_or_initialize_pseudonym_for_account(@account1)).to be_nil
# delegated auth
@account3.authentication_providers.create!(:auth_type => 'cas')
@account3.authentication_providers.first.move_to_bottom
expect(@account3).to be_delegated_authentication
@user.pseudonyms.create!(:account => @account3, :unique_id => 'jacob@instructure.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
expect(@user.find_or_initialize_pseudonym_for_account(@account1)).to be_nil
# conflict
@user2 = User.create! { |u| u.workflow_state = 'registered' }
@user2.pseudonyms.create!(:account => @account1, :unique_id => 'jt@instructure.com', :password => 'abcdefgh', :password_confirmation => 'abcdefgh')
@user.pseudonyms.create!(:unique_id => 'jt@instructure.com', :password => 'ghijklmn', :password_confirmation => 'ghijklmn')
expect(@user.find_or_initialize_pseudonym_for_account(@account1)).to be_nil
end
context "sharding" do
specs_require_sharding
before :once do
@shard1.activate do
account = Account.create!
user_with_pseudonym(:active_all => 1, :account => account, :password => 'qwertyuiop')
end
end
it "should copy a pseudonym from another shard" do
p = @user.find_or_initialize_pseudonym_for_account(Account.site_admin)
expect(p).to be_new_record
p.save!
expect(p.valid_password?('qwertyuiop')).to be_truthy
end
end
end
describe "can_be_enrolled_in_course?" do
before :once do
course_factory active_all: true
end
it "should allow a user with a pseudonym in the course's root account" do
user_with_pseudonym account: @course.root_account, active_all: true
expect(@user.can_be_enrolled_in_course?(@course)).to be_truthy
end
it "should allow a temporary user with an existing enrollment but no pseudonym" do
@user = User.create! { |u| u.workflow_state = 'creation_pending' }
@course.enroll_student(@user)
expect(@user.can_be_enrolled_in_course?(@course)).to be_truthy
end
it "should not allow a registered user with an existing enrollment but no pseudonym" do
user_factory active_all: true
@course.enroll_student(@user)
expect(@user.can_be_enrolled_in_course?(@course)).to be_falsey
end
it "should not allow a user with neither an enrollment nor a pseudonym" do
user_factory active_all: true
expect(@user.can_be_enrolled_in_course?(@course)).to be_falsey
end
end
describe "email_channel" do
it "should not return retired channels" do
u = User.create!
retired = u.communication_channels.create!(:path => 'retired@example.com', :path_type => 'email') { |cc| cc.workflow_state = 'retired'}
expect(u.email_channel).to be_nil
active = u.communication_channels.create!(:path => 'active@example.com', :path_type => 'email') { |cc| cc.workflow_state = 'active'}
expect(u.email_channel).to eq active
end
end
describe "email=" do
it "should work" do
@user = User.create!
@user.email = 'john@example.com'
expect(@user.communication_channels.map(&:path)).to eq ['john@example.com']
expect(@user.email).to eq 'john@example.com'
end
it "doesn't create channels with empty paths" do
@user = User.create!
expect(-> {@user.email = ''}).to raise_error("Validation failed: Path can't be blank, Email is invalid")
expect(@user.communication_channels.any?).to be_falsey
end
it "restores retired channels" do
@user = User.create!
path = 'john@example.com'
@user.communication_channels.create!(:path => path, :workflow_state => "retired")
@user.email = path
expect(@user.communication_channels.first).to be_unconfirmed
expect(@user.email).to eq 'john@example.com'
end
end
describe "event methods" do
describe "upcoming_events" do
before(:once) { course_with_teacher(:active_all => true) }
it "handles assignments where the applied due_at is nil" do
assignment = @course.assignments.create!(:title => "Should not throw",
:due_at => 1.days.from_now)
assignment2 = @course.assignments.create!(:title => "Should not throw2",
:due_at => 1.days.from_now)
section = @course.course_sections.create!(:name => "VDD Section")
override = assignment.assignment_overrides.build
override.set = section
override.due_at = nil
override.due_at_overridden = true
override.save!
events = []
# handles comparison of nil due dates if that is what applies to the
# user instead of failing.
expect do
events = @user.upcoming_events(:end_at => 1.week.from_now)
end.to_not raise_error
expect(events.first).to eq assignment2
expect(events.second).to eq assignment
end
it "doesn't show unpublished assignments" do
assignment = @course.assignments.create!(:title => "not published", :due_at => 1.days.from_now)
assignment.unpublish
assignment2 = @course.assignments.create!(:title => "published", :due_at => 1.days.from_now)
assignment2.publish
events = []
events = @user.upcoming_events(:end_at => 1.week.from_now)
expect(events.first).to eq assignment2
end
it "doesn't include events for enrollments that are inactive due to date" do
@enrollment.start_at = 1.day.ago
@enrollment.end_at = 2.days.from_now
@enrollment.save!
event = @course.calendar_events.create!(title: 'published', start_at: 4.days.from_now)
expect(@user.upcoming_events).to include(event)
Timecop.freeze(3.days.from_now) do
EnrollmentState.recalculate_expired_states # runs periodically in background
expect(User.find(@user.id).upcoming_events).not_to include(event) # re-find user to clear cached_contexts
end
end
context "after db section context_code filtering" do
before do
course_with_teacher(:active_all => true)
@student = user_factory(active_user: true)
@sections = []
@events = []
3.times { @sections << @course.course_sections.create! }
start_at = 1.day.from_now
# create three sections and three child events that will be retrieved in the same order
data = {}
@sections.each_with_index do |section, i|
data[i] = {:start_at => start_at, :end_at => start_at + 1.day, :context_code => section.asset_string}
start_at += 1.day
end
event = @course.calendar_events.build(:title => 'event', :child_event_data => data)
event.updating_user = @teacher
event.save!
@events = event.child_events.sort_by(&:context_code)
end
it "should be able to filter section events after fetching" do
# trigger the after db filtering
allow(Setting).to receive(:get).with(anything, anything).and_return('')
allow(Setting).to receive(:get).with('filter_events_by_section_code_threshold', anything).and_return(0)
@course.enroll_student(@student, :section => @sections[1], :enrollment_state => 'active', :allow_multiple_enrollments => true)
expect(@student.upcoming_events(:limit => 2)).to eq [@events[1]]
end
it "should use the old behavior as a fallback" do
allow(Setting).to receive(:get).with(anything, anything).and_return('')
allow(Setting).to receive(:get).with('filter_events_by_section_code_threshold', anything).and_return(0)
# the optimized call will retrieve the first two events, and then filter them out
# since it didn't retrieve enough events it will use the old code as a fallback
@course.enroll_student(@student, :section => @sections[2], :enrollment_state => 'active', :allow_multiple_enrollments => true)
expect(@student.upcoming_events(:limit => 2)).to eq [@events[2]]
end
end
end
end
describe "select_upcoming_assignments" do
it "filters based on assignment date for asignments the user cannot delete" do
time = Time.now + 1.day
context = double
assignments = [double, double, double]
user = User.new
allow(context).to receive(:grants_right?).with(user, :manage_assignments).and_return false
assignments.each do |assignment|
allow(assignment).to receive_messages(:due_at => time)
allow(assignment).to receive(:context).and_return(context)
end
expect(user.select_upcoming_assignments(assignments,{:end_at => time})).to eq assignments
end
it "returns assignments that have an override between now and end_at opt" do
assignments = [double, double, double, double]
context = double
Timecop.freeze(Time.utc(2013,3,13,0,0)) do
user = User.new
allow(context).to receive(:grants_right?).with(user, :manage_assignments).and_return true
due_date1 = {:due_at => Time.now + 1.day}
due_date2 = {:due_at => Time.now + 1.week}
due_date3 = {:due_at => 2.weeks.from_now }
due_date4 = {:due_at => nil }
assignments.each do |assignment|
allow(assignment).to receive(:context).and_return(context)
end
expect(assignments.first).to receive(:dates_hash_visible_to).with(user).
and_return [due_date1]
expect(assignments.second).to receive(:dates_hash_visible_to).with(user).
and_return [due_date2]
expect(assignments.third).to receive(:dates_hash_visible_to).with(user).
and_return [due_date3]
expect(assignments[3]).to receive(:dates_hash_visible_to).with(user).
and_return [due_date4]
upcoming_assignments = user.select_upcoming_assignments(assignments,{
:end_at => 1.week.from_now
})
expect(upcoming_assignments).to include assignments.first
expect(upcoming_assignments).to include assignments.second
expect(upcoming_assignments).not_to include assignments.third
expect(upcoming_assignments).not_to include assignments[3]
end
end
end
describe "avatar_key" do
it "should return a valid avatar key for a valid user id" do
expect(User.avatar_key(1)).to eq "1-#{Canvas::Security.hmac_sha1('1')[0,10]}"
expect(User.avatar_key("1")).to eq "1-#{Canvas::Security.hmac_sha1('1')[0,10]}"
expect(User.avatar_key("2")).to eq "2-#{Canvas::Security.hmac_sha1('2')[0,10]}"
expect(User.avatar_key("161612461246")).to eq "161612461246-#{Canvas::Security.hmac_sha1('161612461246')[0,10]}"
end
it" should return '0' for an invalid user id" do
expect(User.avatar_key(nil)).to eq "0"
expect(User.avatar_key("")).to eq "0"
expect(User.avatar_key(0)).to eq "0"
end
end
describe "user_id_from_avatar_key" do
it "should return a valid user id for a valid avatar key" do
expect(User.user_id_from_avatar_key("1-#{Canvas::Security.hmac_sha1('1')[0,10]}")).to eq '1'
expect(User.user_id_from_avatar_key("2-#{Canvas::Security.hmac_sha1('2')[0,10]}")).to eq '2'
expect(User.user_id_from_avatar_key("1536394658-#{Canvas::Security.hmac_sha1('1536394658')[0,10]}")).to eq '1536394658'
end
it "should return nil for an invalid avatar key" do
expect(User.user_id_from_avatar_key("1-#{Canvas::Security.hmac_sha1('1')}")).to eq nil
expect(User.user_id_from_avatar_key("1")).to eq nil
expect(User.user_id_from_avatar_key("2-123456")).to eq nil
expect(User.user_id_from_avatar_key("a")).to eq nil
expect(User.user_id_from_avatar_key(nil)).to eq nil
expect(User.user_id_from_avatar_key("")).to eq nil
expect(User.user_id_from_avatar_key("-")).to eq nil
expect(User.user_id_from_avatar_key("-159135")).to eq nil
end
end
describe "order_by_sortable_name" do
let_once :ids do
ids = []
ids << User.create!(:name => "John Johnson")
ids << User.create!(:name => "John John")
ids << User.create!(:name => "john john")
end
context 'given pg_collkey extension is present' do
before do
skip_unless_pg_collkey_present
end
it "sorts lexicographically" do
ascending_sortable_names = User.order_by_sortable_name.where(id: ids).map(&:sortable_name)
expect(ascending_sortable_names).to eq(["john, john", "John, John", "Johnson, John"])
end
it "sorts support direction toggle" do
descending_sortable_names = User.order_by_sortable_name(:direction => :descending).
where(id: ids).map(&:sortable_name)
expect(descending_sortable_names).to eq(["Johnson, John", "John, John", "john, john"])
end
it "sorts support direction toggle with a prior select" do
descending_sortable_names = User.select([:id, :sortable_name]).order_by_sortable_name(:direction => :descending).
where(id: ids).map(&:sortable_name)
expect(descending_sortable_names).to eq ["Johnson, John", "John, John", "john, john"]
end
it "sorts by the current locale" do
I18n.locale = :es
expect(User.sortable_name_order_by_clause).to match(/'es'/)
expect(User.sortable_name_order_by_clause).not_to match(/'root'/)
# english has no specific sorting rules, so use root
I18n.locale = :en
expect(User.sortable_name_order_by_clause).not_to match(/'es'/)
expect(User.sortable_name_order_by_clause).to match(/'root'/)
end
end
it "breaks ties with user id" do
ids = 5.times.map { User.create!(:name => "Abcde").id }.sort
expect(User.order_by_sortable_name.where(id: ids).map(&:id)).to eq(ids)
end
it "breaks ties in the direction of the order" do
users = [
User.create!(:name => "Gary"),
User.create!(:name => "Gary")
]
ids = users.map(&:id)
descending_user_ids = User.where(id: ids).order_by_sortable_name(direction: :descending).map(&:id)
expect(descending_user_ids).to eq(ids.reverse)
end
end
describe "quota" do
before(:once) { user_factory }
it "should default to User.default_storage_quota" do
expect(@user.quota).to eql User.default_storage_quota
end
it "should sum up associated root account quotas" do
@user.associated_root_accounts << Account.create! << (a = Account.create!)
a.update_attribute :default_user_storage_quota_mb, a.default_user_storage_quota_mb + 10
expect(@user.quota).to eql(2 * User.default_storage_quota + 10.megabytes)
end
end
it "should build a profile if one doesn't already exist" do
user = User.create! :name => "John Johnson"
profile = user.profile
expect(profile.id).to be_nil
profile.bio = "bio!"
profile.save!
expect(user.profile).to eq profile
end
describe "common_account_chain" do
before :once do
user_with_pseudonym
end
let_once(:root_acct1) { Account.create! }
let_once(:root_acct2) { Account.create! }
it "work for just root accounts" do
@user.user_account_associations.create!(:account_id => root_acct2.id)
@user.reload
expect(@user.common_account_chain(root_acct1)).to eq []
expect(@user.common_account_chain(root_acct2)).to eql [root_acct2]
end
it "should work for one level of sub accounts" do
root_acct = root_acct1
sub_acct1 = Account.create!(:parent_account => root_acct)
sub_acct2 = Account.create!(:parent_account => root_acct)
@user.user_account_associations.create!(:account_id => root_acct.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct]
@user.user_account_associations.create!(:account_id => sub_acct1.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct, sub_acct1]
@user.user_account_associations.create!(:account_id => sub_acct2.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct]
end
context "two levels of sub accounts" do
let_once(:root_acct) { root_acct1 }
let_once(:sub_acct1) { Account.create!(:parent_account => root_acct) }
let_once(:sub_sub_acct1) { Account.create!(:parent_account => sub_acct1) }
let_once(:sub_sub_acct2) { Account.create!(:parent_account => sub_acct1) }
let_once(:sub_acct2) { Account.create!(:parent_account => root_acct) }
it "finds the correct branch point" do
@user.user_account_associations.create!(:account_id => root_acct.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct]
@user.user_account_associations.create!(:account_id => sub_acct1.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct, sub_acct1]
@user.user_account_associations.create!(:account_id => sub_sub_acct1.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct, sub_acct1, sub_sub_acct1]
@user.user_account_associations.create!(:account_id => sub_sub_acct2.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct, sub_acct1]
@user.user_account_associations.create!(:account_id => sub_acct2.id)
expect(@user.reload.common_account_chain(root_acct)).to eql [root_acct]
end
it "breaks early if a user has an enrollment partway down the chain" do
course_with_student(user: @user, account: sub_acct1, active_all: true)
@user.user_account_associations.create!(:account_id => sub_sub_acct1.id)
@user.reload
full_chain = [root_acct, sub_acct1, sub_sub_acct1]
overlap = @user.user_account_associations.map(&:account_id) & full_chain.map(&:id)
expect(overlap.sort).to eql full_chain.map(&:id)
expect(@user.common_account_chain(root_acct)).to(
eql([root_acct, sub_acct1])
)
end
end
end
describe "mfa_settings" do
let_once(:user) { User.create! }
it "should be :disabled for unassociated users" do
user = User.new
expect(user.mfa_settings).to eq :disabled
end
it "should inherit from the account" do
user.pseudonyms.create!(:account => Account.default, :unique_id => 'user')
Account.default.settings[:mfa_settings] = :required
Account.default.save!
expect(user.mfa_settings).to eq :required
Account.default.settings[:mfa_settings] = :optional
Account.default.save!
user = User.find(user().id)
expect(user.mfa_settings).to eq :optional
end
it "should be the most-restrictive if associated with multiple accounts" do
disabled_account = Account.create!(:settings => { :mfa_settings => :disabled })
optional_account = Account.create!(:settings => { :mfa_settings => :optional })
required_account = Account.create!(:settings => { :mfa_settings => :required })
p1 = user.pseudonyms.create!(:account => disabled_account, :unique_id => 'user')
user = User.find(user().id)
expect(user.mfa_settings).to eq :disabled
p2 = user.pseudonyms.create!(:account => optional_account, :unique_id => 'user')
user = User.find(user.id)
expect(user.mfa_settings).to eq :optional
p3 = user.pseudonyms.create!(:account => required_account, :unique_id => 'user')
user = User.find(user.id)
expect(user.mfa_settings).to eq :required
p1.destroy
user = User.find(user.id)
expect(user.mfa_settings).to eq :required
p2.destroy
user = User.find(user.id)
expect(user.mfa_settings).to eq :required
end
it "should be required if admin and required_for_admins" do
account = Account.create!(:settings => { :mfa_settings => :required_for_admins })
user.pseudonyms.create!(:account => account, :unique_id => 'user')
expect(user.mfa_settings).to eq :optional
account.account_users.create!(user: user)
user.reload
expect(user.mfa_settings).to eq :required
end
it "required_for_admins shouldn't get confused by admins in other accounts" do
account = Account.create!(:settings => { :mfa_settings => :required_for_admins })
user.pseudonyms.create!(:account => account, :unique_id => 'user')
user.pseudonyms.create!(:account => Account.default, :unique_id => 'user')
Account.default.account_users.create!(user: user)
expect(user.mfa_settings).to eq :optional
end
it "short circuits when a hint is provided" do
account = Account.create!(:settings => { :mfa_settings => :required_for_admins })
p = user.pseudonyms.create!(:account => account, :unique_id => 'user')
account.account_users.create!(user: user)
expect(user).to receive(:pseudonyms).never
expect(user.mfa_settings(pseudonym_hint: p)).to eq :required
end
end
context "crocodoc attributes" do
before :once do
Setting.set 'crocodoc_counter', 998
@user = User.create! :short_name => "Bob"
end
it "should generate a unique crocodoc_id" do
expect(@user.crocodoc_id).to be_nil
expect(@user.crocodoc_id!).to eql 999
expect(@user.crocodoc_user).to eql '999,Bob'
end
it "should scrub commas from the user name" do
@user.short_name = "Smith, Bob"
@user.save!
expect(@user.crocodoc_user).to eql '999,Smith Bob'
end
it "should not change a user's crocodoc_id" do
@user.update_attribute :crocodoc_id, 2
expect(@user.crocodoc_id!).to eql 2
expect(Setting.get('crocodoc_counter', 0).to_i).to eql 998
end
end
describe "select_available_assignments" do
before :once do
course_with_student :active_all => true
@assignment = @course.assignments.create! title: 'blah!', due_at: 1.day.from_now, submission_types: 'not_graded'
end
it "should not include concluded enrollments by default" do
expect(@student.select_available_assignments([@assignment]).count).to eq 1
@course.enrollment_term.update_attribute(:end_at, 1.day.from_now)
Timecop.travel(2.days) do
EnrollmentState.recalculate_expired_states
expect(@student.select_available_assignments([@assignment]).count).to eq 0
end
end
it "should included concluded enrollments if specified" do
@course.enrollment_term.update_attribute(:end_at, 1.day.from_now)
Timecop.travel(2.days) do
EnrollmentState.recalculate_expired_states
expect(@student.select_available_assignments([@assignment], :include_concluded => true).count).to eq 1
end
end
end
describe ".initial_enrollment_type_from_type" do
it "should return supported initial_enrollment_type values" do
expect(User.initial_enrollment_type_from_text('StudentEnrollment')).to eq 'student'
expect(User.initial_enrollment_type_from_text('StudentViewEnrollment')).to eq 'student'
expect(User.initial_enrollment_type_from_text('TeacherEnrollment')).to eq 'teacher'
expect(User.initial_enrollment_type_from_text('TaEnrollment')).to eq 'ta'
expect(User.initial_enrollment_type_from_text('ObserverEnrollment')).to eq 'observer'
expect(User.initial_enrollment_type_from_text('DesignerEnrollment')).to be_nil
expect(User.initial_enrollment_type_from_text('UnknownThing')).to be_nil
expect(User.initial_enrollment_type_from_text(nil)).to be_nil
# Non-enrollment type strings
expect(User.initial_enrollment_type_from_text('student')).to eq 'student'
expect(User.initial_enrollment_type_from_text('teacher')).to eq 'teacher'
expect(User.initial_enrollment_type_from_text('ta')).to eq 'ta'
expect(User.initial_enrollment_type_from_text('observer')).to eq 'observer'
end
end
describe "adminable_accounts" do
specs_require_sharding
it "should include accounts from multiple shards" do
user_factory
Account.site_admin.account_users.create!(user: @user)
@shard1.activate do
@account2 = Account.create!
@account2.account_users.create!(user: @user)
end
expect(@user.adminable_accounts.map(&:id).sort).to eq [Account.site_admin, @account2].map(&:id).sort
end
it "should exclude deleted accounts" do
user_factory
Account.site_admin.account_users.create!(user: @user)
@shard1.activate do
@account2 = Account.create!
@account2.account_users.create!(user: @user)
@account2.destroy
end
expect(@user.adminable_accounts.map(&:id).sort).to eq [Account.site_admin].map(&:id).sort
end
end
describe "all_pseudonyms" do
specs_require_sharding
it "should include pseudonyms from multiple shards" do
user_with_pseudonym(:active_all => 1)
@p1 = @pseudonym
@shard1.activate do
account = Account.create!
@p2 = account.pseudonyms.create!(:user => @user, :unique_id => 'abcd')
end
expect(@user.all_pseudonyms).to eq [@p1, @p2]
end
end
describe "active_pseudonyms" do
before :once do
user_with_pseudonym(:active_all => 1)
end
it "should include active pseudonyms" do
expect(@user.active_pseudonyms).to eq [@pseudonym]
end
it "should not include deleted pseudonyms" do
@pseudonym.destroy
expect(@user.active_pseudonyms).to be_empty
end
end
describe "preferred_gradebook_version" do
subject { user.preferred_gradebook_version }
let(:user) { User.new }
it "returns default gradebook when preferred" do
user.preferences[:gradebook_version] = 'default'
is_expected.to eq 'default'
end
it "returns individual gradebook when preferred" do
user.preferences[:gradebook_version] = 'individual'
is_expected.to eq 'individual'
end
it "returns default gradebook when not set" do
is_expected.to eq 'default'
end
end
describe "manual_mark_as_read" do
let(:user) { User.new }
subject { user.manual_mark_as_read? }
context 'default' do
it { is_expected.to be_falsey }
end
context 'after being set to true' do
before { allow(user).to receive_messages(preferences: { manual_mark_as_read: true }) }
it { is_expected.to be_truthy }
end
context 'after being set to false' do
before { allow(user).to receive_messages(preferences: { manual_mark_as_read: false }) }
it { is_expected.to be_falsey }
end
end
describe "create_announcements_unlocked" do
it "defaults to false if preference not set" do
user = User.create!
expect(user.create_announcements_unlocked?).to be_falsey
end
end
describe "things excluded from json serialization" do
it "excludes collkey" do
# Ruby 1.9 does not like html that includes the collkey, so
# don't ship it to the page (even as json).
User.create!
users = User.order_by_sortable_name
expect(users.first.as_json['user'].keys).not_to include('collkey')
end
end
describe 'permissions' do
it "should not allow account admin to modify admin privileges of other account admins" do
expect(RoleOverride.readonly_for(Account.default, :manage_role_overrides, admin_role)).to be_truthy
expect(RoleOverride.readonly_for(Account.default, :manage_account_memberships, admin_role)).to be_truthy
expect(RoleOverride.readonly_for(Account.default, :manage_account_settings, admin_role)).to be_truthy
end
describe ":reset_mfa" do
let(:account1) {
a = Account.default
a.settings[:admins_can_view_notifications] = true
a.save!
a
}
let(:account2) { Account.create! }
let(:sally) { account_admin_user(
user: student_in_course(account: account2).user,
account: account1) }
let(:bob) { student_in_course(
user: student_in_course(account: account2).user,
course: course_factory(account: account1)).user }
let(:charlie) { student_in_course(account: account1).user }
let(:alice) { account_admin_user_with_role_changes(
account: account1,
role: custom_account_role('StrongerAdmin', account: account1),
role_changes: { view_notifications: true }) }
it "should grant non-admins :reset_mfa on themselves" do
pseudonym(charlie, account: account1)
expect(charlie).to be_grants_right(charlie, :reset_mfa)
end
it "should grant admins :reset_mfa on themselves" do
pseudonym(sally, account: account1)
expect(sally).to be_grants_right(sally, :reset_mfa)
end
it "should grant admins :reset_mfa on fully admined users" do
pseudonym(charlie, account: account1)
expect(charlie).to be_grants_right(sally, :reset_mfa)
end
it "should not grant admins :reset_mfa on partially admined users" do
account1.settings[:mfa_settings] = :required
account1.save!
account2.settings[:mfa_settings] = :required
account2.save!
pseudonym(bob, account: account1)
pseudonym(bob, account: account2)
expect(bob).not_to be_grants_right(sally, :reset_mfa)
end
it "should not grant subadmins :reset_mfa on stronger admins" do
account1.settings[:mfa_settings] = :required
account1.save!
sub = Account.create(root_account_id: account1)
AccountUser.create(account: sub, user: bob)
pseudonym(alice, account: account1)
expect(alice).not_to be_grants_right(bob, :reset_mfa)
end
context "MFA is required on the account" do
before do
account1.settings[:mfa_settings] = :required
account1.save!
end
it "should no longer grant non-admins :reset_mfa on themselves" do
pseudonym(charlie, account: account1)
expect(charlie).not_to be_grants_right(charlie, :reset_mfa)
end
it "should no longer grant admins :reset_mfa on themselves" do
pseudonym(sally, account: account1)
expect(sally).not_to be_grants_right(sally, :reset_mfa)
end
it "should still grant admins :reset_mfa on other fully admined users" do
pseudonym(charlie, account: account1)
expect(charlie).to be_grants_right(sally, :reset_mfa)
end
end
end
describe ":merge" do
let(:account1) {
a = Account.default
a.settings[:admins_can_view_notifications] = true
a.save!
a
}
let(:account2) { Account.create! }
let(:sally) { account_admin_user(
user: student_in_course(account: account2).user,
account: account1) }
let(:bob) { student_in_course(
user: student_in_course(account: account2).user,
course: course_factory(account: account1)).user }
let(:charlie) { student_in_course(account: account2).user }
let(:alice) { account_admin_user_with_role_changes(
account: account1,
role: custom_account_role('StrongerAdmin', account: account1),
role_changes: { view_notifications: true }) }
it "should grant admins :merge on themselves" do
pseudonym(sally, account: account1)
expect(sally).to be_grants_right(sally, :merge)
end
it "should not grant non-admins :merge on themselves" do
pseudonym(bob, account: account1)
expect(bob).not_to be_grants_right(bob, :merge)
end
it "should not grant non-admins :merge on other users" do
pseudonym(sally, account: account1)
expect(sally).not_to be_grants_right(bob, :merge)
end
it "should grant admins :merge on partially admined users" do
pseudonym(bob, account: account1)
pseudonym(bob, account: account2)
expect(bob).to be_grants_right(sally, :merge)
end
it "should not grant admins :merge on users from other accounts" do
pseudonym(charlie, account: account2)
expect(charlie).not_to be_grants_right(sally, :merge)
end
it "should not grant subadmins :merge on stronger admins" do
pseudonym(alice, account: account1)
expect(alice).not_to be_grants_right(sally, :merge)
end
end
describe ":manage_user_details" do
before :once do
@root_account = Account.default
@root_admin = account_admin_user(account: @root_account)
@sub_account = Account.create! root_account: @root_account
@sub_admin = account_admin_user(account: @sub_account)
@student = course_with_student(account: @sub_account, active_all: true).user
end
it "is granted to root account admins" do
expect(@student.grants_right?(@root_admin, :manage_user_details)).to eq true
end
it "is not granted to root account admins w/o :manage_user_logins" do
@root_account.role_overrides.create!(role: admin_role, enabled: false, permission: :manage_user_logins)
expect(@student.grants_right?(@root_admin, :manage_user_details)).to eq false
end
it "is not granted to sub-account admins" do
expect(@student.grants_right?(@sub_admin, :manage_user_details)).to eq false
end
it "is not granted to custom sub-account admins with inherited roles" do
custom_role = custom_account_role("somerole", :account => @root_account)
@root_account.role_overrides.create!(role: custom_role, enabled: true, permission: :manage_user_logins)
@custom_sub_admin = account_admin_user(account: @sub_account, role: custom_role)
expect(@student.grants_right?(@custom_sub_admin, :manage_user_details)).to eq false
end
it "is not granted to root account admins on other root account admins who are invited as students" do
other_admin = account_admin_user account: Account.create!
course_with_student account: @root_account, user: other_admin, enrollment_state: 'invited'
expect(@root_admin.grants_right?(other_admin, :manage_user_details)).to eq false
end
end
describe ":generate_observer_pairing_code" do
before :once do
@root_account = Account.default
@root_admin = account_admin_user(account: @root_account)
@sub_account = Account.create! root_account: @root_account
@sub_admin = account_admin_user(account: @sub_account)
@student = course_with_student(account: @sub_account, active_all: true).user
end
it "is granted to self" do
expect(@student.grants_right?(@student, :generate_observer_pairing_code)).to eq true
end
it "is granted to root account admins" do
expect(@student.grants_right?(@root_admin, :generate_observer_pairing_code)).to eq true
end
it "is not granted to root account w/o :generate_observer_pairing_code" do
@root_account.role_overrides.create!(role: admin_role, enabled: false, permission: :generate_observer_pairing_code)
expect(@student.grants_right?(@root_admin, :generate_observer_pairing_code)).to eq false
end
it "is granted to sub-account admins" do
expect(@student.grants_right?(@sub_admin, :generate_observer_pairing_code)).to eq true
end
it "is not granted to sub-account admins w/o :generate_observer_pairing_code" do
@root_account.role_overrides.create!(role: admin_role, enabled: false, permission: :generate_observer_pairing_code)
expect(@student.grants_right?(@sub_admin, :generate_observer_pairing_code)).to eq false
end
end
describe ":moderate_user_content" do
before(:once) do
root_account = Account.default
@root_admin = account_admin_user(account: root_account)
sub_account = Account.create!(root_account: root_account)
@sub_admin = account_admin_user(account: sub_account)
@student = course_with_student(account: sub_account, active_all: true).user
end
it "cannot moderate your own content" do
expect(@student.grants_right?(@student, :moderate_user_content)).to be false
end
it "cannot moderate content if you are an admin without permission to moderate user content" do
Account.default.role_overrides.create!(role: admin_role, enabled: false, permission: :moderate_user_content)
expect(@student.grants_right?(@root_admin, :moderate_user_content)).to be false
end
it "cannot moderate content if you are a subadmin without permission to moderate user content" do
Account.default.role_overrides.create!(role: admin_role, enabled: false, permission: :moderate_user_content)
expect(@student.grants_right?(@sub_admin, :moderate_user_content)).to be false
end
it "can moderate content if you are an admin with permission to moderate user content" do
Account.default.role_overrides.create!(role: admin_role, enabled: true, permission: :moderate_user_content)
expect(@student.grants_right?(@root_admin, :moderate_user_content)).to be true
end
it "can moderate content if you are a subadmin with permission to moderate user content" do
Account.default.role_overrides.create!(role: admin_role, enabled: true, permission: :moderate_user_content)
expect(@student.grants_right?(@sub_admin, :moderate_user_content)).to be true
end
end
end
describe "check_accounts_right?" do
describe "sharding" do
specs_require_sharding
it "should check for associated accounts on shards the user shares with the seeker" do
# create target user on defualt shard
target = user_factory()
# create account on another shard
account = @shard1.activate{ Account.create! }
# associate target user with that account
account_admin_user(user: target, account: account, role: Role.get_built_in_role('AccountMembership'))
# create seeking user as admin on that account
seeker = account_admin_user(account: account, role: Role.get_built_in_role('AccountAdmin'))
# ensure seeking user gets permissions it should on target user
expect(target.grants_right?(seeker, :view_statistics)).to be_truthy
end
it 'checks all shards, even if not actually associated' do
target = user_factory()
# create account on another shard
account = @shard1.activate{ Account.create! }
# associate target user with that account
account_admin_user(user: target, account: account, role: Role.get_built_in_role('AccountMembership'))
# create seeking user as admin on that account
seeker = account_admin_user(account: account, role: Role.get_built_in_role('AccountAdmin'))
allow(seeker).to receive(:associated_shards).and_return([])
# ensure seeking user gets permissions it should on target user
expect(target.grants_right?(seeker, :view_statistics)).to eq true
end
end
end
describe "#conversation_context_codes" do
before :once do
@user = user_factory(active_all: true)
course_with_student(:user => @user, :active_all => true)
group_with_user(:user => @user, :active_all => true)
end
it "should include courses" do
expect(@user.conversation_context_codes).to include(@course.asset_string)
end
it "should include concluded courses" do
@enrollment.workflow_state = 'completed'
@enrollment.save!
expect(@user.conversation_context_codes).to include(@course.asset_string)
end
it "should optionally not include concluded courses" do
@enrollment.update_attribute(:workflow_state, 'completed')
expect(@user.conversation_context_codes(false)).not_to include(@course.asset_string)
end
it "should include groups" do
expect(@user.conversation_context_codes).to include(@group.asset_string)
end
describe "sharding" do
specs_require_sharding
before :once do
@shard1_account = @shard1.activate{ Account.create! }
end
it "should include courses on other shards" do
course_with_student(:account => @shard1_account, :user => @user, :active_all => true)
expect(@user.conversation_context_codes).to include(@course.asset_string)
end
it "should include concluded courses on other shards" do
course_with_student(:account => @shard1_account, :user => @user, :active_all => true)
@enrollment.workflow_state = 'completed'
@enrollment.save!
expect(@user.conversation_context_codes).to include(@course.asset_string)
end
it "should optionally not include concluded courses on other shards" do
course_with_student(:account => @shard1_account, :user => @user, :active_all => true)
@enrollment.update_attribute(:workflow_state, 'completed')
expect(@user.conversation_context_codes(false)).not_to include(@course.asset_string)
end
it "should include groups on other shards" do
# course is just to associate the get shard1 in @user's associated shards
course_with_student(:account => @shard1_account, :user => @user, :active_all => true)
@shard1.activate{ group_with_user(:user => @user, :active_all => true) }
expect(@user.conversation_context_codes).to include(@group.asset_string)
end
it "should include the default shard version of the asset string" do
course_with_student(:account => @shard1_account, :user => @user, :active_all => true)
default_asset_string = @course.asset_string
@shard1.activate{ expect(@user.conversation_context_codes).to include(default_asset_string) }
end
end
end
describe "#stamp_logout_time!" do
before :once do
user_model
end
it "should update last_logged_out" do
now = Time.zone.now
Timecop.freeze(now) { @user.stamp_logout_time! }
expect(@user.reload.last_logged_out.to_i).to eq now.to_i
end
context "sharding" do
specs_require_sharding
it "should update regardless of current shard" do
@shard1.activate{ @user.stamp_logout_time! }
expect(@user.reload.last_logged_out).not_to be_nil
end
end
end
describe "delete_enrollments" do
before do
course_factory
2.times { @course.course_sections.create! }
2.times { @course.assignments.create! }
end
it "should batch DueDateCacher jobs" do
expect(DueDateCacher).to receive(:recompute).never
expect(DueDateCacher).to receive(:recompute_users_for_course).twice # sync_enrollments and destroy_enrollments
test_student = @course.student_view_student
test_student.destroy
test_student.reload.enrollments.each { |e| expect(e).to be_deleted }
end
end
describe "otp remember me cookie" do
before do
@user = User.new
@user.otp_secret_key = ROTP::Base32.random
end
it "should add an ip to an existing cookie" do
cookie1 = @user.otp_secret_key_remember_me_cookie(Time.now.utc, nil, 'ip1')
cookie2 = @user.otp_secret_key_remember_me_cookie(Time.now.utc, cookie1, 'ip2')
expect(@user.validate_otp_secret_key_remember_me_cookie(cookie1, 'ip1')).to be_truthy
expect(@user.validate_otp_secret_key_remember_me_cookie(cookie1, 'ip2')).to be_falsey
expect(@user.validate_otp_secret_key_remember_me_cookie(cookie2, 'ip1')).to be_truthy
expect(@user.validate_otp_secret_key_remember_me_cookie(cookie2, 'ip2')).to be_truthy
end
end
it "should reset its conversation counter when told to" do
user = user_model
allow(user).to receive(:conversations).and_return Struct.new(:unread).new(Array.new(5))
user.reset_unread_conversations_counter
expect(user.reload.unread_conversations_count).to eq 5
end
describe 'group_memberships' do
before :once do
course_with_student active_all: true
@group = Group.create! context: @course, name: "group"
@group.users << @student
@group.save!
end
it "doesn't include deleted groups in current_group_memberships" do
expect(@student.current_group_memberships.size).to eq 1
@group.destroy
expect(@student.current_group_memberships.size).to eq 0
end
it "doesn't include deleted groups in group_memberships_for" do
expect(@student.group_memberships_for(@course).size).to eq 1
@group.destroy
expect(@student.group_memberships_for(@course).size).to eq 0
end
it 'should show if user has group_membership' do
expect(@student.current_active_groups?).to eq true
end
it "excludes groups in concluded courses with current_group_memberships_by_date" do
ag = Account.default.groups.create! name: "ag"
ag.users << @student
ag.save!
expect(@student.cached_current_group_memberships_by_date.map(&:group)).to match_array([@group, ag])
@course.start_at = 1.year.ago
@course.conclude_at = 1.hour.ago
@course.restrict_enrollments_to_course_dates = true
@course.save!
expect(User.find(@student.id).cached_current_group_memberships_by_date.map(&:group)).to match_array([ag])
end
end
describe 'visible_groups' do
it "should include groups in published courses" do
course_with_student active_all:true
@group = Group.create! context: @course, name: "GroupOne"
@group.users << @student
@group.save!
expect(@student.visible_groups.size).to eq 1
end
it "should not include groups that belong to unpublished courses" do
course_with_student
@group = Group.create! context: @course, name: "GroupOne"
@group.users << @student
@group.save!
expect(@student.visible_groups.size).to eq 0
end
it 'excludes groups in courses with concluded enrollments' do
course_with_student
@course.conclude_at = Time.zone.now - 2.days
@course.restrict_enrollments_to_course_dates = true
@course.save!
@group = Group.create! context: @course, name: 'GroupOne'
@group.users << @student
@group.save!
expect(@student.visible_groups.size).to eq 0
end
it "should include account groups" do
account = account_model(:parent_account => Account.default)
student = user_factory active_all: true
@group = Group.create! context: account, name: "GroupOne"
@group.users << student
@group.save!
expect(student.visible_groups.size).to eq 1
end
end
describe 'roles' do
before(:once) do
user_factory(active_all: true)
course_factory(active_course: true)
@account = Account.default
end
it "always includes 'user'" do
expect(@user.roles(@account)).to eq %w[user]
end
it "includes 'student' if the user has a student enrollment" do
@enrollment = @course.enroll_user(@user, 'StudentEnrollment', enrollment_state: 'active')
expect(@user.roles(@account)).to eq %w[user student]
end
it "includes 'student' if the user has a student view student enrollment" do
@user = @course.student_view_student
expect(@user.roles(@account)).to eq %w[user student]
end
it "includes 'teacher' if the user has a teacher enrollment" do
@enrollment = @course.enroll_user(@user, 'TeacherEnrollment', enrollment_state: 'active')
expect(@user.roles(@account)).to eq %w[user teacher]
end
it "includes 'teacher' if the user has a ta enrollment" do
@enrollment = @course.enroll_user(@user, 'TaEnrollment', enrollment_state: 'active')
expect(@user.roles(@account)).to eq %w[user teacher]
end
it "includes 'teacher' if the user has a designer enrollment" do
@enrollment = @course.enroll_user(@user, 'DesignerEnrollment', enrollment_state: 'active')
expect(@user.roles(@account)).to eq %w[user teacher]
end
it "includes 'observer' if the user has an observer enrollment" do
@enrollment = @course.enroll_user(@user, 'ObserverEnrollment', enrollment_state: 'active')
expect(@user.roles(@account)).to eq %w[user observer]
end
it "includes 'admin' if the user has a sub-account admin user record" do
sub_account = @account.sub_accounts.create!
sub_account.account_users.create!(:user => @user, :role => admin_role)
expect(@user.roles(@account)).to eq %w[user admin]
end
it "includes 'root_admin' if the user has a root account admin user record" do
@account.account_users.create!(:user => @user, :role => admin_role)
expect(@user.roles(@account)).to eq %w[user admin root_admin]
end
it 'caches results' do
enable_cache do
sub_account = @account.sub_accounts.create!
sub_account.account_users.create!(:user => @user, :role => admin_role)
result = @user.roles(@account)
sub_account.destroy!
expect(@user.roles(@account)).to eq result
end
end
context 'exclude_deleted_accounts' do
it 'does not include admin if user has a sub-account admin user record in deleted account' do
sub_account = @account.sub_accounts.create!
sub_account.account_users.create!(:user => @user, :role => admin_role)
@user.roles(@account)
sub_account.destroy!
expect(@user.roles(@account, true)).to eq %w[user]
end
it 'does not cache results when exclude_deleted_accounts is true' do
sub_account = @account.sub_accounts.create!
sub_account.account_users.create!(:user => @user, :role => admin_role)
@user.roles(@account, true)
expect(@user.roles(@account)).to eq %w[user admin]
end
end
end
it "should not grant user_notes rights to restricted users" do
course_with_ta(:active_all => true)
student_in_course(:course => @course, :active_all => true)
@course.account.role_overrides.create!(role: ta_role, enabled: false, permission: :manage_user_notes)
expect(@student.grants_right?(@ta, :create_user_notes)).to be_falsey
expect(@student.grants_right?(@ta, :read_user_notes)).to be_falsey
end
it "should change avatar state on reporting" do
user_factory
@user.report_avatar_image!
@user.reload
expect(@user.avatar_state).to eq :reported
end
describe "submissions_folder" do
before(:once) do
student_in_course
end
it "creates the root submissions folder on demand" do
f = @user.submissions_folder
expect(@user.submissions_folders.where(parent_folder_id: Folder.root_folders(@user).first, name: 'Submissions').first).to eq f
end
it "finds the existing root submissions folder" do
f = @user.folders.build
f.parent_folder_id = Folder.root_folders(@user).first
f.name = 'blah'
f.submission_context_code = 'root'
f.save!
expect(@user.submissions_folder).to eq f
end
it "creates a submissions folder for a course" do
f = @user.submissions_folder(@course)
expect(@user.submissions_folders.where(submission_context_code: @course.asset_string, parent_folder_id: @user.submissions_folder, name: @course.name).first).to eq f
end
it "finds an existing submissions folder for a course" do
f = @user.folders.build
f.parent_folder_id = @user.submissions_folder
f.name = 'bleh'
f.submission_context_code = @course.asset_string
f.save!
expect(@user.submissions_folder(@course)).to eq f
end
end
describe "#authenticate_one_time_password" do
let(:user) { User.create! }
let(:otp) { user.one_time_passwords.create! }
it "marks it as used" do
expect(user.authenticate_one_time_password(otp.code)).to eq otp
expect(otp.reload).to be_used
end
it "doesn't allow using a used code" do
otp.update_attribute(:used, true)
expect(user.authenticate_one_time_password(otp.code)).to be_nil
end
end
describe "#generate_one_time_passwords" do
let(:user) { User.create! }
it "generates them" do
user.generate_one_time_passwords
expect(user.one_time_passwords.count).to eq 10
end
it "doesn't clobber them if they already exist" do
user.generate_one_time_passwords
otps = user.one_time_passwords.order(:id).to_a
user.reload
user.generate_one_time_passwords
expect(user.one_time_passwords.order(:id).to_a).to eq otps
end
it "does clobber them if you want it to" do
user.generate_one_time_passwords
otps = user.one_time_passwords.order(:id).to_a
user.generate_one_time_passwords(regenerate: true)
otps.each do |otp|
expect { otp.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe "#has_student_enrollment" do
let(:user) { User.create! }
it "returns false by default" do
expect(user.has_student_enrollment?).to eq false
end
it "returns true when user is student and a course is active" do
course_with_student(:user => user, :active_all => true)
expect(user.has_student_enrollment?).to eq true
end
it "returns true when user is student and no courses are active" do
course_with_student(:user => user, :active_all => false)
expect(user.has_student_enrollment?).to eq true
end
it "returns false when user is teacher" do
course_with_teacher(:user => user)
expect(user.has_student_enrollment?).to eq false
end
it "returns false when user is TA" do
course_with_ta(:user => user)
expect(user.has_student_enrollment?).to eq false
end
it "returns false when user is designer" do
course_with_designer(:user => user)
expect(user.has_student_enrollment?).to eq false
end
end
describe "#participating_student_current_and_concluded_course_ids" do
let(:user) { User.create! }
before :each do
course_with_student(user: user, active_all: true)
end
it "includes courses for current enrollments" do
expect(user.participating_student_current_and_concluded_course_ids).to include(@course.id)
end
it "includes concluded courses" do
@course.soft_conclude!
@course.save
expect(user.participating_student_current_and_concluded_course_ids).to include(@course.id)
end
it "includes courses for concluded enrollments" do
user.enrollments.last.conclude
expect(user.participating_student_current_and_concluded_course_ids).to include(@course.id)
end
end
describe "from_tokens" do
specs_require_sharding
let(:users) { [User.create!, @shard1.activate { User.create! }] }
let(:tokens) { users.map(&:token) }
it "generates tokens made of id/md5(uuid) pairs" do
tokens.each_with_index do |token, i|
expect(token).to eq "#{users[i].id}_#{Digest::MD5.hexdigest(users[i].uuid)}"
end
end
it "instantiates users by token" do
expect(User.from_tokens(tokens)).to match_array(users)
end
it "excludes bad tokens" do
broken_tokens = tokens.map { |token| token + 'ff' }
expect(User.from_tokens(broken_tokens)).to be_empty
end
end
describe '#dashboard_view' do
before(:each) do
course_factory
user_factory(active_all: true)
user_session(@user)
end
it "defaults to 'cards' if not set at the user or account level" do
@user.dashboard_view = nil
@user.save!
@user.account.default_dashboard_view = nil
@user.account.save!
expect(@user.dashboard_view).to eql('cards')
end
it "defaults to account setting if user's isn't set" do
@user.dashboard_view = nil
@user.save!
@user.account.default_dashboard_view = 'activity'
@user.account.save!
expect(@user.dashboard_view).to eql('activity')
end
it "uses the user's setting as precedence" do
@user.dashboard_view = 'cards'
@user.save!
@user.account.default_dashboard_view = 'activity'
@user.account.save!
expect(@user.dashboard_view).to eql('cards')
end
end
describe "user_can_edit_name?" do
before(:once) do
user_with_pseudonym
@pseudonym.account.settings[:users_can_edit_name] = false
@pseudonym.account.save!
end
it "does not allow editing user name by default" do
expect(@user.user_can_edit_name?).to eq false
end
it "allows editing user name if the pseudonym allows this" do
@pseudonym.account.settings[:users_can_edit_name] = true
@pseudonym.account.save!
expect(@user.user_can_edit_name?).to eq true
end
describe "multiple pseudonyms" do
before(:once) do
@other_account = Account.create :name => 'Other Account'
@other_account.settings[:users_can_edit_name] = true
@other_account.save!
user_with_pseudonym(:user => @user, :account => @other_account)
end
it "allows editing if one pseudonym's account allows this" do
expect(@user.user_can_edit_name?).to eq true
end
it "doesn't allow editing if only a deleted pseudonym's account allows this" do
@user.pseudonyms.where(account_id: @other_account).first.destroy
expect(@user.user_can_edit_name?).to eq false
end
end
end
describe "limit_parent_app_web_access?" do
before(:once) do
user_with_pseudonym
@pseudonym.account.settings[:limit_parent_app_web_access] = nil
@pseudonym.account.save!
end
it "does not limit parent app web access by default" do
expect(@user.limit_parent_app_web_access?).to eq false
end
it "does limit if the pseudonym limits this" do
@pseudonym.account.settings[:limit_parent_app_web_access] = true
@pseudonym.account.save!
expect(@user.limit_parent_app_web_access?).to eq true
end
describe "multiple pseudonyms" do
before(:once) do
@other_account = Account.create :name => 'Other Account'
@other_account.settings[:limit_parent_app_web_access] = true
@other_account.save!
user_with_pseudonym(:user => @user, :account => @other_account)
end
it "limits if one pseudonym's account limits this" do
expect(@user.limit_parent_app_web_access?).to eq true
end
it "doesn't limit if only a deleted pseudonym's account limits this" do
@user.pseudonyms.where(account_id: @other_account).first.destroy
expect(@user.limit_parent_app_web_access?).to eq false
end
end
end
describe 'generate_observer_pairing_code' do
before(:once) do
course_with_student
end
it 'doesnt create overlapping active codes' do
allow(SecureRandom).to receive(:base64).and_return('abc123', 'abc123', '123abc')
@student.generate_observer_pairing_code
pairing_code = @student.generate_observer_pairing_code
expect(pairing_code.code).to eq '123abc'
end
end
describe "#prefers_no_celebrations?" do
let(:user) { user_model }
it "returns false by default" do
expect(user.prefers_no_celebrations?).to eq false
end
context "user has opted out of celebrations" do
before :each do
user.enable_feature!(:disable_celebrations)
end
it "returns true" do
expect(user.prefers_no_celebrations?).to eq true
end
end
end
end