# 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
  it_behaves_like "outcome import context examples"

  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?(: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, :manage_grading_schemes]

    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

  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 "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

  context "partitioned_sub_account_ids_recursive" do
    subject { Account.partitioned_sub_account_ids_recursive(account_ids) }

    let(:account_ids) { [root.id, sub_1.id, sub_2.id] }
    let(:sub_5) { account_model(parent_account: sub_3) }
    let(:sub_4) { account_model(parent_account: sub_1) }
    let(:sub_3) { account_model(parent_account: sub_1) }
    let(:sub_2) { account_model(parent_account: root) }
    let(:sub_1) { account_model(parent_account: root) }
    let(:root) { account_model }

    before do
      root
      sub_1
      sub_2
      sub_3
      sub_4
      sub_5
    end

    context "when given no account ids" do
      let(:account_ids) { [] }

      it "returns an empty hash" do
        expect(subject).to eq({})
      end
    end

    context "with ids across multiple shards" do
      specs_require_sharding

      let(:account) { account_model }
      let(:xaccount) { @shard2.activate { account_model } }
      let(:account_ids) { [account.id, xaccount.id] }

      it "errors" do
        expect { subject }.to raise_error(ArgumentError, "all parent_account_ids must be in the same shard")
      end
    end

    it "returns empty array for an account with no sub-accounts" do
      expect(subject[sub_2.id]).to eq([])
    end

    it "returns sub-account ids recursively for a given account" do
      expect(subject[root.id]).to match_array([sub_1.id, sub_2.id, sub_3.id, sub_4.id, sub_5.id])
    end

    it "returns sub-account ids recursively for a given sub-account" do
      expect(subject[sub_1.id]).to match_array([sub_3.id, sub_4.id, sub_5.id])
    end
  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

  describe "users_with_permission" do
    before :once do
      @account = Account.default.sub_accounts.create!
      @admin1 = account_admin_user(account: @account)
      @admin2 = account_admin_user_with_role_changes(account: @account,
                                                     role: Role.get_built_in_role("AccountMembership", root_account_id: Account.default),
                                                     role_changes: { view_feature_flags: true })
    end

    it "returns active users with the specified permission" do
      expect(@account.users_with_permission(:manage_storage_quotas)).to match_array [@admin1]
      expect(@account.users_with_permission(:view_feature_flags)).to match_array [@admin1, @admin2]
    end

    it "excludes inactive users" do
      @admin1.account_users.find_by(account_id: @account).update!(workflow_state: "deleted")
      expect(@account.users_with_permission(:manage_storage_quotas)).to be_empty
    end
  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

  describe "group and differentiation tag associations" do
    before(:once) do
      @account = Account.default
      @collaborative_category = GroupCategory.create!(context: @account, name: "Collab Category")
      @course = course_factory(account: @account)
      @non_collab_category = GroupCategory.create!(context: @course, name: "Tag Category", non_collaborative: true)

      @collaborative_group = Group.create!(context: @account, group_category: @collaborative_category, name: "Collab Group")
      @differentiation_tag = Group.create!(context: @course, group_category: @non_collab_category, name: "Tag")

      @deleted_collab_group = Group.create!(context: @account, group_category: @collaborative_category, name: "Deleted Collab")
      @deleted_collab_group.destroy

      @sub_account = @account.sub_accounts.create!
      @sub_course = course_factory(account: @sub_account)
      @sub_collab_group = Group.create!(context: @sub_account, group_category: @collaborative_category, name: "Sub Collab", root_account: @account)
      @sub_tag = Group.create!(context: @sub_course, group_category: @non_collab_category, name: "Sub Tag", root_account: @account)
    end

    context "collaborative groups" do
      it "filters group categories by collaborative flag" do
        expect(@account.group_categories).to contain_exactly(@collaborative_category)
        expect(@account.all_group_categories).to contain_exactly(@collaborative_category)
      end

      it "filters groups by collaborative flag" do
        expect(@account.groups).to contain_exactly(@collaborative_group, @deleted_collab_group)
        expect(@account.all_groups).to contain_exactly(@collaborative_group, @deleted_collab_group, @sub_collab_group)
      end
    end

    context "differentiation tags" do
      it "filters group categories by non-collaborative flag" do
        expect(@account.differentiation_tag_categories).to be_empty
        expect(@account.all_differentiation_tag_categories).to contain_exactly(@non_collab_category)
      end

      it "filters groups by non-collaborative flag" do
        expect(@account.differentiation_tags).to be_empty
        expect(@account.all_differentiation_tags).to contain_exactly(@differentiation_tag, @sub_tag)
      end
    end

    describe "memberships" do
      before(:once) do
        @student2 = user_factory
        @student = user_factory
        @collaborative_group.add_user(@student)
        @differentiation_tag.add_user(@student2)
      end

      it "accesses memberships through all_groups/all_differentiation_tags" do
        expect(@account.all_group_memberships.map(&:user_id)).to contain_exactly(@student.id)
        expect(@account.all_differentiation_tag_memberships.map(&:user_id)).to contain_exactly(@student2.id)
      end
    end
  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_edit to determine question bank tab visibility" do
      account_admin_user_with_role_changes(account: @account, role_changes: { manage_assignments_edit: 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

    it "works if the account model has changes" do
      acct = Account.default
      acct.name = "changed"
      expect(acct.manually_created_courses_account).to be_present
    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 "#update_lti_context_controls" do
    let(:account) { account_model(parent_account:) }
    let(:parent_account) { account_model }
    let(:new_parent_account) { account_model }

    before do
      allow(Lti::ContextControl).to receive(:update_paths_for_reparent)
    end

    describe "when account parent changes" do
      it "updates paths for Controls" do
        account.update!(parent_account: new_parent_account)
        expect(Lti::ContextControl).to have_received(:update_paths_for_reparent).with(account, parent_account.id, new_parent_account.id)
      end
    end

    describe "with new account" do
      let(:new_account) { account_model }

      it "does not update paths" do
        new_account
        expect(Lti::ContextControl).not_to have_received(:update_paths_for_reparent)
      end
    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!

      @account.reload
      @sub1.reload
      @sub2.reload
      @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!

      @account.reload
      @sub1.reload
      @sub2.reload
      @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.reload.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.reload.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.reload.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.reload.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

  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

    describe "account_chain_ids_for_multiple_accounts" do
      let(:account1) { Account.default.sub_accounts.create! }
      let(:account2) { Account.default.sub_accounts.create! }
      let(:account3) { account1.sub_accounts.create! }

      before do
        account1
        account2
      end

      it "is correct" do
        expect(Account.account_chain_ids_for_multiple_accounts([account1.id, account2.id, account3.id])).to eq(
          {
            account1.id => [account1.id, Account.default.id],
            account2.id => [account2.id, Account.default.id],
            account3.id => [account3.id, account1.id, Account.default.id],
          }
        )
      end

      it "can handle account chain changes" do
        account3.update_attribute(:parent_account, account2)
        expect(Account.account_chain_ids_for_multiple_accounts([account1.id, account2.id, account3.id])).to eq(
          {
            account1.id => [account1.id, Account.default.id],
            account2.id => [account2.id, Account.default.id],
            account3.id => [account3.id, account2.id, Account.default.id],
          }
        )
      end

      it "can handle root accounts" do
        expect(Account.account_chain_ids_for_multiple_accounts([Account.default.id, account1.id])).to eq(
          {
            Account.default.id => [Account.default.id],
            account1.id => [account1.id, Account.default.id],
          }
        )
      end

      it "can handle lots of accounts" do
        accounts = Array.new(100) { Account.default.sub_accounts.create! }
        expect(Account.account_chain_ids_for_multiple_accounts(accounts.map(&:id))).to eq(
          accounts.each_with_object({}) do |account, hash|
            hash[account.id] = [account.id, Account.default.id]
          end
        )
      end
    end
  end

  describe "#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

  describe "#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
      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.attribute_names).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.attribute_names).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(:distributed_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(:distributed_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(:distributed_increment).with("account.settings.restrict_quantitative_data.enabled").once.ordered
      expect(InstStatsd::Statsd).to have_received(:distributed_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(:distributed_increment).with("account.settings.restrict_quantitative_data.enabled")
      expect(InstStatsd::Statsd).not_to have_received(:distributed_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(:distributed_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(:distributed_increment).with("account.settings.restrict_quantitative_data.disabled")
    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

  describe "available ip filters" do
    before do
      @account1 = Account.create!(name: "Account 1")
      @course1 = @account1.courses.create!(name: "Test Course 1")

      @account2 = Account.create!(name: "Account 2", consortium_parent_account: @account1)
      @course2 = @account2.courses.create!(name: "Test Course 2")

      @account3 = @account2.sub_accounts.create!(name: "Account 3")
      @course3 = @account3.courses.create!(name: "Test Course 3")

      @account4 = @account3.sub_accounts.create!(name: "Account 4")
      @course4 = @account4.courses.create!(name: "Test Course 4")
    end

    context "when filters exist in the account chain" do
      before do
        @account1.settings[:ip_filters] = {
          "filter1" => "192.168.1.0",
          "filter2" => "10.0.0.0"
        }
        @account1.save!

        @account3.settings[:ip_filters] = {
          "filter3" => "172.16.0.0"
        }
        @account3.save!
      end

      it "gets all the filters up the chain" do
        expected_filters = [
          { name: "filter1", account: @account1.name, filter: "192.168.1.0" },
          { name: "filter2", account: @account1.name, filter: "10.0.0.0" },
          { name: "filter3", account: @account3.name, filter: "172.16.0.0" }
        ]

        expect(Account.default.available_ip_filters(@course4.uuid)).to match_array(expected_filters)
      end
    end

    context "when no filters exist in the account chain" do
      it "returns an empty array" do
        expect(Account.default.available_ip_filters(@course4.uuid)).to be_empty
      end
    end

    context "when settings is nil" do
      before do
        allow(@account1).to receive(:settings).and_return(nil)
      end

      it "handles nil settings gracefully" do
        expect { Account.default.available_ip_filters(@course4.uuid) }.not_to raise_error
        expect(Account.default.available_ip_filters(@course4.uuid)).to be_empty
      end
    end

    context "when ip_filters is nil" do
      before do
        @account1.settings[:ip_filters] = nil
        @account1.save!
      end

      it "handles nil ip_filters gracefully" do
        expect { Account.default.available_ip_filters(@course4.uuid) }.not_to raise_error
        expect(Account.default.available_ip_filters(@course4.uuid)).to be_empty
      end
    end

    context "when there are duplicate filter names in different accounts" do
      before do
        @account1.settings[:ip_filters] = { "common_filter" => "192.168.1.0" }
        @account1.save!
        @account3.settings[:ip_filters] = { "common_filter" => "10.0.0.0" }
        @account3.save!
      end

      it "includes both filters" do
        expected_filters = [
          { name: "common_filter", account: @account1.name, filter: "192.168.1.0" },
          { name: "common_filter", account: @account3.name, filter: "10.0.0.0" }
        ]
        expect(Account.default.available_ip_filters(@course4.uuid)).to match_array(expected_filters)
      end
    end

    context "when accessing filters from different levels" do
      before do
        @account1.settings[:ip_filters] = { "filter1" => "192.168.1.0" }
        @account1.save!
        @account2.settings[:ip_filters] = { "filter2" => "172.16.0.0" }
        @account2.save!
        @account3.settings[:ip_filters] = { "filter3" => "10.0.0.0" }
        @account3.save!
      end

      it "returns correct filters for each account level" do
        expect(Account.default.available_ip_filters(@course4.uuid).size).to eq(3)
        expect(Account.default.available_ip_filters(@course3.uuid).size).to eq(3)
        expect(Account.default.available_ip_filters(@course2.uuid).size).to eq(2)
        expect(Account.default.available_ip_filters(@course1.uuid).size).to eq(1)
      end
    end
  end

  describe "#recompute_assignments_using_account_default" do
    let_once(:data1) { [["A", 0.94], ["F", 0]] }
    let_once(:data2) { [["A", 0.5], ["F", 0]] }

    before do
      Account.site_admin.enable_feature!(:archived_grading_schemes)
      Account.site_admin.enable_feature!(:default_account_grading_scheme)
      @root_account = Account.default
      sub_account = @root_account.sub_accounts.create!
      sub_sub_account = sub_account.sub_accounts.create!
      admin = account_admin_user(account: @root_account)
      user_session(admin)
      grading_standard = GradingStandard.create!(context: @root_account, workflow_state: "active", data: data1, title: "current grading scheme")
      @new_grading_standard = GradingStandard.create!(context: @root_account, workflow_state: "active", data: data2, title: "new grading scheme")
      @root_account.update!(grading_standard_id: grading_standard.id)
      course_inheriting_from_root = course_factory(account: @root_account)
      course_not_inheriting_from_root = course_factory(account: @root_account)
      course_not_inheriting_from_root.update!(grading_standard_id: grading_standard.id)
      course_inheriting_from_sub = course_factory(account: sub_account)
      course_inheriting_from_sub_sub = course_factory(account: sub_sub_account)
      student1 = course_inheriting_from_root.enroll_user(user_factory(active_user: true), "StudentEnrollment", enrollment_state: "active").user
      student2 = course_not_inheriting_from_root.enroll_user(user_factory(active_user: true), "StudentEnrollment", enrollment_state: "active").user
      student3 = course_inheriting_from_sub.enroll_user(user_factory(active_user: true), "StudentEnrollment", enrollment_state: "active").user
      student4 = course_inheriting_from_sub_sub.enroll_user(user_factory(active_user: true), "StudentEnrollment", enrollment_state: "active").user
      assignment_root = course_inheriting_from_root.assignments.create!(title: "hi", grading_standard_id: nil, points_possible: 10, grading_type: "letter_grade")
      assignment_not_inheriting = course_not_inheriting_from_root.assignments.create!(title: "hello", grading_standard_id: nil, points_possible: 10, grading_type: "letter_grade")
      assignment_sub = course_inheriting_from_sub.assignments.create!(title: "hi2", grading_standard_id: nil, points_possible: 10, grading_type: "letter_grade")
      assignment_sub_sub = course_inheriting_from_sub_sub.assignments.create!(title: "hi3", grading_standard_id: nil, points_possible: 10, grading_type: "letter_grade")
      @submission_root = assignment_root.grade_student(student1, score: 6, grader: admin).first
      @submission_not_inheriting = assignment_not_inheriting.grade_student(student2, score: 6, grader: admin).first
      @submission_sub = assignment_sub.grade_student(student3, score: 6, grader: admin).first
      @submission_sub_sub = assignment_sub_sub.grade_student(student4, score: 6, grader: admin).first
    end

    it "updates submission grades in account inheriting courses/assignments and all sub account courses/assignments" do
      @root_account.recompute_assignments_using_account_default(@new_grading_standard)

      expect(@submission_root.reload.grade).to eq "A"
      expect(@submission_not_inheriting.reload.grade).to eq "F"
      expect(@submission_sub.reload.grade).to eq "A"
      expect(@submission_sub_sub.reload.grade).to eq "A"
    end

    it "updates the most recent submission version in all inheriting account and sub account courses" do
      @root_account.recompute_assignments_using_account_default(@new_grading_standard)

      expect(@submission_root.reload.versions.first.model.grade).to eq "A"
      expect(@submission_not_inheriting.reload.versions.first.model.grade).to eq "F"
      expect(@submission_sub.reload.versions.first.model.grade).to eq "A"
      expect(@submission_sub_sub.reload.versions.first.model.grade).to eq "A"
    end
  end

  describe "#recaptcha_key" do
    let(:root_account) { Account.create! }

    before do
      allow(root_account).to receive_messages(root_account?: true, self_registration_captcha?: true)
    end

    it "returns the recaptcha_client_key when root_account? and self_registration_captcha? are true" do
      allow(DynamicSettings).to receive(:find).with(tree: "private").and_return(
        instance_double(DynamicSettings::PrefixProxy, :[] => "test_key")
      )
      expect(root_account.recaptcha_key).to eq("test_key")
    end

    it "returns nil if not a root account" do
      allow(root_account).to receive(:root_account?).and_return(false)
      expect(root_account.recaptcha_key).to be_nil
    end

    it "returns nil if self_registration_captcha? is false" do
      allow(root_account).to receive(:self_registration_captcha?).and_return(false)
      expect(root_account.recaptcha_key).to be_nil
    end

    it "returns nil if recaptcha_client_key is not present in DynamicSettings" do
      allow(DynamicSettings).to receive(:find).with(tree: "private").and_return(
        instance_double(DynamicSettings::PrefixProxy, :[] => nil)
      )
      expect(root_account.recaptcha_key).to be_nil
    end

    it "returns nil if not a root account even if self_registration_captcha? is true" do
      allow(root_account).to receive_messages(root_account?: false, self_registration_captcha?: true)
      expect(root_account.recaptcha_key).to be_nil
    end

    it "returns nil if both root_account? and self_registration_captcha? are false" do
      allow(root_account).to receive_messages(root_account?: false, self_registration_captcha?: false)
      expect(root_account.recaptcha_key).to be_nil
    end
  end

  describe "#get_role_by_name" do
    let(:account) { Account.default }
    let(:role_name) { "Course Admin" }
    let!(:active_role) do
      account.roles.create(name: role_name, base_role_type: "TeacherEnrollment", workflow_state: "active")
    end
    let!(:inactive_role) do
      account.roles.create(name: role_name, base_role_type: "TaEnrollment", workflow_state: "inactive")
    end

    it "returns the active role if both active and inactive roles are present" do
      result = account.get_role_by_name(role_name)
      expect(result).to eq(active_role)
    end

    it "returns the inactive role if no active role is present" do
      active_role.destroy
      result = account.get_role_by_name(role_name)
      expect(result).to eq(inactive_role)
    end

    it "returns nil if no roles are present" do
      active_role.destroy
      inactive_role.destroy
      result = account.get_role_by_name(role_name)
      expect(result).to be_nil
    end
  end

  describe "horizon account" do
    before(:once) do
      @account = Account.default
    end

    describe "#horizon_account" do
      it "returns false by default" do
        expect(@account.horizon_account[:value]).to be false
      end

      it "returns the enabled inherited value if set on parent account" do
        root_account = Account.create!
        root_account.settings[:horizon_account] = { value: true }
        root_account.save!
        subaccount = root_account.sub_accounts.create!
        expect(subaccount.horizon_account[:value]).to be true
        expect(subaccount.horizon_account[:inherited]).to be true
      end

      it "returns the disabled inherited value if set on parent account" do
        root_account = Account.create!
        root_account.settings[:horizon_account] = { value: false }
        root_account.save!
        subaccount = root_account.sub_accounts.create!
        expect(subaccount.horizon_account[:value]).to be false
        expect(subaccount.horizon_account[:inherited]).to be true
      end
    end
  end

  describe "allow_assign_to_differentiation_tags?" do
    before :once do
      @account = Account.default
      @account.settings[:allow_assign_to_differentiation_tags] = { value: true }
      @account.save!
    end

    it "returns true if the setting is enabled" do
      expect(@account.allow_assign_to_differentiation_tags?).to be true
    end

    it "returns false if the setting is disabled" do
      @account.settings[:allow_assign_to_differentiation_tags] = { value: false }
      @account.save!
      expect(@account.allow_assign_to_differentiation_tags?).to be false
    end
  end

  context "number separator validation" do
    let(:account) { Account.new(name: "test account") }

    it "is valid when separators are not set" do
      expect(account).to be_valid
    end

    it "is valid when both separators are different" do
      account.settings[:decimal_separator] = { value: "." }
      account.settings[:thousand_separator] = { value: "," }
      expect(account).to be_valid
    end

    it "is invalid when both separators are the same" do
      account.settings[:decimal_separator] = { value: "," }
      account.settings[:thousand_separator] = { value: "," }
      expect(account).not_to be_valid
    end

    it "is invalid if decimal_separator is present but thousand_separator is blank" do
      account.settings[:decimal_separator] = { value: "." }
      account.settings[:thousand_separator] = { value: "" }
      expect(account).not_to be_valid
    end

    it "is invalid if thousand_separator is present but decimal_separator is blank" do
      account.settings[:thousand_separator] = { value: "," }
      account.settings[:decimal_separator] = { value: "" }
      expect(account).not_to be_valid
    end
  end

  describe "#restricted_file_access_for_user?" do
    let(:root_account) { Account.create!(root_account: nil) }
    let(:sub_account) { Account.create!(root_account:) }
    let!(:user)         { User.create! }

    context "when the feature flag is disabled" do
      before do
        root_account.disable_feature!(:restrict_student_access)
      end

      it "always returns false, even if the user has enrollments" do
        course = course_factory(account: sub_account)
        Enrollment.create!(user:, course:, type: "StudentEnrollment")
        expect(sub_account.restricted_file_access_for_user?(user)).to be_falsey
      end
    end

    context "when the feature flag is enabled" do
      before do
        root_account.enable_feature!(:restrict_student_access)
      end

      context "and the user has a student enrollment in that account" do
        before do
          course = course_factory(account: sub_account)
          Enrollment.create!(user:, course:, type: "StudentEnrollment")
        end

        it "returns true" do
          expect(sub_account.restricted_file_access_for_user?(user)).to be_truthy
        end
      end

      context "and the user does NOT have a student enrollment in that account" do
        it "returns false" do
          expect(sub_account.restricted_file_access_for_user?(user)).to be_falsey
        end
      end
    end
  end

  describe "denormalize_horizon_account_if_changed" do
    let(:root_account) { Account.create! }
    let(:sub_account) { Account.create!(parent_account: root_account) }

    it "does nothing when settings haven't changed" do
      expect(root_account).not_to receive(:save!)
      sub_account.denormalize_horizon_account_if_changed
    end

    it "does nothing when horizon_account settings haven't changed" do
      sub_account.settings = { horizon_account: { value: true, locked: true } }
      sub_account.save!

      sub_account.settings[:unrelated_setting] = "updated"

      expect(root_account).not_to receive(:save!)
      sub_account.denormalize_horizon_account_if_changed
    end

    it "adds account id to horizon_account_ids when enabled" do
      sub_account.horizon_account = true
      sub_account.save!

      expect(root_account.reload.settings[:horizon_account_ids]).to include(sub_account.id)
      expect(sub_account.reload.settings[:horizon_account][:locked]).to be true
    end

    it "removes account id from horizon_account_ids when disabled" do
      sub_account.horizon_account = true
      sub_account.save!

      expect(root_account.reload.settings[:horizon_account_ids]).to include(sub_account.id)

      sub_account.horizon_account = false
      sub_account.save!

      expect(root_account.reload.settings[:horizon_account_ids]).not_to include(sub_account.id)
      expect(sub_account.reload.settings[:horizon_account][:locked]).to be false
    end

    it "sets horizon_course to false on associated courses when disabled" do
      sub_account.horizon_account = true
      sub_account.save!

      course = Course.create!(account: sub_account)
      course.update!(horizon_course: true)

      sub_account.horizon_account = false
      sub_account.save!

      expect(course.reload.horizon_course).to be false
    end

    it "doesn't save root_account if this is the root account" do
      root_account.horizon_account = true

      expect(root_account).not_to receive(:save!)
      root_account.denormalize_horizon_account_if_changed
    end
  end

  describe "#enqueue_a11y_scan_if_enabled" do
    let(:account) { account_model }

    context "when the account is not a root account" do
      before do
        allow(account).to receive(:root_account?).and_return(false)
      end

      it "does not enqueue the scan" do
        expect(Accessibility::RootAccountScannerService).not_to receive(:call)

        account.update!(settings: { enable_content_a11y_checker: true })
      end
    end

    context "when settings have not changed" do
      it "does not enqueue the scan" do
        expect(Accessibility::RootAccountScannerService).not_to receive(:call)

        account.update!(name: "Something New")
      end
    end

    context "when the content accessibility checker is not enabled" do
      it "does not enqueue the scan" do
        expect(Accessibility::RootAccountScannerService).not_to receive(:call)

        account.update!(settings: { enable_content_a11y_checker: false })
      end
    end

    context "when old settings already enabled content accessibility checker" do
      before do
        account.update!(settings: { enable_content_a11y_checker: true })
      end

      it "does not enqueue the scan" do
        expect(Accessibility::RootAccountScannerService).not_to receive(:call)

        account.update!(settings: { enable_content_a11y_checker: true })
      end
    end

    context "when enable_content_a11y_checker is changed from false to true" do
      it "calls Accessibility::RootAccountScannerService" do
        expect(Accessibility::RootAccountScannerService).to receive(:call).with(account:)

        account.update!(settings: { enable_content_a11y_checker: true })
      end
    end
  end
end
