# frozen_string_literal: true

#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require_relative "../api_spec_helper"

describe "Roles API", type: :request do
  before :once do
    @account = Account.default
    account_admin_user(account: @account)
    user_with_pseudonym(user: @admin)
  end

  describe "Roles CRUD" do
    before :once do
      @role_name = "NewRole"
      @permission = "read_reports"
      @initial_count = @account.role_overrides.size
    end

    def api_call_with_settings(settings = {})
      settings.delete(:admin)
      account = settings.delete(:account) || @admin.account
      role = settings.delete(:role) || @role_name
      base_role_type = settings.delete(:base_role_type)

      permission = settings.delete(:permission) || @permission

      parameters = { role:, permissions: { permission => settings } }
      parameters[:base_role_type] = base_role_type if base_role_type.present?

      json = api_call(:post,
                      "/api/v1/accounts/#{account.id}/roles",
                      { controller: "role_overrides", action: "add_role", format: "json", account_id: account.id.to_s },
                      parameters)
      @role = Role.find_by(id: json["id"])
      json
    end

    describe "add_role" do
      it "includes base_role_type_label" do
        json = api_call_with_settings(base_role_type: "StudentEnrollment")
        expect(json).to include("base_role_type_label" => "Student")
      end

      it "adds the role to the account" do
        api_call_with_settings(explicit: "1", enabled: "1")
        @account.reload
        expect(@account.available_account_roles).to include(@role)
      end
    end

    describe "index" do
      it "indexes roles" do
        api_call_with_settings(explicit: "1", enabled: "1")
        json = api_call(:get,
                        "/api/v1/accounts/#{@account.id}/roles",
                        { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param })

        expect(json.pluck("role").sort).to eq (["NewRole"] + Role.visible_built_in_roles(root_account_id: @account.id).map(&:name)).sort
        expect(json.find { |role| role["role"] == "StudentEnrollment" }["workflow_state"]).to eq "built_in"
        expect(json.find { |role| role["role"] == "NewRole" }["workflow_state"]).to eq "active"
      end

      it "includes inherited roles if requested" do
        role = @account.roles.new(name: "inherited role")
        role.base_role_type = "StudentEnrollment"
        role.save!

        sub_account = @account.sub_accounts.create!
        account_admin_user(account: sub_account, active_user: true)

        json = api_call(:get,
                        "/api/v1/accounts/#{sub_account.id}/roles?show_inherited=1",
                        { controller: "role_overrides",
                          action: "api_index",
                          format: "json",
                          account_id: sub_account.id.to_param,
                          show_inherited: "1" })

        expect(json.pluck("id")).to match_array([role.id] + Role.visible_built_in_roles(root_account_id: @account.id).map(&:id))
        expect(json.detect { |r| r["id"] == role.id }["account"]["id"]).to eq @account.id

        json2 = api_call(:get,
                         "/api/v1/accounts/#{sub_account.id}/roles",
                         { controller: "role_overrides",
                           action: "api_index",
                           format: "json",
                           account_id: sub_account.id.to_param })
        expect(json2.pluck("id")).to match_array(Role.visible_built_in_roles(root_account_id: @account.id).map(&:id))
      end

      it "paginates" do
        api_call_with_settings(explicit: "1", enabled: "1")
        json = api_call(:get,
                        "/api/v1/accounts/#{@account.id}/roles?per_page=5",
                        { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param, per_page: "5" })
        expect(response.headers["Link"]).to match(%r{<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=2.*>; rel="next",<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=1.*>; rel="first",<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=2.*>; rel="last"})
        expect(json.size).to eq 5
        json += api_call(:get,
                         "/api/v1/accounts/#{@account.id}/roles?per_page=5&page=2",
                         { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param, per_page: "5", page: "2" })
        expect(response.headers["Link"]).to match(%r{<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=1.*>; rel="prev",<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=1.*>; rel="first",<http://www.example.com/api/v1/accounts/#{@account.id}/roles\?.*page=2.*>; rel="last"})
        expect(json.size).to eq 7
        expect(json.pluck("role").sort).to eq (["NewRole"] + Role.visible_built_in_roles(root_account_id: @account.id).map(&:name)).sort
      end

      context "with state parameter" do
        before :once do
          role = @account.roles.create name: "inactive_role"
          role.base_role_type = "StudentEnrollment"
          role.workflow_state = "inactive"
          role.save!
        end

        it "lists inactive roles" do
          json = api_call(:get,
                          "/api/v1/accounts/#{@account.id}/roles?state[]=inactive",
                          { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param, state: %w[inactive] })
          expect(json.size).to eq 1
          expect(json[0]["role"]).to eq "inactive_role"
        end

        it "omits inactive roles if unspecified" do
          json = api_call(:get,
                          "/api/v1/accounts/#{@account.id}/roles",
                          { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param })
          expect(json.size).to eq 6
          expect(json.pluck("role")).to be_exclude "inactive_role"
        end

        it "accepts multiple states" do
          json = api_call(:get,
                          "/api/v1/accounts/#{@account.id}/roles?state[]=inactive&state[]=active",
                          { controller: "role_overrides", action: "api_index", format: "json", account_id: @account.id.to_param, state: %w[inactive active] })
          expect(json.size).to eq 7
          expect(json.pluck("role")).to include "inactive_role"
        end
      end
    end

    describe "show" do
      it "shows a built-in role" do
        json = api_call(:get,
                        "/api/v1/accounts/#{@account.id}/roles/#{admin_role.id}",
                        { controller: "role_overrides", action: "show", format: "json", account_id: @account.id.to_param, id: admin_role.id.to_param })
        expect(json["role"]).to eq "AccountAdmin"
        expect(json["base_role_type"]).to eq "AccountMembership"
        expect(json["workflow_state"]).to eq "built_in"
      end

      it "shows a custom role" do
        role = @account.roles.create name: "Assistant Grader"
        role.base_role_type = "TaEnrollment"
        role.workflow_state = "inactive"
        role.save!
        json = api_call(:get,
                        "/api/v1/accounts/#{@account.id}/roles/#{role.id}",
                        { controller: "role_overrides", action: "show", format: "json", account_id: @account.id.to_param, id: role.id.to_param })
        expect(json["role"]).to eq "Assistant Grader"
        expect(json["base_role_type"]).to eq "TaEnrollment"
        expect(json["workflow_state"]).to eq "inactive"
      end

      it "does not show a deleted role" do
        role = @account.roles.create name: "Deleted"
        role.base_role_type = "AccountMembership"
        role.workflow_state = "deleted"
        role.save!
        api_call(:get,
                 "/api/v1/accounts/#{@account.id}/roles/#{role.id}",
                 { controller: "role_overrides", action: "show", format: "json", account_id: @account.id.to_param, id: role.id.to_param },
                 {},
                 {},
                 { expected_status: 404 })
      end
    end

    it "adds a course-level role to the account" do
      base_role_type = "TeacherEnrollment"

      expect(@account.roles).to be_empty
      json = api_call_with_settings(base_role_type:, explicit: "1", enabled: "1")
      @account.reload

      expect(@account.available_account_roles.map(&:name)).to_not include(@role_name)
      expect(@account.roles.count).to eq 1
      new_role = @account.roles.first
      expect(new_role.name).to eq @role_name
      expect(new_role.base_role_type).to eq base_role_type

      expect(json["base_role_type"]).to eq base_role_type
    end

    it "accepts the usual forms of booleans in addition to 0 / 1" do
      json = api_call(:post,
                      "/api/v1/accounts/#{@account.id}/roles",
                      { controller: "role_overrides", action: "add_role", format: "json", account_id: @account.id.to_s },
                      { "role" => "WeirdStudent",
                        "base_role_type" => "StudentEnrollment",
                        "permissions" => { "read_forum" => { "enabled" => "true", "explicit" => "true" },
                                           "moderate_forum" => { "explicit" => true, "enabled" => false },
                                           "post_to_forum" => { "explicit" => "false" },
                                           "send_messages" => { "explicit" => "on", "locked" => "yes", "enabled" => "off" } } })
      @account.reload

      role = Role.find_by(id: json["id"])
      overrides = @account.role_overrides.where(role_id: role.id).index_by(&:permission)
      expect(overrides["read_forum"].enabled).to be_truthy
      expect(overrides["read_forum"].locked).to be_falsey
      expect(overrides["moderate_forum"].enabled).to be_falsey
      expect(overrides["moderate_forum"].locked).to be_falsey
      expect(overrides["send_messages"].enabled).to be_falsey
      expect(overrides["send_messages"].locked).to be_truthy
      expect(overrides["post_to_forum"]).to be_nil
    end

    context "when there are enrollments using a course-level role" do
      before :once do
        @role = custom_teacher_role(@role_name, account: @account)
        course1 = Course.create!(name: "blah", account: @account)
        user1 = user_factory

        enrollment1 = course1.enroll_user(user1, "TeacherEnrollment", role: @role)
        enrollment1.invite
        enrollment1.accept
        enrollment1.save!
        @user = @admin
      end

      it "deactivates a course-level role" do
        json = api_call(:delete,
                        "/api/v1/accounts/#{@account.id}/roles/#{@role.id}",
                        { controller: "role_overrides", action: "remove_role", format: "json", account_id: @account.id.to_param, id: @role.id },
                        {})

        @role.reload
        expect(@role.workflow_state).to eq "inactive"
        expect(json["workflow_state"]).to eq "inactive"
      end

      it "is not able to deactivate a role from a sub-account" do
        sub_account = @account.sub_accounts.create!

        api_call(:delete,
                 "/api/v1/accounts/#{sub_account.id}/roles/#{@role.id}",
                 { controller: "role_overrides", action: "remove_role", format: "json", account_id: sub_account.id.to_param, id: @role.id },
                 {},
                 {},
                 { expected_status: 404 })

        @role.reload
        expect(@role.workflow_state).to eq "active"
      end

      it "reactivates an inactive role" do
        @role.update_attribute(:workflow_state, "inactive")

        json = api_call(:post,
                        "/api/v1/accounts/#{@account.id}/roles/#{@role.id}/activate",
                        { controller: "role_overrides", action: "activate_role", format: "json", account_id: @account.id.to_param, id: @role.id },
                        {})

        @account.reload
        @role.reload
        expect(@role.workflow_state).to eq "active"
        expect(json["workflow_state"]).to eq "active"
      end

      it "does not recycle a deleted role" do
        @role.destroy
        expect(@account.roles.active.map(&:name)).to_not include @role_name

        json = api_call(:post,
                        "/api/v1/accounts/#{@account.id}/roles",
                        { controller: "role_overrides",
                          action: "add_role",
                          format: "json",
                          account_id: @account.id.to_param },
                        { role: @role_name, base_role_type: "TeacherEnrollment" })

        new_role = Role.where(id: json["id"]).first
        expect(@role.id).to_not eq new_role.id
      end
    end

    it "requires a role" do
      raw_api_call(:post,
                   "/api/v1/accounts/#{@admin.account.id}/roles",
                   { controller: "role_overrides", action: "add_role", format: "json", account_id: @admin.account.id.to_s },
                   { permissions: { @permission => { explicit: "1", enabled: "1" } } })
      assert_status(400)
      expect(JSON.parse(response.body)).to eq({ "message" => "missing required parameter 'role'" })
    end

    it "fails for course role without a valid base role type" do
      raw_api_call(:post,
                   "/api/v1/accounts/#{@admin.account.id}/roles",
                   { controller: "role_overrides", action: "add_role", format: "json", account_id: @admin.account.id.to_s },
                   { role: @role_name, base_role_type: "notagoodbaserole" })
      assert_status(400)
      expect(JSON.parse(response.body)["errors"]["base_role_type"].first["message"]).to eq("is invalid")
    end

    it "fails for a course role with a reserved name" do
      raw_api_call(:post,
                   "/api/v1/accounts/#{@admin.account.id}/roles",
                   { controller: "role_overrides", action: "add_role", format: "json", account_id: @admin.account.id.to_s },
                   { role: "student", base_role_type: "StudentEnrollment" })
      assert_status(400)
      expect(JSON.parse(response.body)["errors"]["name"].first["message"]).to eq("is reserved")
    end

    it "does not create an override for course role for account-only permissions" do
      api_call_with_settings(
        permission: "manage_master_courses",
        base_role_type: "TeacherEnrollment",
        explicit: "1",
        enabled: "1"
      )
      expect(@account.role_overrides.reload.size).to eq @initial_count
    end

    it "does not create an override if enabled is nil and locked is not 1" do
      api_call_with_settings(explicit: "1", locked: "0")
      expect(@account.role_overrides.reload.size).to eq @initial_count
    end

    it "does not create an override if explicit is not 1 and locked is not 1" do
      api_call_with_settings(explicit: "0", enabled: "1", locked: "0")
      expect(@account.role_overrides.reload.size).to eq @initial_count
    end

    it "creates the override if explicit is 1 and enabled has a value" do
      api_call_with_settings(explicit: "1", enabled: "0")
      expect(@account.role_overrides.reload.size).to eq @initial_count + 1
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(override.enabled).to be_falsey
    end

    it "creates an override for course-level roles" do
      api_call_with_settings(base_role_type: "TeacherEnrollment", explicit: "1", enabled: "0")
      expect(@account.role_overrides.reload.size).to eq @initial_count + 1
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(override.enabled).to be_falsey
    end

    it "creates the override if enabled is nil but locked is 1" do
      api_call_with_settings(locked: "1")
      expect(@account.role_overrides.reload.size).to eq @initial_count + 1
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(override.locked).to be_truthy
    end

    it "sets applies to descendents" do
      json = api_call_with_settings(enabled: "1", explicit: "1", applies_to_descendants: "0")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(json["permissions"][@permission]["applies_to_self"]).to be true
      expect(json["permissions"][@permission]["applies_to_descendants"]).to be false
      expect(override.applies_to_self).to be true
      expect(override.applies_to_descendants).to be false
    end

    it "sets applies to self" do
      json = api_call_with_settings(enabled: "1", explicit: "1", applies_to_self: "0")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(json["permissions"][@permission]["applies_to_self"]).to be false
      expect(json["permissions"][@permission]["applies_to_descendants"]).to be true
      expect(override.applies_to_self).to be false
      expect(override.applies_to_descendants).to be true
    end

    it "requires at least applies to self or applies to descendents" do
      options = { controller: "role_overrides", action: "add_role", format: "json", account_id: @account.id.to_s }
      settings = { locked: "1", applies_to_descendants: "0", applies_to_self: "0" }
      parameters = { role: @role_name, permissions: { @permission => settings } }

      response = raw_api_call(:post, "/api/v1/accounts/#{@account.id}/roles", options, parameters)
      expect(response).to eq 400
    end

    it "cascades applies to descendents" do
      sub = @account.sub_accounts.create!
      json = api_call_with_settings(enabled: "1", explicit: "1", applies_to_descendants: "1", applies_to_self: "0")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(json["permissions"][@permission]["applies_to_self"]).to be false
      expect(json["permissions"][@permission]["applies_to_descendants"]).to be true
      expect(override.applies_to_self).to be false
      expect(override.applies_to_descendants).to be true

      json = api_call(:get,
                      "/api/v1/accounts/#{sub.id}/roles",
                      { controller: "role_overrides",
                        action: "api_index",
                        format: "json",
                        account_id: sub.id.to_s,
                        show_inherited: "1" })
      perm = json.find { |role| role["role"] == @role.name }["permissions"][@permission]
      expect(perm["applies_to_self"]).to be true
      expect(perm["applies_to_descendants"]).to be true
    end

    it "does not cascade if applies to descendents is false" do
      sub = @account.sub_accounts.create!
      json = api_call_with_settings(enabled: "1", explicit: "1", applies_to_descendants: "0", applies_to_self: "1")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(json["permissions"][@permission]["applies_to_self"]).to be true
      expect(json["permissions"][@permission]["applies_to_descendants"]).to be false
      expect(override.applies_to_self).to be true
      expect(override.applies_to_descendants).to be false

      json = api_call(:get,
                      "/api/v1/accounts/#{sub.id}/roles",
                      { controller: "role_overrides",
                        action: "api_index",
                        format: "json",
                        account_id: sub.id.to_s,
                        show_inherited: "1" })
      perm = json.find { |role| role["role"] == @role.name }["permissions"][@permission]
      expect(perm["enabled"]).to be false
    end

    it "only sets the parts that are specified" do
      api_call_with_settings(explicit: "1", enabled: "0")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(override.enabled).to be false
      expect(override.locked).to be false

      override.destroy
      r = @account.roles.first
      r.workflow_state = "deleted"
      r.save!

      api_call_with_settings(locked: "1")
      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
      expect(override.enabled).to be true
      expect(override.locked).to be true
    end

    it "discards restricted permissions" do
      # @admin.account is not Account.site_admin, so the site_admin permission
      # (and a few others) is not available to roles on that account.
      restricted_permission = "site_admin"

      json = api_call(:post,
                      "/api/v1/accounts/#{@admin.account.id}/roles",
                      { controller: "role_overrides", action: "add_role", format: "json", account_id: @admin.account.id.to_s },
                      { role: @role_name,
                        permissions: {
                          @permission => { explicit: "1", enabled: "1" },
                          restricted_permission => { explicit: "1", enabled: "1" }
                        } })

      @role = Role.find_by(id: json["id"])

      expect(@account.role_overrides.reload.size).to eq @initial_count + 1 # not 2
      override = @account.role_overrides.where(permission: restricted_permission, role_id: @role.id).first
      expect(override).to be_nil

      override = @account.role_overrides.where(permission: @permission, role_id: @role.id).first
      expect(override).to_not be_nil
    end

    describe "json response" do
      it "returns the expected json format with granular user permissions" do
        expected_perms = %w[
          allow_course_admin_actions
          add_teacher_to_course
          add_ta_to_course
          add_designer_to_course
          add_student_to_course
          add_observer_to_course
          remove_teacher_from_course
          remove_ta_from_course
          remove_designer_from_course
          remove_student_from_course
          remove_observer_from_course
        ]

        json = api_call_with_settings
        expect(json.keys.sort).to eq %w[
          account
          base_role_type
          created_at
          id
          is_account_role
          label
          last_updated_at
          permissions
          role
          workflow_state
        ]
        expect(json["account"]["id"]).to eq @account.id
        expect(json["id"]).to eq @role.id
        expect(json["role"]).to eq @role_name
        expect(json["base_role_type"]).to eq Role::DEFAULT_ACCOUNT_TYPE

        # make sure all the expected keys are there, but don't assert on a
        # *only* the expected keys, since plugins may have added more.
        expect(expected_perms - json["permissions"].keys).to be_empty

        expect(json["permissions"][@permission]).to eq({
                                                         "explicit" => false,
                                                         "readonly" => false,
                                                         "enabled" => false,
                                                         "locked" => false
                                                       })
      end

      it "returns the expected json format with granular manage courses permissions" do
        expected_perms = %w[
          manage_courses_add
          manage_courses_admin
          manage_courses_publish
          manage_courses_conclude
          manage_courses_delete
        ]

        json = api_call_with_settings
        expect(json.keys.sort).to eq %w[
          account
          base_role_type
          created_at
          id
          is_account_role
          label
          last_updated_at
          permissions
          role
          workflow_state
        ]
        expect(json["account"]["id"]).to eq @account.id
        expect(json["id"]).to eq @role.id
        expect(json["role"]).to eq @role_name
        expect(json["base_role_type"]).to eq Role::DEFAULT_ACCOUNT_TYPE

        # make sure all the expected keys are there, but don't assert on a
        # *only* the expected keys, since plugins may have added more.
        expect(expected_perms - json["permissions"].keys).to be_empty

        expect(json["permissions"][@permission]).to eq({
                                                         "explicit" => false,
                                                         "readonly" => false,
                                                         "enabled" => false,
                                                         "locked" => false
                                                       })
      end

      it "returns the expected json format with granular manage groups permissions" do
        expected_perms = %w[
          manage_groups_add
          manage_groups_manage
          manage_groups_delete
        ]

        json = api_call_with_settings
        expect(json.keys.sort).to eq %w[
          account
          base_role_type
          created_at
          id
          is_account_role
          label
          last_updated_at
          permissions
          role
          workflow_state
        ]
        expect(json["account"]["id"]).to eq @account.id
        expect(json["id"]).to eq @role.id
        expect(json["role"]).to eq @role_name
        expect(json["base_role_type"]).to eq Role::DEFAULT_ACCOUNT_TYPE

        # make sure all the expected keys are there, but don't assert on a
        # *only* the expected keys, since plugins may have added more.
        expect(expected_perms - json["permissions"].keys).to be_empty

        expect(json["permissions"][@permission]).to eq({
                                                         "explicit" => false,
                                                         "readonly" => false,
                                                         "enabled" => false,
                                                         "locked" => false
                                                       })
      end

      it "returns the expected json format with granular manage course content permissions" do
        expected_perms = %w[
          manage_course_content_add
          manage_course_content_edit
          manage_course_content_delete
        ]

        json = api_call_with_settings
        expect(json.keys.sort).to eq %w[
          account
          base_role_type
          created_at
          id
          is_account_role
          label
          last_updated_at
          permissions
          role
          workflow_state
        ]
        expect(json["account"]["id"]).to eq @account.id
        expect(json["id"]).to eq @role.id
        expect(json["role"]).to eq @role_name
        expect(json["base_role_type"]).to eq Role::DEFAULT_ACCOUNT_TYPE

        expect(expected_perms.intersection(json["permissions"].keys)).to eq expected_perms
      end

      it "only returns manageable permissions" do
        # set up a subaccount and admin in subaccount
        subaccount = @account.sub_accounts.create!

        # add a role in that subaccount
        json = api_call_with_settings(account: subaccount)
        expect(json["account"]["id"]).to eq subaccount.id

        # become_user is a permission restricted to root account roles. it
        # shouldn't be in the response for this subaccount role.
        expect(json["permissions"].keys).not_to include("become_user")
      end

      it "sets explicit and prior default if enabled was provided" do
        json = api_call_with_settings(explicit: "1", enabled: "1")
        expect(json["permissions"][@permission]).to eq({
                                                         "explicit" => true,
                                                         "readonly" => false,
                                                         "enabled" => true,
                                                         "applies_to_descendants" => true,
                                                         "applies_to_self" => true,
                                                         "locked" => false,
                                                         "prior_default" => false
                                                       })
      end
    end
  end

  describe "create permission overrides" do
    before :once do
      @account = Account.default
      @path = "/api/v1/accounts/#{@account.id}/roles/#{teacher_role.id}"
      @path_options = { controller: "role_overrides",
                        action: "update",
                        account_id: @account.id.to_param,
                        format: "json",
                        id: teacher_role.id }
      @permissions = { permissions: {
        read_question_banks: { explicit: 1,
                               enabled: 0,
                               locked: 1 }
      } }
    end

    context "an authorized user" do
      it "is able to change permissions" do
        json = api_call(:put, @path, @path_options, @permissions)
        expect(json["permissions"]["read_question_banks"]).to eq({
                                                                   "enabled" => false,
                                                                   "locked" => true,
                                                                   "readonly" => false,
                                                                   "prior_default" => true,
                                                                   "explicit" => true
                                                                 })
        expect(json["id"]).to eql teacher_role.id
        expect(json["role"]).to eql "TeacherEnrollment"
      end

      it "is not able to edit read-only permissions" do
        sub = @account.sub_accounts.create!
        @path = "/api/v1/accounts/#{sub.id}/roles/#{teacher_role.id}"
        @path_options[:account_id] = sub.id.to_param
        o = @account.role_overrides.create(permission: "read_forum", role: teacher_role, enabled: true)
        o.locked = true
        o.save!

        json = api_call(:put, @path, @path_options, { permission: {
                          read_forum: { explicit: 1, enabled: 0 }
                        } })

        # permissions should remain unchanged
        expect(json["permissions"]["read_forum"]).to eq({
                                                          "explicit" => false,
                                                          "enabled" => true,
                                                          "readonly" => true,
                                                          "applies_to_descendants" => true,
                                                          "applies_to_self" => true,
                                                          "locked" => true
                                                        })
      end

      it "is not able to create permissions for nonexistent roles" do
        api_call(:put,
                 "/api/v1/accounts/#{@account.id}/roles/nonexistent",
                 @path_options.merge(id: "nonexistent"),
                 { permissions: { read_forum: { explicit: 1, enabled: 0 } } },
                 {},
                 { expected_status: 404 })
      end

      it "is able to change permissions for account admins" do
        json =
          api_call(
            :put,
            "/api/v1/accounts/#{@account.id}/roles/#{admin_role.id}",
            @path_options.merge(id: admin_role.id),
            { permissions: { manage_master_courses: { explicit: 1, enabled: 0 } } }
          )
        expect(json["permissions"]["manage_master_courses"]["enabled"]).to be false
      end

      it "is not able to add an unavailable permission for a base role" do
        @path = "/api/v1/accounts/#{@account.id}/roles/#{student_role.id}"
        @path_options[:id] = student_role.id
        @permissions[:permissions][:read_question_banks][:enabled] = 1
        json = api_call(:put, @path, @path_options, @permissions)
        expect(json["permissions"]["read_question_banks"]).to eq({
                                                                   "enabled" => false,
                                                                   "locked" => true,
                                                                   "readonly" => true,
                                                                   "explicit" => false
                                                                 })
      end

      it "is not able to add an unavailable permission for a course role" do
        role_name = "new role"
        json = api_call(:post,
                        "/api/v1/accounts/#{@account.id}/roles",
                        { controller: "role_overrides", action: "add_role", format: "json", account_id: @account.id.to_s },
                        { role: role_name, base_role_type: "StudentEnrollment" })

        role = Role.find_by(id: json["id"])
        @path = "/api/v1/accounts/#{@account.id}/roles/#{role.id}"
        @path_options[:id] = role.id
        @permissions[:permissions][:read_question_banks][:enabled] = 1

        json = api_call(:put, @path, @path_options, @permissions)
        expect(json["permissions"]["read_question_banks"]["enabled"]).to be false

        @account.reload

        override = @account.role_overrides.where(permission: "read_question_banks", role_id: role.id).first
        expect(override).to be_nil
      end
    end

    context "an unauthorized user" do
      it "returns 403 forbidden" do
        user_with_pseudonym
        raw_api_call(:put, @path, @path_options, @permissions)
        expect(response).to have_http_status :forbidden
      end
    end

    context "updating name" do
      it "is able to update role name for custom roles" do
        role = custom_account_role("Roll", account: @account)
        json = api_call(:put,
                        "/api/v1/accounts/#{@account.id}/roles/#{role.id}",
                        { controller: "role_overrides",
                          action: "update",
                          account_id: @account.id.to_param,
                          format: "json",
                          id: role.id },
                        { label: "newname" })
        expect(json["label"]).to eq "newname"
        role.reload
        expect(role.name).to eq "newname"
      end

      it "is not able to update role name for inherited roles" do
        role = custom_account_role("Roll", account: @account)
        sub_account = @account.sub_accounts.create!(name: "sub")
        json = api_call(:put,
                        "/api/v1/accounts/#{sub_account.id}/roles/#{role.id}",
                        { controller: "role_overrides",
                          action: "update",
                          account_id: sub_account.id.to_param,
                          format: "json",
                          id: role.id },
                        { label: "newname" },
                        {},
                        { expected_status: 400 })
        expect(json["message"]).to eq "cannot update the 'label' for an inherited role"
      end

      it "is not able to update role name for built-in roles" do
        json = api_call(:put,
                        "/api/v1/accounts/#{@account.id}/roles/#{admin_role.id}",
                        { controller: "role_overrides",
                          action: "update",
                          account_id: @account.id.to_param,
                          format: "json",
                          id: admin_role.id },
                        { label: "newname" },
                        {},
                        { expected_status: 400 })
        expect(json["message"]).to eq "cannot update the 'label' for a built-in role"
      end
    end
  end

  describe "manageable_permissions" do
    before do
      allow(RoleOverride).to receive(:manageable_permissions).with(Account.default).and_return(
        {
          view_analytics: {
            label: -> { "Analytics - View" },
            group: :analytics_stuff,
            group_label: -> { "Analytics" },
            available_to: %w[AccountAdmin TaEnrollment TeacherEnrollment StudentEnrollment AccountMembership],
            true_for: %w[AccountAdmin TaEnrollment TeacherEnrollment]
          },
          fabricate_analytics: {
            group: :analytics_stuff,
            label: -> { "Analytics - Create" },
            available_to: %w[AccountAdmin AccountMembership],
            true_for: %w[AccountAdmin]
          },
          play_404_game: {
            label: -> { "Play 404 Games" },
            group: :joy,
            available_to: %w[AccountAdmin StudentEnrollment],
            true_for: %w[AccountAdmin]
          }
        }
      )
      allow(Permissions).to receive(:group_label) { |group| group.to_s.titleize }
    end

    before :once do
      account_admin_user(account: Account.default)
    end

    it "requires :manage_role_overrides permission" do
      user_factory
      api_call(:get,
               "/api/v1/accounts/#{Account.default.id}/roles/permissions",
               { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param },
               {},
               {},
               { expected_status: 403 })
    end

    it "returns the expected information" do
      json = api_call(:get,
                      "/api/v1/accounts/#{Account.default.id}/roles/permissions",
                      { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param })
      expect(json).to eq([
                           {
                             "key" => "view_analytics",
                             "label" => "Analytics - View",
                             "group" => "analytics_stuff",
                             "group_label" => "Analytics Stuff",
                             "available_to" => %w[AccountAdmin TaEnrollment TeacherEnrollment StudentEnrollment AccountMembership],
                             "true_for" => %w[AccountAdmin TaEnrollment TeacherEnrollment]
                           },
                           {
                             "key" => "fabricate_analytics",
                             "group" => "analytics_stuff",
                             "group_label" => "Analytics Stuff",
                             "label" => "Analytics - Create",
                             "available_to" => %w[AccountAdmin AccountMembership],
                             "true_for" => %w[AccountAdmin]
                           },
                           {
                             "key" => "play_404_game",
                             "label" => "Play 404 Games",
                             "group" => "joy",
                             "group_label" => "Joy",
                             "available_to" => %w[AccountAdmin StudentEnrollment],
                             "true_for" => %w[AccountAdmin]
                           }
                         ])
    end

    describe "searching" do
      it "searches by key" do
        json = api_call(:get,
                        "/api/v1/accounts/#{Account.default.id}/roles/permissions?search_term=404_game",
                        { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param, search_term: "404_game" })
        expect(json.pluck("key")).to contain_exactly("play_404_game")
      end

      it "searches by label" do
        json = api_call(:get,
                        "/api/v1/accounts/#{Account.default.id}/roles/permissions?search_term=CREATE",
                        { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param, search_term: "CREATE" })
        expect(json.pluck("key")).to contain_exactly("fabricate_analytics")
      end

      it "searches by group" do
        json = api_call(:get,
                        "/api/v1/accounts/#{Account.default.id}/roles/permissions?search_term=stuff",
                        { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param, search_term: "stuff" })
        expect(json.pluck("key")).to contain_exactly("view_analytics", "fabricate_analytics")
      end

      it "searches by group_label" do
        json = api_call(:get,
                        "/api/v1/accounts/#{Account.default.id}/roles/permissions?search_term=joy",
                        { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param, search_term: "joy" })
        expect(json.pluck("key")).to contain_exactly("play_404_game")
      end

      it "returns an empty array if no permissions match" do
        json = api_call(:get,
                        "/api/v1/accounts/#{Account.default.id}/roles/permissions?search_term=blah",
                        { controller: "role_overrides", action: "manageable_permissions", format: "json", account_id: Account.default.to_param, search_term: "blah" })
        expect(json).to eq([])
      end
    end
  end
end
