canvas-lms/spec/models/account_spec.rb

2760 lines
104 KiB
Ruby

# frozen_string_literal: true
#
# 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/>.
#
describe Account do
include_examples "outcome import context examples"
describe "relationships" do
it { is_expected.to have_many(:feature_flags) }
it { is_expected.to have_one(:outcome_proficiency).dependent(:destroy) }
it { is_expected.to have_many(:lti_resource_links).class_name("Lti::ResourceLink") }
it { is_expected.to have_many(:lti_registrations).class_name("Lti::Registration").dependent(:destroy) }
it { is_expected.to have_many(:lti_registration_account_bindings).class_name("Lti::RegistrationAccountBinding").dependent(:destroy) }
end
describe "validations" do
it { is_expected.to validate_inclusion_of(:account_calendar_subscription_type).in_array(Account::CALENDAR_SUBSCRIPTION_TYPES) }
end
context "domain_method" do
it "retrieves correct account domain" do
root_account = Account.create!
AccountDomain.create!(host: "canvas.instructure.com", account: root_account)
expect(root_account.domain).to eq "canvas.instructure.com"
end
end
context "environment_specific_domain" do
let(:root_account) { Account.create! }
before do
allow(HostUrl).to receive(:context_host).and_call_original
allow(HostUrl).to receive(:context_host).with(root_account, "beta").and_return("canvas.beta.instructure.com")
AccountDomain.create!(host: "canvas.instructure.com", account: root_account)
end
it "retrieves correct beta domain" do
allow(ApplicationController).to receive(:test_cluster_name).and_return("beta")
expect(root_account.environment_specific_domain).to eq "canvas.beta.instructure.com"
end
it "retrieves correct prod domain" do
allow(ApplicationController).to receive(:test_cluster_name).and_return(nil)
expect(root_account.environment_specific_domain).to eq "canvas.instructure.com"
end
end
context "resolved_outcome_proficiency_method" do
before do
@root_account = Account.create!
@subaccount = @root_account.sub_accounts.create!
end
it "retrieves parent account's outcome proficiency" do
proficiency = outcome_proficiency_model(@root_account)
expect(@subaccount.resolved_outcome_proficiency).to eq proficiency
end
it "ignores soft deleted calculation methods" do
proficiency = outcome_proficiency_model(@root_account)
subproficiency = outcome_proficiency_model(@subaccount)
subproficiency.update! workflow_state: :deleted
expect(@subaccount.outcome_proficiency).to eq subproficiency
expect(@subaccount.resolved_outcome_proficiency).to eq proficiency
end
context "cache" do
it "uses the cache" do
enable_cache do
proficiency = outcome_proficiency_model(@root_account)
# prime the cache
@root_account.resolved_outcome_proficiency
# update without callbacks
OutcomeProficiency.where(id: proficiency.id).update_all workflow_state: "deleted"
# verify cached version wins with new AR object
cached = Account.find(@root_account.id).resolved_outcome_proficiency
expect(cached.workflow_state).not_to eq "deleted"
end
end
it "updates when account chain is changed" do
enable_cache do
other_subaccount = @root_account.sub_accounts.create!
other_proficiency = outcome_proficiency_model(other_subaccount)
expect(@subaccount.resolved_outcome_proficiency).to eq @root_account.resolved_outcome_proficiency
@subaccount.update! parent_account: other_subaccount
expect(@subaccount.resolved_outcome_proficiency).to eq other_proficiency
end
end
it "updates when outcome_proficiency_id cache changed" do
enable_cache do
subsubaccount = @subaccount.sub_accounts.create!
old_proficiency = outcome_proficiency_model(@root_account)
expect(subsubaccount.reload.resolved_outcome_proficiency).to eq old_proficiency
new_proficiency = outcome_proficiency_model(@subaccount)
expect(subsubaccount.reload.resolved_outcome_proficiency).to eq new_proficiency
new_proficiency.destroy!
expect(subsubaccount.reload.resolved_outcome_proficiency).to eq old_proficiency
end
end
it "does not conflict with other caches" do
enable_cache do
Timecop.freeze do
outcome_proficiency_model(@root_account)
outcome_calculation_method_model(@root_account)
# cache proficiency
@root_account.resolved_outcome_proficiency
calc_method = @root_account.resolved_outcome_calculation_method
expect(calc_method.class).to eq OutcomeCalculationMethod
end
end
end
end
context "with the account_level_mastery_scales FF enabled" do
before do
@root_account.enable_feature!(:account_level_mastery_scales)
end
it "returns a OutcomeProficiency default at the root level if no proficiency exists" do
expect(@root_account.outcome_proficiency).to be_nil
expect(@subaccount.outcome_proficiency).to be_nil
expect(@subaccount.resolved_outcome_proficiency).to eq OutcomeProficiency.find_or_create_default!(@root_account)
expect(@root_account.resolved_outcome_proficiency).to eq OutcomeProficiency.find_or_create_default!(@root_account)
end
end
context "with the account_level_mastery_scales FF disabled" do
it "can be nil" do
@root_account.disable_feature!(:account_level_mastery_scales)
expect(@root_account.resolved_outcome_proficiency).to be_nil
expect(@subaccount.resolved_outcome_proficiency).to be_nil
end
end
end
context "resolved_outcome_calculation_method" do
before do
@root_account = Account.create!
@subaccount = @root_account.sub_accounts.create!
end
it "retrieves parent account's outcome calculation method" do
method = OutcomeCalculationMethod.create! context: @root_account, calculation_method: :highest
expect(@root_account.outcome_calculation_method).to eq method
expect(@subaccount.outcome_calculation_method).to be_nil
expect(@root_account.resolved_outcome_calculation_method).to eq method
expect(@subaccount.resolved_outcome_calculation_method).to eq method
end
it "can override parent account's outcome calculation method" do
method = OutcomeCalculationMethod.create! context: @root_account, calculation_method: :highest
submethod = OutcomeCalculationMethod.create! context: @subaccount, calculation_method: :latest
expect(@root_account.outcome_calculation_method).to eq method
expect(@subaccount.outcome_calculation_method).to eq submethod
expect(@root_account.resolved_outcome_calculation_method).to eq method
expect(@subaccount.resolved_outcome_calculation_method).to eq submethod
end
it "ignores soft deleted calculation methods" do
method = OutcomeCalculationMethod.create! context: @root_account, calculation_method: :highest
submethod = OutcomeCalculationMethod.create! context: @subaccount, calculation_method: :latest, workflow_state: :deleted
expect(@subaccount.outcome_calculation_method).to eq submethod
expect(@subaccount.resolved_outcome_calculation_method).to eq method
end
context "cache" do
it "uses the cache" do
enable_cache do
method = outcome_calculation_method_model(@root_account)
# prime the cache
@root_account.resolved_outcome_calculation_method
# update without callbacks
OutcomeCalculationMethod.where(id: method.id).update_all workflow_state: "deleted"
# verify cached version wins with new AR object
cached = Account.find(@root_account.id).resolved_outcome_calculation_method
expect(cached.workflow_state).not_to eq "deleted"
end
end
it "updates when account chain is changed" do
enable_cache do
other_subaccount = @root_account.sub_accounts.create!
other_method = outcome_calculation_method_model(other_subaccount)
expect(@subaccount.resolved_outcome_calculation_method).to eq @root_account.resolved_outcome_calculation_method
@subaccount.update! parent_account: other_subaccount
expect(@subaccount.resolved_outcome_calculation_method).to eq other_method
end
end
it "updates when outcome_calculation_method_id cache changed" do
enable_cache do
subsubaccount = @subaccount.sub_accounts.create!
old_method = outcome_calculation_method_model(@root_account)
expect(subsubaccount.reload.resolved_outcome_calculation_method).to eq old_method
new_method = outcome_calculation_method_model(@subaccount)
expect(subsubaccount.reload.resolved_outcome_calculation_method).to eq new_method
new_method.destroy!
expect(subsubaccount.reload.resolved_outcome_calculation_method).to eq old_method
end
end
end
context "with the account_level_mastery_scales FF enabled" do
before do
@root_account.enable_feature!(:account_level_mastery_scales)
end
it "returns a OutcomeCalculationMethod default if no method exists" do
expect(@root_account.outcome_calculation_method).to be_nil
expect(@subaccount.outcome_calculation_method).to be_nil
expect(@root_account.resolved_outcome_calculation_method).to eq OutcomeCalculationMethod.find_or_create_default!(@root_account)
expect(@subaccount.resolved_outcome_calculation_method).to eq OutcomeCalculationMethod.find_or_create_default!(@root_account)
end
end
context "with the account_level_mastery_scales FF disabled" do
it "can be nil" do
@root_account.disable_feature!(:account_level_mastery_scales)
expect(@root_account.resolved_outcome_calculation_method).to be_nil
expect(@subaccount.resolved_outcome_calculation_method).to be_nil
end
end
end
it "provides a list of courses" do
expect { Account.new.courses }.not_to raise_error
end
context "equella_settings" do
it "responds to :equella_settings" do
expect(Account.new).to respond_to(:equella_settings)
expect(Account.new.equella_settings).to be_nil
end
it "returns the equella_settings data if defined" do
a = Account.new
a.equella_endpoint = "http://oer.equella.com/signon.do"
expect(a.equella_settings).not_to be_nil
expect(a.equella_settings[:endpoint]).to eql("http://oer.equella.com/signon.do")
expect(a.equella_settings[:default_action]).not_to be_nil
end
end
# it "should have an atom feed" do
# account_model
# @a.to_atom.should be_is_a(Atom::Entry)
# end
#
context "pronouns" do
it "uses an empty array if the setting is not on" do
account = Account.create!
expect(account.pronouns).to be_empty
# still returns empty array even if you explicitly set some
account.pronouns = ["Dude/Guy", "Dudette/Gal"]
expect(account.pronouns).to be_empty
end
it "uses defaults if setting is enabled and nothing is explicitly set" do
account = Account.create!
account.settings[:can_add_pronouns] = true
expect(account.pronouns).to eq ["She/Her", "He/Him", "They/Them"]
end
it "uses custom set things if explicitly provided (and strips whitespace)" do
account = Account.create!
account.settings[:can_add_pronouns] = true
account.pronouns = [" Dude/Guy ", "She/Her "]
# it "untranslates" "she/her" when it serializes it to the db
expect(account.settings[:pronouns]).to eq ["Dude/Guy", "she_her"]
# it "translates" "she/her" when it reads it
expect(account.pronouns).to eq ["Dude/Guy", "She/Her"]
end
end
context "services" do
before do
@a = Account.new
end
it "is able to specify a list of enabled services" do
@a.allowed_services = "fakeService"
# expect(@a.service_enabled?(:twitter)).to be_truthy
expect(@a.service_enabled?(:diigo)).to be_falsey
expect(@a.service_enabled?(:avatars)).to be_falsey
end
it "does not enable services off by default" do
expect(@a.service_enabled?(:avatars)).to be_falsey
end
it "adds and remove services from the defaults" do
@a.allowed_services = "+avatars,-myplugin"
expect(@a.service_enabled?(:avatars)).to be_truthy
expect(@a.service_enabled?(:myplugin)).to be_falsey
end
it "allows settings services" do
expect { @a.enable_service(:completly_bogs) }.to raise_error("Invalid Service")
@a.disable_service(:avatars)
expect(@a.service_enabled?(:avatars)).to be_falsey
@a.enable_service(:avatars)
expect(@a.service_enabled?(:avatars)).to be_truthy
end
it "uses + and - by default when setting service availability" do
@a.disable_service(:avatars)
expect(@a.service_enabled?(:avatars)).to be_falsey
expect(@a.allowed_services).not_to match("avatars")
@a.enable_service(:avatars)
expect(@a.service_enabled?(:avatars)).to be_truthy
expect(@a.allowed_services).to match("\\+avatars")
end
it "is able to set service availibity for previously hard-coded values" do
@a.allowed_services = "avatars"
@a.enable_service(:avatars)
expect(@a.service_enabled?(:avatars)).to be_truthy
expect(@a.allowed_services).to match(/avatars/)
expect(@a.allowed_services).not_to match(/[+-]/)
@a.disable_service(:avatars)
expect(@a.allowed_services).to be_nil
end
it "does not wipe out services that are substrings of each other" do
AccountServices.register_service(
:google_docs_prev,
{
name: "My google docs prev", description: "", expose_to_ui: :service, default: true
}
)
@a.disable_service("google_docs_previews")
@a.disable_service("google_docs_prev")
expect(@a.allowed_services).to eq "-google_docs_previews,-google_docs_prev"
end
describe "services_exposed_to_ui_hash" do
it "returns all ui services by default" do
expected_services = AccountServices.allowable_services.reject { |_, k| !k[:expose_to_ui] || (k[:expose_to_ui_proc] && !k[:expose_to_ui_proc].call(nil)) }.keys
expect(Account.services_exposed_to_ui_hash.keys).to eq expected_services
end
it "returns services of a type if specified" do
expected_services = AccountServices.allowable_services.reject { |_, k| k[:expose_to_ui] != :setting || (k[:expose_to_ui_proc] && !k[:expose_to_ui_proc].call(nil)) }.keys
expect(Account.services_exposed_to_ui_hash(:setting).keys).to eq expected_services
end
it "filters based on user and account if a proc is specified" do
user1 = User.create!
user2 = User.create!
AccountServices.register_service(:myservice, {
name: "My Test Service",
description: "Nope",
expose_to_ui: :setting,
default: false,
expose_to_ui_proc: proc { |user, account| user == user2 && account == Account.default },
})
expect(Account.services_exposed_to_ui_hash(:setting).keys).not_to include(:myservice)
expect(Account.services_exposed_to_ui_hash(:setting, user1, Account.default).keys).not_to include(:myservice)
expect(Account.services_exposed_to_ui_hash(:setting, user2, Account.default).keys).to include(:myservice)
end
end
describe "plugin services" do
before do
AccountServices.register_service(:myplugin, { name: "My Plugin", description: "", expose_to_ui: :setting, default: false })
end
it "returns the service" do
expect(AccountServices.allowable_services.keys).to include(:myplugin)
end
it "allows setting the service" do
expect(@a.service_enabled?(:myplugin)).to be_falsey
@a.enable_service(:myplugin)
expect(@a.service_enabled?(:myplugin)).to be_truthy
expect(@a.allowed_services).to match(/\+myplugin/)
@a.disable_service(:myplugin)
expect(@a.service_enabled?(:myplugin)).to be_falsey
expect(@a.allowed_services).to be_blank
end
describe "services_exposed_to_ui_hash" do
it "returns services defined in a plugin" do
expect(Account.services_exposed_to_ui_hash.keys).to include(:myplugin)
expect(Account.services_exposed_to_ui_hash(:setting).keys).to include(:myplugin)
end
end
end
end
context "settings=" do
it "filters non-hash hash settings" do
a = Account.new
a.settings = { "sis_default_grade_export" => "string" }.with_indifferent_access
expect(a.settings[:error_reporting]).to be_nil
a.settings = { "sis_default_grade_export" => {
"value" => true
} }.with_indifferent_access
expect(a.settings[:sis_default_grade_export]).to be_is_a(Hash)
expect(a.settings[:sis_default_grade_export][:value]).to be true
end
end
context "allow_global_includes?" do
let(:root) { Account.default }
it "false unless they've checked the box to allow it" do
expect(root.allow_global_includes?).to be_falsey
end
it "true if they've checked the box to allow it" do
root.settings = { "global_includes" => true }
expect(root.allow_global_includes?).to be_truthy
end
describe "subaccount" do
let(:sub_account) { root.sub_accounts.create! }
it "false if root account hasn't checked global_includes AND subaccount branding" do
expect(sub_account.allow_global_includes?).to be_falsey
sub_account.root_account.settings = { "global_includes" => true, "sub_account_includes" => false }
expect(sub_account.allow_global_includes?).to be_falsey
sub_account.root_account.settings = { "global_includes" => false, "sub_account_includes" => true }
expect(sub_account.allow_global_includes?).to be_falsey
end
it "true if root account HAS checked global_includes and turned on subaccount branding" do
sub_account.root_account.settings = { "global_includes" => true, "sub_account_includes" => true }
expect(sub_account.allow_global_includes?).to be_truthy
end
end
end
context "turnitin secret" do
it "decrypts the turnitin secret to the original value" do
a = Account.new
a.turnitin_shared_secret = "asdf"
expect(a.turnitin_shared_secret).to eql("asdf")
a.turnitin_shared_secret = "2t87aot72gho8a37gh4g[awg'waegawe-,v-3o7fya23oya2o3"
expect(a.turnitin_shared_secret).to eql("2t87aot72gho8a37gh4g[awg'waegawe-,v-3o7fya23oya2o3")
end
end
context "closest_turnitin_originality" do
before do
@root_account = Account.create!(turnitin_pledge: "root")
@root_account.turnitin_originality = "after_grading"
@root_account.save!
end
it "finds closest_turnitin_originality from root account" do
expect(@root_account.closest_turnitin_originality).to eq("after_grading")
end
it "finds closest_turnitin_originality from sub account" do
sub_account = Account.create(name: "sub", parent_account: @root_account)
sub_account.turnitin_originality = "never"
expect(sub_account.closest_turnitin_originality).to eq("never")
end
it "finds closest_turnitin_originality from sub account when set on root account" do
sub_account = Account.create(name: "sub", parent_account: @root_account)
expect(sub_account.closest_turnitin_originality).to eq("after_grading")
end
end
context "closest_turnitin_pledge" do
it "works for custom sub, custom root" do
root_account = Account.create!(turnitin_pledge: "root")
sub_account = Account.create!(parent_account: root_account, turnitin_pledge: "sub")
expect(root_account.closest_turnitin_pledge).to eq "root"
expect(sub_account.closest_turnitin_pledge).to eq "sub"
end
it "works for nil sub, custom root" do
root_account = Account.create!(turnitin_pledge: "root")
sub_account = Account.create!(parent_account: root_account)
expect(root_account.closest_turnitin_pledge).to eq "root"
expect(sub_account.closest_turnitin_pledge).to eq "root"
end
it "works for nil sub, nil root" do
root_account = Account.create!
sub_account = Account.create!(parent_account: root_account)
expect(root_account.closest_turnitin_pledge).not_to be_empty
expect(sub_account.closest_turnitin_pledge).not_to be_empty
end
it "uses the default message if pledge is nil or empty" do
account = Account.create!(turnitin_pledge: "")
expect(account.closest_turnitin_pledge).to eq "This assignment submission is my own, original work"
end
end
it "makes a default enrollment term if necessary" do
a = Account.create!(name: "nada")
expect(a.enrollment_terms.size).to eq 1
expect(a.enrollment_terms.first.name).to eq EnrollmentTerm::DEFAULT_TERM_NAME
# don't create a new default term for sub-accounts
a2 = a.all_accounts.create!(name: "sub")
expect(a2.enrollment_terms.size).to eq 0
end
def account_with_admin_and_restricted_user(account, restricted_role)
admin = User.create
user = User.create
account.account_users.create!(user: admin, role: admin_role)
account.account_users.create!(user:, role: restricted_role)
[admin, user]
end
it "sets up access policy correctly" do
# double out any "if" permission conditions
RoleOverride.permissions.each_value do |v|
next unless v[:if]
allow_any_instance_of(Account).to receive(v[:if]).and_return(true)
end
site_admin = Account.site_admin
# Set up a hierarchy of 4 accounts - a root account, a sub account,
# a sub sub account, and SiteAdmin account. Create a 'Restricted Admin'
# role available for each one, and create an admin user and a user in that restricted role
@sa_role = custom_account_role("Restricted SA Admin", account: site_admin)
site_admin.settings[:mfa_settings] = "required"
site_admin.save!
root_account = Account.create
@root_role = custom_account_role("Restricted Root Admin", account: root_account)
sub_account = Account.create(parent_account: root_account)
sub_sub_account = Account.create(parent_account: sub_account)
hash = {}
hash[:site_admin] = { account: Account.site_admin }
hash[:root] = { account: root_account }
hash[:sub] = { account: sub_account }
hash[:sub_sub] = { account: sub_sub_account }
hash.each do |k, v|
v[:account].update_attribute(:settings, { no_enrollments_can_create_courses: false })
admin, user = account_with_admin_and_restricted_user(v[:account], ((k == :site_admin) ? @sa_role : @root_role))
hash[k][:admin] = admin
hash[k][:user] = user
end
limited_access = %i[read read_as_admin manage update delete read_outcomes read_terms read_files launch_external_tool]
conditional_access = RoleOverride.permissions.select { |_, v| v[:account_allows] }.map(&:first)
conditional_access += [:view_bounced_emails, :view_account_calendar_details] # since this depends on :view_notifications
disabled_by_default = RoleOverride.permissions.select { |_, v| v[:true_for].empty? }.map(&:first)
full_access = RoleOverride.permissions.keys +
limited_access - disabled_by_default - conditional_access +
[:create_courses]
full_access << :create_tool_manually unless root_account.feature_enabled?(:granular_permissions_manage_lti)
full_root_access = full_access - RoleOverride.permissions.select { |_k, v| v[:account_only] == :site_admin }.map(&:first)
full_sub_access = full_root_access - RoleOverride.permissions.select { |_k, v| v[:account_only] == :root }.map(&:first)
# site admin has access to everything everywhere
hash.each do |k, v|
account = v[:account]
common_siteadmin_privileges = []
common_siteadmin_privileges += [:read_global_outcomes] if k == :site_admin
admin_privileges = full_access + common_siteadmin_privileges
user_privileges = limited_access + common_siteadmin_privileges
expect(account.check_policy(hash[:site_admin][:admin]) - conditional_access).to match_array admin_privileges
expect(account.check_policy(hash[:site_admin][:user]) - conditional_access).to match_array user_privileges
end
# root admin has access to everything except site admin
account = hash[:site_admin][:account]
expect(account.check_policy(hash[:root][:admin])).to match_array [:read_global_outcomes]
expect(account.check_policy(hash[:root][:user])).to match_array [:read_global_outcomes]
hash.each do |k, v|
next if k == :site_admin
account = v[:account]
expect(account.check_policy(hash[:root][:admin]) - conditional_access).to match_array full_root_access
expect(account.check_policy(hash[:root][:user])).to match_array limited_access
end
# sub account has access to sub and sub_sub
hash.each do |k, v|
next unless k == :site_admin || k == :root
account = v[:account]
expect(account.check_policy(hash[:sub][:admin])).to match_array((k == :site_admin) ? [:read_global_outcomes] : %i[read_outcomes read_terms launch_external_tool])
expect(account.check_policy(hash[:sub][:user])).to match_array((k == :site_admin) ? [:read_global_outcomes] : %i[read_outcomes read_terms launch_external_tool])
end
hash.each do |k, v|
next if k == :site_admin || k == :root
account = v[:account]
expect(account.check_policy(hash[:sub][:admin]) - conditional_access).to match_array full_sub_access
expect(account.check_policy(hash[:sub][:user])).to match_array limited_access
end
# Grant 'Restricted Admin' a specific permission, and re-check everything
some_access = [:read_reports] + limited_access
hash.each do |k, v|
account = v[:account]
account.role_overrides.create!(permission: "read_reports", role: ((k == :site_admin) ? @sa_role : @root_role), enabled: true)
account.role_overrides.create!(permission: "reset_any_mfa", role: @sa_role, enabled: true)
# clear caches
account.tap do |a|
a.settings[:mfa_settings] = :optional
a.save!
end
v[:account] = Account.find(account.id)
end
AdheresToPolicy::Cache.clear
hash.each do |k, v|
account = v[:account]
admin_privileges = full_access.clone
admin_privileges += [:read_global_outcomes] if k == :site_admin
user_array = some_access + [:reset_any_mfa] +
((k == :site_admin) ? [:read_global_outcomes] : [])
expect(account.check_policy(hash[:site_admin][:admin]) - conditional_access).to match_array admin_privileges
expect(account.check_policy(hash[:site_admin][:user])).to match_array user_array
end
account = hash[:site_admin][:account]
expect(account.check_policy(hash[:root][:admin])).to match_array [:read_global_outcomes]
expect(account.check_policy(hash[:root][:user])).to match_array [:read_global_outcomes]
hash.each do |k, v|
next if k == :site_admin
account = v[:account]
expect(account.check_policy(hash[:root][:admin]) - conditional_access).to match_array full_root_access
expect(account.check_policy(hash[:root][:user])).to match_array some_access
end
# sub account has access to sub and sub_sub
hash.each do |k, v|
next unless k == :site_admin || k == :root
account = v[:account]
expect(account.check_policy(hash[:sub][:admin])).to match_array((k == :site_admin) ? [:read_global_outcomes] : %i[read_outcomes read_terms launch_external_tool])
expect(account.check_policy(hash[:sub][:user])).to match_array((k == :site_admin) ? [:read_global_outcomes] : %i[read_outcomes read_terms launch_external_tool])
end
hash.each do |k, v|
next if k == :site_admin || k == :root
account = v[:account]
expect(account.check_policy(hash[:sub][:admin]) - conditional_access).to match_array full_sub_access
expect(account.check_policy(hash[:sub][:user])).to match_array some_access
end
end
context "sharding" do
specs_require_sharding
it "does not query when the target account is site admin" do
teacher_in_course
site_admin = Account.site_admin
expect(Course).not_to receive(:connection)
expect(site_admin.grants_right?(@user, :read)).to be false
end
it "queries for enrollments correctly when another shard is active" do
teacher_in_course
@enrollment.accept!
@shard1.activate do
expect(@course.grants_right?(@user, :read_sis)).to be true
end
end
it "returns sub account ids recursively when another shard is active" do
a = Account.default
subs = []
sub = Account.create!(name: "sub", parent_account: a)
subs << grand_sub = Account.create!(name: "grand_sub", parent_account: sub)
subs << great_grand_sub = Account.create!(name: "great_grand_sub", parent_account: grand_sub)
subs << Account.create!(name: "great_great_grand_sub", parent_account: great_grand_sub)
@shard1.activate do
expect(Account.select(:id).sub_accounts_recursive(sub.id, :pluck).sort).to eq(subs.map(&:id).sort)
expect(Account.sub_accounts_recursive(sub.id).sort_by(&:id)).to eq(subs.sort_by(&:id))
end
end
it "properly returns site admin permissions regardless of active shard" do
enable_cache do
user_factory
site_admin = Account.site_admin
site_admin.account_users.create!(user: @user)
@shard1.activate do
expect(site_admin.grants_right?(@user, :manage_site_settings)).to be_truthy
end
expect(site_admin.grants_right?(@user, :manage_site_settings)).to be_truthy
user_factory
@shard1.activate do
expect(site_admin.grants_right?(@user, :manage_site_settings)).to be_falsey
end
expect(site_admin.grants_right?(@user, :manage_site_settings)).to be_falsey
end
end
end
# TODO: deprecated; need to look into removing this setting
it "allows no_enrollments_can_create_courses correctly" do
a = Account.default
a.disable_feature!(:granular_permissions_manage_courses)
a.settings = { no_enrollments_can_create_courses: true }
a.save!
user_factory
expect(a.manually_created_courses_account.grants_right?(@user, :create_courses)).to be_truthy
end
it "does not allow create_courses even to admins on site admin and children" do
a = Account.site_admin
a.settings = { no_enrollments_can_create_courses: true }
a.save!
manual = a.manually_created_courses_account
user_factory
expect(a.grants_right?(@user, :create_courses)).to be false
expect(manual.grants_right?(@user, :create_courses)).to be false
end
it "does not allow create courses for student view students" do
a = Account.default
a.settings = { no_enrollments_can_create_courses: true }
a.save!
manual = a.manually_created_courses_account
course = manual.courses.create!
user = course.student_view_student
expect(a.grants_right?(user, :create_courses)).to be false
expect(manual.grants_right?(user, :create_courses)).to be false
end
it "does not allow create courses for student view students (granular permissions)" do
a = Account.default
a.settings = { no_enrollments_can_create_courses: true }
a.save!
a.enable_feature!(:granular_permissions_manage_courses)
manual = a.manually_created_courses_account
course = manual.courses.create!
user = course.student_view_student
expect(a.grants_right?(user, :create_courses)).to be false
expect(manual.grants_right?(user, :create_courses)).to be false
end
it "returns sub-accounts as options correctly" do
a = Account.default
sub = Account.create!(name: "sub", parent_account: a)
sub2 = Account.create!(name: "sub2", parent_account: a)
sub2_1 = Account.create!(name: "sub2-1", parent_account: sub2)
options = a.sub_accounts_as_options
expect(options).to eq(
[
["Default Account", a.id],
["&nbsp;&nbsp;sub", sub.id],
["&nbsp;&nbsp;sub2", sub2.id],
["&nbsp;&nbsp;&nbsp;&nbsp;sub2-1", sub2_1.id]
]
)
end
it "correctly returns sub-account_ids recursively" do
a = Account.default
subs = []
sub = Account.create!(name: "sub", parent_account: a)
subs << grand_sub = Account.create!(name: "grand_sub", parent_account: sub)
subs << great_grand_sub = Account.create!(name: "great_grand_sub", parent_account: grand_sub)
subs << Account.create!(name: "great_great_grand_sub", parent_account: great_grand_sub)
expect(Account.select(:id).sub_accounts_recursive(sub.id, :pluck).sort).to eq(subs.map(&:id).sort)
expect(Account.limit(10).sub_accounts_recursive(sub.id).sort).to eq(subs.sort_by(&:id))
end
it "returns the correct user count" do
a = Account.default
expect(a.all_users.count).to eq a.user_count
expect(a.user_count).to eq 0
u = User.create!
a.account_users.create!(user: u)
expect(a.all_users.count).to eq a.user_count
expect(a.user_count).to eq 1
course_with_teacher
@teacher.update_account_associations
expect(a.all_users.count).to eq a.user_count
expect(a.user_count).to eq 2
a2 = a.sub_accounts.create!
course_with_teacher(account: a2)
@teacher.update_account_associations
expect(a.all_users.count).to eq a.user_count
expect(a.user_count).to eq 3
user_with_pseudonym
expect(a.all_users.count).to eq a.user_count
expect(a.user_count).to eq 4
end
it "group_categories should not include deleted categories" do
account = Account.default
expect(account.group_categories.count).to eq 0
category1 = account.group_categories.create(name: "category 1")
category2 = account.group_categories.create(name: "category 2")
expect(account.group_categories.count).to eq 2
category1.destroy
account.reload
expect(account.group_categories.count).to eq 1
expect(account.group_categories.to_a).to eq [category2]
end
it "group_categories.active should not include deleted categories" do
account = Account.default
expect(account.group_categories.active.count).to eq 0
category1 = account.group_categories.create(name: "category 1")
category2 = account.group_categories.create(name: "category 2")
expect(account.group_categories.active.count).to eq 2
category1.destroy
account.reload
expect(account.group_categories.active.count).to eq 1
expect(account.all_group_categories.count).to eq 2
expect(account.group_categories.active.to_a).to eq [category2]
end
it "returns correct values for login_handle_name_with_inference" do
account = Account.default
expect(account.login_handle_name_with_inference).to eq "Email"
config = account.authentication_providers.create!(auth_type: "cas")
account.authentication_providers.first.move_to_bottom
expect(account.login_handle_name_with_inference).to eq "Login"
config.destroy
config = account.authentication_providers.create!(auth_type: "saml")
account.authentication_providers.active.first.move_to_bottom
expect(account.reload.login_handle_name_with_inference).to eq "Login"
config.destroy
account.authentication_providers.create!(auth_type: "ldap")
account.authentication_providers.active.first.move_to_bottom
expect(account.reload.login_handle_name_with_inference).to eq "Email"
account.login_handle_name = "LDAP Login"
account.save!
expect(account.reload.login_handle_name_with_inference).to eq "LDAP Login"
end
context "users_not_in_groups" do
before :once do
@account = Account.default
@user1 = account_admin_user(account: @account)
@user2 = account_admin_user(account: @account)
@user3 = account_admin_user(account: @account)
end
it "does not include deleted users" do
@user1.destroy
expect(@account.users_not_in_groups([]).size).to eq 2
end
it "does not include users in one of the groups" do
group = @account.groups.create
group.add_user(@user1)
users = @account.users_not_in_groups([group])
expect(users.size).to eq 2
expect(users).not_to include(@user1)
end
it "includes users otherwise" do
group = @account.groups.create
group.add_user(@user1)
users = @account.users_not_in_groups([group])
expect(users).to include(@user2)
expect(users).to include(@user3)
end
it "allows ordering by user's sortable name" do
@user1.sortable_name = "jonny"
@user1.save
@user2.sortable_name = "bob"
@user2.save
@user3.sortable_name = "richard"
@user3.save
users = @account.users_not_in_groups([], order: User.sortable_name_order_by_clause("users"))
expect(users.map(&:id)).to eq [@user2.id, @user1.id, @user3.id]
end
end
context "tabs_available" do
before :once do
@account = Account.default.sub_accounts.create!(name: "sub-account")
end
it "includes 'Developer Keys' for the authorized users of the site_admin account" do
account_admin_user(account: Account.site_admin)
tabs = Account.site_admin.tabs_available(@admin)
expect(tabs.pluck(:id)).to include(Account::TAB_DEVELOPER_KEYS)
tabs = Account.site_admin.tabs_available(nil)
expect(tabs.pluck(:id)).not_to include(Account::TAB_DEVELOPER_KEYS)
end
it "includes 'Developer Keys' for the admin users of an account" do
account = Account.create!
account_admin_user(account:)
tabs = account.tabs_available(@admin)
expect(tabs.pluck(:id)).to include(Account::TAB_DEVELOPER_KEYS)
tabs = account.tabs_available(nil)
expect(tabs.pluck(:id)).not_to include(Account::TAB_DEVELOPER_KEYS)
end
it "does not include 'Developer Keys' for non-site_admin accounts" do
tabs = @account.tabs_available(nil)
expect(tabs.pluck(:id)).not_to include(Account::TAB_DEVELOPER_KEYS)
tabs = @account.root_account.tabs_available(nil)
expect(tabs.pluck(:id)).not_to include(Account::TAB_DEVELOPER_KEYS)
end
it "does not include external tools if not configured for account navigation" do
tool = @account.context_external_tools.new(name: "bob", consumer_key: "bob", shared_secret: "bob", domain: "example.com")
tool.user_navigation = { url: "http://www.example.com", text: "Example URL" }
tool.save!
expect(tool.has_placement?(:account_navigation)).to be false
tabs = @account.tabs_available(nil)
expect(tabs.pluck(:id)).not_to include(tool.asset_string)
end
it "includes active external tools if configured on the account" do
tools = Array.new(2) do
t = @account.context_external_tools.new(
name: "bob",
consumer_key: "bob",
shared_secret: "bob",
domain: "example.com"
)
t.account_navigation = {
text: "Example URL",
url: "http://www.example.com",
}
t.tap(&:save!)
end
tool1, tool2 = tools
tool2.destroy
tools.each { |t| expect(t.has_placement?(:account_navigation)).to be true }
tabs = @account.tabs_available
tab_ids = tabs.pluck(:id)
expect(tab_ids).to include(tool1.asset_string)
expect(tab_ids).not_to include(tool2.asset_string)
tab = tabs.detect { |t| t[:id] == tool1.asset_string }
expect(tab[:label]).to eq tool1.settings[:account_navigation][:text]
expect(tab[:href]).to eq :account_external_tool_path
expect(tab[:args]).to eq [@account.id, tool1.id]
end
it "includes external tools if configured on the root account" do
tool = @account.context_external_tools.new(name: "bob", consumer_key: "bob", shared_secret: "bob", domain: "example.com")
tool.account_navigation = { url: "http://www.example.com", text: "Example URL" }
tool.save!
expect(tool.has_placement?(:account_navigation)).to be true
tabs = @account.tabs_available(nil)
expect(tabs.pluck(:id)).to include(tool.asset_string)
tab = tabs.detect { |t| t[:id] == tool.asset_string }
expect(tab[:label]).to eq tool.settings[:account_navigation][:text]
expect(tab[:href]).to eq :account_external_tool_path
expect(tab[:args]).to eq [@account.id, tool.id]
end
it "does not include external tools for subaccounts if 'root_account_only' is used" do
expect(@account.root_account?).to be false
course_with_teacher(account: @account.root_account)
tool = @account.root_account.context_external_tools.new(name: "bob", consumer_key: "bob", shared_secret: "bob", domain: "example.com")
tool.account_navigation = { url: "http://www.example.com", text: "Example URL", root_account_only: true }
tool.save!
expect(@account.root_account.tabs_available(@teacher).pluck(:id)).to include(tool.asset_string)
expect(@account.tabs_available(@teacher).pluck(:id)).to_not include(tool.asset_string)
end
it "does not include external tools for non-admins if visibility is set" do
course_with_teacher(account: @account)
tool = @account.context_external_tools.new(name: "bob", consumer_key: "bob", shared_secret: "bob", domain: "example.com")
tool.account_navigation = { url: "http://www.example.com", text: "Example URL", visibility: "admins" }
tool.save!
expect(tool.has_placement?(:account_navigation)).to be true
tabs = @account.tabs_available(@teacher)
expect(tabs.pluck(:id)).to_not include(tool.asset_string)
admin = account_admin_user(account: @account)
tabs = @account.tabs_available(admin)
expect(tabs.pluck(:id)).to include(tool.asset_string)
end
it "uses localized labels" do
tool = @account.context_external_tools.new(name: "bob",
consumer_key: "test",
shared_secret: "secret",
url: "http://example.com")
account_navigation = {
text: "this should not be the title",
url: "http://www.example.com",
labels: {
"en" => "English Label",
"sp" => "Spanish Label"
}
}
tool.settings[:account_navigation] = account_navigation
tool.save!
tabs = @account.external_tool_tabs({}, User.new)
expect(tabs.first[:label]).to eq "English Label"
end
it "includes message handlers" do
mock_tab = {
id: "1234",
label: "my_label",
css_class: "1234",
href: :launch_path_helper,
visibility: nil,
external: true,
hidden: false,
args: [1, 2]
}
allow(Lti::MessageHandler).to receive(:lti_apps_tabs).and_return([mock_tab])
expect(@account.tabs_available(nil)).to include(mock_tab)
end
it "uses :manage_assignments to determine question bank tab visibility" do
account_admin_user_with_role_changes(account: @account, role_changes: { manage_assignments: true, manage_grades: false })
tabs = @account.tabs_available(@admin)
expect(tabs.pluck(:id)).to include(Account::TAB_QUESTION_BANKS)
end
describe "account calendars tab" do
it "is shown if the user has manage_account_calendar_visibility permission" do
account_admin_user_with_role_changes(account: @account)
expect(@account.tabs_available(@admin).pluck(:id)).to include(Account::TAB_ACCOUNT_CALENDARS)
end
it "is not shown if the user lacks manage_account_calendar_visibility permission" do
account_admin_user_with_role_changes(account: @account, role_changes: { manage_account_calendar_visibility: false })
expect(@account.tabs_available(@admin).pluck(:id)).not_to include(Account::TAB_ACCOUNT_CALENDARS)
end
end
describe "'ePortfolio Moderation' tab" do
let(:tab_ids) { @account.tabs_available(@admin).pluck(:id) }
it "is shown if the user has the moderate_user_content permission" do
account_admin_user_with_role_changes(account: @account, role_changes: { moderate_user_content: true })
expect(tab_ids).to include(Account::TAB_EPORTFOLIO_MODERATION)
end
it "is not shown if the user lacks the moderate_user_content permission" do
account_admin_user_with_role_changes(account: @account, role_changes: { moderate_user_content: false })
expect(tab_ids).not_to include(Account::TAB_EPORTFOLIO_MODERATION)
end
end
describe "rubrics permissions" do
let(:tab_ids) { @account.tabs_available(@admin).pluck(:id) }
it "returns the rubrics tab for admins by default" do
account_admin_user(account: @account)
expect(tab_ids).to include(Account::TAB_RUBRICS)
end
it "the rubrics tab is not shown if the user lacks permission (manage_rubrics)" do
account_admin_user_with_role_changes(account: @account, role_changes: { manage_rubrics: false })
expect(tab_ids).not_to include(Account::TAB_RUBRICS)
end
end
end
describe "fast_all_users" do
it "preserves sortable_name" do
user_with_pseudonym(active_all: 1)
@user.update(name: "John St. Clair", sortable_name: "St. Clair, John")
@johnstclair = @user
user_with_pseudonym(active_all: 1, username: "jt@instructure.com", name: "JT Olds")
@jtolds = @user
expect(Account.default.fast_all_users).to eq [@jtolds, @johnstclair]
end
end
it "does not allow setting an sis id for a root account" do
@account = Account.create!
@account.sis_source_id = "abc"
expect(@account.save).to be_falsey
end
describe "user_list_search_mode_for" do
let_once(:account) { Account.default }
it "is preferred for anyone if open registration is turned on" do
account.settings = { open_registration: true }
expect(account.user_list_search_mode_for(nil)).to eq :preferred
expect(account.user_list_search_mode_for(user_factory)).to eq :preferred
end
it "is preferred for account admins" do
expect(account.user_list_search_mode_for(nil)).to eq :closed
expect(account.user_list_search_mode_for(user_factory)).to eq :closed
user_factory
account.account_users.create!(user: @user)
expect(account.user_list_search_mode_for(@user)).to eq :preferred
end
end
context "permissions" do
before(:once) { Account.default }
it "grants :read_global_outcomes to any user iff site_admin" do
@site_admin = Account.site_admin
expect(@site_admin.grants_right?(User.new, :read_global_outcomes)).to be_truthy
@subaccount = @site_admin.sub_accounts.create!
expect(@subaccount.grants_right?(User.new, :read_global_outcomes)).to be_falsey
end
shared_examples_for "a permission granted to account admins and enrollees" do |perm|
it "does not grant #{perm} to user's outside the account" do
expect(Account.default.grants_right?(User.new, perm)).to be_falsey
end
it "grants #{perm} to account admins" do
account_admin_user(account: Account.default)
expect(Account.default.grants_right?(@admin, perm)).to be_truthy
end
it "grants #{perm} to subaccount admins" do
account_admin_user(account: Account.default.sub_accounts.create!)
expect(Account.default.grants_right?(@admin, perm)).to be_truthy
end
it "grants #{perm} to enrollees in account courses" do
course_factory(account: Account.default)
teacher_in_course
student_in_course
expect(Account.default.grants_right?(@teacher, perm)).to be_truthy
expect(Account.default.grants_right?(@student, perm)).to be_truthy
end
it "grants #{perm} to enrollees in subaccount courses" do
course_factory(account: Account.default.sub_accounts.create!)
teacher_in_course
student_in_course
expect(Account.default.grants_right?(@teacher, perm)).to be_truthy
expect(Account.default.grants_right?(@student, perm)).to be_truthy
end
it "grants launch_external_tool to site admin users" do
sa_user = account_admin_user account: Account.site_admin
expect(Account.default.grants_right?(sa_user, perm)).to be_truthy
end
end
describe "read_outcomes permission" do
it_behaves_like "a permission granted to account admins and enrollees", :read_outcomes
end
describe "launch_external_tool permission" do
it_behaves_like "a permission granted to account admins and enrollees", :launch_external_tool
end
context "view_account_calendar_details permission" do
before :once do
@account = Account.default
end
it "is true for an account admin on a visible calendar" do
@account.account_calendar_visible = true
@account.save!
account_admin_user(active_all: true, account: @account)
expect(@account.grants_right?(@admin, :view_account_calendar_details)).to be_truthy
end
it "is true for an account admin with :manage_account_calendar_visibility on a hidden calendar" do
account_admin_user(active_all: true, account: @account)
expect(@account.grants_right?(@admin, :view_account_calendar_details)).to be_truthy
end
it "is true for an account admin without :manage_account_calendar_visibility on a visible calendar" do
@account.account_calendar_visible = true
@account.save!
account_admin_user_with_role_changes(active_all: true,
account: @account,
role_changes: { manage_account_calendar_visibility: false })
expect(@account.grants_right?(@admin, :view_account_calendar_details)).to be_truthy
end
it "is false for an account admin without :manage_account_calendar_visibility on a hidden calendar" do
account_admin_user_with_role_changes(active_all: true,
account: @account,
role_changes: { manage_account_calendar_visibility: false })
expect(@account.grants_right?(@admin, :view_account_calendar_details)).to be_falsey
end
it "is true for an account admin on a random subaccount" do
subaccount = @account.sub_accounts.create!
account_admin_user(active_all: true, account: @account)
expect(subaccount.grants_right?(@admin, :view_account_calendar_details)).to be_truthy
end
it "is true for a student only on associated accounts with a visible calendar" do
subaccount1 = @account.sub_accounts.create!
subaccount2 = @account.sub_accounts.create!
[@account, subaccount1, subaccount2].each do |a|
a.account_calendar_visible = true
a.save!
end
course_with_student(active_all: true, account: subaccount1)
expect(@account.grants_right?(@student, :view_account_calendar_details)).to be_truthy
expect(subaccount1.grants_right?(@student, :view_account_calendar_details)).to be_truthy
expect(subaccount2.grants_right?(@student, :view_account_calendar_details)).to be_falsey
end
it "is false for a student on associated accounts with hidden calendars" do
@account.account_calendar_visible = true
@account.save!
subaccount = @account.sub_accounts.create!
course_with_student(active_all: true, account: subaccount)
expect(@account.grants_right?(@student, :view_account_calendar_details)).to be_truthy
expect(subaccount.grants_right?(@student, :view_account_calendar_details)).to be_falsey
end
end
end
describe "authentication_providers.active" do
let(:account) { Account.default }
let!(:aac) { account.authentication_providers.create!(auth_type: "facebook") }
it "pulls active AACS" do
expect(account.authentication_providers.active).to include(aac)
end
it "ignores deleted AACs" do
aac.destroy
expect(account.authentication_providers.active).to_not include(aac)
end
end
describe "delegated_authentication?" do
let(:account) { Account.default }
before do
account.authentication_providers.scope.delete_all
end
it "is false for LDAP" do
account.authentication_providers.create!(auth_type: "ldap")
expect(account.delegated_authentication?).to be false
end
it "is true for CAS" do
account.authentication_providers.create!(auth_type: "cas")
expect(account.delegated_authentication?).to be true
end
end
describe "#non_canvas_auth_configured?" do
let(:account) { Account.default }
it "is false for no aacs" do
expect(account.non_canvas_auth_configured?).to be_falsey
end
it "is true for having aacs" do
Account.default.authentication_providers.create!(auth_type: "ldap")
expect(account.non_canvas_auth_configured?).to be_truthy
end
it "is false after aacs deleted" do
Account.default.authentication_providers.create!(auth_type: "ldap")
account.authentication_providers.destroy_all
expect(account.non_canvas_auth_configured?).to be_falsey
end
end
describe "#find_child" do
it "works for root accounts" do
sub = Account.default.sub_accounts.create!
expect(Account.default.find_child(sub.id)).to eq sub
end
it "works for children accounts" do
sub = Account.default.sub_accounts.create!
sub_sub = sub.sub_accounts.create!
sub_sub_sub = sub_sub.sub_accounts.create!
expect(sub.find_child(sub_sub_sub.id)).to eq sub_sub_sub
end
it "raises for out-of-tree accounts" do
sub = Account.default.sub_accounts.create!
sub_sub = sub.sub_accounts.create!
sibling = sub.sub_accounts.create!
expect { sub_sub.find_child(sibling.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "manually created courses account" do
it "still works with existing manually created courses accounts" do
acct = Account.default
sub = acct.sub_accounts.create!(name: "Manually-Created Courses")
manual_courses_account = acct.manually_created_courses_account
expect(manual_courses_account.id).to eq sub.id
expect(acct.reload.settings[:manually_created_courses_account_id]).to eq sub.id
end
it "does not create a duplicate manual courses account when locale changes" do
acct = Account.default
sub1 = acct.manually_created_courses_account
sub2 = I18n.with_locale(:es) do
acct.manually_created_courses_account
end
expect(sub1.id).to eq sub2.id
end
it "works if the saved account id doesn't exist" do
acct = Account.default
acct.settings[:manually_created_courses_account_id] = acct.id + 1000
acct.save!
expect(acct.manually_created_courses_account).to be_present
end
it "works if the saved account id is not a sub-account" do
acct = Account.default
bad_acct = Account.create!
acct.settings[:manually_created_courses_account_id] = bad_acct.id
acct.save!
manual_course_account = acct.manually_created_courses_account
expect(manual_course_account.id).not_to eq bad_acct.id
end
end
describe "account_users_for" do
it "is cache coherent for site admin" do
enable_cache do
user_factory
sa = Account.site_admin
expect(sa.account_users_for(@user)).to eq []
au = sa.account_users.create!(user: @user)
# out-of-proc cache should clear, but we have to manually clear
# the in-proc cache
sa = Account.find(sa.id)
expect(sa.account_users_for(@user)).to eq [au]
au.destroy
# ditto
sa = Account.find(sa.id)
expect(sa.account_users_for(@user)).to eq []
end
end
context "sharding" do
specs_require_sharding
it "is cache coherent for site admin" do
enable_cache do
user_factory
sa = Account.site_admin
@shard1.activate do
expect(sa.account_users_for(@user)).to eq []
au = sa.account_users.create!(user: @user)
# out-of-proc cache should clear, but we have to manually clear
# the in-proc cache
sa = Account.find(sa.id)
expect(sa.account_users_for(@user)).to eq [au]
au.destroy
# ditto
sa = Account.find(sa.id)
expect(sa.account_users_for(@user)).to eq []
end
end
end
end
end
describe "available_custom_course_roles" do
before :once do
account_model
@roleA = @account.roles.create name: "A"
@roleA.base_role_type = "StudentEnrollment"
@roleA.save!
@roleB = @account.roles.create name: "B"
@roleB.base_role_type = "StudentEnrollment"
@roleB.save!
@sub_account = @account.sub_accounts.create!
@roleC = @sub_account.roles.create name: "C"
@roleC.base_role_type = "StudentEnrollment"
@roleC.save!
end
it "returns roles indexed by name" do
expect(@account.available_custom_course_roles.sort_by(&:id)).to eq [@roleA, @roleB].sort_by(&:id)
end
it "does not return inactive roles" do
@roleB.deactivate!
expect(@account.available_custom_course_roles).to eq [@roleA]
end
it "does not return deleted roles" do
@roleA.destroy
expect(@account.available_custom_course_roles).to eq [@roleB]
end
it "derives roles from parents" do
expect(@sub_account.available_custom_course_roles.sort_by(&:id)).to eq [@roleA, @roleB, @roleC].sort_by(&:id)
end
it "includes built-in roles when called" do
expect(@sub_account.available_course_roles.sort_by(&:id)).to eq ([@roleA, @roleB, @roleC] + Role.built_in_course_roles(root_account_id: @account.id)).sort_by(&:id)
end
end
describe "account_chain" do
context "sharding" do
specs_require_sharding
it "finds parent accounts when not on the correct shard" do
@shard1.activate do
@account1 = Account.create!
@account2 = @account1.sub_accounts.create!
@account3 = @account2.sub_accounts.create!
end
expect(@account3.account_chain).to eq [@account3, @account2, @account1]
end
end
it "returns parent accounts in order up the tree" do
account1 = Account.create!
account2 = account1.sub_accounts.create!
account3 = account2.sub_accounts.create!
account4 = account3.sub_accounts.create!
chain = account4.account_chain
expect(chain).to eq [account4, account3, account2, account1]
# ensure pre-loading worked correctly
expect(chain.map { |a| a.association(:parent_account).loaded? }).to eq [true, true, true, true]
expect(chain.map(&:parent_account)).to eq [account3, account2, account1, nil]
expect(chain.map { |a| a.association(:root_account).loaded? }).to eq [true, true, true, false]
expect(chain.map(&:root_account)).to eq [account1, account1, account1, account1]
chain = account4.account_chain(include_site_admin: true)
sa = Account.site_admin
expect(chain).to eq [account4, account3, account2, account1, sa]
# ensure pre-loading worked correctly
expect(chain.map { |a| a.association(:parent_account).loaded? }).to eq [true, true, true, true, true]
expect(chain.map(&:parent_account)).to eq [account3, account2, account1, nil, nil]
expect(chain.map { |a| a.association(:root_account).loaded? }).to eq [true, true, true, false, false]
expect(chain.map(&:root_account)).to eq [account1, account1, account1, account1, sa]
end
end
describe "#can_see_admin_tools_tab?" do
let_once(:account) { Account.create! }
it "returns false if no user is present" do
expect(account.can_see_admin_tools_tab?(nil)).to be_falsey
end
it "returns false if you are a site admin" do
admin = account_admin_user(account: Account.site_admin)
expect(Account.site_admin.can_see_admin_tools_tab?(admin)).to be_falsey
end
it "doesn't have permission, it returns false" do
allow(account).to receive(:grants_right?).and_return(false)
account_admin_user(account:)
expect(account.can_see_admin_tools_tab?(@admin)).to be_falsey
end
it "does have permission, it returns true" do
allow(account).to receive(:grants_right?).and_return(true)
account_admin_user(account:)
expect(account.can_see_admin_tools_tab?(@admin)).to be_truthy
end
end
describe "#update_account_associations" do
before do
@account = Account.default.sub_accounts.create!
@c1 = @account.courses.create!
@c2 = @account.courses.create!
@account.course_account_associations.scope.delete_all
end
it "updates associations for all courses" do
expect(@account.associated_courses).to eq []
@account.update_account_associations
@account.reload
expect(@account.associated_courses.sort_by(&:id)).to eq [@c1, @c2]
end
it "can update associations in batch" do
expect(@account.associated_courses).to eq []
Account.update_all_update_account_associations
@account.reload
expect(@account.associated_courses.sort_by(&:id)).to eq [@c1, @c2]
end
end
describe "default_time_zone" do
context "root account" do
before :once do
@account = Account.create!
end
it "uses provided value when set" do
@account.default_time_zone = "America/New_York"
expect(@account.default_time_zone).to eq ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
end
it "has a sensible default if not set" do
expect(@account.default_time_zone).to eq ActiveSupport::TimeZone[Account.time_zone_attribute_defaults[:default_time_zone]]
end
end
context "sub account" do
before :once do
@root_account = Account.create!
@account = @root_account.sub_accounts.create!
@account.root_account = @root_account
end
it "uses provided value when set, regardless of root account setting" do
@root_account.default_time_zone = "America/Chicago"
@account.default_time_zone = "America/New_York"
expect(@account.default_time_zone).to eq ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
end
it "defaults to root account value if not set" do
@root_account.default_time_zone = "America/Chicago"
expect(@account.default_time_zone).to eq ActiveSupport::TimeZone["Central Time (US & Canada)"]
end
it "has a sensible default if neither is set" do
expect(@account.default_time_zone).to eq ActiveSupport::TimeZone[Account.time_zone_attribute_defaults[:default_time_zone]]
end
end
end
it "sets allow_sis_import if root_account" do
account = Account.create!
expect(account.allow_sis_import).to be true
sub = account.sub_accounts.create!
expect(sub.allow_sis_import).to be false
end
describe "#ensure_defaults" do
it "assigns an lti_guid postfixed by canvas-lms" do
account = Account.new
account.uuid = "12345"
account.ensure_defaults
expect(account.lti_guid).to eq "12345:canvas-lms"
end
it "does not change existing an lti_guid" do
account = Account.new
account.lti_guid = "12345"
account.ensure_defaults
expect(account.lti_guid).to eq "12345"
end
it "removes carriage returns from the name" do
account = Account.new
account.name = "Hello\r\nWorld"
account.ensure_defaults
expect(account.name).to eq "Hello\nWorld"
end
end
it "formats a referer url" do
account = Account.new
expect(account.format_referer(nil)).to be_nil
expect(account.format_referer("")).to be_nil
expect(account.format_referer("not a url")).to be_nil
expect(account.format_referer("http://example.com/")).to eq "http://example.com"
expect(account.format_referer("http://example.com/index.html")).to eq "http://example.com"
expect(account.format_referer("http://example.com:80")).to eq "http://example.com"
expect(account.format_referer("https://example.com:443")).to eq "https://example.com"
expect(account.format_referer("http://example.com:3000")).to eq "http://example.com:3000"
end
it "formats trusted referers when set" do
account = Account.new
account.trusted_referers = "https://example.com/,http://example.com:80,http://example.com:3000"
expect(account.settings[:trusted_referers]).to eq "https://example.com,http://example.com,http://example.com:3000"
account.trusted_referers = nil
expect(account.settings[:trusted_referers]).to be_nil
account.trusted_referers = ""
expect(account.settings[:trusted_referers]).to be_nil
end
describe "trusted_referer?" do
let!(:account) do
account = Account.new
account.settings[:trusted_referers] = "https://example.com,http://example.com,http://example.com:3000"
account
end
it "is true when a referer is trusted" do
expect(account.trusted_referer?("http://example.com")).to be_truthy
expect(account.trusted_referer?("http://example.com:3000")).to be_truthy
expect(account.trusted_referer?("http://example.com:80")).to be_truthy
expect(account.trusted_referer?("https://example.com:443")).to be_truthy
end
it "is false when a referer is not provided" do
expect(account.trusted_referer?(nil)).to be_falsey
expect(account.trusted_referer?("")).to be_falsey
end
it "is false when a referer is not trusted" do
expect(account.trusted_referer?("https://example.com:5000")).to be_falsey
end
it "is false when the account has no trusted referer setting" do
account.settings.delete(:trusted_referers)
expect(account.trusted_referer?("https://example.com")).to be_falsey
end
it "is false when the account has nil trusted referer setting" do
account.settings[:trusted_referers] = nil
expect(account.trusted_referer?("https://example.com")).to be_falsey
end
it "is false when the account has empty trusted referer setting" do
account.settings[:trusted_referers] = ""
expect(account.trusted_referer?("https://example.com")).to be_falsey
end
end
context "quota cache" do
it "only clears the quota cache if something changes" do
account = account_model
expect(Account).to receive(:invalidate_inherited_caches).once
account.default_storage_quota = 10.decimal_megabytes
account.save! # clear here
account.reload
account.save!
account.default_storage_quota = 10.decimal_megabytes
account.save!
end
it "inherits from a parent account's default_storage_quota" do
enable_cache do
account = account_model
account.default_storage_quota = 10.decimal_megabytes
account.save!
subaccount = account.sub_accounts.create!
expect(subaccount.default_storage_quota).to eq 10.decimal_megabytes
account.default_storage_quota = 20.decimal_megabytes
account.save!
# should clear caches
account = Account.find(account.id)
expect(account.default_storage_quota).to eq 20.decimal_megabytes
subaccount = Account.find(subaccount.id)
expect(subaccount.default_storage_quota).to eq 20.decimal_megabytes
end
end
it "inherits from a new parent account's default_storage_quota if parent account changes" do
enable_cache do
account = account_model
sub1 = account.sub_accounts.create!
sub2 = account.sub_accounts.create!
sub2.update(default_storage_quota: 10.decimal_megabytes)
to_be_subaccount = sub1.sub_accounts.create!
expect(to_be_subaccount.default_storage_quota).to eq Account::DEFAULT_STORAGE_QUOTA
# should clear caches
Timecop.travel(1.second.from_now) do
to_be_subaccount.update(parent_account: sub2)
to_be_subaccount = Account.find(to_be_subaccount.id)
expect(to_be_subaccount.default_storage_quota).to eq 10.decimal_megabytes
end
end
end
end
context "inheritable settings" do
before :once do
@settings = [:restrict_student_future_view, :lock_all_announcements]
end
before :once do
account_model
@sub1 = @account.sub_accounts.create!
@sub2 = @sub1.sub_accounts.create!
end
it "uses the default value if nothing is set anywhere" do
expected = { locked: false, value: false }
[@account, @sub1, @sub2].each do |a|
expect(a.restrict_student_future_view).to eq expected
expect(a.lock_all_announcements).to eq expected
end
end
it "is able to lock values for sub-accounts" do
@settings.each do |key|
@sub1.settings[key] = { locked: true, value: true }
end
@sub1.save!
# should ignore the subaccount's wishes
@settings.each do |key|
@sub2.settings[key] = { locked: true, value: false }
end
@sub2.save!
@settings.each do |key|
expect(@account.send(key)).to eq({ locked: false, value: false })
expect(@sub1.send(key)).to eq({ locked: true, value: true })
expect(@sub2.send(key)).to eq({ locked: true, value: true, inherited: true })
end
end
it "grandfathers old pre-hash values in" do
@settings.each do |key|
@account.settings[key] = true
end
@account.save!
@settings.each do |key|
@sub2.settings[key] = false
end
@sub2.save!
@settings.each do |key|
expect(@account.send(key)).to eq({ locked: false, value: true })
expect(@sub1.send(key)).to eq({ locked: false, value: true, inherited: true })
expect(@sub2.send(key)).to eq({ locked: false, value: false })
end
end
it "translates string values in mass-assignment" do
settings = {}
settings[:restrict_student_future_view] = { "value" => "1", "locked" => "0" }
settings[:lock_all_announcements] = { "value" => "1", "locked" => "0" }
@account.settings = settings
@account.save!
expect(@account.restrict_student_future_view).to eq({ locked: false, value: true })
expect(@account.lock_all_announcements).to eq({ locked: false, value: true })
end
context "empty setting elision" do
before :once do
@account.update settings: { sis_assignment_name_length_input: { value: "100" } }
@sub1.update settings: { sis_assignment_name_length_input: { value: "150" } }
@sub2.update settings: { sis_assignment_name_length_input: { value: "200" } }
end
it "elides an empty setting" do
@sub1.update settings: { sis_assignment_name_length_input: { value: "" } }
expect(@sub1.sis_assignment_name_length_input).to eq({ value: "100", inherited: true })
end
it "elides a nil setting" do
@sub1.update settings: { sis_assignment_name_length_input: { value: nil } }
expect(@sub1.sis_assignment_name_length_input).to eq({ value: "100", inherited: true })
end
it "elides an explicitly-unlocked setting" do
@sub1.update settings: { sis_assignment_name_length_input: { value: nil, locked: false } }
expect(@sub1.sis_assignment_name_length_input).to eq({ value: "100", inherited: true })
end
it "doesn't elide a locked setting" do
@sub1.update settings: { sis_assignment_name_length_input: { value: nil, locked: true } }
expect(@sub2.sis_assignment_name_length_input).to eq({ value: nil, inherited: true, locked: true })
end
end
context "caching" do
specs_require_sharding
it "clears cached values correctly" do
enable_cache do
# preload the cached values
[@account, @sub1, @sub2].each(&:restrict_student_future_view)
[@account, @sub1, @sub2].each(&:lock_all_announcements)
@sub1.settings = @sub1.settings.merge(restrict_student_future_view: { locked: true, value: true }, lock_all_announcements: { locked: true, value: true })
@sub1.save!
# hard reload
@account = Account.find(@account.id)
@sub1 = Account.find(@sub1.id)
@sub2 = Account.find(@sub2.id)
expect(@account.restrict_student_future_view).to eq({ locked: false, value: false })
expect(@account.lock_all_announcements).to eq({ locked: false, value: false })
expect(@sub1.restrict_student_future_view).to eq({ locked: true, value: true })
expect(@sub1.lock_all_announcements).to eq({ locked: true, value: true })
expect(@sub2.restrict_student_future_view).to eq({ locked: true, value: true, inherited: true })
expect(@sub2.lock_all_announcements).to eq({ locked: true, value: true, inherited: true })
end
end
end
end
context "require terms of use" do
describe "#terms_required?" do
it "returns true by default" do
expect(account_model.terms_required?).to be true
end
it "returns false by default for new accounts" do
TermsOfService.skip_automatic_terms_creation = false
expect(account_model.terms_required?).to be false
end
it "returns false if Setting is false" do
Setting.set(:terms_required, "false")
expect(account_model.terms_required?).to be false
end
it "returns false if account setting is false" do
account = account_model(settings: { account_terms_required: false })
expect(account.terms_required?).to be false
end
it "consults root account setting" do
parent_account = account_model(settings: { account_terms_required: false })
child_account = Account.create!(parent_account:)
expect(child_account.terms_required?).to be false
end
end
end
context "account cache" do
specs_require_sharding
describe ".find_cached" do
let(:nonsense_id) { 987_654_321 }
it "works relative to a different shard" do
@shard1.activate do
a = Account.create!
expect(Account.find_cached(a.id)).to eq a
end
end
it "errors if infrastructure fails and we can't see the account" do
expect { Account.find_cached(nonsense_id) }.to raise_error(Canvas::AccountCacheError)
end
it "includes the account id in the error message" do
Account.find_cached(nonsense_id)
rescue Canvas::AccountCacheError => e
expect(e.message).to eq("Couldn't find Account with 'id'=#{nonsense_id}")
end
end
describe ".invalidate_cache" do
it "works relative to a different shard" do
enable_cache do
@shard1.activate do
a = Account.create!
Account.find_cached(a.id) # set the cache
expect(Account.invalidate_cache(a.id)).to be true
end
end
end
end
end
describe "#users_name_like" do
context "sharding" do
specs_require_sharding
it "works cross-shard" do
@shard1.activate do
@account = Account.create!
@user = user_factory(name: "silly name")
@user.account_users.create(account: @account)
end
expect(@account.users_name_like("silly").first).to eq @user
end
end
end
describe "#migrate_to_canvadocs?" do
before(:once) do
@account = Account.create!
end
it "is true if hijack_crocodoc_sessions is true" do
allow(Canvadocs).to receive(:hijack_crocodoc_sessions?).and_return(true)
expect(@account).to be_migrate_to_canvadocs
end
it "is false if hijack_crocodoc_sessions is false" do
allow(Canvadocs).to receive(:hijack_crocodoc_sessions?).and_return(false)
expect(@account).not_to be_migrate_to_canvadocs
end
end
it "clears special account cache on updates to special accounts" do
expect(Account.default.settings[:blah]).to be_nil
non_cached = Account.find(Account.default.id)
non_cached.settings[:blah] = true
non_cached.save!
expect(Account.default.settings[:blah]).to be true
end
it_behaves_like "a learning outcome context"
describe "#default_dashboard_view" do
before(:once) do
@account = Account.create!
end
it "is nil by default" do
expect(@account.default_dashboard_view).to be_nil
end
it "updates if view is valid" do
@account.default_dashboard_view = "activity"
@account.save!
expect(@account.default_dashboard_view).to eq "activity"
end
it "does not update if view is invalid" do
@account.default_dashboard_view = "junk"
expect { @account.save! }.not_to change { @account.default_dashboard_view }
end
it "contains planner" do
@account.default_dashboard_view = "planner"
@account.save!
expect(@account.default_dashboard_view).to eq "planner"
end
end
describe "#update_user_dashboards" do
before :once do
@account = Account.create!
@user1 = user_factory(active_all: true)
@account.pseudonyms.create!(unique_id: "user1", user: @user1)
@user1.dashboard_view = "activity"
@user1.save
@user2 = user_factory(active_all: true)
@account.pseudonyms.create!(unique_id: "user2", user: @user2)
@user2.dashboard_view = "cards"
@user2.save
end
it "adds or overwrite all account users' dashboard_view preference" do
@account.default_dashboard_view = "planner"
@account.save!
@account.reload
expect([@user1.dashboard_view(@account), @user2.dashboard_view(@account)]).to match_array(["activity", "cards"])
@account.update_user_dashboards(synchronous: true)
@account.reload
expect([@user1.reload.dashboard_view(@account), @user2.reload.dashboard_view(@account)]).to match_array(Array.new(2, "planner"))
end
end
it "only sends new account user notifications to active admins" do
active_admin = account_admin_user(active_all: true)
deleted_admin = account_admin_user(active_all: true)
deleted_admin.account_users.destroy_all
Account.default.reload
n = Notification.create(name: "New Account User", category: "TestImmediately")
[active_admin, deleted_admin].each do |u|
NotificationPolicy.create(notification: n, communication_channel: u.communication_channel, frequency: "immediately")
end
user_factory(active_all: true)
au = Account.default.account_users.create!(user: @user)
expect(au.messages_sent[n.name].map(&:user)).to match_array [active_admin, @user]
end
context "fancy redis caching" do
specs_require_cache(:redis_cache_store)
describe "cached_account_users_for" do
before do
@account = Account.create!
@user = User.create!
end
def cached_account_users
%i[@account_users_cache @account_chain_ids @account_chain].each do |iv|
@account.instance_variable_set(iv, nil)
end
@account.cached_account_users_for(@user)
end
it "caches" do
expect_any_instantiation_of(@account).to receive(:account_users_for).once.and_call_original
2.times { cached_account_users }
end
it "skips cache if disabled" do
allow(Canvas::CacheRegister).to receive(:enabled?).and_return(false)
expect_any_instantiation_of(@account).to receive(:account_users_for).exactly(2).times.and_call_original
2.times { cached_account_users }
end
it "updates if the account chain changes" do
other_account = Account.create!
au = AccountUser.create!(account: other_account, user: @user)
expect(cached_account_users).to eq []
@account.update_attribute(:parent_account, other_account)
@account.reload
expect(cached_account_users).to eq [au]
end
it "updates if the user has an account user added" do
expect(cached_account_users).to eq []
au = AccountUser.create!(account: @account, user: @user)
expect(cached_account_users).to eq [au]
end
end
describe "account_chain_ids" do
let(:account1) { Account.default.sub_accounts.create! }
before do
account1
end
it "caches" do
expect(Account.connection).to receive(:select_values).once.and_call_original
2.times { Account.account_chain_ids(Account.default.id) }
end
it "skips cache if disabled" do
allow(Canvas::CacheRegister).to receive(:enabled?).and_return(false)
expect(Account.connection).to receive(:select_values).exactly(2).times.and_call_original
2.times { Account.account_chain_ids(Account.default.id) }
end
it "updates if the account chain changes" do
account2 = Account.default.sub_accounts.create!
expect(Account.account_chain_ids(account2.id)).to eq [account2.id, Account.default.id]
account2.update_attribute(:parent_account, account1)
expect(Account.account_chain_ids(account2.id)).to eq [account2.id, account1.id, Account.default.id]
end
def expect_id_chain_for_account(account, id_chain)
# frd disable caching for testing, so that calls with either
# Account or id still exercise all logic
allow(Account).to receive(:cache_key_for_id).and_return(nil)
expect(Account.account_chain_ids(account.id)).to eq id_chain
expect(Account.account_chain_ids(account)).to eq id_chain
end
it "returns local ids" do
expect_id_chain_for_account(account1, [account1.id, Account.default.id])
end
context "on another shard" do
specs_require_sharding
it "returns correct global ids" do
@shard1.activate do
expect_id_chain_for_account(account1, [account1.global_id, Account.default.global_id])
end
end
it "returns correct global ids when used twice on different shards (doesn't cache across shards)" do
expect(account1.account_chain_ids).to eq([account1.id, Account.default.id])
@shard1.activate do
expect(account1.account_chain_ids).to eq([account1.id, Account.default.id])
end
end
end
end
end
context "#destroy on sub accounts" do
before :once do
@root_account = Account.create!
@sub_account = @root_account.sub_accounts.create!
end
it "wont let you destroy if there are active sub accounts" do
@sub_account.sub_accounts.create!
expect { @sub_account.destroy! }.to raise_error ActiveRecord::RecordInvalid
end
it "wont let you destroy if there are active courses" do
@sub_account.courses.create!
expect { @sub_account.destroy! }.to raise_error ActiveRecord::RecordInvalid
end
it "destroys associated account users" do
account_user1 = @sub_account.account_users.create!(user: User.create!)
account_user2 = @sub_account.account_users.create!(user: User.create!)
@sub_account.destroy!
expect(account_user1.reload.workflow_state).to eq "deleted"
expect(account_user2.reload.workflow_state).to eq "deleted"
end
end
context "custom help link validation" do
before do
account_model
end
it "is valid if custom help links are not present" do
@account.settings[:foo] = "bar"
expect(@account.valid?).to be true
end
it "is valid if custom help links are valid" do
@account.settings[:custom_help_links] = [{ is_new: true, is_featured: false }, { is_new: false, is_featured: true }]
expect(@account.valid?).to be true
end
it "is not valid if custom help links are invalid" do
@account.settings[:custom_help_links] = [{ is_new: true, is_featured: true }]
expect(@account.valid?).to be false
end
it "does not check custom help links if not changed" do
@account.update_attribute(:settings, [{ is_new: true, is_featured: true }]) # skips validation
@account.name = "foo"
expect(@account.valid?).to be true
end
end
describe "#allow_disable_post_to_sis_when_grading_period_closed?" do
let(:root_account) { Account.create!(root_account: nil) }
let(:subaccount) { Account.create!(root_account:) }
it "returns false if the account is not a root account" do
root_account.enable_feature!(:new_sis_integrations)
root_account.enable_feature!(:disable_post_to_sis_when_grading_period_closed)
expect(subaccount).not_to be_allow_disable_post_to_sis_when_grading_period_closed
end
context "for a root account" do
it "returns false if the root account does not enable the relevant feature flag" do
root_account.enable_feature!(:disable_post_to_sis_when_grading_period_closed)
expect(root_account).not_to be_allow_disable_post_to_sis_when_grading_period_closed
end
it "returns false if this account does not enable the new_sis_integrations feature flag" do
root_account.enable_feature!(:new_sis_integrations)
expect(root_account).not_to be_allow_disable_post_to_sis_when_grading_period_closed
end
it "returns true when the relevant feature flags are enabled" do
root_account.enable_feature!(:new_sis_integrations)
root_account.enable_feature!(:disable_post_to_sis_when_grading_period_closed)
expect(root_account).to be_allow_disable_post_to_sis_when_grading_period_closed
end
end
end
context "default_locale cached recursive search" do
specs_require_cache(:redis_cache_store)
it "caches" do
sub_acc1 = Account.default.sub_accounts.create!(default_locale: "es")
sub_acc2 = sub_acc1.sub_accounts.create!
expect(Account.recursive_default_locale_for_id(sub_acc2.id)).to eq "es"
Account.where(id: sub_acc1).update_all(default_locale: "de") # directly update db - shouldn't invalidate cache
expect(Account.recursive_default_locale_for_id(sub_acc2.id)).to eq "es"
sub_acc1.update_attribute(:default_locale, "en") # should invalidate cache downstream
expect(Account.recursive_default_locale_for_id(sub_acc2.id)).to eq "en"
end
end
context "effective_brand_config caching" do
specs_require_cache(:redis_cache_store)
it "caches the brand config" do
@parent_account = Account.default
config1 = BrandConfig.create(variables: { "ic-brand-primary" => "#321" })
config2 = BrandConfig.create(variables: { "ic-brand-primary" => "#123" })
Account.default.update_attribute(:brand_config_md5, config1.md5)
sub_acc1 = Account.default.sub_accounts.create!
sub_acc2 = sub_acc1.sub_accounts.create!
expect(sub_acc2.effective_brand_config).to eq config1
Account.where(id: sub_acc1).update_all(brand_config_md5: config2.md5) # directly update db - shouldn't invalidate cache
expect(Account.find(sub_acc2.id).effective_brand_config).to eq config1
Account.default.update_attribute(:brand_config_md5, config2.md5) # should invalidate downstream
expect(Account.find(sub_acc2.id).effective_brand_config).to eq config2
end
end
context "#roles_with_enabled_permission" do
def create_role_override(permission, role, context, enabled = true)
RoleOverride.create!(
context:,
permission:,
role:,
enabled:
)
end
let(:account) { account_model }
it "returns expected roles with the given permission" do
account.disable_feature!(:granular_permissions_manage_courses)
role = account.roles.create name: "AssistantGrader"
role.base_role_type = "TaEnrollment"
role.workflow_state = "active"
role.save!
create_role_override("change_course_state", role, account)
expect(
account.roles_with_enabled_permission(:change_course_state).map(&:name).sort
).to eq %w[AccountAdmin AssistantGrader DesignerEnrollment TeacherEnrollment]
end
it "returns expected roles with the given permission (granular permissions)" do
account.enable_feature!(:granular_permissions_manage_courses)
role = account.roles.create name: "TeacherAdmin"
role.base_role_type = "TeacherEnrollment"
role.workflow_state = "active"
role.save!
create_role_override("manage_courses_add", role, account)
create_role_override("manage_courses_publish", role, account)
create_role_override("manage_courses_conclude", role, account)
create_role_override("manage_courses_reset", role, account)
create_role_override("manage_courses_delete", role, account)
expect(
account.roles_with_enabled_permission(:manage_courses_add).map(&:name).sort
).to eq %w[AccountAdmin]
expect(
account.roles_with_enabled_permission(:manage_courses_publish).map(&:name).sort
).to eq %w[AccountAdmin DesignerEnrollment TeacherAdmin TeacherEnrollment]
expect(
account.roles_with_enabled_permission(:manage_courses_conclude).map(&:name).sort
).to eq %w[AccountAdmin DesignerEnrollment TeacherAdmin TeacherEnrollment]
expect(
account.roles_with_enabled_permission(:manage_courses_reset).map(&:name).sort
).to eq %w[AccountAdmin TeacherAdmin]
expect(
account.roles_with_enabled_permission(:manage_courses_delete).map(&:name).sort
).to eq %w[AccountAdmin TeacherAdmin]
end
end
describe "#invalidate_caches_if_changed" do
it "works for root accounts" do
Account.default.name = "Something new"
expect(Account).to receive(:invalidate_cache).with(Account.default.id).at_least(1)
allow(Rails.cache).to receive(:delete)
expect(Rails.cache).to receive(:delete).with(["account2", Account.default.id].cache_key)
Account.default.save!
end
it "works for sub accounts" do
a = Account.default.manually_created_courses_account
a.name = "something else"
expect(Rails.cache).to receive(:delete).with("short_name_lookup/account_#{a.id}").ordered
expect(Rails.cache).to receive(:delete).with(["account2", a.id].cache_key).ordered
a.save!
end
end
describe "allow_observers_in_appointment_groups?" do
before :once do
@account = Account.default
@account.settings[:allow_observers_in_appointment_groups] = { value: true }
@account.save!
end
it "returns true if the setting is enabled and the observer_appointment_groups flag is enabled" do
expect(@account.allow_observers_in_appointment_groups?).to be true
end
it "returns false if the observer_appointment_groups flag is disabled" do
Account.site_admin.disable_feature!(:observer_appointment_groups)
expect(@account.allow_observers_in_appointment_groups?).to be false
end
end
describe "enable_as_k5_account setting" do
it "enable_as_k5_account? helper returns false by default" do
account = Account.create!
expect(account).not_to be_enable_as_k5_account
end
it "enable_as_k5_account? and enable_as_k5_account helpers return correct values" do
account = Account.create!
account.settings[:enable_as_k5_account] = {
value: true,
locked: true
}
expect(account).to be_enable_as_k5_account
expect(account.enable_as_k5_account[:value]).to be_truthy
expect(account.enable_as_k5_account[:locked]).to be_truthy
end
end
describe "#multi_parent_sub_accounts_recursive" do
subject { Account.multi_parent_sub_accounts_recursive(parent_account_ids) }
let_once(:root_account) { Account.create! }
let_once(:level_one_sub_accounts) do
3.times do |i|
root_account.sub_accounts.create!(name: "Level 1 - Sub-account #{i}")
end
root_account.sub_accounts
end
let_once(:level_two_sub_accounts) do
root_account.sub_accounts.map do |sa|
sa.sub_accounts.create!(name: "Level 2 - Sub account")
end
end
context "with empty parent account ids" do
let(:parent_account_ids) { [] }
it { is_expected.to match_array [] }
end
context "with a single root account id" do
let(:parent_account_ids) { [root_account.id] }
it "returns all sub-accounts" do
expect(subject).to match_array(
level_one_sub_accounts + level_two_sub_accounts + [root_account]
)
end
end
context "with a single sub-account parent account id" do
let(:parent_sub_account) { level_two_sub_accounts.first }
let(:parent_account_ids) { [parent_sub_account.id] }
it "returns all sub-accounts that belong to the parent account" do
expect(subject).to match_array(
parent_sub_account.sub_accounts + [parent_sub_account]
)
end
end
context "with multiple parent account ids" do
let(:parent_account_one) { level_one_sub_accounts.first }
let(:parent_account_two) { level_one_sub_accounts.second }
let(:parent_account_ids) { [parent_account_one.id, parent_account_two.id] }
it "returns all sub-accounts that belong to the parent accounts" do
expect(subject).to match_array(
parent_account_one.sub_accounts + parent_account_two.sub_accounts + [parent_account_one, parent_account_two]
)
end
context "and not all parent account IDs are on the same shard" do
specs_require_sharding
let(:cross_shard_parent_account) { @shard1.activate { Account.create! } }
let(:parent_account_ids) { [root_account.id, cross_shard_parent_account.id] }
it "raises an argument error" do
expect { subject }.to raise_error(
ArgumentError,
"all parent_account_ids must be in the same shard"
)
end
end
context "and another shard is active" do
specs_require_sharding
subject { @shard1.activate { Account.multi_parent_sub_accounts_recursive(parent_account_ids) } }
let(:parent_account_one) { level_one_sub_accounts.first }
let(:parent_account_two) { level_one_sub_accounts.second }
let(:parent_account_ids) { [parent_account_one.global_id, parent_account_two.global_id] }
it "returns all sub-accounts that belong to the parent accounts" do
expect(subject).to match_array(
parent_account_one.sub_accounts + parent_account_two.sub_accounts + [parent_account_one, parent_account_two]
)
end
end
context "and there is overlap in the sub accounts" do
let(:parent_account_one) { root_account }
let(:parent_account_two) { level_one_sub_accounts.first }
let(:parent_account_ids) { [parent_account_one.id, parent_account_two.id] }
it "Does not include duplicate accounts" do
expect(subject).to match_array(
level_one_sub_accounts + level_two_sub_accounts + [root_account]
)
end
end
end
end
describe "#effective_course_template" do
let(:root_account) { Account.create! }
let(:sub_account) { root_account.sub_accounts.create! }
let(:template) { root_account.courses.create!(template: true) }
it "returns an explicit template" do
sub_account.update!(course_template: template)
expect(sub_account.effective_course_template).to eq template
end
it "inherits a template" do
root_account.update!(course_template: template)
expect(sub_account.effective_course_template).to eq template
end
it "doesn't use an explicit non-template" do
root_account.update!(course_template: template)
Course.ensure_dummy_course
sub_account.update!(course_template_id: 0)
expect(sub_account.effective_course_template).to be_nil
end
end
describe "#course_template_id" do
it "resets id of 0 to nil on root accounts" do
a = Account.new
a.course_template_id = 0
expect(a).to be_valid
expect(a.course_template_id).to be_nil
end
it "requires the course template to be in the same root account" do
a = Account.create!
a2 = Account.create!
c = a2.courses.create!(template: true)
a.course_template = c
expect(a).not_to be_valid
expect(a.errors.to_h.keys).to eq [:course_template_id]
end
it "requires the course template to actually be a template" do
a = Account.create!
c = a.courses.create!
a.course_template = c
expect(a).not_to be_valid
expect(a.errors.to_h.keys).to eq [:course_template_id]
end
it "allows a valid course template" do
a = Account.create!
c = a.courses.create!(template: true)
a.course_template = c
expect(a).to be_valid
end
end
describe "#dummy?" do
it "returns false for most accounts" do
act = Account.new(id: 1)
expect(act.dummy?).to be_falsey
end
it "is true for a 0-id account" do
act = Account.new(id: 0)
expect(act.dummy?).to be_truthy
end
it "determines the outcome of `unless_dummy`" do
act = Account.new(id: 0)
expect(act.unless_dummy).to be_nil
act.id = 1
expect(act.unless_dummy).to be(act)
end
end
describe "logging Restrict Quantitative Data (RQD) setting enable/disable" do
before do
# @account = Account.create!
account_model
@account.enable_feature!(:restrict_quantitative_data)
allow(InstStatsd::Statsd).to receive(:increment)
end
it "restrict_quantitative_data? helper returns false by default" do
expect(@account.restrict_quantitative_data?).to be false
end
it "increments enabled log when setting is turned on" do
@account.settings[:restrict_quantitative_data] = { locked: false, value: true }
@account.save!
expect(@account.restrict_quantitative_data?).to be true
expect(InstStatsd::Statsd).to have_received(:increment).with("account.settings.restrict_quantitative_data.enabled").once
end
it "increments disabled log when setting is turned off" do
@account.settings[:restrict_quantitative_data] = { locked: false, value: true }
@account.save!
expect(@account.restrict_quantitative_data?).to be true
@account.settings[:restrict_quantitative_data] = { locked: false, value: false }
@account.save!
expect(@account.restrict_quantitative_data?).to be false
expect(InstStatsd::Statsd).to have_received(:increment).with("account.settings.restrict_quantitative_data.enabled").once.ordered
expect(InstStatsd::Statsd).to have_received(:increment).with("account.settings.restrict_quantitative_data.disabled").once.ordered
end
it "doesn't increment either log when settings update but RQD setting is unchanged" do
expect(@account.restrict_student_future_view[:value]).to be false
@account.settings[:restrict_student_future_view] = { locked: false, value: true }
@account.save!
expect(@account.restrict_student_future_view[:value]).to be true
expect(InstStatsd::Statsd).not_to have_received(:increment).with("account.settings.restrict_quantitative_data.enabled")
expect(InstStatsd::Statsd).not_to have_received(:increment).with("account.settings.restrict_quantitative_data.disabled")
end
it "doesn't increment either counter when parent account setting is changed" do
@sub_account = @account.sub_accounts.create!
@sub_account.settings[:restrict_quantitative_data] = { locked: false, value: true }
@sub_account.save!
expect(@sub_account.restrict_quantitative_data?).to be true
expect(InstStatsd::Statsd).to have_received(:increment).with("account.settings.restrict_quantitative_data.enabled").once
@account.settings[:restrict_quantitative_data] = { locked: true, value: false }
@account.save!
# Ignores changes completely
expect(@sub_account.restrict_quantitative_data?).to be true
expect(InstStatsd::Statsd).not_to have_received(:increment).with("account.settings.restrict_quantitative_data.disabled")
end
end
describe "#enable_user_notes" do
let(:account) { account_model(enable_user_notes: true) }
context "when the deprecate_faculty_journal flag is enabled" do
before { Account.site_admin.enable_feature!(:deprecate_faculty_journal) }
it "returns false" do
expect(account.enable_user_notes).to be false
end
end
context "when the deprecate_faculty_journal flag is disabled" do
before { Account.site_admin.disable_feature!(:deprecate_faculty_journal) }
it "returns the value stored on the account model" do
expect(account.enable_user_notes).to be true
account.update_attribute(:enable_user_notes, false)
expect(account.enable_user_notes).to be false
end
end
end
describe ".having_user_notes_enabled" do
let!(:enabled_account) { account_model(enable_user_notes: true) }
before { account_model(enable_user_notes: false) }
context "when the deprecate_faculty_journal flag is disabled" do
before { Account.site_admin.disable_feature!(:deprecate_faculty_journal) }
it "only returns accounts having user notes enabled" do
expect(Account.having_user_notes_enabled).to match_array [enabled_account]
end
end
it "returns no accounts" do
expect(Account.having_user_notes_enabled).to be_empty
end
end
context "account grading standards" do
before do
account_model
end
def example_grading_standard(context)
gs = GradingStandard.new(context:, workflow_state: "active")
gs.data = [["A", 0.9], ["B", 0.8], ["C", -0.7]]
gs.save(validate: false)
gs
end
describe "#grading_standard_enabled" do
it "returns false by default" do
expect(@account.grading_standard_enabled?).to be false
end
it "returns true when grading_standard is set" do
@account.grading_standard = example_grading_standard(@account)
@account.save!
expect(@account.grading_standard_enabled?).to be true
end
it "returns true if a parent account has a grading_standard" do
@account.grading_standard_id = example_grading_standard(@account).id
@account.save
@sub_account = @account.sub_accounts.create!
expect(@sub_account.grading_standard_enabled?).to be true
end
it "returns false if no parent account has a grading standard" do
@sub_account1 = @account.sub_accounts.create!
@sub_account2 = @sub_account1.sub_accounts.create!
@sub_account3 = @sub_account2.sub_accounts.create!
expect(@sub_account3.grading_standard_enabled?).to be false
end
it "returns true if a deeply nested parent account has a grading_standard" do
@account.grading_standard = example_grading_standard(@account)
@account.save
@sub_account1 = @account.sub_accounts.create!
@sub_account2 = @sub_account1.sub_accounts.create!
@sub_account3 = @sub_account2.sub_accounts.create!
expect(@sub_account3.grading_standard_enabled?).to be true
end
end
describe "#default_grading_standard" do
it "returns nil by default" do
expect(@account.default_grading_standard).to be_nil
end
it "returns the grading_standard if set" do
@account.grading_standard = example_grading_standard(@account)
expect(@account.default_grading_standard).to eq @account.grading_standard
end
it "returns the parent account's grading_standard if set" do
@account.grading_standard = example_grading_standard(@account)
@account.save
@sub_account = @account.sub_accounts.create!
expect(@sub_account.default_grading_standard).to eq @account.grading_standard
end
it "returns nil if no parent account has a grading standard" do
@sub_account1 = @account.sub_accounts.create!
@sub_account2 = @sub_account1.sub_accounts.create!
@sub_account3 = @sub_account2.sub_accounts.create!
expect(@sub_account3.default_grading_standard).to be_nil
end
it "returns a deeply nested parent account's grading_standard if set" do
@account.grading_standard = example_grading_standard(@account)
@account.save
@sub_account1 = @account.sub_accounts.create!
@sub_account2 = @sub_account1.sub_accounts.create!
@sub_account3 = @sub_account2.sub_accounts.create!
expect(@sub_account3.default_grading_standard).to eq @account.grading_standard
end
it "returns correct parent's grading standard in deeply nested accounts" do
@account.grading_standard = example_grading_standard(@account)
@account.save
@sub_account1 = @account.sub_accounts.create!
@sub_account1.grading_standard = example_grading_standard(@sub_account1)
@sub_account1.save
@sub_account2 = @sub_account1.sub_accounts.create!
@sub_account3 = @sub_account2.sub_accounts.create!
expect(@sub_account3.default_grading_standard).to eq @sub_account1.grading_standard
end
end
end
end