Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,3 @@ GOOGLE_CLIENT_SECRET=changeme
# E2E tests can supply this to enable test utility endpoints
RESEED_API_KEY=changeme

# Enable immediate onboarding for schools
ENABLE_IMMEDIATE_SCHOOL_ONBOARDING=true
7 changes: 2 additions & 5 deletions app/models/school.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ class School < ApplicationRecord

before_save :format_uk_postal_code, if: :should_format_uk_postal_code?

# TODO: Remove the conditional once the feature flag is retired
after_create :generate_code!, if: -> { FeatureFlags.immediate_school_onboarding? }
after_create :generate_code!

def self.find_for_user!(user)
school = Role.find_by(user_id: user.id)&.school || find_by(creator_id: user.id)
Expand All @@ -72,9 +71,7 @@ def rejected?
end

def verify!
# TODO: Remove this line once the feature flag is retired
generate_code! unless FeatureFlags.immediate_school_onboarding?

generate_code!
update!(verified_at: Time.zone.now)
end

Expand Down
7 changes: 0 additions & 7 deletions app/models/teacher_invitation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class TeacherInvitation < ApplicationRecord
belongs_to :school
validates :email_address,
format: { with: EmailValidator.regexp, message: I18n.t('validations.invitation.email_address') }
validate :school_is_verified, unless: -> { FeatureFlags.immediate_school_onboarding? }
after_create_commit :send_invitation_email
encrypts :email_address

Expand All @@ -16,12 +15,6 @@ class TeacherInvitation < ApplicationRecord

private

def school_is_verified
return if school.verified?

errors.add(:school, 'is not verified')
end

def send_invitation_email
InvitationMailer.with(invitation: self).invite_teacher.deliver_later
end
Expand Down
12 changes: 4 additions & 8 deletions app/services/school_verification_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,20 @@ def initialize(school)
@school = school
end

def verify(token: nil)
def verify(token:)
success = false
School.transaction do
school.verify!

# TODO: Remove this line, once the feature flag is retired
success = FeatureFlags.immediate_school_onboarding? || SchoolOnboardingService.new(school).onboard(token: token)

# TODO: Remove this line, once the feature flag is retired
success = SchoolOnboardingService.new(school).onboard(token: token)
raise ActiveRecord::Rollback unless success
end

success
rescue StandardError => e
Sentry.capture_exception(e)
Rails.logger.error { "Failed to verify school #{@school.id}: #{e.message}" }
false
else
# TODO: Return 'true', once the feature flag is retired
success
end

delegate :reject, to: :school
Expand Down
7 changes: 2 additions & 5 deletions lib/concepts/school/operations/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ def call(school_params:, creator_id:, token:)
School.transaction do
response[:school].save!

# TODO: Remove this conditional once the feature flag is retired
if FeatureFlags.immediate_school_onboarding?
onboarded = SchoolOnboardingService.new(response[:school]).onboard(token:)
raise 'School onboarding failed' unless onboarded
end
onboarded = SchoolOnboardingService.new(response[:school]).onboard(token:)
raise 'School onboarding failed' unless onboarded
Comment on lines 10 to +14
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

School::Create.call wraps save+onboarding in a School.transaction but then rescues the raised error and returns a failure response. If School::Create.call is invoked inside an outer transaction (e.g. SchoolImportJob wraps it in School.transaction), this can prevent the outer transaction from rolling back while still returning failure, leaving the created school (and possibly roles) committed. To keep the operation atomic regardless of caller context, consider using School.transaction(requires_new: true) (savepoint) or otherwise ensuring the DB changes are rolled back when onboarding fails before returning a failure response.

Copilot uses AI. Check for mistakes.
end

response
Expand Down
8 changes: 2 additions & 6 deletions lib/concepts/school_student/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ def create_student(school, school_student_params, token)
validate(
username:,
password:,
name:,
school: (FeatureFlags.immediate_school_onboarding? ? nil : school)
name:
)

response = ProfileApiClient.create_school_student(token:, username:, password:, name:, school_id:)
Expand All @@ -35,13 +34,10 @@ def create_student(school, school_student_params, token)
user_id
end

def validate(username:, password:, name:, school: nil)
def validate(username:, password:, name:)
raise ArgumentError, "username '#{username}' is invalid" if username.blank?
raise ArgumentError, "password '#{password}' is invalid" if password.size < 8
raise ArgumentError, "name '#{name}' is invalid" if name.blank?

return unless school
raise ArgumentError, 'school must be verified' unless school.verified?
end
end
end
Expand Down
7 changes: 0 additions & 7 deletions lib/feature_flags.rb

This file was deleted.

29 changes: 1 addition & 28 deletions spec/concepts/school/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,7 @@
end
end

describe 'when immediate onboarding is enabled' do
# TODO: Remove this block once the feature flag is retired
around do |example|
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
example.run
end
end

describe 'immediate onboarding' do
let(:onboarding_service) { instance_spy(SchoolOnboardingService, onboard: true) }

before do
Expand All @@ -102,24 +95,4 @@
expect(onboarding_service).to have_received(:onboard).with(token:)
end
end

# TODO: Remove these examples once the feature flag is retired
describe 'when immediate onboarding is disabled' do
around do |example|
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: nil) do
example.run
end
end

let(:onboarding_service) { instance_spy(SchoolOnboardingService) }

before do
allow(SchoolOnboardingService).to receive(:new).and_return(onboarding_service)
end

it 'does not call the onboarding service' do
described_class.call(school_params:, creator_id:, token:)
expect(onboarding_service).not_to have_received(:onboard)
end
end
end
23 changes: 0 additions & 23 deletions spec/concepts/school_student/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,6 @@
end
end

context 'when the school is not verified' do
let(:school) { create(:school) } # not verified by default

context 'when immediate_school_onboarding is FALSE' do
it 'returns the error message in the operation response' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'false') do
response = described_class.call(school:, school_student_params:, token:)
expect(response[:error]).to match(/school must be verified/)
end
end
end

context 'when immediate_school_onboarding is TRUE' do
it 'does not error due to school verification' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
response = described_class.call(school:, school_student_params:, token:)

expect(response[:error]).to be_nil
end
end
end
end

context 'when the student cannot be created in profile api because of a 422 response' do
let(:error) { { 'message' => "something's up with the username" } }
let(:exception) { ProfileApiClient::Student422Error.new(error) }
Expand Down
22 changes: 0 additions & 22 deletions spec/concepts/school_teacher/invite_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,4 @@
expect(Sentry).to have_received(:capture_exception).with(kind_of(StandardError))
end
end

context 'when the school is not verified' do
let(:school) { create(:school) }

context 'when immediate_school_onboarding is FALSE' do
it 'does return an error message in the operation response' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'false') do
response = described_class.call(school:, school_teacher_params:, token:)
expect(response[:error]).to match(/is not verified/)
end
end
end

context 'when immediate_school_onboarding is TRUE' do
it 'does not return an error message in the operation response' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
response = described_class.call(school:, school_teacher_params:, token:)
expect(response[:error]).to be_blank
end
end
end
end
end
37 changes: 0 additions & 37 deletions spec/lib/feature_flags_spec.rb

This file was deleted.

12 changes: 0 additions & 12 deletions spec/models/school_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,6 @@
end

describe 'code validations' do
around do |example|
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
example.run
end
end

it 'requires #code to be unique' do
school # ensure existing school has a code
another_school = build(:school)
Expand Down Expand Up @@ -532,12 +526,6 @@
end

describe 'code generation' do
around do |example|
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
example.run
end
end

it 'automatically generates a code after creation' do
new_school = described_class.create!(build(:school).attributes.except('id', 'created_at', 'updated_at'))
expect(new_school.reload.code).to be_present
Expand Down
18 changes: 0 additions & 18 deletions spec/models/teacher_invitation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,6 @@
expect(invitation).not_to be_valid
end

it 'is valid with an unverified school and immediate_school_onboarding is TRUE' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'true') do
school = build(:school, verified_at: nil)
invitation = build(:teacher_invitation, school:)

expect(invitation).to be_valid
end
end

it 'is invalid with an unverified school and immediate_school_onboarding is FALSE' do
ClimateControl.modify(ENABLE_IMMEDIATE_SCHOOL_ONBOARDING: 'false') do
school = build(:school, verified_at: nil)
invitation = build(:teacher_invitation, school:)

expect(invitation).not_to be_valid
end
end

it 'sends an invitation email after create' do
school = create(:verified_school)

Expand Down
Loading
Loading