diff --git a/config/database.yml b/config/database.yml index 9300ccacb..c4ddd41f6 100644 --- a/config/database.yml +++ b/config/database.yml @@ -2,6 +2,7 @@ default: &default adapter: postgresql encoding: unicode host: <%= ENV.fetch('POSTGRES_HOST', 'db') %> + port: <%= ENV.fetch('POSTGRES_PORT', '5432') %> username: <%= ENV.fetch('POSTGRES_USER', 'no_pg_user_set') %> password: <%= ENV.fetch('POSTGRES_PASSWORD', '') %> pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %> diff --git a/lib/tasks/school_management.rake b/lib/tasks/school_management.rake new file mode 100644 index 000000000..67ac71707 --- /dev/null +++ b/lib/tasks/school_management.rake @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +namespace :school_management do + desc 'Transfer ownership of a school' + task :transfer_ownership, %i[old_owner_email new_owner_email keep_old_owner_as_teacher] => :environment do |_task, args| + old_owner = UserInfoApiClient.find_user_by_email(args[:old_owner_email]) + new_owner = UserInfoApiClient.find_user_by_email(args[:new_owner_email]) + + unless old_owner + Rails.logger.error("No user found for email #{args[:old_owner_email]}. Did you spell it correctly?") + next + end + + unless new_owner + Rails.logger.error("No user found for email #{args[:new_owner_email]}. Did you spell it correctly?") + next + end + + if Role.exists?(user_id: new_owner[:id], role: 'owner') + Rails.logger.error("User #{new_owner[:id]} is already the owner of a school") + next + end + + if School.exists?(creator_id: new_owner[:id]) + Rails.logger.error("User #{new_owner[:id]} is already the creator of a school") + next + end + + school = Role.find_by(roles: { user_id: old_owner[:id], role: 'owner' }).school + + school.transaction do + remove_old_owner(school, old_owner[:id], args[:keep_old_owner_as_teacher]) + assign_roles_to_new_owner(school, new_owner[:id]) + + school.update!(creator_id: new_owner[:id], creator_agree_to_ux_contact: false) + end + + Rails.logger.info "Ownership transfered to #{new_owner[:email]} successfully." + Rails.logger.warn '⚠️ You must now manually remove the owner safeguarding flag from the old owner.' + Rails.logger.warn 'Open a bash console on the rpf-profile app: `heroku run bash -a rpf-profile`' + Rails.logger.warn "Remove the owner safeguarding flag from the old owner: `node profile-cli remove-safeguarding-flag #{args[:old_owner_email]} school:owner`" + end + + def remove_old_owner(school, user_id, keep_old_owner_as_teacher) + school.roles.owner.find_by(user_id: user_id).destroy! + school.roles.teacher.find_by(user_id: user_id)&.destroy! unless keep_old_owner_as_teacher + end + + def assign_roles_to_new_owner(school, user_id) + school.roles.create!(user_id: user_id, role: 'owner') + school.roles.find_or_create_by!(user_id: user_id, role: 'teacher') + end +end diff --git a/spec/lib/tasks/school_management_spec.rb b/spec/lib/tasks/school_management_spec.rb new file mode 100644 index 000000000..8dd669c71 --- /dev/null +++ b/spec/lib/tasks/school_management_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'rake' + +RSpec.describe 'school_management', type: :task do + describe ':transfer_ownership' do + let(:task) { Rake::Task['school_management:transfer_ownership'] } + let(:old_user_id) { SecureRandom.uuid } + let(:new_user_id) { SecureRandom.uuid } + let(:school) { create(:school, creator_id: old_user_id) } + + before do + stub_user_info_api_find_by_email( + email: 'old_owner@example.com', + user: { id: old_user_id, email: 'old_owner@example.com' } + ) + + stub_user_info_api_find_by_email( + email: 'new_owner@example.com', + user: { id: new_user_id, email: 'new_owner@example.com' } + ) + + create(:owner_role, school:, user_id: old_user_id) + create(:teacher_role, school:, user_id: old_user_id) + end + + it "exits early if new owner doesn't exist" do + allow(UserInfoApiClient).to receive(:find_user_by_email) + .with('not_real_owner@example.com') + .and_return(nil) + + task.invoke('old_owner@example.com', 'not_real_owner@example.com') + + expect(school.creator_id).to eq(old_user_id) + end + + it "exits early if old owner doesn't exist" do + allow(UserInfoApiClient).to receive(:find_user_by_email) + .with('not_real_owner@example.com') + .and_return(nil) + + task.invoke('old_owner@example.com', 'new_owner@example.com') + + expect(school.creator_id).to eq(old_user_id) + end + + it 'exits early if new owner is already owner of a school' do + create(:owner_role, school:, user_id: new_user_id) + + task.invoke('old_owner@example.com', 'new_owner@example.com') + + expect(school.creator_id).to eq(old_user_id) + end + + it 'exits early if new owner is already creator of a school' do + create(:school, creator_id: new_user_id) + + task.invoke('old_owner@example.com', 'new_owner@example.com') + + expect(school.creator_id).to eq(old_user_id) + end + + it 'creates owner and teacher roles for the new owner' do + task.invoke('old_owner@example.com', 'new_owner@example.com') + + owners = school.roles.owner + owner_user_ids = owners.map(&:user_id) + teachers = school.roles.teacher + teacher_user_ids = teachers.map(&:user_id) + + expect(owner_user_ids).to eq([new_user_id]) + expect(teacher_user_ids).to include(new_user_id) + end + + it 'does not error if new owner already has teacher role' do + create(:teacher_role, school:, user_id: new_user_id) + task.invoke('old_owner@example.com', 'new_owner@example.com') + + owners = school.roles.owner + owner_user_ids = owners.map(&:user_id) + teachers = school.roles.teacher + teacher_user_ids = teachers.map(&:user_id) + + expect(owner_user_ids).to eq([new_user_id]) + expect(teacher_user_ids).to include(new_user_id) + end + + it 'keeps the old owner as a teacher if keep parameter is true' do + task.invoke('old_owner@example.com', 'new_owner@example.com', 'true') + teachers = school.roles.teacher + teacher_user_ids = teachers.map(&:user_id) + expect(teacher_user_ids).to include(old_user_id) + end + + it 'removes the old owner as a teacher by default' do + task.invoke('old_owner@example.com', 'new_owner@example.com') + teachers = school.roles.teacher + teacher_user_ids = teachers.map(&:user_id) + expect(teacher_user_ids).not_to include(old_user_id) + end + + it 'switches creator to the new owner' do + task.invoke('old_owner@example.com', 'new_owner@example.com') + school.reload + expect(school.creator_id).to eq(new_user_id) + end + + it 'sets the school UX contact flag to false' do + school.update!(creator_agree_to_ux_contact: true) + + task.invoke('old_owner@example.com', 'new_owner@example.com') + school.reload + + expect(school.creator_agree_to_ux_contact).to be(false) + end + end +end