Remove immediate onboarding feature flag - #741
Conversation
Test coverage90.09% line coverage reported by SimpleCov. |
There was a problem hiding this comment.
Pull request overview
Removes the ENABLE_IMMEDIATE_SCHOOL_ONBOARDING feature flag and updates the application and specs to assume immediate onboarding behavior unconditionally.
Changes:
- Deleted the
FeatureFlags.immediate_school_onboarding?flag and removed related ENV/config/spec coverage. - Made school code generation and onboarding paths unconditional (and removed conditional validation/guardrails that depended on the flag).
- Simplified multiple specs by removing flag-on/flag-off branches.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/services/school_verification_service_spec.rb | Removes feature-flag branches; now expects verification to succeed without flag toggling. |
| spec/models/teacher_invitation_spec.rb | Removes flag-dependent validity expectations for unverified schools. |
| spec/models/school_spec.rb | Removes flag setup around code validation/generation specs. |
| spec/lib/feature_flags_spec.rb | Removes tests for the deleted flag method (file is now empty aside from requires). |
| spec/concepts/school/create_spec.rb | Updates expectations to always call onboarding service. |
| spec/concepts/school_teacher/invite_spec.rb | Removes flag-dependent behavior checks for unverified schools. |
| spec/concepts/school_student/create_spec.rb | Removes flag-dependent behavior checks for unverified schools. |
| lib/feature_flags.rb | Removes the immediate onboarding flag method (module now empty). |
| lib/concepts/school/operations/create.rb | Always runs SchoolOnboardingService after saving the school. |
| lib/concepts/school_student/create.rb | Removes verification gating from student creation validation. |
| app/services/school_verification_service.rb | Removes feature-flag logic and now calls onboarding unconditionally during verification. |
| app/models/teacher_invitation.rb | Removes the “school must be verified” validation guard. |
| app/models/school.rb | Makes code generation unconditional and removes flag-based code generation in verify!. |
| .env.example | Removes the ENABLE_IMMEDIATE_SCHOOL_ONBOARDING example entry. |
Comments suppressed due to low confidence (1)
lib/concepts/school/operations/create.rb:21
SchoolOnboardingServiceintentionally avoids sendingProfileApiClient::UnauthorizedErrorto Sentry (it logs a warning and returnsfalse), but this operation converts anyfalseinto a raisedRuntimeError('School onboarding failed') which is then captured bySentry.capture_exception(e). That defeats the “no noise to sentry” intent and will likely increase Sentry volume now that onboarding is unconditional. Suggestion: handleonboarded == falsewithout raising/capturing (e.g., set an error on the response and roll back the transaction) or propagate a structured failure reason so the rescue can decide whether to report to Sentry.
School.transaction do
response[:school].save!
onboarded = SchoolOnboardingService.new(response[:school]).onboard(token:)
raise 'School onboarding failed' unless onboarded
end
response
rescue StandardError => e
Sentry.capture_exception(e)
response[:error] = response[:school].errors.presence || [e.message]
response[:error_types] = response[:school].errors.details
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
spec/lib/feature_flags_spec.rb:2
- This spec file no longer contains any examples and can be removed to keep the spec suite tidy and avoid implying there is still behavior under test for feature flags.
# frozen_string_literal: true
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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 |
There was a problem hiding this comment.
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.
| describe '#verify' do | ||
| 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 | ||
| describe 'when school can be saved' do | ||
| it 'saves the school' do | ||
| service.verify(token:) | ||
| expect(school).to be_persisted |
There was a problem hiding this comment.
SchoolVerificationService#verify now always performs onboarding and rolls back on onboarding failure, but the spec no longer covers the onboarding-failure path (e.g., when SchoolOnboardingService#onboard returns false/raises) to assert the transaction is rolled back (school not persisted / verified_at not set). Adding an example for this failure case would help prevent regressions in the new always-onboarding behavior.
| success = SchoolOnboardingService.new(school).onboard(token: token) | ||
| raise ActiveRecord::Rollback unless success |
There was a problem hiding this comment.
Is this right? my reading of the old code is when FeatureFlags.immediate_school_onboarding? was true SchoolOnboardingService.new(school).onboard(token: token) would not be called because of the ||.
But also maybe we want to call to call onboard when an admin clicks verify if they signed up before immediate onboarding?
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
spec/models/teacher_invitation_spec.rb:27
- After removing the feature-flagged verification rule from
TeacherInvitation, this spec no longer asserts the intended behavior for invitations on unverified schools. Add a focused example that documents whether an invitation should be valid or invalid whenschool.verified_atis nil, so the new always-on behavior can’t regress silently.
it 'is invalid with an incorrectly formatted email address' do
invitation = build(:teacher_invitation, email_address: 'not-an-email-address')
expect(invitation).not_to be_valid
end
it 'sends an invitation email after create' do
school = create(:verified_school)
invitation = described_class.create!(email_address: 'teacher@example.com', school:)
assert_enqueued_email_with InvitationMailer, :invite_teacher, params: { invitation: }
end
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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 | ||
| end |
There was a problem hiding this comment.
SchoolOnboardingService#onboard intentionally avoids reporting ProfileApiClient::UnauthorizedError to Sentry (it returns false and logs a warning). Here, when onboard returns false (including unauthorized), the code raises and the rescue block reports the raised exception to Sentry anyway, which reintroduces Sentry noise and loses the original failure reason. Consider handling unauthorized (and possibly other expected failures) explicitly without raising/capturing, or have the onboarding service return a richer result so the caller can set response[:error] without generating a new exception.
| onboarded = SchoolOnboardingService.new(response[:school]).onboard(token:) | ||
| raise 'School onboarding failed' unless onboarded | ||
| end |
There was a problem hiding this comment.
School::Create now always performs onboarding and fails the operation if onboarding returns false. There’s no spec coverage for the onboarding-failure path (e.g., SchoolOnboardingService#onboard returns false) to ensure the transaction rolls back and the response is marked as failure with a useful error. Add a unit test that stubs the onboarding service to return false and asserts the school is not persisted and response.failure? is true.
| def create_student(school, school_student_params, token) | ||
| school_id = school.id | ||
| username = school_student_params.fetch(:username) | ||
| encrypted_password = school_student_params.fetch(:password) | ||
| password = DecryptionHelpers.decrypt_password(encrypted_password) | ||
| name = school_student_params.fetch(:name) | ||
|
|
||
| validate( | ||
| username:, | ||
| password:, | ||
| name:, | ||
| school: (FeatureFlags.immediate_school_onboarding? ? nil : school) | ||
| name: | ||
| ) | ||
|
|
||
| response = ProfileApiClient.create_school_student(token:, username:, password:, name:, school_id:) | ||
| user_id = response[:created].first | ||
| Role.student.create!(school:, user_id:) | ||
| 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 |
There was a problem hiding this comment.
The feature-flag-dependent verification requirement for creating students was removed from SchoolStudent::Create#validate, but specs only exercise creation with create(:verified_school). Add an example using an unverified school (create(:school) / verified_at: nil) to pin down the intended behavior now that the feature flag is gone (either allowing creation or asserting a specific error).
zetter-rpf
left a comment
There was a problem hiding this comment.
Nice one, great tidy up 🧹
Status
What's changed?