From 14f25a4d8157d77d3048912a0766a2f072f703d5 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Wed, 23 Sep 2026 16:46:57 +0200 Subject: [PATCH] feat: email the new owner when their ownership transfer completes --- app/mailers/school_ownership_mailer.rb | 10 ++++ app/models/ownership_transfer.rb | 6 +++ .../complete_ownership_transfer.text.erb | 16 ++++++ .../school_ownership_mailer_preview.rb | 13 +++++ spec/mailers/school_ownership_mailer_spec.rb | 53 +++++++++++++++++++ spec/models/ownership_transfer_spec.rb | 36 ++++++++++++- 6 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 app/views/school_ownership_mailer/complete_ownership_transfer.text.erb diff --git a/app/mailers/school_ownership_mailer.rb b/app/mailers/school_ownership_mailer.rb index 730bbeac3..970cfddc3 100644 --- a/app/mailers/school_ownership_mailer.rb +++ b/app/mailers/school_ownership_mailer.rb @@ -25,6 +25,16 @@ def cancel_ownership_transfer message_stream: 'outbound') end + def complete_ownership_transfer + @school = ownership_transfer.school + @new_owner_name = users_by_id[ownership_transfer.nominated_user_id]&.name.presence || 'there' + + mail(to: ownership_transfer.email_address, + subject: "You're now the owner of the Code Classroom account for #{@school.name}", + track_opens: 'true', + message_stream: 'outbound') + end + private def ownership_transfer diff --git a/app/models/ownership_transfer.rb b/app/models/ownership_transfer.rb index 53d33574e..a2bb552f8 100644 --- a/app/models/ownership_transfer.rb +++ b/app/models/ownership_transfer.rb @@ -21,6 +21,8 @@ class OwnershipTransfer < ApplicationRecord after_create_commit :send_ownership_transfer_request_email after_update_commit :send_ownership_transfer_cancelled_email, if: -> { saved_change_to_status?(from: 'pending', to: 'cancelled') } + after_update_commit :send_ownership_transfer_completed_email, + if: -> { saved_change_to_status?(from: 'pending', to: 'completed') } encrypts :email_address private @@ -41,4 +43,8 @@ def send_ownership_transfer_request_email def send_ownership_transfer_cancelled_email SchoolOwnershipMailer.with(ownership_transfer: self).cancel_ownership_transfer.deliver_later end + + def send_ownership_transfer_completed_email + SchoolOwnershipMailer.with(ownership_transfer: self).complete_ownership_transfer.deliver_later + end end diff --git a/app/views/school_ownership_mailer/complete_ownership_transfer.text.erb b/app/views/school_ownership_mailer/complete_ownership_transfer.text.erb new file mode 100644 index 000000000..ae3fca090 --- /dev/null +++ b/app/views/school_ownership_mailer/complete_ownership_transfer.text.erb @@ -0,0 +1,16 @@ +Hi <%= @new_owner_name %>, + +Ownership of the Code Classroom account for <%= @school.name %> has been transferred to you. + +As the owner, you can now: + +- Manage school members: https://classroom.raspberrypi.org/en-US/school/members +- Access school settings and feature controls: https://classroom.raspberrypi.org/en-US/school/settings +- Invite a new teacher to the account +- Transfer ownership to someone else if needed +- Delete students + +To access more resources, please visit our Help Center: https://help.editor.raspberrypi.org/hc/en-us/categories/115000275003-Code-Editor-for-Education + +Kind Regards, +The Code Classroom team diff --git a/spec/mailers/previews/school_ownership_mailer_preview.rb b/spec/mailers/previews/school_ownership_mailer_preview.rb index b5a215cc8..01b858283 100644 --- a/spec/mailers/previews/school_ownership_mailer_preview.rb +++ b/spec/mailers/previews/school_ownership_mailer_preview.rb @@ -30,6 +30,19 @@ def cancel_ownership_transfer with_stubbed_user_info_api { SchoolOwnershipMailer.with(ownership_transfer:).cancel_ownership_transfer.message } end + def complete_ownership_transfer + school = School.new(name: 'Elmwood Secondary School') + ownership_transfer = OwnershipTransfer.new( + email_address: 'teacher@example.com', + school:, + nominated_user_id: NOMINEE[:id], + requested_by_user_id: REQUESTED_OWNER[:id], + status: :completed + ) + + with_stubbed_user_info_api { SchoolOwnershipMailer.with(ownership_transfer:).complete_ownership_transfer.message } + end + private # fake the user info response, but only for the duration of diff --git a/spec/mailers/school_ownership_mailer_spec.rb b/spec/mailers/school_ownership_mailer_spec.rb index 4f0c08f18..5afa971c2 100644 --- a/spec/mailers/school_ownership_mailer_spec.rb +++ b/spec/mailers/school_ownership_mailer_spec.rb @@ -121,4 +121,57 @@ end end end + + describe 'complete_ownership_transfer' do + subject(:email) { described_class.with(ownership_transfer:).complete_ownership_transfer } + + let(:school) { create(:verified_school) } + let(:nominee) { create(:teacher, school:) } + let(:requested_owner) { create(:owner, school:) } + let(:ownership_transfer) do + create( + :ownership_transfer, + school:, + nominated_user_id: nominee.id, + requested_by_user_id: requested_owner.id, + status: :completed + ) + end + + before do + stub_user_info_api_fetch_by_ids( + user_ids: [nominee.id, requested_owner.id], + users: [{ id: nominee.id, name: nominee.name }, { id: requested_owner.id, name: requested_owner.name }] + ) + end + + it 'is sent to the new owner' do + expect(email.to).to eq([ownership_transfer.email_address]) + end + + it 'includes the new owner\'s name in the body' do + expect(email.body.to_s).to include(nominee.name) + end + + it 'includes the school name in the body' do + expect(email.body.to_s).to include(ownership_transfer.school.name) + end + + it 'includes the school name in the subject' do + expect(email.subject).to include(ownership_transfer.school.name) + end + + context 'when the new owner is missing from the user-info response' do + before do + stub_user_info_api_fetch_by_ids( + user_ids: [nominee.id, requested_owner.id], + users: [{ id: requested_owner.id, name: requested_owner.name }] + ) + end + + it 'greets them generically instead of leaving the greeting blank' do + expect(email.body.to_s).to include('Hi there,') + end + end + end end diff --git a/spec/models/ownership_transfer_spec.rb b/spec/models/ownership_transfer_spec.rb index 694d0438f..537955bc4 100644 --- a/spec/models/ownership_transfer_spec.rb +++ b/spec/models/ownership_transfer_spec.rb @@ -187,7 +187,7 @@ it 'is not enqueued when the transfer resolves to a different status' do assert_no_enqueued_emails do - ownership_transfer.update!(status: :completed) + ownership_transfer.update!(status: :rejected) end end @@ -207,4 +207,38 @@ end end end + + describe 'the completion email' do + before { ownership_transfer.save! } + + it 'is enqueued with the transfer as the mailer param when the transfer completes' do + ownership_transfer.update!(status: :completed) + + assert_enqueued_email_with( + SchoolOwnershipMailer, :complete_ownership_transfer, params: { ownership_transfer: } + ) + end + + it 'is not enqueued when the transfer resolves to a different status' do + assert_no_enqueued_emails do + ownership_transfer.update!(status: :rejected) + end + end + + it 'is not enqueued when an already-completed transfer is saved again unchanged' do + ownership_transfer.update!(status: :completed) + + assert_no_enqueued_emails do + ownership_transfer.update!(status: :completed) + end + end + + it 'is not enqueued when a non-pending transfer is corrected to completed' do + ownership_transfer.update!(status: :rejected) + + assert_no_enqueued_emails do + ownership_transfer.update!(status: :completed) + end + end + end end