From bc0df90b8d49b4e75f6b2ca17df7557986e0cee3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 5 Oct 2026 17:17:59 +0000 Subject: [PATCH] Add "invite"-related coverage for user model (#40495) --- spec/models/user_spec.rb | 100 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f082767e5d0..a80ed908e8b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -24,7 +24,10 @@ RSpec.describe User do describe 'Associations' do it { is_expected.to belong_to(:account).required } + it { is_expected.to belong_to(:invite).optional.counter_cache(:uses) } + it { is_expected.to have_many(:invites).inverse_of(:user).dependent(false) } it { is_expected.to have_many(:login_activities) } + it { is_expected.to have_one(:invite_request).inverse_of(:user).dependent(:destroy).class_name(UserInviteRequest) } end describe 'Validations' do @@ -46,6 +49,18 @@ RSpec.describe User do it { is_expected.to allow_value(10.seconds.ago).for(:registration_form_time) } it { is_expected.to_not allow_value(1.second.ago).for(:registration_form_time).against(:base) } end + + context 'when invite request is required by settings' do + before do + Setting.require_invite_text = true + Setting.registrations_mode = 'none' + subject.invite_id = nil + subject.external = false + subject.bypass_registration_checks = false + end + + it { is_expected.to_not allow_values(nil).for(:invite_request) } + end end describe 'Normalizations' do @@ -65,6 +80,10 @@ RSpec.describe User do end end + describe 'Nesting' do + it { is_expected.to accept_nested_attributes_for(:invite_request) } + end + describe 'scopes', :inline_jobs do describe 'recent' do it 'returns an array of recent users ordered by id' do @@ -220,6 +239,87 @@ RSpec.describe User do end end + describe '#invited?' do + subject { user.invited? } + + let(:user) { Fabricate.build :user } + + context 'when invite is present' do + before { user.invite = Fabricate(:invite) } + + it { is_expected.to be(true) } + end + + context 'when invite is not present' do + before { user.invite = nil } + + it { is_expected.to be(false) } + end + end + + describe '#valid_invitation?' do + subject { user.valid_invitation? } + + let(:user) { Fabricate.build :user } + let(:invite) { Fabricate :invite } + + context 'when invite is present' do + before { user.invite = invite } + + context 'when invite is valid for use' do + before { allow(invite).to receive(:valid_for_use?).and_return(true) } + + it { is_expected.to be(true) } + end + + context 'when invite is not valid for use' do + before { allow(invite).to receive(:valid_for_use?).and_return(false) } + + it { is_expected.to be(false) } + end + end + end + + describe '#invite_code=' do + let(:user) { Fabricate.build :user } + + before { user.invite_code = code } + + context 'when code is nil' do + let(:code) { nil } + + it 'sets invite code to nil and does not populate invite' do + expect(user.invite) + .to be_nil + expect(user.invite_code) + .to be_nil + end + end + + context 'when code is present but does not match an invite' do + let(:code) { 'ABC123' } + + it 'sets invite code to value and does not populate invite' do + expect(user.invite) + .to be_nil + expect(user.invite_code) + .to eq(code) + end + end + + context 'when code is present and does match an invite' do + let(:code) { invite.code } + let(:invite) { Fabricate :invite } + + it 'sets invite code to value and populates invite' do + expect(user.invite) + .to eq(invite) + expect(user.invite_code) + .to eq(code) + end + end + end + describe '#approve!' do subject { user.approve! }