mirror of
https://github.com/discourse/discourse.git
synced 2025-03-26 23:35:51 +08:00
DEV: Add tests for invite system (#12524)
This commit is contained in:
parent
013b4f353b
commit
dffc3a2f8e
app
assets/javascripts/discourse
app
tests/integration/components
controllers
models
spec
test/javascripts/integration/components
@ -317,6 +317,11 @@ export default Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
sendCloseModal() {
|
||||
this.attrs.close();
|
||||
},
|
||||
|
||||
@action
|
||||
createInvite() {
|
||||
if (this.disabled) {
|
||||
@ -388,7 +393,7 @@ export default Component.extend({
|
||||
},
|
||||
|
||||
@action
|
||||
generateInvitelink() {
|
||||
generateInviteLink() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@
|
||||
{{#if inviteModel.finished}}
|
||||
{{d-button
|
||||
class="btn-primary"
|
||||
action=(route-action "closeModal")
|
||||
action=(action "sendCloseModal")
|
||||
label="close"}}
|
||||
{{else}}
|
||||
{{d-button
|
||||
@ -99,7 +99,7 @@
|
||||
{{#if showCopyInviteButton}}
|
||||
{{d-button
|
||||
icon="link"
|
||||
action=(action "generateInvitelink")
|
||||
action=(action "generateInviteLink")
|
||||
class="btn-primary generate-invite-link"
|
||||
disabled=disabledCopyLink
|
||||
label="user.invited.generate_link"}}
|
||||
|
@ -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"
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
@ -247,7 +247,6 @@ class InvitesController < ApplicationController
|
||||
raise Discourse::InvalidParameters.new(:email) if invite.blank?
|
||||
invite.resend_invite
|
||||
render json: success_json
|
||||
|
||||
rescue RateLimiter::LimitExceeded
|
||||
render_json_error(I18n.t("rate_limiter.slow_down"))
|
||||
end
|
||||
|
@ -70,7 +70,7 @@ class Invite < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def redeemable?
|
||||
!redeemed? && !expired? && !destroyed? && link_valid?
|
||||
!redeemed? && !expired? && !deleted_at? && !destroyed? && link_valid?
|
||||
end
|
||||
|
||||
def redeemed?
|
||||
|
@ -3,194 +3,90 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Invite do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
||||
it { is_expected.to validate_presence_of :invited_by_id }
|
||||
context 'validators' do
|
||||
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' }
|
||||
|
||||
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)
|
||||
it 'allows invites with valid emails' do
|
||||
invite = Fabricate.build(:invite, email: 'test@example.com', invited_by: user)
|
||||
expect(invite).to be_valid
|
||||
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')
|
||||
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
|
||||
|
||||
context '#create' do
|
||||
context 'saved' do
|
||||
subject { Fabricate(:invite) }
|
||||
context '::generate' do
|
||||
it 'saves an invites' do
|
||||
invite = Invite.generate(user, email: 'TEST@EXAMPLE.COM')
|
||||
expect(invite.invite_key).to be_present
|
||||
expect(invite.email).to eq('test@example.com')
|
||||
end
|
||||
|
||||
it "works" do
|
||||
expect(subject.invite_key).to be_present
|
||||
expect(subject.email_already_exists).to eq(false)
|
||||
it 'can succeed for staged users emails' do
|
||||
Fabricate(:staged, email: 'test@example.com')
|
||||
invite = Invite.generate(user, email: 'test@example.com')
|
||||
expect(invite.email).to eq('test@example.com')
|
||||
end
|
||||
|
||||
it 'raises an error when inviting an existing user' do
|
||||
expect { Invite.generate(user, email: user.email) }
|
||||
.to raise_error(Invite::UserExists)
|
||||
end
|
||||
|
||||
context 'via email' do
|
||||
it 'enqueues a job to email the invite' do
|
||||
invite = Invite.generate(user, email: 'test@example.com')
|
||||
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:sending])
|
||||
expect(Jobs::InviteEmail.jobs.size).to eq(1)
|
||||
end
|
||||
|
||||
it 'should store a lower case version of the email' do
|
||||
expect(subject.email).to eq(iceking)
|
||||
it 'can skip the job to email the invite' do
|
||||
invite = Invite.generate(user, email: 'test@example.com', skip_email: true)
|
||||
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required])
|
||||
expect(Jobs::InviteEmail.jobs.size).to eq(0)
|
||||
end
|
||||
|
||||
it 'can invite the same user after their invite was destroyed' do
|
||||
Invite.generate(user, email: 'test@example.com').destroy!
|
||||
invite = Invite.generate(user, email: 'test@example.com')
|
||||
expect(invite).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'to a topic' do
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
let(:inviter) { topic.user }
|
||||
|
||||
context 'email' do
|
||||
it 'enqueues a job to email the invite' do
|
||||
expect do
|
||||
Invite.generate(inviter, email: iceking, topic: topic)
|
||||
end.to change { Jobs::InviteEmail.jobs.size }
|
||||
end
|
||||
context 'via link' do
|
||||
it 'does not enqueue a job to email the invite' do
|
||||
invite = Invite.generate(user, skip_email: true)
|
||||
expect(invite.emailed_status).to eq(Invite.emailed_status_types[:not_required])
|
||||
expect(Jobs::InviteEmail.jobs.size).to eq(0)
|
||||
end
|
||||
|
||||
context 'links' do
|
||||
it 'does not enqueue a job to email the invite' do
|
||||
expect { Invite.generate(inviter, email: iceking, topic: topic, skip_email: true) }
|
||||
.not_to change { Jobs::InviteEmail.jobs.size }
|
||||
end
|
||||
end
|
||||
|
||||
context 'destroyed' do
|
||||
it "can invite the same user after their invite was destroyed" do
|
||||
Invite.generate(inviter, email: iceking, topic: topic).destroy!
|
||||
invite = Invite.generate(inviter, email: iceking, topic: topic)
|
||||
expect(invite).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'after created' do
|
||||
let(:invite) { Invite.generate(inviter, email: iceking, topic: topic) }
|
||||
|
||||
it 'belongs to the topic' do
|
||||
expect(topic.invites).to eq([invite])
|
||||
expect(invite.topics).to eq([topic])
|
||||
end
|
||||
|
||||
context 'when added by another user' do
|
||||
fab!(:coding_horror) { Fabricate(:coding_horror) }
|
||||
|
||||
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)
|
||||
it 'can be created' do
|
||||
invite = Invite.generate(user, max_redemptions_allowed: 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.emailed_status).to eq(Invite.emailed_status_types[:not_required])
|
||||
@ -198,342 +94,285 @@ describe Invite do
|
||||
end
|
||||
|
||||
it 'checks for max_redemptions_allowed range' do
|
||||
SiteSetting.invite_link_max_redemptions_limit = 1000
|
||||
expect { Invite.generate(inviter, max_redemptions_allowed: 1001) }
|
||||
.to raise_error(ActiveRecord::RecordInvalid)
|
||||
SiteSetting.invite_link_max_redemptions_limit_users = 3
|
||||
expect { Invite.generate(user, max_redemptions_allowed: 4) }.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
|
||||
|
||||
context 'when sending an invite to the same user' do
|
||||
fab!(:invite) { Invite.generate(user, email: 'test@example.com') }
|
||||
|
||||
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 'does not enqueue a job to email the invite' do
|
||||
expect { Invite.generate(inviter) }
|
||||
.not_to change { Jobs::InviteEmail.jobs.size }
|
||||
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
|
||||
|
||||
context 'an existing user' 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
|
||||
|
||||
context '#redeem' do
|
||||
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
|
||||
expect { invite.redeem }.to change(Notification, :count)
|
||||
expect { invite.redeem }.to change { Notification.count }
|
||||
end
|
||||
|
||||
it 'wont redeem an expired invite' do
|
||||
SiteSetting.invite_expiry_days = 10
|
||||
invite.update_column(:expires_at, 20.days.ago)
|
||||
it 'does not work with expired invites' do
|
||||
invite.update!(expires_at: 1.day.ago)
|
||||
expect(invite.redeem).to be_blank
|
||||
end
|
||||
|
||||
it 'wont redeem a deleted invite' do
|
||||
invite.destroy
|
||||
it 'does not work with deleted invites' do
|
||||
invite.trash!
|
||||
expect(invite.redeem).to be_blank
|
||||
end
|
||||
|
||||
it "won't redeem an invalidated invite" do
|
||||
invite.invalidated_at = 1.day.ago
|
||||
it 'does not work with deleted invites' do
|
||||
invite.destroy!
|
||||
expect(invite.redeem).to be_blank
|
||||
end
|
||||
|
||||
context "deletes duplicate invites" do
|
||||
fab!(:another_user) { Fabricate(:user) }
|
||||
|
||||
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
|
||||
|
||||
it 'does not delete already redeemed invite' do
|
||||
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
|
||||
it 'does not work with invalidated invites' do
|
||||
invite.update(invalidated_at: 1.day.ago)
|
||||
expect(invite.redeem).to be_blank
|
||||
end
|
||||
|
||||
context "as a moderator" do
|
||||
it "will give the user a moderator flag" do
|
||||
invite.invited_by = Fabricate(:admin)
|
||||
invite.moderator = true
|
||||
invite.save
|
||||
it 'deletes duplicate invite' do
|
||||
another_invite = Fabricate(:invite, email: invite.email, invited_by: Fabricate(:user))
|
||||
another_redeemed_invite = Fabricate(:invite, email: invite.email, invited_by: Fabricate(:user))
|
||||
Fabricate(:invited_user, invite: another_redeemed_invite)
|
||||
|
||||
user = invite.redeem
|
||||
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
|
||||
|
||||
context 'as a moderator' do
|
||||
it 'will give the user a moderator flag' do
|
||||
invite.update!(moderator: true, invited_by: Fabricate(:admin))
|
||||
|
||||
user = invite.redeem
|
||||
expect(user).to be_moderator
|
||||
end
|
||||
|
||||
it "will not give the user a moderator flag if the inviter is not staff" do
|
||||
invite.moderator = true
|
||||
invite.save
|
||||
it 'will not give the user a moderator flag if the inviter is not staff' do
|
||||
invite.update!(moderator: true)
|
||||
|
||||
user = invite.redeem
|
||||
expect(user).not_to be_moderator
|
||||
end
|
||||
end
|
||||
|
||||
context "when inviting to groups" do
|
||||
it "add the user to the correct groups" do
|
||||
context 'when inviting to groups' do
|
||||
it 'add the user to the correct groups' do
|
||||
group = Fabricate(:group)
|
||||
group.add_owner(invite.invited_by)
|
||||
invite.invited_groups.build(group_id: group.id)
|
||||
invite.save
|
||||
invite.invited_groups.create!(group_id: group.id)
|
||||
|
||||
user = invite.redeem
|
||||
expect(user.groups.count).to eq(1)
|
||||
expect(user.groups).to contain_exactly(group)
|
||||
end
|
||||
end
|
||||
|
||||
context "invite trust levels" 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
|
||||
it 'activates user when must_approve_users? is enabled' do
|
||||
SiteSetting.must_approve_users = true
|
||||
invite.invited_by = Fabricate(:admin)
|
||||
|
||||
user = invite.redeem
|
||||
expect(user.approved?).to eq(true)
|
||||
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
|
||||
user = invite.redeem
|
||||
expect(user.approved?).to eq(true)
|
||||
end
|
||||
end
|
||||
context 'invite to a topic' do
|
||||
fab!(:topic) { Fabricate(:private_message_topic) }
|
||||
fab!(:another_topic) { Fabricate(:private_message_topic) }
|
||||
|
||||
context 'simple invite' do
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
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)
|
||||
before do
|
||||
invite.topic_invites.create!(topic: topic)
|
||||
end
|
||||
|
||||
context 'after redeeming' do
|
||||
before do
|
||||
invite.reload
|
||||
end
|
||||
|
||||
it 'works correctly' do
|
||||
# has set the user_id attribute
|
||||
expect(invite.invited_users.first.user).to eq(user)
|
||||
|
||||
# returns true for redeemed
|
||||
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
|
||||
|
||||
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(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)
|
||||
it 'adds the user to topic_users' do
|
||||
invited_user = invite.redeem
|
||||
expect(invited_user).not_to eq(nil)
|
||||
expect(topic.reload.allowed_users.include?(invited_user)).to eq(true)
|
||||
expect(Guardian.new(invited_user).can_see?(topic)).to eq(true)
|
||||
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') }
|
||||
describe '#redeem_from_email' do
|
||||
fab!(:invite) { Fabricate(:invite, email: 'test@example.com') }
|
||||
fab!(:user) { Fabricate(:user, email: invite.email) }
|
||||
|
||||
it 'redeems the invite from email' do
|
||||
Invite.redeem_from_email(user.email)
|
||||
invite.reload
|
||||
expect(invite).to be_redeemed
|
||||
expect(invite.reload).to be_redeemed
|
||||
end
|
||||
|
||||
it 'does not redeem the invite if email does not match' do
|
||||
Invite.redeem_from_email('test24@example.com')
|
||||
invite.reload
|
||||
expect(invite).not_to be_redeemed
|
||||
Invite.redeem_from_email('test2@example.com')
|
||||
expect(invite.reload).not_to be_redeemed
|
||||
end
|
||||
end
|
||||
|
||||
describe '#emailed_status_types' do
|
||||
context "verify enum sequence" do
|
||||
before do
|
||||
@emailed_status_types = Invite.emailed_status_types
|
||||
context 'scopes' do
|
||||
fab!(:inviter) { Fabricate(:user) }
|
||||
|
||||
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
|
||||
|
||||
fab!(:partially_redeemed_and_expired_invite) { Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5) }
|
||||
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
|
||||
|
||||
describe '#pending' do
|
||||
it 'returns pending invites only' do
|
||||
expect(Invite.pending(inviter)).to contain_exactly(
|
||||
pending_invite, pending_link_invite, partially_redeemed_invite
|
||||
)
|
||||
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 "'not_required' should be at 0 position" do
|
||||
expect(@emailed_status_types[:not_required]).to eq(0)
|
||||
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!)
|
||||
|
||||
it "'sent' should be at 4th position" do
|
||||
expect(@emailed_status_types[:sent]).to eq(4)
|
||||
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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3377,6 +3377,7 @@ RSpec.describe TopicsController do
|
||||
end.to change { Invite.where(invited_by_id: user.id).count }.by(1)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(Jobs::InviteEmail.jobs.first['args'].first['invite_to_topic']).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user