diff --git a/app/jobs/gpo_expiration_job.rb b/app/jobs/gpo_expiration_job.rb index 6e9bc06d0c3..5403d9008e7 100644 --- a/app/jobs/gpo_expiration_job.rb +++ b/app/jobs/gpo_expiration_job.rb @@ -1,32 +1,39 @@ class GpoExpirationJob < ApplicationJob queue_as :low - def initialize(analytics: nil) + def initialize(analytics: nil, on_profile_expired: nil) @analytics = analytics + @on_profile_expired = on_profile_expired end - def expire_profile(profile:) - gpo_verification_pending_at = profile.gpo_verification_pending_at - - profile.deactivate_due_to_gpo_expiration - - analytics.idv_gpo_expired( - user_id: profile.user.uuid, - user_has_active_profile: profile.user.active_profile.present?, - letters_sent: profile.gpo_confirmation_codes.count, - gpo_verification_pending_at: gpo_verification_pending_at, - ) - end - - def perform(now: Time.zone.now, limit: nil, min_profile_age: nil) + def perform( + dry_run: false, + limit: nil, + min_profile_age: nil, + now: Time.zone.now, + statement_timeout: 10.minutes + ) profiles = gpo_profiles_that_should_be_expired(as_of: now, min_profile_age: min_profile_age) if limit.present? profiles = profiles.limit(limit) end - profiles.find_each do |profile| - expire_profile(profile: profile) + with_statement_timeout(statement_timeout) do + profiles.find_each do |profile| + gpo_verification_pending_at = profile.gpo_verification_pending_at + + if gpo_verification_pending_at.blank? + raise "Profile #{profile.id} does not have gpo_verification_pending_at" + end + + expire_profile(profile: profile) unless dry_run + + on_profile_expired&.call( + profile: profile, + gpo_verification_pending_at: gpo_verification_pending_at, + ) + end end end @@ -40,6 +47,31 @@ def gpo_profiles_that_should_be_expired(as_of:, min_profile_age: nil) private + attr_reader :on_profile_expired + + def expire_profile(profile:) + gpo_verification_pending_at = profile.gpo_verification_pending_at + + profile.deactivate_due_to_gpo_expiration + + analytics.idv_gpo_expired( + user_id: profile.user.uuid, + user_has_active_profile: profile.user.active_profile.present?, + letters_sent: profile.gpo_confirmation_codes.count, + gpo_verification_pending_at: gpo_verification_pending_at, + ) + end + + def with_statement_timeout(timeout) + ActiveRecord::Base.transaction do + quoted_timeout = ActiveRecord::Base.connection.quote("#{timeout.seconds}s") + ActiveRecord::Base.connection.execute( + "SET LOCAL statement_timeout = #{quoted_timeout}", + ) + yield + end + end + def analytics @analytics ||= Analytics.new(user: AnonymousUser.new, request: nil, session: {}, sp: nil) end diff --git a/lib/tasks/backfill_gpo_expiration.rake b/lib/tasks/backfill_gpo_expiration.rake index 63a73505e7f..59d1db6f9d0 100644 --- a/lib/tasks/backfill_gpo_expiration.rake +++ b/lib/tasks/backfill_gpo_expiration.rake @@ -13,34 +13,34 @@ namespace :profiles do task backfill_gpo_expiration: :environment do |_task, _args| min_profile_age = (ENV['MIN_PROFILE_AGE_IN_DAYS'].to_i || 100).days update_profiles = ENV['UPDATE_PROFILES'] == 'true' - - job = GpoExpirationJob.new - - profiles = job.gpo_profiles_that_should_be_expired( - as_of: Time.zone.now, - min_profile_age: min_profile_age, - ) + statement_timeout = ENV['STATEMENT_TIMEOUT_IN_SECONDS'].to_i.seconds || 10.minutes count = 0 + earliest = nil + latest = nil - profiles.find_each do |profile| + on_profile_expired = ->(profile:, gpo_verification_pending_at:) do count += 1 - gpo_verification_pending_at = profile.gpo_verification_pending_at - - if gpo_verification_pending_at.blank? - raise "Profile #{profile.id} does not have gpo_verification_pending_at" - end + earliest = [earliest, gpo_verification_pending_at].compact.min + latest = [latest, gpo_verification_pending_at].compact.max puts "#{profile.id},#{gpo_verification_pending_at.iso8601}" - if update_profiles - warn "Expired #{count} profiles" if count % 100 == 0 - job.expire_profile(profile: profile) - elsif count % 100 == 0 - warn "Found #{count} profiles" + if count % 100 == 0 + verb = update_profiles ? 'Expired' : 'Found' + warn "#{verb} #{count} profiles (earliest: #{earliest}, latest: #{latest})" end end + + job = GpoExpirationJob.new(on_profile_expired: on_profile_expired) + + job.perform( + now: Time.zone.now, + min_profile_age: min_profile_age, + dry_run: !update_profiles, + statement_timeout: statement_timeout, + ) end ## diff --git a/spec/jobs/gpo_expiration_job_spec.rb b/spec/jobs/gpo_expiration_job_spec.rb index f92c499437a..c1e841eb2dd 100644 --- a/spec/jobs/gpo_expiration_job_spec.rb +++ b/spec/jobs/gpo_expiration_job_spec.rb @@ -113,6 +113,30 @@ ) end + context 'when a callback is provided' do + it 'calls it for expired profiles' do + profile = users[:user_with_one_expired_gpo_profile].reload.gpo_verification_pending_profile + gpo_verification_pending_at = profile.gpo_verification_pending_at + + on_profile_expired = spy + expect(on_profile_expired).to receive(:call).with( + profile: profile, + gpo_verification_pending_at: gpo_verification_pending_at, + ) + + job = described_class.new(analytics: analytics, on_profile_expired: on_profile_expired) + job.perform + end + end + + context('when dry_run is specified') do + it 'does not write changes' do + job.perform(dry_run: true) + profile = users[:user_with_one_expired_gpo_profile].reload.gpo_verification_pending_profile + expect(profile.gpo_verification_expired_at).to eql(nil) + end + end + context 'when the user has an active profile' do let!(:active_profile) do create(:profile, :active, user: users[:user_with_one_expired_gpo_profile])