FEATURE: multiple use invite links (#9813)

This commit is contained in:
Arpit Jalan
2020-06-09 20:49:32 +05:30
committed by GitHub
parent 6b7a2d6d4d
commit 3094459cd9
48 changed files with 1280 additions and 351 deletions

View File

@@ -122,7 +122,7 @@ describe EmailToken do
Jobs.run_immediately!
end
fab!(:invite) { Fabricate(:invite, email: 'test@example.com', user_id: nil) }
fab!(:invite) { Fabricate(:invite, email: 'test@example.com') }
fab!(:invited_user) { Fabricate(:user, active: false, email: invite.email) }
let(:user_email_token) { invited_user.email_tokens.first }
let!(:confirmed_invited_user) { EmailToken.confirm(user_email_token.token) }

View File

@@ -6,7 +6,8 @@ describe InviteRedeemer do
describe '#create_user_from_invite' do
it "should be created correctly" do
user = InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com'), 'walter', 'Walter White')
invite = Fabricate(:invite, email: 'walter.white@email.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user).to be_active
@@ -17,7 +18,8 @@ describe InviteRedeemer do
it "can set the password and ip_address" do
password = 's3cure5tpasSw0rD'
ip_address = '192.168.1.1'
user = InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com'), 'walter', 'Walter White', password, nil, ip_address)
invite = Fabricate(:invite, email: 'walter.white@email.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', password: password, ip_address: ip_address)
expect(user).to have_password
expect(user.confirm_password?(password)).to eq(true)
expect(user.approved).to eq(true)
@@ -27,8 +29,9 @@ describe InviteRedeemer do
it "raises exception with record and errors" do
error = nil
invite = Fabricate(:invite, email: 'walter.white@email.com')
begin
InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com'), 'walter', 'Walter White', 'aaa')
InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', password: 'aaa')
rescue ActiveRecord::RecordInvalid => e
error = e
end
@@ -38,7 +41,8 @@ describe InviteRedeemer do
it "should unstage user" do
staged_user = Fabricate(:staged, email: 'staged@account.com', active: true, username: 'staged1', name: 'Stage Name')
user = InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'staged@account.com'), 'walter', 'Walter White')
invite = Fabricate(:invite, email: 'staged@account.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.id).to eq(staged_user.id)
expect(user.username).to eq('walter')
@@ -49,7 +53,9 @@ describe InviteRedeemer do
end
it "should not activate user invited via links" do
user = InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:not_required]), 'walter', 'Walter White')
invite = Fabricate(:invite, email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:not_required])
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user.email).to eq('walter.white@email.com')
@@ -63,7 +69,7 @@ describe InviteRedeemer do
let(:name) { 'john snow' }
let(:username) { 'kingofthenorth' }
let(:password) { 'know5nOthiNG' }
let(:invite_redeemer) { InviteRedeemer.new(invite, username, name) }
let(:invite_redeemer) { InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name) }
it "should redeem the invite if invited by staff" do
SiteSetting.must_approve_users = true
@@ -112,19 +118,13 @@ describe InviteRedeemer do
expect(user.approved).to eq(true)
end
it "should not blow up if invited_by user has been removed" do
it "should delete invite if invited_by user has been removed" do
invite.invited_by.destroy!
invite.reload
user = invite_redeemer.redeem
expect(user.name).to eq(name)
expect(user.username).to eq(username)
expect(user.invited_by).to eq(nil)
expect { invite.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it "can set password" do
user = InviteRedeemer.new(invite, username, name, password).redeem
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password).redeem
expect(user).to have_password
expect(user.confirm_password?(password)).to eq(true)
expect(user.approved).to eq(true)
@@ -137,7 +137,7 @@ describe InviteRedeemer do
required_field.id.to_s => 'value1',
optional_field.id.to_s => 'value2'
}
user = InviteRedeemer.new(invite, username, name, password, user_fields).redeem
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password, user_custom_fields: user_fields).redeem
expect(user).to be_present
expect(user.custom_fields["user_field_#{required_field.id}"]).to eq('value1')
@@ -147,7 +147,7 @@ describe InviteRedeemer do
it "adds user to group" do
group = Fabricate(:group, grant_trust_level: 2)
InvitedGroup.create(group_id: group.id, invite_id: invite.id)
user = InviteRedeemer.new(invite, username, name, password).redeem
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password).redeem
expect(user.group_users.count).to eq(4)
expect(user.trust_level).to eq(2)
@@ -160,7 +160,7 @@ describe InviteRedeemer do
user.email = "john@example.com"
user.save!
another_invite_redeemer = InviteRedeemer.new(invite, username, name)
another_invite_redeemer = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name)
another_user = another_invite_redeemer.redeem
expect(another_user).to eq(nil)
end
@@ -176,7 +176,40 @@ describe InviteRedeemer do
expect(user.invited_by).to eq(inviter)
expect(inviter.notifications.count).to eq(1)
expect(invite.redeemed_at).not_to eq(nil)
expect(invite.invited_users.first).to be_present
end
context 'invite_link' do
fab!(:invite_link) { Fabricate(:invite, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required]) }
let(:invite_redeemer) { InviteRedeemer.new(invite: invite_link, email: 'foo@example.com') }
it 'works as expected' do
user = invite_redeemer.redeem
invite_link.reload
expect(user.send_welcome_message).to eq(true)
expect(user.trust_level).to eq(SiteSetting.default_invitee_trust_level)
expect(user.active).to eq(false)
expect(invite_link.redemption_count).to eq(1)
end
it "should not redeem the invite if InvitedUser record already exists for email" do
user = invite_redeemer.redeem
invite_link.reload
another_invite_redeemer = InviteRedeemer.new(invite: invite_link, email: 'foo@example.com')
another_user = another_invite_redeemer.redeem
expect(another_user).to eq(nil)
end
it "should redeem the invite if InvitedUser record does not exists for email" do
user = invite_redeemer.redeem
invite_link.reload
another_invite_redeemer = InviteRedeemer.new(invite: invite_link, email: 'bar@example.com')
another_user = another_invite_redeemer.redeem
expect(another_user.is_a?(User)).to eq(true)
end
end
end

View File

@@ -22,7 +22,6 @@ describe Invite do
it "should not allow a user to invite themselves" do
expect(invite.email_already_exists).to eq(true)
end
end
context 'email validators' do
@@ -52,7 +51,6 @@ describe Invite do
end
context '#create' do
context 'saved' do
subject { Fabricate(:invite) }
@@ -81,7 +79,7 @@ describe Invite do
context 'links' do
it 'does not enqueue a job to email the invite' do
expect do
Invite.generate_invite_link(iceking, inviter, topic)
Invite.generate_single_use_invite_link(iceking, inviter, topic)
end.not_to change { Jobs::InviteEmail.jobs.size }
end
end
@@ -142,7 +140,7 @@ describe Invite do
it 'returns a new invite if the other has expired' do
SiteSetting.invite_expiry_days = 1
invite.update!(updated_at: 2.days.ago)
invite.update!(expires_at: 2.days.ago)
new_invite = Invite.invite_by_email(
'iceking@adventuretime.ooo', inviter, topic
@@ -166,7 +164,7 @@ describe Invite do
it 'resets expiry of a resent invite' do
SiteSetting.invite_expiry_days = 2
invite.update!(updated_at: 10.days.ago)
invite.update!(expires_at: 10.days.ago)
expect(invite).to be_expired
invite.resend_invite
@@ -183,17 +181,43 @@ describe Invite do
it 'does not mark emailed_status as sending after generating invite link' do
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:sending])
Invite.generate_invite_link(iceking, inviter, topic)
Invite.generate_single_use_invite_link(iceking, inviter, topic)
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:not_required])
Invite.invite_by_email(iceking, inviter, topic)
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:not_required])
Invite.generate_invite_link(iceking, inviter, topic)
Invite.generate_single_use_invite_link(iceking, inviter, topic)
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:not_required])
end
end
end
context 'invite links' do
let(:inviter) { Fabricate(:user) }
it "has sane defaults" do
Invite.generate_multiple_use_invite_link(invited_by: inviter)
invite_link = Invite.last
expect(invite_link.max_redemptions_allowed).to eq(5)
expect(invite_link.expires_at.to_date).to eq(1.month.from_now.to_date)
expect(invite_link.emailed_status).to eq(Invite.emailed_status_types[:not_required])
expect(invite_link.is_invite_link?).to eq(true)
end
it 'checks for max_redemptions_allowed range' do
SiteSetting.invite_link_max_redemptions_limit = 1000
expect do
Invite.generate_multiple_use_invite_link(invited_by: inviter, max_redemptions_allowed: 1001)
end.to raise_error(ActiveRecord::RecordInvalid)
end
it 'does not enqueue a job to email the invite' do
expect do
Invite.generate_multiple_use_invite_link(invited_by: inviter)
end.not_to change { Jobs::InviteEmail.jobs.size }
end
end
end
context 'an existing user' do
@@ -205,7 +229,6 @@ describe Invite do
Invite.invite_by_email(coding_horror.email, topic.user, topic)
end.to raise_error(Invite::UserExists)
end
end
context 'a staged user' do
@@ -228,7 +251,7 @@ describe Invite do
it 'wont redeem an expired invite' do
SiteSetting.invite_expiry_days = 10
invite.update_column(:updated_at, 20.days.ago)
invite.update_column(:expires_at, 20.days.ago)
expect(invite.redeem).to be_blank
end
@@ -253,12 +276,12 @@ describe Invite do
end
it 'does not delete already redeemed invite' do
redeemed_invite = Fabricate(:invite, email: invite.email, invited_by: another_user, redeemed_at: 1.day.ago)
redeemed_invite = Fabricate(:invite, email: invite.email, invited_by: another_user)
Fabricate(:invited_user, invite: invite, user: Fabricate(:user))
invite.redeem
used_invite = Invite.find_by(id: redeemed_invite.id)
expect(used_invite).not_to be_nil
end
end
context "as a moderator" do
@@ -308,7 +331,6 @@ describe Invite do
end
context 'simple invite' do
let!(:user) { invite.redeem }
it 'works correctly' do
@@ -324,7 +346,7 @@ describe Invite do
it 'works correctly' do
# has set the user_id attribute
expect(invite.user).to eq(user)
expect(invite.invited_users.first.user).to eq(user)
# returns true for redeemed
expect(invite).to be_redeemed
@@ -336,7 +358,6 @@ describe Invite do
end
end
end
end
context 'invited to topics' do
@@ -379,15 +400,36 @@ describe Invite do
end
end
end
context 'invite_link' do
fab!(:invite_link) { Fabricate(:invite, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required]) }
it 'works correctly' do
user = invite_link.redeem_invite_link(email: 'foo@example.com')
expect(user.is_a?(User)).to eq(true)
expect(user.send_welcome_message).to eq(true)
expect(user.trust_level).to eq(SiteSetting.default_invitee_trust_level)
expect(user.active).to eq(false)
invite_link.reload
expect(invite_link.redemption_count).to eq(1)
end
it 'returns error if user with that email already exists' do
user = Fabricate(:user)
expect do
invite_link.redeem_invite_link(email: user.email)
end.to raise_error(Invite::UserExists)
end
end
end
describe '.find_all_invites_from' do
describe '.find_all_pending_invites_from' do
context 'with user that has invited' do
it 'returns invites' do
inviter = Fabricate(:user)
invite = Fabricate(:invite, invited_by: inviter)
invites = Invite.find_all_invites_from(inviter)
invites = Invite.find_all_pending_invites_from(inviter)
expect(invites).to include invite
end
@@ -398,7 +440,7 @@ describe Invite do
user = Fabricate(:user)
Fabricate(:invite)
invites = Invite.find_all_invites_from(user)
invites = Invite.find_all_pending_invites_from(user)
expect(invites).to be_empty
end
@@ -408,17 +450,16 @@ describe Invite do
describe '.find_pending_invites_from' do
it 'returns pending invites only' do
inviter = Fabricate(:user)
Fabricate(
redeemed_invite = Fabricate(
:invite,
invited_by: inviter,
user_id: 123,
email: 'redeemed@example.com'
)
Fabricate(:invited_user, invite: redeemed_invite, user: Fabricate(:user))
pending_invite = Fabricate(
:invite,
invited_by: inviter,
user_id: nil,
email: 'pending@example.com'
)
@@ -437,24 +478,70 @@ describe Invite do
Fabricate(
:invite,
invited_by: inviter,
user_id: nil,
email: 'pending@example.com'
)
redeemed_invite = Fabricate(
:invite,
invited_by: inviter,
user_id: 123,
email: 'redeemed@example.com'
)
Fabricate(:invited_user, invite: redeemed_invite, user: Fabricate(:user))
invites = Invite.find_redeemed_invites_from(inviter)
expect(invites.length).to eq(1)
expect(invites.first).to eq redeemed_invite
expect(invites.first).to eq redeemed_invite.invited_users.first
expect(Invite.find_redeemed_invites_count(inviter)).to eq(1)
end
it 'returns redeemed invites for invite links' do
inviter = Fabricate(:user)
invite_link = Fabricate(
:invite,
invited_by: inviter,
max_redemptions_allowed: 50
)
Fabricate(:invited_user, invite: invite_link, user: Fabricate(:user))
Fabricate(:invited_user, invite: invite_link, user: Fabricate(:user))
Fabricate(:invited_user, invite: invite_link, user: Fabricate(:user))
invites = Invite.find_redeemed_invites_from(inviter)
expect(invites.length).to eq(3)
expect(Invite.find_redeemed_invites_count(inviter)).to eq(3)
end
end
describe '.find_links_invites_from' do
it 'returns invite links only' do
inviter = Fabricate(:user)
Fabricate(
:invite,
invited_by: inviter,
email: 'pending@example.com'
)
invite_link_1 = Fabricate(
:invite,
invited_by: inviter,
max_redemptions_allowed: 5
)
invite_link_2 = Fabricate(
:invite,
invited_by: inviter,
max_redemptions_allowed: 50
)
invites = Invite.find_links_invites_from(inviter)
expect(invites.length).to eq(2)
expect(invites.first).to eq(invite_link_2)
expect(invites.first.max_redemptions_allowed).to eq(50)
expect(Invite.find_links_invites_count(inviter)).to eq(2)
end
end
describe '.invalidate_for_email' do
@@ -466,14 +553,14 @@ describe Invite do
end
it 'sets the matching invite to be invalid' do
invite = Fabricate(:invite, invited_by: Fabricate(:user), user_id: nil, email: email)
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: email)
expect(subject).to eq(invite)
expect(subject.link_valid?).to eq(false)
expect(subject).to be_valid
end
it 'sets the matching invite to be invalid without being case-sensitive' do
invite = Fabricate(:invite, invited_by: Fabricate(:user), user_id: nil, email: 'invite.me2@Example.COM')
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: 'invite.me2@Example.COM')
result = described_class.invalidate_for_email('invite.me2@EXAMPLE.com')
expect(result).to eq(invite)
expect(result.link_valid?).to eq(false)
@@ -483,7 +570,7 @@ describe Invite do
describe '.redeem_from_email' do
fab!(:inviter) { Fabricate(:user) }
fab!(:invite) { Fabricate(:invite, invited_by: inviter, email: 'test@example.com', user_id: nil) }
fab!(:invite) { Fabricate(:invite, invited_by: inviter, email: 'test@example.com') }
fab!(:user) { Fabricate(:user, email: invite.email) }
it 'redeems the invite from email' do
@@ -497,7 +584,6 @@ describe Invite do
invite.reload
expect(invite).not_to be_redeemed
end
end
describe '.rescind_all_expired_invites_from' do
@@ -507,7 +593,7 @@ describe Invite do
invite_1 = Fabricate(:invite, invited_by: user)
invite_2 = Fabricate(:invite, invited_by: user)
expired_invite = Fabricate(:invite, invited_by: user)
expired_invite.update!(updated_at: 2.days.ago)
expired_invite.update!(expires_at: 2.days.ago)
Invite.rescind_all_expired_invites_from(user)
invite_1.reload
invite_2.reload