Extract User::Confirmation concern (#35582)

This commit is contained in:
Matt Jankowski 2025-07-30 06:33:57 -04:00 committed by GitHub
parent 15b72591d4
commit 6dc55a2f4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 138 additions and 97 deletions

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module User::Confirmation
extend ActiveSupport::Concern
included do
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :unconfirmed, -> { where(confirmed_at: nil) }
def confirm
wrap_email_confirmation { super }
end
end
def confirmed?
confirmed_at.present?
end
def unconfirmed?
!confirmed?
end
end

View File

@ -58,6 +58,7 @@ class User < ApplicationRecord
include LanguagesHelper
include Redisable
include User::Confirmation
include User::HasSettings
include User::LdapAuthenticable
include User::Omniauthable
@ -118,8 +119,6 @@ class User < ApplicationRecord
scope :recent, -> { order(id: :desc) }
scope :pending, -> { where(approved: false) }
scope :approved, -> { where(approved: true) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :unconfirmed, -> { where(confirmed_at: nil) }
scope :enabled, -> { where(disabled: false) }
scope :disabled, -> { where(disabled: true) }
scope :active, -> { confirmed.signed_in_recently.account_not_suspended }
@ -184,10 +183,6 @@ class User < ApplicationRecord
current_sign_in_at.present? && current_sign_in_at >= ACTIVE_DURATION.ago
end
def confirmed?
confirmed_at.present?
end
def invited?
invite_id.present?
end
@ -212,12 +207,6 @@ class User < ApplicationRecord
account_id
end
def confirm
wrap_email_confirmation do
super
end
end
# Mark current email as confirmed, bypassing Devise
def mark_email_as_confirmed!
wrap_email_confirmation do
@ -264,10 +253,6 @@ class User < ApplicationRecord
confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial?
end
def unconfirmed?
!confirmed?
end
def unconfirmed_or_pending?
unconfirmed? || pending?
end

View File

@ -10,6 +10,7 @@ RSpec.describe User do
let(:account) { Fabricate(:account, username: 'alice') }
it_behaves_like 'two_factor_backupable'
it_behaves_like 'User::Confirmation'
describe 'otp_secret' do
it 'encrypts the saved value' do
@ -65,14 +66,6 @@ RSpec.describe User do
end
end
describe 'confirmed' do
it 'returns an array of users who are confirmed' do
Fabricate(:user, confirmed_at: nil)
confirmed_user = Fabricate(:user, confirmed_at: Time.zone.now)
expect(described_class.confirmed).to contain_exactly(confirmed_user)
end
end
describe 'signed_in_recently' do
it 'returns a relation of users who have signed in during the recent period' do
recent_sign_in_user = Fabricate(:user, current_sign_in_at: within_duration_window_days.ago)
@ -228,79 +221,6 @@ RSpec.describe User do
end
end
describe '#confirmed?' do
it 'returns true when a confirmed_at is set' do
user = Fabricate.build(:user, confirmed_at: Time.now.utc)
expect(user.confirmed?).to be true
end
it 'returns false if a confirmed_at is nil' do
user = Fabricate.build(:user, confirmed_at: nil)
expect(user.confirmed?).to be false
end
end
describe '#confirm' do
subject { user.confirm }
let(:new_email) { 'new-email@example.com' }
before do
allow(TriggerWebhookWorker).to receive(:perform_async)
end
context 'when the user is already confirmed' do
let!(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: true, unconfirmed_email: new_email) }
it 'sets email to unconfirmed_email and does not trigger web hook' do
expect { subject }.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id)
end
end
context 'when the user is a new user' do
let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) }
context 'when the user is already approved' do
before do
Setting.registrations_mode = 'approved'
user.approve!
end
it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do
expect { subject }.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once
end
end
context 'when the user does not require explicit approval' do
before do
Setting.registrations_mode = 'open'
end
it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do
expect { subject }.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once
end
end
context 'when the user requires explicit approval but is not approved' do
before do
Setting.registrations_mode = 'approved'
end
it 'sets email to unconfirmed_email and does not trigger web hook' do
expect { subject }.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id)
end
end
end
end
describe '#approve!' do
subject { user.approve! }

View File

@ -0,0 +1,114 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.shared_examples 'User::Confirmation' do
describe 'Scopes' do
let!(:unconfirmed_user) { Fabricate :user, confirmed_at: nil }
let!(:confirmed_user) { Fabricate :user, confirmed_at: Time.now.utc }
describe '.confirmed' do
it 'returns users who are confirmed' do
expect(described_class.confirmed)
.to contain_exactly(confirmed_user)
end
end
describe '.unconfirmed' do
it 'returns users who are not confirmed' do
expect(described_class.unconfirmed)
.to contain_exactly(unconfirmed_user)
end
end
end
describe '#confirmed?' do
subject { Fabricate.build(:user, confirmed_at:) }
context 'when confirmed_at is set' do
let(:confirmed_at) { Time.now.utc }
it { is_expected.to be_confirmed }
end
context 'when confirmed_at is not set' do
let(:confirmed_at) { nil }
it { is_expected.to_not be_confirmed }
end
end
describe '#unconfirmed?' do
subject { Fabricate.build(:user, confirmed_at:) }
context 'when confirmed_at is set' do
let(:confirmed_at) { Time.now.utc }
it { is_expected.to_not be_unconfirmed }
end
context 'when confirmed_at is not set' do
let(:confirmed_at) { nil }
it { is_expected.to be_unconfirmed }
end
end
describe '#confirm' do
subject { user.confirm }
let(:new_email) { 'new-email@host.example' }
before { allow(TriggerWebhookWorker).to receive(:perform_async) }
context 'when the user is already confirmed' do
let!(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: true, unconfirmed_email: new_email) }
it 'sets email to unconfirmed_email and does not trigger web hook' do
expect { subject }
.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker)
.to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id)
end
end
context 'when the user is a new user' do
let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) }
context 'when the user does not require explicit approval' do
before { Setting.registrations_mode = 'open' }
it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do
expect { subject }
.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker)
.to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once
end
end
context 'when registrations mode is approved' do
before { Setting.registrations_mode = 'approved' }
context 'when the user is already approved' do
before { user.approve! }
it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do
expect { subject }
.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker)
.to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once
end
end
context 'when the user is not approved' do
it 'sets email to unconfirmed_email and does not trigger web hook' do
expect { subject }
.to change { user.reload.email }.to(new_email)
expect(TriggerWebhookWorker)
.to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id)
end
end
end
end
end
end