DEV: Add tests for invite system (#12524)

This commit is contained in:
Dan Ungureanu 2021-03-25 18:26:22 +02:00 committed by GitHub
parent 013b4f353b
commit dffc3a2f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 744 additions and 923 deletions

View File

@ -317,6 +317,11 @@ export default Component.extend({
}); });
}, },
@action
sendCloseModal() {
this.attrs.close();
},
@action @action
createInvite() { createInvite() {
if (this.disabled) { if (this.disabled) {
@ -388,7 +393,7 @@ export default Component.extend({
}, },
@action @action
generateInvitelink() { generateInviteLink() {
if (this.disabled) { if (this.disabled) {
return; return;
} }

View File

@ -87,7 +87,7 @@
{{#if inviteModel.finished}} {{#if inviteModel.finished}}
{{d-button {{d-button
class="btn-primary" class="btn-primary"
action=(route-action "closeModal") action=(action "sendCloseModal")
label="close"}} label="close"}}
{{else}} {{else}}
{{d-button {{d-button
@ -99,7 +99,7 @@
{{#if showCopyInviteButton}} {{#if showCopyInviteButton}}
{{d-button {{d-button
icon="link" icon="link"
action=(action "generateInvitelink") action=(action "generateInviteLink")
class="btn-primary generate-invite-link" class="btn-primary generate-invite-link"
disabled=disabledCopyLink disabled=disabledCopyLink
label="user.invited.generate_link"}} label="user.invited.generate_link"}}

View File

@ -0,0 +1,56 @@
import { set } from "@ember/object";
import { click, fillIn } from "@ember/test-helpers";
import User from "discourse/models/user";
import componentTest, {
setupRenderingTest,
} from "discourse/tests/helpers/component-test";
import pretender from "discourse/tests/helpers/create-pretender";
import {
discourseModule,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import hbs from "htmlbars-inline-precompile";
discourseModule("Integration | Component | invite-panel", function (hooks) {
setupRenderingTest(hooks);
componentTest("shows the invite link after it is generated", {
template: hbs`{{invite-panel panel=panel}}`,
beforeEach() {
pretender.get("/u/search/users", () => {
return [200, { "Content-Type": "application/json" }, { users: [] }];
});
pretender.post("/invites", () => {
return [
200,
{ "Content-Type": "application/json" },
{
link: "http://example.com/invites/92c297e886a0ca03089a109ccd6be155",
},
];
});
set(this.currentUser, "details", { can_invite_via_email: true });
this.set("panel", {
id: "invite",
model: { inviteModel: User.create(this.currentUser) },
});
},
async test(assert) {
const input = selectKit(".invite-user-input");
await input.expand();
await fillIn(".invite-user-input .filter-input", "eviltrout@example.com");
await input.selectRowByValue("eviltrout@example.com");
assert.ok(queryAll(".send-invite:disabled").length === 0);
await click(".generate-invite-link");
assert.equal(
find(".invite-link-input")[0].value,
"http://example.com/invites/92c297e886a0ca03089a109ccd6be155"
);
},
});
});

View File

@ -247,7 +247,6 @@ class InvitesController < ApplicationController
raise Discourse::InvalidParameters.new(:email) if invite.blank? raise Discourse::InvalidParameters.new(:email) if invite.blank?
invite.resend_invite invite.resend_invite
render json: success_json render json: success_json
rescue RateLimiter::LimitExceeded rescue RateLimiter::LimitExceeded
render_json_error(I18n.t("rate_limiter.slow_down")) render_json_error(I18n.t("rate_limiter.slow_down"))
end end

View File

@ -70,7 +70,7 @@ class Invite < ActiveRecord::Base
end end
def redeemable? def redeemable?
!redeemed? && !expired? && !destroyed? && link_valid? !redeemed? && !expired? && !deleted_at? && !destroyed? && link_valid?
end end
def redeemed? def redeemed?

View File

@ -3,194 +3,90 @@
require 'rails_helper' require 'rails_helper'
describe Invite do describe Invite do
fab!(:user) { Fabricate(:user) }
context 'validators' do
it { is_expected.to validate_presence_of :invited_by_id } it { is_expected.to validate_presence_of :invited_by_id }
it { is_expected.to rate_limit } it { is_expected.to rate_limit }
let(:iceking) { 'iceking@adventuretime.ooo' } it 'allows invites with valid emails' do
invite = Fabricate.build(:invite, email: 'test@example.com', invited_by: user)
context 'user validators' do
fab!(:coding_horror) { Fabricate(:coding_horror) }
fab!(:user) { Fabricate(:user) }
let(:invite) { Invite.create(email: user.email, invited_by: coding_horror) }
it "should not allow an invite with the same email as an existing user" do
expect(invite).not_to be_valid
end
it "should not allow a user to invite themselves" do
expect(invite.email_already_exists).to eq(true)
end
end
context 'email validators' do
fab!(:coding_horror) { Fabricate(:coding_horror) }
it "should not allow an invite with unformatted email address" do
invite = Fabricate.build(:invite, email: "John Doe <john.doe@example.com>")
expect(invite.valid?).to eq(false)
expect(invite.errors.details[:email].first[:error]).to eq(I18n.t("user.email.invalid"))
end
it "should not allow an invite with blocklisted email" do
invite = Invite.create(email: "test@mailinator.com", invited_by: coding_horror)
expect(invite).not_to be_valid
end
it "should allow an invite with non-blocklisted email" do
invite = Fabricate(:invite, email: "test@mail.com", invited_by: coding_horror)
expect(invite).to be_valid expect(invite).to be_valid
end end
it "should not allow an invalid email address" do it 'does not allow invites with invalid emails' do
invite = Fabricate.build(:invite, email: 'John Doe <john.doe@example.com>')
expect(invite.valid?).to eq(false)
expect(invite.errors.details[:email].first[:error]).to eq(I18n.t('user.email.invalid'))
end
it 'does not allow an invite with the same email as an existing user' do
invite = Fabricate.build(:invite, email: Fabricate(:user).email, invited_by: user)
expect(invite).not_to be_valid
invite = Fabricate.build(:invite, email: user.email, invited_by: user)
expect(invite).not_to be_valid
end
it 'does not allow an invite with blocked email' do
invite = Invite.create(email: 'test@mailinator.com', invited_by: user)
expect(invite).not_to be_valid
end
it 'does not allow an invalid email address' do
invite = Fabricate.build(:invite, email: 'asjdso') invite = Fabricate.build(:invite, email: 'asjdso')
expect(invite.valid?).to eq(false) expect(invite.valid?).to eq(false)
expect(invite.errors.details[:email].first[:error]).to eq(I18n.t("user.email.invalid")) expect(invite.errors.details[:email].first[:error]).to eq(I18n.t('user.email.invalid'))
end end
end end
context '#create' do context '::generate' do
context 'saved' do it 'saves an invites' do
subject { Fabricate(:invite) } invite = Invite.generate(user, email: 'TEST@EXAMPLE.COM')
expect(invite.invite_key).to be_present
it "works" do expect(invite.email).to eq('test@example.com')
expect(subject.invite_key).to be_present
expect(subject.email_already_exists).to eq(false)
end end
it 'should store a lower case version of the email' do it 'can succeed for staged users emails' do
expect(subject.email).to eq(iceking) Fabricate(:staged, email: 'test@example.com')
end invite = Invite.generate(user, email: 'test@example.com')
expect(invite.email).to eq('test@example.com')
end end
context 'to a topic' do it 'raises an error when inviting an existing user' do
fab!(:topic) { Fabricate(:topic) } expect { Invite.generate(user, email: user.email) }
let(:inviter) { topic.user } .to raise_error(Invite::UserExists)
end
context 'email' do context 'via email' do
it 'enqueues a job to email the invite' do it 'enqueues a job to email the invite' do
expect do invite = Invite.generate(user, email: 'test@example.com')
Invite.generate(inviter, email: iceking, topic: topic) expect(invite.emailed_status).to eq(Invite.emailed_status_types[:sending])
end.to change { Jobs::InviteEmail.jobs.size } expect(Jobs::InviteEmail.jobs.size).to eq(1)
end
end end
context 'links' do it 'can skip the job to email the invite' do
it 'does not enqueue a job to email the invite' do invite = Invite.generate(user, email: 'test@example.com', skip_email: true)
expect { Invite.generate(inviter, email: iceking, topic: topic, skip_email: true) } expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required])
.not_to change { Jobs::InviteEmail.jobs.size } expect(Jobs::InviteEmail.jobs.size).to eq(0)
end
end end
context 'destroyed' do it 'can invite the same user after their invite was destroyed' do
it "can invite the same user after their invite was destroyed" do Invite.generate(user, email: 'test@example.com').destroy!
Invite.generate(inviter, email: iceking, topic: topic).destroy! invite = Invite.generate(user, email: 'test@example.com')
invite = Invite.generate(inviter, email: iceking, topic: topic)
expect(invite).to be_present expect(invite).to be_present
end end
end end
context 'after created' do context 'via link' do
let(:invite) { Invite.generate(inviter, email: iceking, topic: topic) } it 'does not enqueue a job to email the invite' do
invite = Invite.generate(user, skip_email: true)
it 'belongs to the topic' do expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required])
expect(topic.invites).to eq([invite]) expect(Jobs::InviteEmail.jobs.size).to eq(0)
expect(invite.topics).to eq([topic])
end end
context 'when added by another user' do it 'can be created' do
fab!(:coding_horror) { Fabricate(:coding_horror) } invite = Invite.generate(user, max_redemptions_allowed: 5)
let(:new_invite) do
Invite.generate(coding_horror, email: iceking, topic: topic)
end
it 'returns a different invite' do
expect(new_invite).not_to eq(invite)
expect(new_invite.invite_key).not_to eq(invite.invite_key)
expect(new_invite.topics).to eq([topic])
end
end
context 'when adding a duplicate' do
it 'returns the original invite' do
%w{
iceking@adventuretime.ooo
iceking@ADVENTURETIME.ooo
ICEKING@adventuretime.ooo
}.each do |email|
expect(Invite.generate(inviter, email: email, topic: topic)).to eq(invite)
end
end
it 'updates timestamp of existing invite' do
freeze_time
invite.update!(created_at: 10.days.ago)
resend_invite = Invite.generate(inviter, email: 'iceking@adventuretime.ooo', topic: topic)
expect(resend_invite.created_at).to eq_time(Time.zone.now)
end
it 'returns a new invite if the other has expired' do
SiteSetting.invite_expiry_days = 1
invite.update!(expires_at: 2.days.ago)
new_invite = Invite.generate(inviter, email: 'iceking@adventuretime.ooo', topic: topic)
expect(new_invite).not_to eq(invite)
expect(new_invite).not_to be_expired
end
end
context 'when adding to another topic' do
fab!(:another_topic) { Fabricate(:topic, user: topic.user) }
it 'should be the same invite' do
new_invite = Invite.generate(inviter, email: iceking, topic: another_topic)
expect(new_invite).to eq(invite)
expect(another_topic.invites).to eq([invite])
expect(invite.topics).to match_array([topic, another_topic])
end
end
it 'resets expiry of a resent invite' do
SiteSetting.invite_expiry_days = 2
invite.update!(invalidated_at: 10.days.ago, expires_at: 10.days.ago)
expect(invite).to be_expired
invite.resend_invite
expect(invite.invalidated_at).to be_nil
expect(invite).not_to be_expired
end
it 'correctly marks invite emailed_status for email invites' do
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:sending])
Invite.generate(inviter, email: iceking, topic: topic)
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:sending])
end
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(inviter, email: iceking, topic: topic, emailed_status: Invite.emailed_status_types[:not_required])
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:not_required])
Invite.generate(inviter, email: iceking, topic: topic)
expect(invite.reload.emailed_status).to eq(Invite.emailed_status_types[:not_required])
Invite.generate(inviter, email: iceking, topic: topic, emailed_status: Invite.emailed_status_types[:not_required])
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 "can be created" do
invite = Invite.generate(inviter, max_redemptions_allowed: 5)
expect(invite.max_redemptions_allowed).to eq(5) expect(invite.max_redemptions_allowed).to eq(5)
expect(invite.expires_at.to_date).to eq(SiteSetting.invite_expiry_days.days.from_now.to_date) expect(invite.expires_at.to_date).to eq(SiteSetting.invite_expiry_days.days.from_now.to_date)
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required]) expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required])
@ -198,342 +94,285 @@ describe Invite do
end end
it 'checks for max_redemptions_allowed range' do it 'checks for max_redemptions_allowed range' do
SiteSetting.invite_link_max_redemptions_limit = 1000 SiteSetting.invite_link_max_redemptions_limit_users = 3
expect { Invite.generate(inviter, max_redemptions_allowed: 1001) } expect { Invite.generate(user, max_redemptions_allowed: 4) }.to raise_error(ActiveRecord::RecordInvalid)
.to raise_error(ActiveRecord::RecordInvalid)
SiteSetting.invite_link_max_redemptions_limit = 3
expect { Invite.generate(Fabricate(:admin), max_redemptions_allowed: 4) }.to raise_error(ActiveRecord::RecordInvalid)
end
end end
it 'does not enqueue a job to email the invite' do context 'when sending an invite to the same user' do
expect { Invite.generate(inviter) } fab!(:invite) { Invite.generate(user, email: 'test@example.com') }
.not_to change { Jobs::InviteEmail.jobs.size }
it 'returns the original invite' do
%w{test@EXAMPLE.com TEST@example.com}.each do |email|
expect(Invite.generate(user, email: email)).to eq(invite)
end
end
it 'updates timestamp of existing invite' do
freeze_time
invite.update!(created_at: 10.days.ago)
resend_invite = Invite.generate(user, email: 'test@example.com')
expect(resend_invite).to eq(invite)
expect(resend_invite.created_at).to eq_time(Time.zone.now)
end
it 'returns a new invite if the other has expired' do
SiteSetting.invite_expiry_days = 1
invite.update!(expires_at: 2.days.ago)
new_invite = Invite.generate(user, email: 'test@example.com')
expect(new_invite).not_to eq(invite)
expect(new_invite).not_to be_expired
end
it 'returns a new invite when invited by a different user' do
invite = Invite.generate(user, email: 'test@example.com')
expect(invite.email).to eq('test@example.com')
another_invite = Invite.generate(Fabricate(:user), email: 'test@example.com')
expect(another_invite.email).to eq('test@example.com')
expect(invite.invite_key).not_to eq(another_invite.invite_key)
end
end
context 'invite to a topic' do
fab!(:topic) { Fabricate(:topic) }
let(:invite) { Invite.generate(topic.user, email: 'test@example.com', topic: topic) }
it 'belongs to the topic' do
expect(topic.invites).to contain_exactly(invite)
expect(invite.topics).to contain_exactly(topic)
end
context 'when adding to another topic' do
fab!(:another_topic) { Fabricate(:topic, user: topic.user) }
it 'should be the same invite' do
new_invite = Invite.generate(topic.user, email: 'test@example.com', topic: another_topic)
expect(invite).to eq(new_invite)
expect(invite.topics).to contain_exactly(topic, another_topic)
expect(topic.invites).to contain_exactly(invite)
expect(another_topic.invites).to contain_exactly(invite)
end
end end
end end
end end
context 'an existing user' do context '#redeem' do
fab!(:topic) { Fabricate(:topic, category_id: nil, archetype: 'private_message') }
fab!(:coding_horror) { Fabricate(:coding_horror) }
it "raises the right error" do
expect { Invite.generate(topic.user, email: coding_horror.email, topic: topic) }
.to raise_error(Invite::UserExists)
end
end
context 'a staged user' do
it 'creates an invite for a staged user' do
Fabricate(:staged, email: 'staged@account.com')
invite = Invite.generate(Fabricate(:coding_horror), email: 'staged@account.com')
expect(invite).to be_valid
expect(invite.email).to eq('staged@account.com')
end
end
context '.redeem' do
fab!(:invite) { Fabricate(:invite) } fab!(:invite) { Fabricate(:invite) }
it 'works' do
user = invite.redeem
expect(invite.invited_users.map(&:user)).to contain_exactly(user)
expect(user.is_a?(User)).to eq(true)
expect(user.trust_level).to eq(SiteSetting.default_invitee_trust_level)
expect(user.send_welcome_message).to eq(true)
expect(invite.reload.redemption_count).to eq(1)
expect(invite.redeem).to be_blank
end
it 'creates a notification for the invitee' do it 'creates a notification for the invitee' do
expect { invite.redeem }.to change(Notification, :count) expect { invite.redeem }.to change { Notification.count }
end end
it 'wont redeem an expired invite' do it 'does not work with expired invites' do
SiteSetting.invite_expiry_days = 10 invite.update!(expires_at: 1.day.ago)
invite.update_column(:expires_at, 20.days.ago)
expect(invite.redeem).to be_blank expect(invite.redeem).to be_blank
end end
it 'wont redeem a deleted invite' do it 'does not work with deleted invites' do
invite.destroy invite.trash!
expect(invite.redeem).to be_blank expect(invite.redeem).to be_blank
end end
it "won't redeem an invalidated invite" do it 'does not work with deleted invites' do
invite.invalidated_at = 1.day.ago invite.destroy!
expect(invite.redeem).to be_blank expect(invite.redeem).to be_blank
end end
context "deletes duplicate invites" do it 'does not work with invalidated invites' do
fab!(:another_user) { Fabricate(:user) } invite.update(invalidated_at: 1.day.ago)
expect(invite.redeem).to be_blank
it 'delete duplicate invite' do
another_invite = Fabricate(:invite, email: invite.email, invited_by: another_user)
invite.redeem
duplicate_invite = Invite.find_by(id: another_invite.id)
expect(duplicate_invite).to be_nil
end end
it 'does not delete already redeemed invite' do it 'deletes duplicate invite' do
redeemed_invite = Fabricate(:invite, email: invite.email, invited_by: another_user) another_invite = Fabricate(:invite, email: invite.email, invited_by: Fabricate(:user))
Fabricate(:invited_user, invite: invite, user: Fabricate(:user)) another_redeemed_invite = Fabricate(:invite, email: invite.email, invited_by: Fabricate(:user))
invite.redeem Fabricate(:invited_user, invite: another_redeemed_invite)
used_invite = Invite.find_by(id: redeemed_invite.id)
expect(used_invite).not_to be_nil user = invite.redeem
end expect(user).not_to eq(nil)
expect(Invite.find_by(id: another_invite.id)).to eq(nil)
expect(Invite.find_by(id: another_redeemed_invite.id)).not_to eq(nil)
end end
context "as a moderator" do context 'as a moderator' do
it "will give the user a moderator flag" do it 'will give the user a moderator flag' do
invite.invited_by = Fabricate(:admin) invite.update!(moderator: true, invited_by: Fabricate(:admin))
invite.moderator = true
invite.save
user = invite.redeem user = invite.redeem
expect(user).to be_moderator expect(user).to be_moderator
end end
it "will not give the user a moderator flag if the inviter is not staff" do it 'will not give the user a moderator flag if the inviter is not staff' do
invite.moderator = true invite.update!(moderator: true)
invite.save
user = invite.redeem user = invite.redeem
expect(user).not_to be_moderator expect(user).not_to be_moderator
end end
end end
context "when inviting to groups" do context 'when inviting to groups' do
it "add the user to the correct groups" do it 'add the user to the correct groups' do
group = Fabricate(:group) group = Fabricate(:group)
group.add_owner(invite.invited_by) group.add_owner(invite.invited_by)
invite.invited_groups.build(group_id: group.id) invite.invited_groups.create!(group_id: group.id)
invite.save
user = invite.redeem user = invite.redeem
expect(user.groups.count).to eq(1) expect(user.groups).to contain_exactly(group)
end end
end end
context "invite trust levels" do it 'activates user when must_approve_users? is enabled' do
it "returns the trust level in default_invitee_trust_level" do
SiteSetting.default_invitee_trust_level = TrustLevel[3]
expect(invite.redeem.trust_level).to eq(TrustLevel[3])
end
end
context 'inviting when must_approve_users? is enabled' do
it 'correctly activates accounts' do
invite.invited_by = Fabricate(:admin)
SiteSetting.must_approve_users = true SiteSetting.must_approve_users = true
invite.invited_by = Fabricate(:admin)
user = invite.redeem user = invite.redeem
expect(user.approved?).to eq(true) expect(user.approved?).to eq(true)
end end
end
context 'simple invite' do context 'invite to a topic' do
let!(:user) { invite.redeem } fab!(:topic) { Fabricate(:private_message_topic) }
fab!(:another_topic) { Fabricate(:private_message_topic) }
it 'works correctly' do
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)
end
context 'after redeeming' do
before do before do
invite.reload invite.topic_invites.create!(topic: topic)
end end
it 'works correctly' do it 'adds the user to topic_users' do
# has set the user_id attribute invited_user = invite.redeem
expect(invite.invited_users.first.user).to eq(user) expect(invited_user).not_to eq(nil)
expect(topic.reload.allowed_users.include?(invited_user)).to eq(true)
# returns true for redeemed expect(Guardian.new(invited_user).can_see?(topic)).to eq(true)
expect(invite).to be_redeemed
end
context 'again' do
it 'will not redeem twice' do
expect(invite.redeem).to be_blank
end
end
end
end
context 'invited to topics' do
fab!(:tl2_user) { Fabricate(:user, trust_level: 2) }
fab!(:topic) { Fabricate(:private_message_topic, user: tl2_user) }
let!(:invite) do
topic.invite(topic.user, 'jake@adventuretime.ooo')
Invite.find_by(invited_by_id: topic.user)
end
context 'redeem topic invite' do
it 'adds the user to the topic_users' do
user = invite.redeem
topic.reload
expect(topic.allowed_users.include?(user)).to eq(true)
expect(Guardian.new(user).can_see?(topic)).to eq(true)
end
end
context 'invited by another user to the same topic' do
fab!(:another_tl2_user) { Fabricate(:user, trust_level: 2) }
let!(:another_invite) { topic.invite(another_tl2_user, 'jake@adventuretime.ooo') }
let!(:user) { invite.redeem }
it 'adds the user to the topic_users' do
topic.reload
expect(topic.allowed_users.include?(user)).to eq(true)
end
end
context 'invited by another user to a different topic' do
let!(:user) { invite.redeem }
fab!(:another_tl2_user) { Fabricate(:user, trust_level: 2) }
fab!(:another_topic) { Fabricate(:topic, user: another_tl2_user) }
it 'adds the user to the topic_users of the first topic' do
expect(another_topic.invite(another_tl2_user, user.username)).to be_truthy # invited via username
expect(topic.allowed_users.include?(user)).to eq(true)
end end
end end
end end
context 'invite_link' do describe '#redeem_from_email' 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]) } fab!(:invite) { Fabricate(:invite, email: 'test@example.com') }
it 'works correctly' do
user = invite_link.redeem(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)
expect(invite_link.reload.redemption_count).to eq(1)
end
it 'returns error if user with that email already exists' do
user = Fabricate(:user)
expect { invite_link.redeem(email: user.email) }.to raise_error(Invite::UserExists)
end
end
end
describe '.pending' do
context 'with user that has invited' do
it 'returns invites' do
inviter = Fabricate(:user)
invite = Fabricate(:invite, invited_by: inviter)
expect(Invite.pending(inviter)).to include(invite)
end
end
context 'with user that has not invited' do
it 'does not return invites' do
user = Fabricate(:user)
Fabricate(:invite)
expect(Invite.pending(user)).to be_empty
end
end
it 'returns pending invites only' do
inviter = Fabricate(:user)
redeemed_invite = Fabricate(:invite, invited_by: inviter, email: 'redeemed@example.com')
redeemed_invite.redeem
pending_invite = Fabricate(:invite, invited_by: inviter, email: 'pending@example.com')
pending_link_invite = Fabricate(:invite, invited_by: inviter, max_redemptions_allowed: 5)
expired_invite = Fabricate(:invite, invited_by: inviter, email: 'expired@example.com', expires_at: 1.day.ago)
expect(Invite.pending(inviter)).to contain_exactly(pending_invite, pending_link_invite)
end
end
describe '.redeemed_users' do
it 'returns redeemed invites only' do
inviter = Fabricate(:user)
Fabricate(:invite, invited_by: inviter, email: 'pending@example.com')
redeemed_invite = Fabricate(:invite, invited_by: inviter, email: 'redeemed@example.com')
Fabricate(:invited_user, invite: redeemed_invite, user: Fabricate(:user))
expect(Invite.redeemed_users(inviter)).to contain_exactly(redeemed_invite.invited_users.first)
end
it 'returns redeemed invites even if trashed' do
inviter = Fabricate(:user)
redeemed_invite = Fabricate(:invite, invited_by: inviter, email: 'redeemed@example.com')
Fabricate(:invited_user, invite: redeemed_invite, user: Fabricate(:user))
redeemed_invite.trash!
expect(Invite.redeemed_users(inviter)).to contain_exactly(redeemed_invite.invited_users.first)
end
it 'returns redeemed invites for invite links' do
inviter = Fabricate(:user)
invite_link = Fabricate(:invite, invited_by: inviter, max_redemptions_allowed: 5)
redeemed = [
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))
]
expect(Invite.redeemed_users(inviter)).to match_array(redeemed)
end
end
describe '.invalidate_for_email' do
let(:email) { 'invite.me@example.com' }
subject { described_class.invalidate_for_email(email) }
it 'returns nil if there is no invite for the given email' do
expect(subject).to eq(nil)
end
it 'sets the matching invite to be invalid' do
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), 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)
expect(result).to be_valid
end
end
describe '.redeem_from_email' do
fab!(:inviter) { Fabricate(:user) }
fab!(:invite) { Fabricate(:invite, invited_by: inviter, email: 'test@example.com') }
fab!(:user) { Fabricate(:user, email: invite.email) } fab!(:user) { Fabricate(:user, email: invite.email) }
it 'redeems the invite from email' do it 'redeems the invite from email' do
Invite.redeem_from_email(user.email) Invite.redeem_from_email(user.email)
invite.reload expect(invite.reload).to be_redeemed
expect(invite).to be_redeemed
end end
it 'does not redeem the invite if email does not match' do it 'does not redeem the invite if email does not match' do
Invite.redeem_from_email('test24@example.com') Invite.redeem_from_email('test2@example.com')
invite.reload expect(invite.reload).not_to be_redeemed
expect(invite).not_to be_redeemed
end end
end end
describe '#emailed_status_types' do context 'scopes' do
context "verify enum sequence" do fab!(:inviter) { Fabricate(:user) }
before do
@emailed_status_types = Invite.emailed_status_types fab!(:pending_invite) { Fabricate(:invite, invited_by: inviter, email: 'pending@example.com') }
fab!(:pending_link_invite) { Fabricate(:invite, invited_by: inviter, max_redemptions_allowed: 5) }
fab!(:pending_invite_from_another_user) { Fabricate(:invite) }
fab!(:expired_invite) { Fabricate(:invite, invited_by: inviter, email: 'expired@example.com', expires_at: 1.day.ago) }
fab!(:redeemed_invite) { Fabricate(:invite, invited_by: inviter, email: 'redeemed@example.com') }
let!(:redeemed_invite_user) { redeemed_invite.redeem }
fab!(:partially_redeemed_invite) { Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5) }
let!(:partially_redeemed_invite_user) { partially_redeemed_invite.redeem(email: 'partially_redeemed_invite@example.com') }
fab!(:redeemed_and_expired_invite) { Fabricate(:invite, invited_by: inviter, email: 'redeemed_and_expired@example.com') }
let!(:redeemed_and_expired_invite_user) do
user = redeemed_and_expired_invite.redeem
redeemed_and_expired_invite.update!(expires_at: 1.day.ago)
user
end end
it "'not_required' should be at 0 position" do fab!(:partially_redeemed_and_expired_invite) { Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5) }
expect(@emailed_status_types[:not_required]).to eq(0) let!(:partially_redeemed_and_expired_invite_user) do
user = partially_redeemed_and_expired_invite.redeem(email: 'partially_redeemed_and_expired_invite@example.com')
partially_redeemed_and_expired_invite.update!(expires_at: 1.day.ago)
user
end end
it "'sent' should be at 4th position" do describe '#pending' do
expect(@emailed_status_types[:sent]).to eq(4) it 'returns pending invites only' do
expect(Invite.pending(inviter)).to contain_exactly(
pending_invite, pending_link_invite, partially_redeemed_invite
)
end end
end end
describe '#expired' do
it 'returns expired invites only' do
expect(Invite.expired(inviter)).to contain_exactly(
expired_invite, partially_redeemed_and_expired_invite
)
end
end
describe '#redeemed_users' do
it 'returns redeemed users' do
expect(Invite.redeemed_users(inviter).map(&:user)).to contain_exactly(
redeemed_invite_user, partially_redeemed_invite_user, redeemed_and_expired_invite_user, partially_redeemed_and_expired_invite_user
)
end
it 'returns redeemed users for trashed invites' do
[redeemed_invite, partially_redeemed_invite, redeemed_and_expired_invite, partially_redeemed_and_expired_invite].each(&:trash!)
expect(Invite.redeemed_users(inviter).map(&:user)).to contain_exactly(
redeemed_invite_user, partially_redeemed_invite_user, redeemed_and_expired_invite_user, partially_redeemed_and_expired_invite_user
)
end
end
end
describe '#invalidate_for_email' do
it 'returns nil if there is no invite for the given email' do
invite = Invite.invalidate_for_email('test@example.com')
expect(invite).to eq(nil)
end
it 'sets the matching invite to be invalid' do
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: 'test@example.com')
result = Invite.invalidate_for_email('test@example.com')
expect(result).to eq(invite)
expect(result.link_valid?).to eq(false)
end
it 'sets the matching invite to be invalid without being case-sensitive' do
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: 'test@Example.COM')
result = Invite.invalidate_for_email('test@EXAMPLE.com')
expect(result).to eq(invite)
expect(result.link_valid?).to eq(false)
end
end
describe '#resend_email' do
fab!(:invite) { Fabricate(:invite) }
it 'resets expiry of a resent invite' do
invite.update!(invalidated_at: 10.days.ago, expires_at: 10.days.ago)
expect(invite).to be_expired
invite.resend_invite
expect(invite).not_to be_expired
expect(invite.invalidated_at).to be_nil
end
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -3377,6 +3377,7 @@ RSpec.describe TopicsController do
end.to change { Invite.where(invited_by_id: user.id).count }.by(1) end.to change { Invite.where(invited_by_id: user.id).count }.by(1)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Jobs::InviteEmail.jobs.first['args'].first['invite_to_topic']).to be_truthy
end end
end end

View File

@ -1,24 +0,0 @@
import EmberObject, { set } from "@ember/object";
import componentTest from "helpers/component-test";
import { moduleForComponent } from "ember-qunit";
import { queryAll } from "discourse/tests/helpers/qunit-helpers";
moduleForComponent("invite-panel", { integration: true });
componentTest("can_invite_via_email", {
template: "{{invite-panel panel=panel}}",
beforeEach() {
set(this.currentUser, "details", { can_invite_via_email: true });
const inviteModel = JSON.parse(JSON.stringify(this.currentUser));
this.set("panel", {
id: "invite",
model: { inviteModel: EmberObject.create(inviteModel) },
});
},
async test(assert) {
await fillIn(".invite-user-input", "eviltrout@example.com");
assert.ok(queryAll(".send-invite:disabled").length === 0);
},
});