mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
Initial release of Discourse
This commit is contained in:
9
spec/models/category_featured_topic_spec.rb
Normal file
9
spec/models/category_featured_topic_spec.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe CategoryFeaturedTopic do
|
||||
|
||||
it { should belong_to :category }
|
||||
it { should belong_to :topic }
|
||||
|
||||
end
|
||||
|
||||
26
spec/models/category_featured_user_spec.rb
Normal file
26
spec/models/category_featured_user_spec.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe CategoryFeaturedUser do
|
||||
|
||||
it { should belong_to :category }
|
||||
it { should belong_to :user }
|
||||
|
||||
|
||||
context 'featuring users' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
CategoryFeaturedUser.feature_users_in(@category)
|
||||
end
|
||||
|
||||
it 'has a featured user' do
|
||||
CategoryFeaturedUser.count.should_not == 0
|
||||
end
|
||||
|
||||
it 'returns the user via the category association' do
|
||||
@category.featured_users.should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
175
spec/models/category_spec.rb
Normal file
175
spec/models/category_spec.rb
Normal file
@@ -0,0 +1,175 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Category do
|
||||
|
||||
it { should validate_presence_of :name }
|
||||
|
||||
it 'validates uniqueness of name' do
|
||||
Fabricate(:category)
|
||||
should validate_uniqueness_of(:name)
|
||||
end
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should have_many :topics }
|
||||
it { should have_many :category_featured_topics }
|
||||
it { should have_many :featured_topics }
|
||||
|
||||
describe "uncategorized name" do
|
||||
|
||||
let(:category) { Fabricate.build(:category, name: SiteSetting.uncategorized_name) }
|
||||
|
||||
it "is invalid to create a category with the reserved name" do
|
||||
category.should_not be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "short name" do
|
||||
let!(:category) { Fabricate(:category, name: 'xx') }
|
||||
|
||||
it "creates the category" do
|
||||
category.should be_present
|
||||
end
|
||||
|
||||
it 'has one topic' do
|
||||
Topic.where(category_id: category.id).count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'caching' do
|
||||
|
||||
it "invalidates the site cache on creation" do
|
||||
Site.expects(:invalidate_cache).once
|
||||
Fabricate(:category)
|
||||
end
|
||||
|
||||
it "invalidates the site cache on update" do
|
||||
cat = Fabricate(:category)
|
||||
Site.expects(:invalidate_cache).once
|
||||
cat.update_attributes(name: 'new name')
|
||||
end
|
||||
|
||||
it "invalidates the site cache on destroy" do
|
||||
cat = Fabricate(:category)
|
||||
Site.expects(:invalidate_cache).once
|
||||
cat.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after create' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
@topic = @category.topic
|
||||
end
|
||||
|
||||
it 'creates a slug' do
|
||||
@category.slug.should == 'amazing-category'
|
||||
end
|
||||
|
||||
it 'has one topic' do
|
||||
Topic.where(category_id: @category).count.should == 1
|
||||
end
|
||||
|
||||
it 'creates a topic post' do
|
||||
@topic.should be_present
|
||||
end
|
||||
|
||||
it 'points back to itself' do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it 'is an invisible topic' do
|
||||
@topic.should_not be_visible
|
||||
end
|
||||
|
||||
it 'is an undeletable topic' do
|
||||
Guardian.new(@category.user).can_delete?(@topic).should be_false
|
||||
end
|
||||
|
||||
it 'should have one post' do
|
||||
@topic.posts.count.should == 1
|
||||
end
|
||||
|
||||
it 'should have an excerpt' do
|
||||
@category.excerpt.should be_present
|
||||
end
|
||||
|
||||
it 'should have a topic url' do
|
||||
@category.topic_url.should be_present
|
||||
end
|
||||
|
||||
describe "trying to change the category topic's category" do
|
||||
|
||||
before do
|
||||
@new_cat = Fabricate(:category, name: '2nd Category', user: @category.user)
|
||||
@topic.change_category(@new_cat.name)
|
||||
@topic.reload
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'still has 0 forum topics' do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
|
||||
it "didn't change the category" do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it "didn't change the category's forum topic" do
|
||||
@category.topic.should == @topic
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'destroy' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
@category_id = @category.id
|
||||
@topic_id = @category.topic_id
|
||||
@category.destroy
|
||||
end
|
||||
|
||||
it 'deletes the category' do
|
||||
Category.exists?(id: @category_id).should be_false
|
||||
end
|
||||
|
||||
it 'deletes the forum topic' do
|
||||
Topic.exists?(id: @topic_id).should be_false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'update_stats' do
|
||||
|
||||
# We're going to test with one topic. That's enough for stats!
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
|
||||
# Create a non-invisible category to make sure count is 1
|
||||
@topic = Fabricate(:topic, user: @category.user, category: @category)
|
||||
|
||||
Category.update_stats
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'updates topics_week' do
|
||||
@category.topics_week.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_month' do
|
||||
@category.topics_month.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_year' do
|
||||
@category.topics_year.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
14
spec/models/draft_sequence_spec.rb
Normal file
14
spec/models/draft_sequence_spec.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe DraftSequence do
|
||||
it 'should produce next sequence for a key' do
|
||||
u = Fabricate(:user)
|
||||
DraftSequence.next!(u, 'test').should == 1
|
||||
DraftSequence.next!(u, 'test').should == 2
|
||||
end
|
||||
|
||||
it 'should return 0 by default' do
|
||||
u = Fabricate(:user)
|
||||
DraftSequence.current(u, 'test').should == 0
|
||||
end
|
||||
end
|
||||
81
spec/models/draft_spec.rb
Normal file
81
spec/models/draft_spec.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Draft do
|
||||
before do
|
||||
@user = Fabricate(:user)
|
||||
end
|
||||
it "can get a draft by user" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.get(@user, "test", 0).should == "data"
|
||||
end
|
||||
|
||||
it "uses the user id and key correctly" do
|
||||
Draft.set(@user, "test", 0,"data")
|
||||
Draft.get(Fabricate(:coding_horror), "test", 0).should be_nil
|
||||
end
|
||||
|
||||
it "should overwrite draft data correctly" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.set(@user, "test", 0, "new data")
|
||||
Draft.get(@user, "test", 0).should == "new data"
|
||||
end
|
||||
|
||||
it "should clear drafts on request" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.clear(@user, "test", 0)
|
||||
Draft.get(@user, "test", 0).should be_nil
|
||||
end
|
||||
|
||||
it "should disregard old draft if sequence decreases" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.set(@user, "test", 1, "hello")
|
||||
Draft.set(@user, "test", 0, "foo")
|
||||
Draft.get(@user, "test", 0).should be_nil
|
||||
Draft.get(@user, "test", 1).should == "hello"
|
||||
end
|
||||
|
||||
|
||||
context 'key expiry' do
|
||||
it 'nukes new topic draft after a topic is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_TOPIC, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u)
|
||||
s = DraftSequence.current(u, Draft::NEW_TOPIC)
|
||||
Draft.get(u, Draft::NEW_TOPIC, s).should be_nil
|
||||
end
|
||||
|
||||
it 'nukes new pm draft after a pm is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_PRIVATE_MESSAGE, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u, archetype: Archetype.private_message)
|
||||
s = DraftSequence.current(t.user, Draft::NEW_PRIVATE_MESSAGE)
|
||||
Draft.get(u, Draft::NEW_PRIVATE_MESSAGE, s).should be_nil
|
||||
end
|
||||
|
||||
it 'does not nuke new topic draft after a pm is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_TOPIC, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u, archetype: Archetype.private_message)
|
||||
s = DraftSequence.current(t.user, Draft::NEW_TOPIC)
|
||||
Draft.get(u, Draft::NEW_TOPIC, s).should == 'my draft'
|
||||
end
|
||||
|
||||
it 'nukes the post draft when a post is created' do
|
||||
p = Fabricate(:post)
|
||||
Draft.set(p.user, p.topic.draft_key, 0,'hello')
|
||||
Fabricate(:post, topic: p.topic, user: p.user)
|
||||
Draft.get(p.user, p.topic.draft_key, DraftSequence.current(p.user, p.topic.draft_key)).should be_nil
|
||||
end
|
||||
|
||||
it 'nukes the post draft when a post is revised' do
|
||||
p = Fabricate(:post)
|
||||
Draft.set(p.user, p.topic.draft_key, 0,'hello')
|
||||
p.revise(p.user, 'another test')
|
||||
s = DraftSequence.current(p.user, p.topic.draft_key)
|
||||
Draft.get(p.user, p.topic.draft_key, s).should be_nil
|
||||
end
|
||||
|
||||
it 'increases the sequence number when a post is revised' do
|
||||
end
|
||||
end
|
||||
end
|
||||
24
spec/models/email_log_spec.rb
Normal file
24
spec/models/email_log_spec.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe EmailLog do
|
||||
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should validate_presence_of :to_address }
|
||||
it { should validate_presence_of :email_type }
|
||||
|
||||
|
||||
context 'after_create with user' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it 'updates the last_emailed_at value for the user' do
|
||||
lambda {
|
||||
user.email_logs.create(email_type: 'blah', to_address: user.email)
|
||||
user.reload
|
||||
}.should change(user, :last_emailed_at)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
128
spec/models/email_token_spec.rb
Normal file
128
spec/models/email_token_spec.rb
Normal file
@@ -0,0 +1,128 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe EmailToken do
|
||||
|
||||
it { should validate_presence_of :user_id }
|
||||
it { should validate_presence_of :email }
|
||||
it { should belong_to :user }
|
||||
|
||||
|
||||
context '#create' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:original_token) { user.email_tokens.first }
|
||||
let!(:email_token) { user.email_tokens.create(email: 'bubblegum@adevnturetime.ooo') }
|
||||
|
||||
it 'should create the email token' do
|
||||
email_token.should be_present
|
||||
end
|
||||
|
||||
it 'is valid' do
|
||||
email_token.should be_valid
|
||||
end
|
||||
|
||||
it 'has a token' do
|
||||
email_token.token.should be_present
|
||||
end
|
||||
|
||||
it 'is not confirmed' do
|
||||
email_token.should_not be_confirmed
|
||||
end
|
||||
|
||||
it 'is not expired' do
|
||||
email_token.should_not be_expired
|
||||
end
|
||||
|
||||
it 'marks the older token as expired' do
|
||||
original_token.reload
|
||||
original_token.should be_expired
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
context '#confirm' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:email_token) { user.email_tokens.first }
|
||||
|
||||
it 'returns nil with a nil token' do
|
||||
EmailToken.confirm(nil).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil with a made up token' do
|
||||
EmailToken.confirm(EmailToken.generate_token).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil unless the token is the right length' do
|
||||
EmailToken.confirm('a').should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil when a token is expired' do
|
||||
email_token.update_column(:expired, true)
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil when a token is older than a specific time' do
|
||||
EmailToken.expects(:valid_after).returns(1.week.ago)
|
||||
email_token.update_column(:created_at, 2.weeks.ago)
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
context 'taken email address' do
|
||||
|
||||
before do
|
||||
@other_user = Fabricate(:coding_horror)
|
||||
email_token.update_attribute :email, @other_user.email
|
||||
end
|
||||
|
||||
it 'returns nil when the email has been taken since the token has been generated' do
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'welcome message' do
|
||||
it 'sends a welcome message when the user is activated' do
|
||||
user = EmailToken.confirm(email_token.token)
|
||||
user.send_welcome_message.should be_true
|
||||
end
|
||||
|
||||
context "when using the code a second time" do
|
||||
before do
|
||||
EmailToken.confirm(email_token.token)
|
||||
end
|
||||
|
||||
it "doesn't send the welcome message" do
|
||||
user = EmailToken.confirm(email_token.token)
|
||||
user.send_welcome_message.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
|
||||
let!(:confirmed_user) { EmailToken.confirm(email_token.token) }
|
||||
|
||||
it "returns the correct user" do
|
||||
confirmed_user.should == user
|
||||
end
|
||||
|
||||
it 'marks the user as active' do
|
||||
confirmed_user.reload
|
||||
confirmed_user.should be_active
|
||||
end
|
||||
|
||||
it 'marks the token as confirmed' do
|
||||
email_token.reload
|
||||
email_token.should be_confirmed
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
58
spec/models/error_log_spec.rb
Normal file
58
spec/models/error_log_spec.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
require 'spec_helper'
|
||||
describe ErrorLog do
|
||||
|
||||
def boom
|
||||
raise "boom"
|
||||
end
|
||||
|
||||
def exception
|
||||
begin
|
||||
boom
|
||||
rescue => e
|
||||
return e
|
||||
end
|
||||
end
|
||||
|
||||
def controller
|
||||
DraftController.new
|
||||
end
|
||||
|
||||
def request
|
||||
ActionController::TestRequest.new(:host => 'test')
|
||||
end
|
||||
|
||||
describe "add_row!" do
|
||||
it "creates a non empty file on first call" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.add_row!(hello: "world")
|
||||
File.exists?(ErrorLog.filename).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "logging data" do
|
||||
it "is able to read the data it writes" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
i = 0
|
||||
ErrorLog.each do |h|
|
||||
i += 1
|
||||
end
|
||||
i.should == 2
|
||||
end
|
||||
|
||||
it "is able to skip rows" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
i = 0
|
||||
ErrorLog.skip(3) do |h|
|
||||
i += 1
|
||||
end
|
||||
i.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
75
spec/models/incoming_link_spec.rb
Normal file
75
spec/models/incoming_link_spec.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe IncomingLink do
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should validate_presence_of :url }
|
||||
|
||||
it { should ensure_length_of(:referer).is_at_least(3).is_at_most(1000) }
|
||||
it { should ensure_length_of(:domain).is_at_least(1).is_at_most(100) }
|
||||
|
||||
describe 'local topic link' do
|
||||
|
||||
it 'should validate properly' do
|
||||
Fabricate.build(:incoming_link).should be_valid
|
||||
end
|
||||
|
||||
describe 'saving local link' do
|
||||
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
@topic = @post.topic
|
||||
@incoming_link = IncomingLink.create(url: "/t/slug/#{@topic.id}/#{@post.post_number}",
|
||||
referer: "http://twitter.com")
|
||||
end
|
||||
|
||||
describe 'incoming link counts' do
|
||||
it "increases the post's incoming link count" do
|
||||
lambda { @incoming_link.save; @post.reload }.should change(@post, :incoming_link_count).by(1)
|
||||
end
|
||||
|
||||
it "increases the topic's incoming link count" do
|
||||
lambda { @incoming_link.save; @topic.reload }.should change(@topic, :incoming_link_count).by(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'after save' do
|
||||
before do
|
||||
@incoming_link.save
|
||||
end
|
||||
|
||||
it 'has a domain' do
|
||||
@incoming_link.domain.should == "twitter.com"
|
||||
end
|
||||
|
||||
it 'has the topic_id' do
|
||||
@incoming_link.topic_id.should == @topic.id
|
||||
end
|
||||
|
||||
it 'has the post_number' do
|
||||
@incoming_link.post_number.should == @post.post_number
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe 'non-topic url' do
|
||||
|
||||
before do
|
||||
@link = Fabricate(:incoming_link_not_topic)
|
||||
end
|
||||
|
||||
it 'has no topic_id' do
|
||||
@link.topic_id.should be_blank
|
||||
end
|
||||
|
||||
it 'has no post_number' do
|
||||
@link.topic_id.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
286
spec/models/invite_spec.rb
Normal file
286
spec/models/invite_spec.rb
Normal file
@@ -0,0 +1,286 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Invite do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should have_many :topic_invites }
|
||||
it { should belong_to :invited_by }
|
||||
it { should have_many :topics }
|
||||
it { should validate_presence_of :email }
|
||||
it { should validate_presence_of :invited_by_id }
|
||||
|
||||
context 'user validators' do
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let(: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
|
||||
invite.should_not be_valid
|
||||
end
|
||||
|
||||
it "should not allow a user to invite themselves" do
|
||||
invite.email_already_exists.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '#create' do
|
||||
|
||||
context 'saved' do
|
||||
subject { Fabricate(:invite) }
|
||||
its(:invite_key) { should be_present }
|
||||
its(:email_already_exists) { should be_false }
|
||||
|
||||
it 'should store a lower case version of the email' do
|
||||
subject.email.should == "iceking@adventuretime.ooo"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'to a topic' do
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let(:inviter) { topic.user }
|
||||
|
||||
context 'email' do
|
||||
it 'enqueues a job to email the invite' do
|
||||
Jobs.expects(:enqueue).with(:invite_email, has_key(:invite_id))
|
||||
topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'destroyed' do
|
||||
it "can invite the same user after their invite was destroyed" do
|
||||
invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
invite.destroy
|
||||
invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
invite.should be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'after created' do
|
||||
before do
|
||||
@invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
|
||||
it 'belongs to the topic' do
|
||||
topic.invites.should == [@invite]
|
||||
end
|
||||
|
||||
it 'has a topic' do
|
||||
@invite.topics.should == [topic]
|
||||
end
|
||||
|
||||
it 'is pending in the invite list for the creator' do
|
||||
InvitedList.new(inviter).pending.should == [@invite]
|
||||
end
|
||||
|
||||
|
||||
context 'when added by another user' do
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let(:new_invite) { topic.invite_by_email(coding_horror, 'iceking@adventuretime.ooo') }
|
||||
|
||||
it 'returns a different invite' do
|
||||
new_invite.should_not == @invite
|
||||
end
|
||||
|
||||
it 'has a different key' do
|
||||
new_invite.invite_key.should_not == @invite.invite_key
|
||||
end
|
||||
|
||||
it 'has the topic relationship' do
|
||||
new_invite.topics.should == [topic]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding a duplicate' do
|
||||
it 'returns the original invite' do
|
||||
topic.invite_by_email(inviter, 'iceking@adventuretime.ooo').should == @invite
|
||||
end
|
||||
|
||||
it 'matches case insensitively' do
|
||||
topic.invite_by_email(inviter, 'ICEKING@adventuretime.ooo').should == @invite
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding to another topic' do
|
||||
let!(:another_topic) { Fabricate(:topic, user: topic.user) }
|
||||
|
||||
before do
|
||||
@new_invite = another_topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
|
||||
it 'should be the same invite' do
|
||||
@new_invite.should == @invite
|
||||
end
|
||||
|
||||
it 'belongs to the new topic' do
|
||||
another_topic.invites.should == [@invite]
|
||||
end
|
||||
|
||||
it 'has references to both topics' do
|
||||
@invite.topics.should =~ [topic, another_topic]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'an existing user' do
|
||||
let(:topic) { Fabricate(:topic, archetype: Archetype.private_message) }
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let!(:invite) { topic.invite_by_email(topic.user, coding_horror.email) }
|
||||
|
||||
it "doesn't create an invite" do
|
||||
invite.should be_blank
|
||||
end
|
||||
|
||||
it "gives the user permission to access the topic" do
|
||||
topic.allowed_users.include?(coding_horror).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context '.redeem' do
|
||||
|
||||
let(:invite) { Fabricate(:invite) }
|
||||
|
||||
it 'creates a notification for the invitee' do
|
||||
lambda { invite.redeem }.should change(Notification, :count)
|
||||
end
|
||||
|
||||
it 'wont redeem an expired invite' do
|
||||
SiteSetting.expects(:invite_expiry_days).returns(10)
|
||||
invite.update_column(:created_at, 20.days.ago)
|
||||
invite.redeem.should be_blank
|
||||
end
|
||||
|
||||
it 'wont redeem a deleted invite' do
|
||||
invite.destroy
|
||||
invite.redeem.should be_blank
|
||||
end
|
||||
|
||||
context 'invite trust levels' do
|
||||
|
||||
it "returns the trust level in default_invitee_trust_level" do
|
||||
SiteSetting.stubs(:default_invitee_trust_level).returns(TrustLevel.Levels[:experienced])
|
||||
invite.redeem.trust_level.should == TrustLevel.Levels[:experienced]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'simple invite' do
|
||||
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'returns a user record' do
|
||||
user.is_a?(User).should be_true
|
||||
end
|
||||
|
||||
it 'wants us to send a welcome message' do
|
||||
user.send_welcome_message.should be_true
|
||||
end
|
||||
|
||||
it 'has the default_invitee_trust_level' do
|
||||
user.trust_level.should == SiteSetting.default_invitee_trust_level
|
||||
end
|
||||
|
||||
context 'after redeeming' do
|
||||
before do
|
||||
invite.reload
|
||||
end
|
||||
|
||||
it 'no longer in the pending list for that user' do
|
||||
InvitedList.new(invite.invited_by).pending.should be_blank
|
||||
end
|
||||
|
||||
it 'is redeeemed in the invite list for the creator' do
|
||||
InvitedList.new(invite.invited_by).redeemed.should == [invite]
|
||||
end
|
||||
|
||||
it 'has set the user_id attribute' do
|
||||
invite.user.should == user
|
||||
end
|
||||
|
||||
it 'returns true for redeemed' do
|
||||
invite.should be_redeemed
|
||||
end
|
||||
|
||||
context 'again' do
|
||||
it 'will not redeem twice' do
|
||||
invite.redeem.should == user
|
||||
end
|
||||
|
||||
it "doesn't want us to send a welcome message" do
|
||||
invite.redeem.send_welcome_message.should be_false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'invited to topics' do
|
||||
let!(:topic) { Fabricate(:private_message_topic) }
|
||||
let!(:invite) { topic.invite(topic.user, 'jake@adventuretime.ooo')}
|
||||
|
||||
context 'redeem topic invite' do
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'can see the private topic' do
|
||||
Guardian.new(user).can_see?(topic).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'invited by another user to the same topic' do
|
||||
let(:coding_horror) { User.where(username: 'CodingHorror').first }
|
||||
let!(:another_invite) { topic.invite(coding_horror, 'jake@adventuretime.ooo') }
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'invited by another user to a different topic' do
|
||||
let(:coding_horror) { User.where(username: 'CodingHorror').first }
|
||||
let(:another_topic) { Fabricate(:topic, archetype: "private_message", user: coding_horror) }
|
||||
let!(:another_invite) { another_topic.invite(coding_horror, 'jake@adventuretime.ooo') }
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users of the first topic' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'adds the user to the topic_users of the second topic' do
|
||||
another_topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'does not redeem the second invite' do
|
||||
another_invite.reload
|
||||
another_invite.should_not be_redeemed
|
||||
end
|
||||
|
||||
context 'if they redeem the other invite afterwards' do
|
||||
|
||||
before do
|
||||
@result = another_invite.redeem
|
||||
end
|
||||
|
||||
it 'returns the same user' do
|
||||
@result.should == user
|
||||
end
|
||||
|
||||
it 'marks the second invite as redeemed' do
|
||||
another_invite.reload
|
||||
another_invite.should be_redeemed
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
18
spec/models/message_bus_observer_spec.rb
Normal file
18
spec/models/message_bus_observer_spec.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe MessageBusObserver do
|
||||
|
||||
context 'after create topic' do
|
||||
|
||||
after do
|
||||
@topic = Fabricate(:topic)
|
||||
end
|
||||
|
||||
it 'publishes the topic to the list' do
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
111
spec/models/notification_spec.rb
Normal file
111
spec/models/notification_spec.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Notification do
|
||||
|
||||
it { should validate_presence_of :notification_type }
|
||||
it { should validate_presence_of :data }
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
|
||||
describe 'unread counts' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context 'a regular notification' do
|
||||
it 'increases unread_notifications' do
|
||||
lambda { Fabricate(:notification, user: user); user.reload }.should change(user, :unread_notifications)
|
||||
end
|
||||
|
||||
it "doesn't increase unread_private_messages" do
|
||||
lambda { Fabricate(:notification, user: user); user.reload }.should_not change(user, :unread_private_messages)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a private message' do
|
||||
it "doesn't increase unread_notifications" do
|
||||
lambda { Fabricate(:private_message_notification, user: user); user.reload }.should_not change(user, :unread_notifications)
|
||||
end
|
||||
|
||||
it "increases unread_private_messages" do
|
||||
lambda { Fabricate(:private_message_notification, user: user); user.reload }.should change(user, :unread_private_messages)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'message bus' do
|
||||
|
||||
it 'updates the notification count on create' do
|
||||
MessageBusObserver.any_instance.expects(:refresh_notification_count).with(instance_of(Notification))
|
||||
Fabricate(:notification)
|
||||
end
|
||||
|
||||
context 'destroy' do
|
||||
|
||||
let!(:notification) { Fabricate(:notification) }
|
||||
|
||||
it 'updates the notification count on destroy' do
|
||||
MessageBusObserver.any_instance.expects(:refresh_notification_count).with(instance_of(Notification))
|
||||
notification.destroy
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe '@mention' do
|
||||
|
||||
it "calls email_user_mentioned on creating a notification" do
|
||||
UserEmailObserver.any_instance.expects(:email_user_mentioned).with(instance_of(Notification))
|
||||
Fabricate(:notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '@mention' do
|
||||
it "calls email_user_quoted on creating a quote notification" do
|
||||
UserEmailObserver.any_instance.expects(:email_user_quoted).with(instance_of(Notification))
|
||||
Fabricate(:quote_notification)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'private message' do
|
||||
before do
|
||||
@topic = Fabricate(:private_message_topic)
|
||||
@post = Fabricate(:post, :topic => @topic, :user => @topic.user)
|
||||
@target = @post.topic.topic_allowed_users.reject{|a| a.user_id == @post.user_id}[0].user
|
||||
end
|
||||
|
||||
it 'should create a private message notification' do
|
||||
@target.notifications.first.notification_type.should == Notification.Types[:private_message]
|
||||
end
|
||||
|
||||
it 'should not add a pm notification for the creator' do
|
||||
@post.user.unread_notifications.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '.post' do
|
||||
|
||||
let(:post) { Fabricate(:post) }
|
||||
let!(:notification) { Fabricate(:notification, user: post.user, topic: post.topic, post_number: post.post_number) }
|
||||
|
||||
it 'returns the post' do
|
||||
notification.post.should == post
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'data' do
|
||||
let(:notification) { Fabricate.build(:notification) }
|
||||
|
||||
it 'should have a data hash' do
|
||||
notification.data_hash.should be_present
|
||||
end
|
||||
|
||||
it 'should have the data within the json' do
|
||||
notification.data_hash[:poison].should == 'ivy'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
11
spec/models/onebox_render_spec.rb
Normal file
11
spec/models/onebox_render_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe OneboxRender do
|
||||
|
||||
it { should validate_presence_of :url }
|
||||
it { should validate_presence_of :cooked }
|
||||
it { should validate_presence_of :expires_at }
|
||||
it { should have_many :post_onebox_renders }
|
||||
it { should have_many :posts }
|
||||
|
||||
end
|
||||
159
spec/models/post_action_spec.rb
Normal file
159
spec/models/post_action_spec.rb
Normal file
@@ -0,0 +1,159 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostAction do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :post }
|
||||
it { should belong_to :post_action_type }
|
||||
it { should rate_limit }
|
||||
|
||||
let(:codinghorror) { Fabricate(:coding_horror) }
|
||||
let(:post) { Fabricate(:post) }
|
||||
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.Types[:bookmark] , post_id: post.id) }
|
||||
|
||||
describe "flag counts" do
|
||||
before do
|
||||
PostAction.update_flagged_posts_count
|
||||
end
|
||||
it "starts of with 0 flag counts" do
|
||||
PostAction.flagged_posts_count.should == 0
|
||||
end
|
||||
|
||||
it "increments the numbers correctly" do
|
||||
PostAction.act(codinghorror, post, PostActionType.Types[:off_topic])
|
||||
PostAction.flagged_posts_count.should == 1
|
||||
|
||||
PostAction.clear_flags!(post, -1)
|
||||
PostAction.flagged_posts_count.should == 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "increases the post's bookmark count when saved" do
|
||||
lambda { bookmark.save; post.reload }.should change(post, :bookmark_count).by(1)
|
||||
end
|
||||
|
||||
it "increases the forum topic's bookmark count when saved" do
|
||||
lambda { bookmark.save; post.topic.reload }.should change(post.topic, :bookmark_count).by(1)
|
||||
end
|
||||
|
||||
|
||||
describe 'when a user likes something' do
|
||||
it 'should increase the post counts when a user likes' do
|
||||
lambda {
|
||||
PostAction.act(codinghorror, post, PostActionType.Types[:like])
|
||||
post.reload
|
||||
}.should change(post, :like_count).by(1)
|
||||
end
|
||||
|
||||
it 'should increase the forum topic like count when a user likes' do
|
||||
lambda {
|
||||
PostAction.act(codinghorror, post, PostActionType.Types[:like])
|
||||
post.topic.reload
|
||||
}.should change(post.topic, :like_count).by(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe 'when a user votes for something' do
|
||||
it 'should increase the vote counts when a user likes' do
|
||||
lambda {
|
||||
PostAction.act(codinghorror, post, PostActionType.Types[:vote])
|
||||
post.reload
|
||||
}.should change(post, :vote_count).by(1)
|
||||
end
|
||||
|
||||
it 'should increase the forum topic vote count when a user votes' do
|
||||
lambda {
|
||||
PostAction.act(codinghorror, post, PostActionType.Types[:vote])
|
||||
post.topic.reload
|
||||
}.should change(post.topic, :vote_count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe 'when deleted' do
|
||||
before do
|
||||
bookmark.save
|
||||
post.reload
|
||||
@topic = post.topic
|
||||
@topic.reload
|
||||
bookmark.deleted_at = DateTime.now
|
||||
bookmark.save
|
||||
end
|
||||
|
||||
it 'reduces the bookmark count of the post' do
|
||||
lambda {
|
||||
post.reload
|
||||
}.should change(post, :bookmark_count).by(-1)
|
||||
end
|
||||
|
||||
it 'reduces the bookmark count of the forum topic' do
|
||||
lambda {
|
||||
@topic.reload
|
||||
}.should change(post.topic, :bookmark_count).by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'flagging' do
|
||||
|
||||
it 'should update counts when you clear flags' do
|
||||
post = Fabricate(:post)
|
||||
u1 = Fabricate(:evil_trout)
|
||||
PostAction.act(u1, post, PostActionType.Types[:spam])
|
||||
|
||||
post.reload
|
||||
post.spam_count.should == 1
|
||||
|
||||
PostAction.clear_flags!(post, -1)
|
||||
post.reload
|
||||
|
||||
post.spam_count.should == 0
|
||||
end
|
||||
|
||||
it 'should follow the rules for automatic hiding workflow' do
|
||||
|
||||
post = Fabricate(:post)
|
||||
u1 = Fabricate(:evil_trout)
|
||||
u2 = Fabricate(:walter_white)
|
||||
|
||||
# we need an admin for the messages
|
||||
admin = Fabricate(:admin)
|
||||
|
||||
SiteSetting.flags_required_to_hide_post = 2
|
||||
|
||||
PostAction.act(u1, post, PostActionType.Types[:spam])
|
||||
PostAction.act(u2, post, PostActionType.Types[:spam])
|
||||
|
||||
post.reload
|
||||
|
||||
post.hidden.should.should be_true
|
||||
post.hidden_reason_id.should == Post::HiddenReason::FLAG_THRESHOLD_REACHED
|
||||
post.topic.visible.should be_false
|
||||
|
||||
post.revise(post.user, post.raw + " ha I edited it ")
|
||||
post.reload
|
||||
|
||||
post.hidden.should be_false
|
||||
post.hidden_reason_id.should be_nil
|
||||
post.topic.visible.should be_true
|
||||
|
||||
PostAction.act(u1, post, PostActionType.Types[:spam])
|
||||
PostAction.act(u2, post, PostActionType.Types[:off_topic])
|
||||
|
||||
post.reload
|
||||
|
||||
post.hidden.should be_true
|
||||
post.hidden_reason_id.should == Post::HiddenReason::FLAG_THRESHOLD_REACHED_AGAIN
|
||||
|
||||
post.revise(post.user, post.raw + " ha I edited it again ")
|
||||
|
||||
post.reload
|
||||
|
||||
post.hidden.should be_true
|
||||
post.hidden_reason_id.should == Post::HiddenReason::FLAG_THRESHOLD_REACHED_AGAIN
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
5
spec/models/post_action_type_spec.rb
Normal file
5
spec/models/post_action_type_spec.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostActionType do
|
||||
|
||||
end
|
||||
104
spec/models/post_alert_observer_spec.rb
Normal file
104
spec/models/post_alert_observer_spec.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostAlertObserver do
|
||||
|
||||
let!(:evil_trout) { Fabricate(:evil_trout) }
|
||||
let(:post) { Fabricate(:post) }
|
||||
|
||||
context 'liking' do
|
||||
context 'when liking a post' do
|
||||
it 'creates a notification' do
|
||||
lambda {
|
||||
PostAction.act(evil_trout, post, PostActionType.Types[:like])
|
||||
}.should change(Notification, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing a liked post' do
|
||||
before do
|
||||
PostAction.act(evil_trout, post, PostActionType.Types[:like])
|
||||
end
|
||||
|
||||
it 'removes a notification' do
|
||||
lambda {
|
||||
PostAction.remove_act(evil_trout, post, PostActionType.Types[:like])
|
||||
}.should change(Notification, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when editing a post' do
|
||||
it 'notifies a user of the revision' do
|
||||
lambda {
|
||||
post.revise(evil_trout, "world")
|
||||
}.should change(post.user.notifications, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'quotes' do
|
||||
it 'notifies a user by display username' do
|
||||
lambda {
|
||||
Fabricate(:post, raw: '[quote="Evil Trout, post:1"]whatup[/quote]')
|
||||
}.should change(evil_trout.notifications, :count).by(1)
|
||||
end
|
||||
|
||||
it 'notifies a user by username' do
|
||||
lambda {
|
||||
Fabricate(:post, raw: '[quote="EvilTrout, post:1"]whatup[/quote]')
|
||||
}.should change(evil_trout.notifications, :count).by(1)
|
||||
end
|
||||
|
||||
it "won't notify the user a second time on revision" do
|
||||
p1 = Fabricate(:post, raw: '[quote="Evil Trout, post:1"]whatup[/quote]')
|
||||
lambda {
|
||||
p1.revise(p1.user, '[quote="Evil Trout, post:1"]whatup now?[/quote]')
|
||||
}.should_not change(evil_trout.notifications, :count)
|
||||
end
|
||||
|
||||
it "doesn't notify the poster" do
|
||||
topic = post.topic
|
||||
lambda {
|
||||
new_post = Fabricate(:post, topic: topic, user: topic.user, raw: '[quote="Bruce Wayne, post:1"]whatup[/quote]')
|
||||
}.should_not change(topic.user.notifications, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context '@mentions' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:mention_post) { Fabricate(:post, user: user, raw: 'Hello @eviltrout')}
|
||||
let(:topic) { mention_post.topic }
|
||||
|
||||
it 'notifies a user' do
|
||||
lambda {
|
||||
mention_post
|
||||
}.should change(evil_trout.notifications, :count).by(1)
|
||||
end
|
||||
|
||||
it "won't notify the user a second time on revision" do
|
||||
mention_post
|
||||
lambda {
|
||||
mention_post.revise(mention_post.user, "New raw content that still mentions @eviltrout")
|
||||
}.should_not change(evil_trout.notifications, :count)
|
||||
end
|
||||
|
||||
|
||||
it "doesn't notify the user who created the topic in regular mode" do
|
||||
topic.notify_regular!(user)
|
||||
mention_post
|
||||
lambda {
|
||||
Fabricate(:post, user: user, raw: 'second post', topic: topic)
|
||||
}.should_not change(user.notifications, :count).by(1)
|
||||
end
|
||||
|
||||
it 'removes notifications' do
|
||||
post = mention_post
|
||||
lambda {
|
||||
post.destroy
|
||||
}.should change(evil_trout.notifications, :count).by(-1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
8
spec/models/post_onebox_render_spec.rb
Normal file
8
spec/models/post_onebox_render_spec.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostOneboxRender do
|
||||
|
||||
it { should belong_to :onebox_render }
|
||||
it { should belong_to :post }
|
||||
|
||||
end
|
||||
8
spec/models/post_reply_spec.rb
Normal file
8
spec/models/post_reply_spec.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostReply do
|
||||
|
||||
it { should belong_to :post }
|
||||
it { should belong_to :reply }
|
||||
|
||||
end
|
||||
704
spec/models/post_spec.rb
Normal file
704
spec/models/post_spec.rb
Normal file
@@ -0,0 +1,704 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Post do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
it { should validate_presence_of :raw }
|
||||
|
||||
# Min/max body lengths, respecting padding
|
||||
it { should_not allow_value("x").for(:raw) }
|
||||
it { should_not allow_value("x" * (SiteSetting.max_post_length + 1)).for(:raw) }
|
||||
it { should_not allow_value((" " * SiteSetting.min_post_length) + "x").for(:raw) }
|
||||
|
||||
it { should have_many :post_replies }
|
||||
it { should have_many :replies }
|
||||
|
||||
it { should rate_limit }
|
||||
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:post_args) do
|
||||
{user: topic.user, topic: topic}
|
||||
end
|
||||
|
||||
|
||||
describe 'post uniqueness' do
|
||||
|
||||
context "disabled" do
|
||||
before do
|
||||
SiteSetting.stubs(:unique_posts_mins).returns(0)
|
||||
Fabricate(:post, post_args)
|
||||
end
|
||||
|
||||
it "returns true for another post with the same content" do
|
||||
Fabricate.build(:post, post_args).should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'enabled' do
|
||||
before do
|
||||
SiteSetting.stubs(:unique_posts_mins).returns(10)
|
||||
Fabricate(:post, post_args)
|
||||
end
|
||||
|
||||
it "returns false for another post with the same content" do
|
||||
Fabricate.build(:post, post_args).should_not be_valid
|
||||
end
|
||||
|
||||
it "returns true for admins" do
|
||||
topic.user.admin = true
|
||||
Fabricate.build(:post, post_args).should be_valid
|
||||
end
|
||||
|
||||
it "returns true for moderators" do
|
||||
topic.user.trust_level = TrustLevel.Levels[:moderator]
|
||||
Fabricate.build(:post, post_args).should be_valid
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe 'message bus' do
|
||||
it 'enqueues the post on the message bus' do
|
||||
topic = self.topic
|
||||
MessageBus.expects(:publish).with("/topic/#{topic.id}", instance_of(Hash))
|
||||
Fabricate(:post, post_args)
|
||||
end
|
||||
end
|
||||
|
||||
describe "maximum images" do
|
||||
let(:post_no_images) { Fabricate.build(:post, post_args) }
|
||||
let(:post_one_image) { Fabricate.build(:post, post_args.merge(raw: "")) }
|
||||
let(:post_two_images) { Fabricate.build(:post, post_args.merge(raw: "<img src='http://discourse.org/logo.png'> <img src='http://bbc.co.uk/sherlock.jpg'>")) }
|
||||
let(:post_with_emoticons) { Fabricate.build(:post, post_args.merge(raw: '<img alt="smiley" title=":smiley:" src="/assets/emoji/smiley.png" class="emoji"> <img alt="wink" title=":wink:" src="/assets/emoji/wink.png" class="emoji">')) }
|
||||
|
||||
it "returns 0 images for an empty post" do
|
||||
Fabricate.build(:post).image_count.should == 0
|
||||
end
|
||||
|
||||
it "finds images from markdown" do
|
||||
post_one_image.image_count.should == 1
|
||||
end
|
||||
|
||||
it "finds images from HTML" do
|
||||
post_two_images.image_count.should == 2
|
||||
end
|
||||
|
||||
it "doesn't count emoticons as images" do
|
||||
post_with_emoticons.image_count.should == 0
|
||||
end
|
||||
|
||||
|
||||
context "validation" do
|
||||
it "allows a new user to make a post with one image" do
|
||||
post_no_images.user.trust_level = TrustLevel.Levels[:new]
|
||||
post_no_images.should be_valid
|
||||
end
|
||||
|
||||
it "doesn't allow multiple images for new accounts" do
|
||||
post_one_image.user.trust_level = TrustLevel.Levels[:new]
|
||||
post_one_image.should_not be_valid
|
||||
end
|
||||
|
||||
it "allows multiple images for basic accounts" do
|
||||
post_one_image.user.trust_level = TrustLevel.Levels[:basic]
|
||||
post_one_image.should be_valid
|
||||
end
|
||||
|
||||
it "doesn't allow a new user to edit their post to insert an image" do
|
||||
post_no_images.user.trust_level = TrustLevel.Levels[:new]
|
||||
post_no_images.save
|
||||
-> {
|
||||
post_no_images.revise(post_no_images.user, post_two_images.raw)
|
||||
post_no_images.reload
|
||||
}.should_not change(post_no_images, :raw)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "maximum links" do
|
||||
let(:post_one_link) { Fabricate.build(:post, post_args.merge(raw: "[sherlock](http://www.bbc.co.uk/programmes/b018ttws)")) }
|
||||
let(:post_two_links) { Fabricate.build(:post, post_args.merge(raw: "<a href='http://discourse.org'>discourse</a> <a href='http://twitter.com'>twitter</a>")) }
|
||||
|
||||
it "returns 0 images for an empty post" do
|
||||
Fabricate.build(:post).link_count.should == 0
|
||||
end
|
||||
|
||||
it "finds images from markdown" do
|
||||
post_one_link.link_count.should == 1
|
||||
end
|
||||
|
||||
it "finds images from HTML" do
|
||||
post_two_links.link_count.should == 2
|
||||
end
|
||||
|
||||
context "validation" do
|
||||
it "allows a new user to make a post with one image" do
|
||||
post_one_link.user.trust_level = TrustLevel.Levels[:new]
|
||||
post_one_link.should be_valid
|
||||
end
|
||||
|
||||
it "doesn't allow multiple images for new accounts" do
|
||||
post_two_links.user.trust_level = TrustLevel.Levels[:new]
|
||||
post_two_links.should_not be_valid
|
||||
end
|
||||
|
||||
it "allows multiple images for basic accounts" do
|
||||
post_two_links.user.trust_level = TrustLevel.Levels[:basic]
|
||||
post_two_links.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe "maximum @mentions" do
|
||||
|
||||
let(:post) { Fabricate.build(:post, post_args.merge(raw: "@Jake @Finn")) }
|
||||
|
||||
it "will accept a post with 2 @mentions as valid" do
|
||||
post.should be_valid
|
||||
end
|
||||
|
||||
context 'raw_mentions' do
|
||||
|
||||
it "returns an empty array with no matches" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "Hello Jake and Finn!"))
|
||||
post.raw_mentions.should == []
|
||||
end
|
||||
|
||||
it "returns lowercase unique versions of the mentions" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "@Jake @Finn @Jake"))
|
||||
post.raw_mentions.should == ['jake', 'finn']
|
||||
end
|
||||
|
||||
it "ignores pre" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "<pre>@Jake</pre> @Finn"))
|
||||
post.raw_mentions.should == ['finn']
|
||||
end
|
||||
|
||||
it "catches content between pre tags" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "<pre>hello</pre> @Finn <pre></pre>"))
|
||||
post.raw_mentions.should == ['finn']
|
||||
end
|
||||
|
||||
it "ignores code" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "@Jake <code>@Finn</code>"))
|
||||
post.raw_mentions.should == ['jake']
|
||||
end
|
||||
|
||||
it "ignores quotes" do
|
||||
post = Fabricate.build(:post, post_args.merge(raw: "[quote=\"Evil Trout\"]@Jake[/quote] @Finn"))
|
||||
post.raw_mentions.should == ['finn']
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "With a @mention limit of 1" do
|
||||
before do
|
||||
SiteSetting.stubs(:max_mentions_per_post).returns(1)
|
||||
end
|
||||
|
||||
it "wont accept the post as valid because there are too many mentions" do
|
||||
post.should_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it 'validates' do
|
||||
Fabricate.build(:post, post_args).should be_valid
|
||||
end
|
||||
|
||||
context "raw_hash" do
|
||||
|
||||
let(:raw) { "this is our test post body"}
|
||||
let(:post) { Fabricate.build(:post, raw: raw) }
|
||||
|
||||
it "returns a value" do
|
||||
post.raw_hash.should be_present
|
||||
end
|
||||
|
||||
it "returns blank for a nil body" do
|
||||
post.raw = nil
|
||||
post.raw_hash.should be_blank
|
||||
end
|
||||
|
||||
it "returns the same value for the same raw" do
|
||||
post.raw_hash.should == Fabricate.build(:post, raw: raw).raw_hash
|
||||
end
|
||||
|
||||
it "returns a different value for a different raw" do
|
||||
post.raw_hash.should_not == Fabricate.build(:post, raw: "something else").raw_hash
|
||||
end
|
||||
|
||||
it "returns the same hash even with different white space" do
|
||||
post.raw_hash.should == Fabricate.build(:post, raw: " thisis ourt est postbody").raw_hash
|
||||
end
|
||||
|
||||
it "returns the same hash even with different text case" do
|
||||
post.raw_hash.should == Fabricate.build(:post, raw: "THIS is OUR TEST post BODy").raw_hash
|
||||
end
|
||||
end
|
||||
|
||||
context 'revise' do
|
||||
|
||||
let(:post) { Fabricate(:post, post_args) }
|
||||
let(:first_version_at) { post.last_version_at }
|
||||
|
||||
it 'has an initial version of 1' do
|
||||
post.cached_version.should == 1
|
||||
end
|
||||
|
||||
it 'has one version in all_versions' do
|
||||
post.all_versions.size.should == 1
|
||||
end
|
||||
|
||||
it "has an initial last_version" do
|
||||
first_version_at.should be_present
|
||||
end
|
||||
|
||||
describe 'with the same body' do
|
||||
|
||||
it 'returns false' do
|
||||
post.revise(post.user, post.raw).should be_false
|
||||
end
|
||||
|
||||
it "doesn't change cached_version" do
|
||||
lambda { post.revise(post.user, post.raw); post.reload }.should_not change(post, :cached_version)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'ninja editing' do
|
||||
before do
|
||||
SiteSetting.expects(:ninja_edit_window).returns(1.minute.to_i)
|
||||
post.revise(post.user, 'updated body', revised_at: post.updated_at + 10.seconds)
|
||||
post.reload
|
||||
end
|
||||
|
||||
it 'does not update cached_version' do
|
||||
post.cached_version.should == 1
|
||||
end
|
||||
|
||||
it 'does not create a new version' do
|
||||
post.all_versions.size.should == 1
|
||||
end
|
||||
|
||||
it "doesn't change the last_version_at" do
|
||||
post.last_version_at.should == first_version_at
|
||||
end
|
||||
end
|
||||
|
||||
describe 'revision much later' do
|
||||
|
||||
let!(:revised_at) { post.updated_at + 2.minutes }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:ninja_edit_window).returns(1.minute.to_i)
|
||||
post.revise(post.user, 'updated body', revised_at: revised_at)
|
||||
post.reload
|
||||
end
|
||||
|
||||
it 'updates the cached_version' do
|
||||
post.cached_version.should == 2
|
||||
end
|
||||
|
||||
it 'creates a new version' do
|
||||
post.all_versions.size.should == 2
|
||||
end
|
||||
|
||||
it "updates the last_version_at" do
|
||||
post.last_version_at.to_i.should == revised_at.to_i
|
||||
end
|
||||
|
||||
describe "new edit window" do
|
||||
|
||||
before do
|
||||
post.revise(post.user, 'yet another updated body', revised_at: revised_at)
|
||||
post.reload
|
||||
end
|
||||
|
||||
it "doesn't create a new version if you do another" do
|
||||
post.cached_version.should == 2
|
||||
end
|
||||
|
||||
it "doesn't change last_version_at" do
|
||||
post.last_version_at.to_i.should == revised_at.to_i
|
||||
end
|
||||
|
||||
context "after second window" do
|
||||
|
||||
let!(:new_revised_at) {revised_at + 2.minutes}
|
||||
|
||||
before do
|
||||
post.revise(post.user, 'yet another, another updated body', revised_at: new_revised_at)
|
||||
post.reload
|
||||
end
|
||||
|
||||
it "does create a new version after the edit window" do
|
||||
post.cached_version.should == 3
|
||||
end
|
||||
|
||||
it "does create a new version after the edit window" do
|
||||
post.last_version_at.to_i.should == new_revised_at.to_i
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'rate limiter' do
|
||||
let(:changed_by) { Fabricate(:coding_horror) }
|
||||
|
||||
it "triggers a rate limiter" do
|
||||
Post::EditRateLimiter.any_instance.expects(:performed!)
|
||||
post.revise(changed_by, 'updated body')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with a new body' do
|
||||
let(:changed_by) { Fabricate(:coding_horror) }
|
||||
let!(:result) { post.revise(changed_by, 'updated body') }
|
||||
|
||||
it 'returns true' do
|
||||
result.should be_true
|
||||
end
|
||||
|
||||
it 'updates the body' do
|
||||
post.raw.should == 'updated body'
|
||||
end
|
||||
|
||||
it 'sets the invalidate oneboxes attribute' do
|
||||
post.invalidate_oneboxes.should == true
|
||||
end
|
||||
|
||||
it 'increased the cached_version' do
|
||||
post.cached_version.should == 2
|
||||
end
|
||||
|
||||
it 'has the new version in all_versions' do
|
||||
post.all_versions.size.should == 2
|
||||
end
|
||||
|
||||
it 'has versions' do
|
||||
post.versions.should be_present
|
||||
end
|
||||
|
||||
it "saved the user who made the change in the version" do
|
||||
post.versions.first.user.should be_present
|
||||
end
|
||||
|
||||
context 'second poster posts again quickly' do
|
||||
before do
|
||||
SiteSetting.expects(:ninja_edit_window).returns(1.minute.to_i)
|
||||
post.revise(changed_by, 'yet another updated body', revised_at: post.updated_at + 10.seconds)
|
||||
post.reload
|
||||
end
|
||||
|
||||
it 'is a ninja edit, because the second poster posted again quickly' do
|
||||
post.cached_version.should == 2
|
||||
end
|
||||
|
||||
it 'is a ninja edit, because the second poster posted again quickly' do
|
||||
post.all_versions.size.should == 2
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
it 'should feature users after create' do
|
||||
Jobs.stubs(:enqueue).with(:process_post, anything)
|
||||
Jobs.expects(:enqueue).with(:feature_topic_users, has_key(:topic_id))
|
||||
Fabricate(:post, post_args)
|
||||
end
|
||||
|
||||
it 'should queue up a post processing job when saved' do
|
||||
Jobs.stubs(:enqueue).with(:feature_topic_users, has_key(:topic_id))
|
||||
Jobs.expects(:enqueue).with(:process_post, has_key(:post_id))
|
||||
Fabricate(:post, post_args)
|
||||
end
|
||||
|
||||
it 'passes the invalidate_oneboxes along to the job if present' do
|
||||
Jobs.stubs(:enqueue).with(:feature_topic_users, has_key(:topic_id))
|
||||
Jobs.expects(:enqueue).with(:process_post, has_key(:invalidate_oneboxes))
|
||||
post = Fabricate.build(:post, post_args)
|
||||
post.invalidate_oneboxes = true
|
||||
post.save
|
||||
end
|
||||
|
||||
it 'passes the image_sizes along to the job if present' do
|
||||
Jobs.stubs(:enqueue).with(:feature_topic_users, has_key(:topic_id))
|
||||
Jobs.expects(:enqueue).with(:process_post, has_key(:image_sizes))
|
||||
post = Fabricate.build(:post, post_args)
|
||||
post.image_sizes = {'http://an.image.host/image.jpg' => {'width' => 17, 'height' => 31}}
|
||||
post.save
|
||||
end
|
||||
|
||||
describe 'notifications' do
|
||||
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
|
||||
describe 'replies' do
|
||||
|
||||
let(:post) { Fabricate(:post, post_args.merge(raw: "Hello @CodingHorror")) }
|
||||
|
||||
it 'notifies the poster on reply' do
|
||||
lambda {
|
||||
@reply = Fabricate(:basic_reply, user: coding_horror, topic: post.topic)
|
||||
}.should change(post.user.notifications, :count).by(1)
|
||||
end
|
||||
|
||||
it "doesn't notify the poster when they reply to their own post" do
|
||||
lambda {
|
||||
@reply = Fabricate(:basic_reply, user: post.user, topic: post.topic)
|
||||
}.should_not change(post.user.notifications, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'watching' do
|
||||
it "does notify watching users of new posts" do
|
||||
post = Fabricate(:post, post_args)
|
||||
user2 = Fabricate(:coding_horror)
|
||||
post_args[:topic].notify_watch!(user2)
|
||||
lambda {
|
||||
Fabricate(:post, user: post.user, topic: post.topic)
|
||||
}.should change(user2.notifications, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'muting' do
|
||||
it "does not notify users of new posts" do
|
||||
post = Fabricate(:post, post_args)
|
||||
user = post_args[:user]
|
||||
user2 = Fabricate(:coding_horror)
|
||||
|
||||
post_args[:topic].notify_muted!(user)
|
||||
lambda {
|
||||
Fabricate(:post, user: user2, topic: post.topic, raw: 'hello @' + user.username)
|
||||
}.should change(user.notifications, :count).by(0)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'after delete' do
|
||||
|
||||
let!(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let!(:post) { Fabricate(:post, post_args.merge(raw: "Hello @CodingHorror")) }
|
||||
|
||||
it "should feature the users again (in case they've changed)" do
|
||||
Jobs.expects(:enqueue).with(:feature_topic_users, has_entries(topic_id: post.topic_id, except_post_id: post.id))
|
||||
post.destroy
|
||||
end
|
||||
|
||||
describe 'with a reply' do
|
||||
|
||||
let!(:reply) { Fabricate(:basic_reply, user: coding_horror, topic: post.topic) }
|
||||
|
||||
it 'changes the post count of the topic' do
|
||||
post.reload
|
||||
lambda {
|
||||
reply.destroy
|
||||
post.topic.reload
|
||||
}.should change(post.topic, :posts_count).by(-1)
|
||||
end
|
||||
|
||||
it 'lowers the reply_count when the reply is deleted' do
|
||||
lambda {
|
||||
reply.destroy
|
||||
post.reload
|
||||
}.should change(post.post_replies, :count).by(-1)
|
||||
end
|
||||
|
||||
it 'should increase the post_number when there are deletion gaps' do
|
||||
reply.destroy
|
||||
p = Fabricate(:post, user: post.user, topic: post.topic)
|
||||
p.post_number.should == 3
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe 'after save' do
|
||||
|
||||
let(:post) { Fabricate(:post, post_args) }
|
||||
|
||||
it 'has a post nubmer' do
|
||||
post.post_number.should be_present
|
||||
end
|
||||
|
||||
it 'has an excerpt' do
|
||||
post.excerpt.should be_present
|
||||
end
|
||||
|
||||
it 'is of the regular post type' do
|
||||
post.post_type.should == Post::REGULAR
|
||||
end
|
||||
|
||||
it 'has no versions' do
|
||||
post.versions.should be_blank
|
||||
end
|
||||
|
||||
it 'has cooked content' do
|
||||
post.cooked.should be_present
|
||||
end
|
||||
|
||||
it 'has an external id' do
|
||||
post.external_id.should be_present
|
||||
end
|
||||
|
||||
it 'has no quotes' do
|
||||
post.quote_count.should == 0
|
||||
end
|
||||
|
||||
it 'has no replies' do
|
||||
post.replies.should be_blank
|
||||
end
|
||||
|
||||
describe 'a forum topic user record for the topic' do
|
||||
|
||||
let(:topic_user) { post.user.topic_users.where(topic_id: topic.id).first }
|
||||
|
||||
it 'exists' do
|
||||
topic_user.should be_present
|
||||
end
|
||||
|
||||
it 'has the posted flag set' do
|
||||
topic_user.should be_posted
|
||||
end
|
||||
|
||||
it 'recorded the latest post as read' do
|
||||
topic_user.last_read_post_number.should == post.post_number
|
||||
end
|
||||
|
||||
it 'recorded the latest post as the last seen' do
|
||||
topic_user.seen_post_count.should == post.post_number
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'quote counts' do
|
||||
|
||||
let!(:post) { Fabricate(:post, post_args) }
|
||||
let(:reply) { Fabricate.build(:post, post_args) }
|
||||
|
||||
it "finds the quote when in the same topic" do
|
||||
reply.raw = "[quote=\"EvilTrout, post:#{post.post_number}, topic:#{post.topic_id}\"]hello[/quote]"
|
||||
reply.extract_quoted_post_numbers
|
||||
reply.quoted_post_numbers.should == [post.post_number]
|
||||
end
|
||||
|
||||
it "doesn't find the quote in a different topic" do
|
||||
reply.raw = "[quote=\"EvilTrout, post:#{post.post_number}, topic:#{post.topic_id+1}\"]hello[/quote]"
|
||||
reply.extract_quoted_post_numbers
|
||||
reply.quoted_post_numbers.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'a new reply' do
|
||||
|
||||
let!(:post) { Fabricate(:post, post_args) }
|
||||
let!(:reply) { Fabricate(:reply, post_args.merge(reply_to_post_number: post.post_number)) }
|
||||
|
||||
it 'has a quote' do
|
||||
reply.quote_count.should == 1
|
||||
end
|
||||
|
||||
it "isn't quoteless" do
|
||||
reply.should_not be_quoteless
|
||||
end
|
||||
|
||||
it 'has a reply to the user of the original user' do
|
||||
reply.reply_to_user.should == post.user
|
||||
end
|
||||
|
||||
it 'increases the reply count of the parent' do
|
||||
post.reload
|
||||
post.reply_count.should == 1
|
||||
end
|
||||
|
||||
it 'increases the reply count of the topic' do
|
||||
topic.reload
|
||||
topic.reply_count.should == 1
|
||||
end
|
||||
|
||||
it 'is the child of the parent post' do
|
||||
post.replies.should == [reply]
|
||||
end
|
||||
|
||||
|
||||
it "doesn't change the post count when you edit the reply" do
|
||||
reply.raw = 'updated raw'
|
||||
reply.save
|
||||
post.reload
|
||||
post.reply_count.should == 1
|
||||
end
|
||||
|
||||
context 'a multi-quote reply' do
|
||||
|
||||
let!(:multi_reply) { Fabricate(:multi_quote_reply, post_args.merge(reply_to_post_number: post.post_number)) }
|
||||
|
||||
it 'has two quotes' do
|
||||
multi_reply.quote_count.should == 2
|
||||
end
|
||||
|
||||
it 'is a child of the parent post' do
|
||||
post.replies.include?(multi_reply).should be_true
|
||||
end
|
||||
|
||||
it 'is a child of the second post quoted' do
|
||||
reply.replies.include?(multi_reply).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'best_of' do
|
||||
let!(:p1) { Fabricate(:post, post_args.merge(score: 4)) }
|
||||
let!(:p2) { Fabricate(:post, post_args.merge(score: 10)) }
|
||||
let!(:p3) { Fabricate(:post, post_args.merge(score: 5)) }
|
||||
|
||||
it "returns the OP and posts above the threshold in best of mode" do
|
||||
SiteSetting.stubs(:best_of_score_threshold).returns(10)
|
||||
Post.best_of.order(:post_number).should == [p1, p2]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'sort_order' do
|
||||
|
||||
context 'regular topic' do
|
||||
|
||||
let!(:p1) { Fabricate(:post, post_args) }
|
||||
let!(:p2) { Fabricate(:post, post_args) }
|
||||
let!(:p3) { Fabricate(:post, post_args) }
|
||||
|
||||
it 'defaults to created order' do
|
||||
Post.regular_order.should == [p1, p2, p3]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
113
spec/models/post_timing_spec.rb
Normal file
113
spec/models/post_timing_spec.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe PostTiming do
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should validate_presence_of :post_number }
|
||||
it { should validate_presence_of :msecs }
|
||||
|
||||
describe 'recording' do
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
@topic = @post.topic
|
||||
@coding_horror = Fabricate(:coding_horror)
|
||||
@timing_attrs = {msecs: 1234, topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number}
|
||||
end
|
||||
|
||||
it 'creates a post timing record' do
|
||||
lambda {
|
||||
PostTiming.record_timing(@timing_attrs)
|
||||
}.should change(PostTiming, :count).by(1)
|
||||
end
|
||||
|
||||
it 'adds a view to the post' do
|
||||
lambda {
|
||||
PostTiming.record_timing(@timing_attrs)
|
||||
@post.reload
|
||||
}.should change(@post, :reads).by(1)
|
||||
end
|
||||
|
||||
describe 'multiple calls' do
|
||||
before do
|
||||
PostTiming.record_timing(@timing_attrs)
|
||||
PostTiming.record_timing(@timing_attrs)
|
||||
@timing = PostTiming.where(topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number).first
|
||||
end
|
||||
|
||||
it 'creates a timing record' do
|
||||
@timing.should be_present
|
||||
end
|
||||
|
||||
it 'sums the msecs together' do
|
||||
@timing.msecs.should == 2468
|
||||
end
|
||||
end
|
||||
|
||||
describe 'avg times' do
|
||||
|
||||
describe 'posts' do
|
||||
it 'has no avg_time by default' do
|
||||
@post.avg_time.should be_blank
|
||||
end
|
||||
|
||||
it "doesn't change when we calculate the avg time for the post because there's no timings" do
|
||||
Post.calculate_avg_time
|
||||
@post.reload
|
||||
@post.avg_time.should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe 'topics' do
|
||||
it 'has no avg_time by default' do
|
||||
@topic.avg_time.should be_blank
|
||||
end
|
||||
|
||||
it "doesn't change when we calculate the avg time for the post because there's no timings" do
|
||||
Topic.calculate_avg_time
|
||||
@topic.reload
|
||||
@topic.avg_time.should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe "it doesn't create an avg time for the same user" do
|
||||
it 'something' do
|
||||
PostTiming.record_timing(@timing_attrs.merge(user_id: @post.user_id))
|
||||
Post.calculate_avg_time
|
||||
@post.reload
|
||||
@post.avg_time.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'with a timing for another user' do
|
||||
before do
|
||||
PostTiming.record_timing(@timing_attrs)
|
||||
Post.calculate_avg_time
|
||||
@post.reload
|
||||
end
|
||||
|
||||
it 'has a post avg_time from the timing' do
|
||||
@post.avg_time.should be_present
|
||||
end
|
||||
|
||||
describe 'forum topics' do
|
||||
before do
|
||||
Topic.calculate_avg_time
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'has an avg_time from the timing' do
|
||||
@topic.avg_time.should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
64
spec/models/site_customization_spec.rb
Normal file
64
spec/models/site_customization_spec.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SiteCustomization do
|
||||
|
||||
let :user do
|
||||
Fabricate(:user)
|
||||
end
|
||||
|
||||
let :customization do
|
||||
SiteCustomization.create!(name: 'my name', user_id: user.id, header: "my awesome header", stylesheet: "my awesome css")
|
||||
end
|
||||
|
||||
it 'should set default key when creating a new customization' do
|
||||
s = SiteCustomization.create!(name: 'my name', user_id: user.id)
|
||||
s.key.should_not == nil
|
||||
end
|
||||
|
||||
context 'caching' do
|
||||
it 'should allow me to lookup a filename containing my preview stylesheet' do
|
||||
SiteCustomization.custom_stylesheet(customization.key).should ==
|
||||
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/stylesheet-cache/#{customization.key}.css?#{customization.stylesheet_hash}\" type=\"text/css\" media=\"screen\">"
|
||||
end
|
||||
|
||||
it 'should fix stylesheet files after changing the stylesheet' do
|
||||
old_file = customization.stylesheet_fullpath
|
||||
original = SiteCustomization.custom_stylesheet(customization.key)
|
||||
|
||||
File.exists?(old_file).should == true
|
||||
customization.stylesheet = "div { clear:both; }"
|
||||
customization.save
|
||||
|
||||
SiteCustomization.custom_stylesheet(customization.key).should_not == original
|
||||
end
|
||||
|
||||
it 'should delete old stylesheet files after deleting' do
|
||||
old_file = customization.stylesheet_fullpath
|
||||
customization.ensure_stylesheet_on_disk!
|
||||
customization.destroy
|
||||
File.exists?(old_file).should == false
|
||||
end
|
||||
|
||||
it 'should nuke old revs out of the cache' do
|
||||
old_style = SiteCustomization.custom_stylesheet(customization.key)
|
||||
|
||||
customization.stylesheet = "hello worldz"
|
||||
customization.save
|
||||
SiteCustomization.custom_stylesheet(customization.key).should_not == old_style
|
||||
end
|
||||
|
||||
|
||||
it 'should compile scss' do
|
||||
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '$black: #000; #a { color: $black; }', header: '')
|
||||
c.stylesheet_baked.should == "#a {\n color: black; }\n"
|
||||
end
|
||||
|
||||
it 'should provide an awesome error on failure' do
|
||||
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: "$black: #000; #a { color: $black; }\n\n\nboom", header: '')
|
||||
|
||||
c.stylesheet_baked.should =~ /Syntax error/
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
110
spec/models/site_setting_spec.rb
Normal file
110
spec/models/site_setting_spec.rb
Normal file
@@ -0,0 +1,110 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SiteSetting do
|
||||
|
||||
describe "int setting" do
|
||||
before :all do
|
||||
SiteSetting.setting(:test_setting, 77)
|
||||
SiteSetting.refresh!
|
||||
end
|
||||
|
||||
it 'should have a key in all_settings' do
|
||||
SiteSetting.all_settings.detect {|s| s[:setting] == :test_setting }.should be_present
|
||||
end
|
||||
|
||||
it "should have the correct desc" do
|
||||
I18n.expects(:t).with("site_settings.test_setting").returns("test description")
|
||||
SiteSetting.description(:test_setting).should == "test description"
|
||||
end
|
||||
|
||||
it "should have the correct default" do
|
||||
SiteSetting.test_setting.should == 77
|
||||
end
|
||||
|
||||
describe "when overidden" do
|
||||
before :all do
|
||||
SiteSetting.test_setting = 100
|
||||
end
|
||||
|
||||
after :all do
|
||||
SiteSetting.remove_override!(:test_setting)
|
||||
end
|
||||
|
||||
it "should have the correct override" do
|
||||
SiteSetting.test_setting.should == 100
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "string setting" do
|
||||
before :all do
|
||||
SiteSetting.setting(:test_str, "str")
|
||||
SiteSetting.refresh!
|
||||
end
|
||||
|
||||
it "should have the correct default" do
|
||||
SiteSetting.test_str.should == "str"
|
||||
end
|
||||
end
|
||||
|
||||
describe "bool setting" do
|
||||
before :all do
|
||||
SiteSetting.setting(:test_hello?, false)
|
||||
SiteSetting.refresh!
|
||||
end
|
||||
|
||||
it "should have the correct default" do
|
||||
SiteSetting.test_hello?.should == false
|
||||
end
|
||||
|
||||
it "should be overridable" do
|
||||
SiteSetting.test_hello = true
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.test_hello?.should == true
|
||||
end
|
||||
|
||||
it "should coerce true strings to true" do
|
||||
SiteSetting.test_hello = "true"
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.test_hello?.should == true
|
||||
end
|
||||
|
||||
it "should coerce all other strings to false" do
|
||||
SiteSetting.test_hello = "f"
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.test_hello?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'call_mothership?' do
|
||||
it 'should be true when enforce_global_nicknames is true and discourse_org_access_key is set' do
|
||||
SiteSetting.enforce_global_nicknames = true
|
||||
SiteSetting.discourse_org_access_key = 'asdfasfsafd'
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.call_mothership?.should == true
|
||||
end
|
||||
|
||||
it 'should be false when enforce_global_nicknames is false and discourse_org_access_key is set' do
|
||||
SiteSetting.enforce_global_nicknames = false
|
||||
SiteSetting.discourse_org_access_key = 'asdfasfsafd'
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.call_mothership?.should == false
|
||||
end
|
||||
|
||||
it 'should be false when enforce_global_nicknames is true and discourse_org_access_key is not set' do
|
||||
SiteSetting.enforce_global_nicknames = true
|
||||
SiteSetting.discourse_org_access_key = ''
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.call_mothership?.should == false
|
||||
end
|
||||
|
||||
it 'should be false when enforce_global_nicknames is false and discourse_org_access_key is not set' do
|
||||
SiteSetting.enforce_global_nicknames = false
|
||||
SiteSetting.discourse_org_access_key = ''
|
||||
SiteSetting.refresh!
|
||||
SiteSetting.call_mothership?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
6
spec/models/topic_allowed_user_spec.rb
Normal file
6
spec/models/topic_allowed_user_spec.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicAllowedUser do
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
end
|
||||
10
spec/models/topic_invite_spec.rb
Normal file
10
spec/models/topic_invite_spec.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicInvite do
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should belong_to :invite }
|
||||
it { should validate_presence_of :topic_id }
|
||||
it { should validate_presence_of :invite_id }
|
||||
|
||||
end
|
||||
130
spec/models/topic_link_click_spec.rb
Normal file
130
spec/models/topic_link_click_spec.rb
Normal file
@@ -0,0 +1,130 @@
|
||||
require 'discourse'
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicLinkClick do
|
||||
|
||||
it { should belong_to :topic_link }
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should validate_presence_of :topic_link_id }
|
||||
|
||||
def test_uri
|
||||
URI.parse('http://test.host')
|
||||
end
|
||||
|
||||
it 'returns blank from counts_for without posts' do
|
||||
TopicLinkClick.counts_for(nil, nil).should be_blank
|
||||
end
|
||||
|
||||
context 'topic_links' do
|
||||
before do
|
||||
@topic = Fabricate(:topic)
|
||||
@post = Fabricate(:post_with_external_links, user: @topic.user, topic: @topic)
|
||||
TopicLink.extract_from(@post)
|
||||
@topic_link = @topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'has 0 clicks at first' do
|
||||
@topic_link.clicks.should == 0
|
||||
end
|
||||
|
||||
context 'create' do
|
||||
before do
|
||||
TopicLinkClick.create(topic_link: @topic_link, ip: '192.168.1.1')
|
||||
end
|
||||
|
||||
it 'creates the forum topic link click' do
|
||||
TopicLinkClick.count.should == 1
|
||||
end
|
||||
|
||||
it 'has 0 clicks at first' do
|
||||
@topic_link.reload
|
||||
@topic_link.clicks.should == 1
|
||||
end
|
||||
|
||||
it 'serializes and deserializes the IP' do
|
||||
TopicLinkClick.first.ip.to_s.should == '192.168.1.1'
|
||||
end
|
||||
|
||||
context 'counts for' do
|
||||
|
||||
before do
|
||||
@counts_for = TopicLinkClick.counts_for(@topic, [@post])
|
||||
end
|
||||
|
||||
it 'has a counts_for result' do
|
||||
@counts_for[@post.id].should be_present
|
||||
end
|
||||
|
||||
it 'contains the click we made' do
|
||||
@counts_for[@post.id].first[:clicks].should == 1
|
||||
end
|
||||
|
||||
it 'has no clicks on another url in the post' do
|
||||
@counts_for[@post.id].find {|l| l[:url] == 'http://google.com'}[:clicks].should == 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'create_from' do
|
||||
|
||||
context 'without a url' do
|
||||
it "doesn't raise an exception" do
|
||||
TopicLinkClick.create_from(url: "url that doesn't exist", post_id: @post.id, ip: '127.0.0.1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'clicking on your own link' do
|
||||
it "should not record the click" do
|
||||
lambda {
|
||||
TopicLinkClick.create_from(url: @topic_link.url, post_id: @post.id, ip: '127.0.0.1', user_id: @post.user_id)
|
||||
}.should_not change(TopicLinkClick, :count)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'with a valid url and post_id' do
|
||||
before do
|
||||
TopicLinkClick.create_from(url: @topic_link.url, post_id: @post.id, ip: '127.0.0.1')
|
||||
@click = TopicLinkClick.last
|
||||
end
|
||||
|
||||
it 'creates a click' do
|
||||
@click.should be_present
|
||||
end
|
||||
|
||||
it 'has the topic_link id' do
|
||||
@click.topic_link.should == @topic_link
|
||||
end
|
||||
|
||||
context "clicking again" do
|
||||
it "should not record the click due to rate limiting" do
|
||||
-> { TopicLinkClick.create_from(url: @topic_link.url, post_id: @post.id, ip: '127.0.0.1') }.should_not change(TopicLinkClick, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid url and topic_id' do
|
||||
before do
|
||||
TopicLinkClick.create_from(url: @topic_link.url, topic_id: @topic.id, ip: '127.0.0.1')
|
||||
@click = TopicLinkClick.last
|
||||
end
|
||||
|
||||
it 'creates a click' do
|
||||
@click.should be_present
|
||||
end
|
||||
|
||||
it 'has the topic_link id' do
|
||||
@click.topic_link.should == @topic_link
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
206
spec/models/topic_link_spec.rb
Normal file
206
spec/models/topic_link_spec.rb
Normal file
@@ -0,0 +1,206 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicLink do
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should belong_to :post }
|
||||
it { should belong_to :user }
|
||||
it { should have_many :topic_link_clicks }
|
||||
it { should validate_presence_of :url }
|
||||
|
||||
def test_uri
|
||||
URI.parse(Discourse.base_url)
|
||||
end
|
||||
|
||||
before do
|
||||
@topic = Fabricate(:topic, title: 'unique topic name')
|
||||
@user = @topic.user
|
||||
end
|
||||
|
||||
it "can't link to the same topic" do
|
||||
ftl = TopicLink.new(url: "/t/#{@topic.id}",
|
||||
topic_id: @topic.id,
|
||||
link_topic_id: @topic.id)
|
||||
ftl.valid?.should be_false
|
||||
end
|
||||
|
||||
describe 'external links' do
|
||||
before do
|
||||
@post = Fabricate(:post_with_external_links, user: @user, topic: @topic)
|
||||
TopicLink.extract_from(@post)
|
||||
end
|
||||
|
||||
it 'has the forum topic links' do
|
||||
@topic.topic_links.count.should == 4
|
||||
end
|
||||
|
||||
it 'works with markdown links' do
|
||||
@topic.topic_links.exists?(url: "http://forumwarz.com").should be_true
|
||||
end
|
||||
|
||||
it 'works with markdown links followed by a period' do
|
||||
@topic.topic_links.exists?(url: "http://www.codinghorror.com/blog").should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'domain-less link' do
|
||||
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/users'>hello</a>") }
|
||||
let!(:link) do
|
||||
TopicLink.extract_from(post)
|
||||
@topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'is extracted' do
|
||||
link.should be_present
|
||||
end
|
||||
|
||||
it 'has the correct domain' do
|
||||
link.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it "is not destroyed when we call extract from again" do
|
||||
TopicLink.extract_from(post)
|
||||
link.reload
|
||||
link.should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'internal links' do
|
||||
|
||||
before do
|
||||
@other_topic = Fabricate(:topic, user: @user)
|
||||
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
|
||||
|
||||
@url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}"
|
||||
|
||||
@topic.posts.create(user: @user, raw: 'initial post')
|
||||
@post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}")
|
||||
|
||||
TopicLink.extract_from(@post)
|
||||
|
||||
@link = @topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'extracted the link' do
|
||||
@link.should be_present
|
||||
end
|
||||
|
||||
it 'is set to internal' do
|
||||
@link.should be_internal
|
||||
end
|
||||
|
||||
it 'has the correct url' do
|
||||
@link.url.should == @url
|
||||
end
|
||||
|
||||
it 'has the extracted domain' do
|
||||
@link.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it 'should have the id of the linked forum' do
|
||||
@link.link_topic_id == @other_topic.id
|
||||
end
|
||||
|
||||
it 'should not be the reflection' do
|
||||
@link.should_not be_reflection
|
||||
end
|
||||
|
||||
describe 'reflection in the other topic' do
|
||||
|
||||
before do
|
||||
@reflection = @other_topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'exists' do
|
||||
@reflection.should be_present
|
||||
end
|
||||
|
||||
it 'is a reflection' do
|
||||
@reflection.should be_reflection
|
||||
end
|
||||
|
||||
it 'has a post_id' do
|
||||
@reflection.post_id.should be_present
|
||||
end
|
||||
|
||||
it 'has the correct host' do
|
||||
@reflection.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it 'has the correct url' do
|
||||
@reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}"
|
||||
end
|
||||
|
||||
it 'links to the original forum topic' do
|
||||
@reflection.link_topic_id.should == @topic.id
|
||||
end
|
||||
|
||||
it 'links to the original post' do
|
||||
@reflection.link_post_id.should == @post.id
|
||||
end
|
||||
|
||||
it 'has the user id of the original link' do
|
||||
@reflection.user_id.should == @link.user_id
|
||||
end
|
||||
end
|
||||
|
||||
context 'removing a link' do
|
||||
|
||||
before do
|
||||
@post.revise(@post.user, "no more linkies")
|
||||
TopicLink.extract_from(@post)
|
||||
end
|
||||
|
||||
it 'should remove the link' do
|
||||
@topic.topic_links.where(post_id: @post.id).should be_blank
|
||||
end
|
||||
|
||||
it 'should remove the reflected link' do
|
||||
@reflection = @other_topic.topic_links.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'internal link from pm' do
|
||||
before do
|
||||
@pm = Fabricate(:topic, user: @user, archetype: 'private_message')
|
||||
@other_post = @pm.posts.create(user: @user, raw: "some content")
|
||||
|
||||
@url = "http://#{test_uri.host}/t/topic-slug/#{@topic.id}"
|
||||
|
||||
@pm.posts.create(user: @user, raw: 'initial post')
|
||||
@linked_post = @pm.posts.create(user: @user, raw: "Link to another topic: #{@url}")
|
||||
|
||||
TopicLink.extract_from(@linked_post)
|
||||
|
||||
@link = @topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'should not create a reflection' do
|
||||
@topic.topic_links.first.should be_nil
|
||||
end
|
||||
|
||||
it 'should not create a normal link' do
|
||||
@pm.topic_links.first.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'internal link with non-standard port' do
|
||||
it 'includes the non standard port if present' do
|
||||
@other_topic = Fabricate(:topic, user: @user)
|
||||
SiteSetting.stubs(:port).returns(5678)
|
||||
alternate_uri = URI.parse(Discourse.base_url)
|
||||
@url = "http://#{alternate_uri.host}:5678/t/topic-slug/#{@other_topic.id}"
|
||||
@post = @topic.posts.create(user: @user,
|
||||
raw: "Link to another topic: #{@url}")
|
||||
TopicLink.extract_from(@post)
|
||||
@reflection = @other_topic.topic_links.first
|
||||
@reflection.url.should == "http://#{alternate_uri.host}:5678/t/unique-topic-name/#{@topic.id}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
960
spec/models/topic_spec.rb
Normal file
960
spec/models/topic_spec.rb
Normal file
@@ -0,0 +1,960 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Topic do
|
||||
|
||||
it { should validate_presence_of :title }
|
||||
it { should_not allow_value("x" * (SiteSetting.max_topic_title_length + 1)).for(:title) }
|
||||
it { should_not allow_value("x").for(:title) }
|
||||
it { should_not allow_value((" " * SiteSetting.min_topic_title_length) + "x").for(:title) }
|
||||
|
||||
it { should belong_to :category }
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :last_poster }
|
||||
it { should belong_to :featured_user1 }
|
||||
it { should belong_to :featured_user2 }
|
||||
it { should belong_to :featured_user3 }
|
||||
it { should belong_to :featured_user4 }
|
||||
|
||||
it { should have_many :posts }
|
||||
it { should have_many :topic_users }
|
||||
it { should have_many :topic_links }
|
||||
it { should have_many :topic_allowed_users }
|
||||
it { should have_many :allowed_users }
|
||||
it { should have_many :invites }
|
||||
|
||||
it { should rate_limit }
|
||||
|
||||
|
||||
context 'topic title uniqueness' do
|
||||
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let(:new_topic) { Fabricate.build(:topic, title: topic.title) }
|
||||
|
||||
context "when duplicates aren't allowed" do
|
||||
before do
|
||||
SiteSetting.expects(:allow_duplicate_topic_titles?).returns(false)
|
||||
end
|
||||
|
||||
it "won't allow another topic to be created with the same name" do
|
||||
new_topic.should_not be_valid
|
||||
end
|
||||
|
||||
it "won't allow another topic with an upper case title to be created" do
|
||||
new_topic.title = new_topic.title.upcase
|
||||
new_topic.should_not be_valid
|
||||
end
|
||||
|
||||
it "allows it when the topic is deleted" do
|
||||
topic.destroy
|
||||
new_topic.should be_valid
|
||||
end
|
||||
|
||||
it "allows a private message to be created with the same topic" do
|
||||
new_topic.archetype = Archetype.private_message
|
||||
new_topic.should be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "when duplicates are allowed" do
|
||||
before do
|
||||
SiteSetting.expects(:allow_duplicate_topic_titles?).returns(true)
|
||||
end
|
||||
|
||||
it "won't allow another topic to be created with the same name" do
|
||||
new_topic.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'message bus' do
|
||||
it 'calls the message bus observer after create' do
|
||||
MessageBusObserver.any_instance.expects(:after_create_topic).with(instance_of(Topic))
|
||||
Fabricate(:topic)
|
||||
end
|
||||
end
|
||||
|
||||
context 'post_numbers' do
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let!(:p1) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||
let!(:p2) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||
let!(:p3) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||
|
||||
it "returns the post numbers of the topic" do
|
||||
topic.post_numbers.should == [1, 2, 3]
|
||||
end
|
||||
|
||||
it "skips deleted posts" do
|
||||
p2.destroy
|
||||
topic.post_numbers.should == [1, 3]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'move_posts' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:category) { Fabricate(:category, user: user) }
|
||||
let!(:topic) { Fabricate(:topic, user: user, category: category) }
|
||||
let!(:p1) { Fabricate(:post, topic: topic, user: user) }
|
||||
let!(:p2) { Fabricate(:post, topic: topic, user: user)}
|
||||
let!(:p3) { Fabricate(:post, topic: topic, user: user)}
|
||||
let!(:p4) { Fabricate(:post, topic: topic, user: user)}
|
||||
|
||||
context 'success' do
|
||||
|
||||
it "enqueues a job to notify users" do
|
||||
topic.stubs(:add_moderator_post)
|
||||
Jobs.expects(:enqueue).with(:notify_moved_posts, post_ids: [p1.id, p4.id], moved_by_id: user.id)
|
||||
topic.move_posts(user, "new topic name", [p1.id, p4.id])
|
||||
end
|
||||
|
||||
it "adds a moderator post at the location of the first moved post" do
|
||||
topic.expects(:add_moderator_post).with(user, instance_of(String), has_entries(post_number: 2))
|
||||
topic.move_posts(user, "new topic name", [p2.id, p4.id])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "errors" do
|
||||
|
||||
it "raises an error when one of the posts doesn't exist" do
|
||||
lambda { topic.move_posts(user, "new topic name", [1003]) }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "raises an error if no posts were moved" do
|
||||
lambda { topic.move_posts(user, "new topic name", []) }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "afterwards" do
|
||||
before do
|
||||
topic.expects(:add_moderator_post)
|
||||
TopicUser.update_last_read(user, topic.id, p4.post_number, 0)
|
||||
end
|
||||
|
||||
let!(:new_topic) { topic.move_posts(user, "new topic name", [p2.id, p4.id]) }
|
||||
|
||||
|
||||
it "updates the user's last_read_post_number" do
|
||||
TopicUser.where(user_id: user.id, topic_id: topic.id).first.last_read_post_number.should == p3.post_number
|
||||
end
|
||||
|
||||
context 'new topic' do
|
||||
it "exists" do
|
||||
new_topic.should be_present
|
||||
end
|
||||
|
||||
it "has the correct category" do
|
||||
new_topic.category.should == category
|
||||
end
|
||||
|
||||
it "has two posts" do
|
||||
new_topic.reload
|
||||
new_topic.posts_count.should == 2
|
||||
end
|
||||
|
||||
it "has the moved posts" do
|
||||
new_topic.posts.should =~ [p2, p4]
|
||||
end
|
||||
|
||||
it "has renumbered the first post" do
|
||||
p2.reload
|
||||
p2.post_number.should == 1
|
||||
end
|
||||
|
||||
it "has changed the first post's sort order" do
|
||||
p2.reload
|
||||
p2.sort_order.should == 1
|
||||
end
|
||||
|
||||
it "has renumbered the forth post" do
|
||||
p4.reload
|
||||
p4.post_number.should == 2
|
||||
end
|
||||
|
||||
it "has changed the fourth post's sort order" do
|
||||
p4.reload
|
||||
p4.sort_order.should == 2
|
||||
end
|
||||
|
||||
it "has the correct highest_post_number" do
|
||||
new_topic.reload
|
||||
new_topic.highest_post_number.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
context "original topic" do
|
||||
before do
|
||||
topic.reload
|
||||
end
|
||||
|
||||
it "has 2 posts now" do
|
||||
topic.posts_count.should == 2
|
||||
end
|
||||
|
||||
it "contains the leftover posts" do
|
||||
topic.posts.should =~ [p1, p3]
|
||||
end
|
||||
|
||||
it "has the correct highest_post_number" do
|
||||
topic.reload
|
||||
topic.highest_post_number.should == p3.post_number
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
context 'private message' do
|
||||
let(:coding_horror) { User.where(username: 'CodingHorror').first }
|
||||
let(:evil_trout) { Fabricate(:evil_trout) }
|
||||
let!(:topic) { Fabricate(:private_message_topic) }
|
||||
|
||||
it "should allow the allowed users to see the topic" do
|
||||
Guardian.new(topic.user).can_see?(topic).should be_true
|
||||
end
|
||||
|
||||
it "should disallow anon to see the topic" do
|
||||
Guardian.new.can_see?(topic).should be_false
|
||||
end
|
||||
|
||||
it "should disallow a different user to see the topic" do
|
||||
Guardian.new(evil_trout).can_see?(topic).should be_false
|
||||
end
|
||||
|
||||
it "should allow the recipient user to see the topic" do
|
||||
Guardian.new(coding_horror).can_see?(topic).should be_true
|
||||
end
|
||||
|
||||
it "should be excluded from the list view" do
|
||||
TopicQuery.new(evil_trout).list_popular.topics.should_not include(topic)
|
||||
end
|
||||
|
||||
context 'invite' do
|
||||
it "returns false if the username doesn't exist" do
|
||||
topic.invite(topic.user, 'duhhhhh').should be_false
|
||||
end
|
||||
|
||||
it "delegates to topic.invite_by_email when the user doesn't exist, but it's an email" do
|
||||
topic.expects(:invite_by_email).with(topic.user, 'jake@adventuretime.ooo')
|
||||
topic.invite(topic.user, 'jake@adventuretime.ooo')
|
||||
end
|
||||
|
||||
context 'existing user' do
|
||||
let(:walter) { Fabricate(:walter_white) }
|
||||
|
||||
context 'by username' do
|
||||
it 'returns true' do
|
||||
topic.invite(topic.user, walter.username).should be_true
|
||||
end
|
||||
|
||||
it 'adds walter to the allowed users' do
|
||||
topic.invite(topic.user, walter.username)
|
||||
topic.allowed_users.include?(walter).should be_true
|
||||
end
|
||||
|
||||
it 'creates a notification' do
|
||||
lambda { topic.invite(topic.user, walter.username) }.should change(Notification, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'by email' do
|
||||
it 'returns true' do
|
||||
topic.invite(topic.user, walter.email).should be_true
|
||||
end
|
||||
|
||||
it 'adds walter to the allowed users' do
|
||||
topic.invite(topic.user, walter.email)
|
||||
topic.allowed_users.include?(walter).should be_true
|
||||
end
|
||||
|
||||
it 'creates a notification' do
|
||||
lambda { topic.invite(topic.user, walter.email) }.should change(Notification, :count)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "user actions" do
|
||||
let(:actions) { topic.user.user_actions }
|
||||
|
||||
it "should not log a user action for the creation of the topic" do
|
||||
actions.map{|a| a.action_type}.should_not include(UserAction::NEW_TOPIC)
|
||||
end
|
||||
|
||||
it "should log a user action for the creation of a private message" do
|
||||
actions.map{|a| a.action_type}.should include(UserAction::NEW_PRIVATE_MESSAGE)
|
||||
end
|
||||
|
||||
it "should log a user action for the recepient of the private message" do
|
||||
coding_horror.user_actions.map{|a| a.action_type}.should include(UserAction::GOT_PRIVATE_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
context "other user" do
|
||||
|
||||
it "sends the other user an email when there's a new post" do
|
||||
UserNotifications.expects(:private_message).with(coding_horror, has_key(:post))
|
||||
Fabricate(:post, topic: topic, user: topic.user)
|
||||
end
|
||||
|
||||
it "doesn't send the user an email when they have them disabled" do
|
||||
coding_horror.update_column(:email_private_messages, false)
|
||||
UserNotifications.expects(:private_message).with(coding_horror, has_key(:post)).never
|
||||
Fabricate(:post, topic: topic, user: topic.user)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'bumping topics' do
|
||||
|
||||
before do
|
||||
@topic = Fabricate(:topic, bumped_at: 1.year.ago)
|
||||
end
|
||||
|
||||
it 'has a bumped at value after creation' do
|
||||
@topic.bumped_at.should be_present
|
||||
end
|
||||
|
||||
it 'updates the bumped_at field when a new post is made' do
|
||||
lambda {
|
||||
Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
@topic.reload
|
||||
}.should change(@topic, :bumped_at)
|
||||
end
|
||||
|
||||
context 'editing posts' do
|
||||
before do
|
||||
@earlier_post = Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
@last_post = Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it "doesn't bump the topic on an edit to the last post that doesn't result in a new version" do
|
||||
lambda {
|
||||
SiteSetting.expects(:ninja_edit_window).returns(5.minutes)
|
||||
@last_post.revise(@last_post.user, 'updated contents', revised_at: @last_post.created_at + 10.seconds)
|
||||
@topic.reload
|
||||
}.should_not change(@topic, :bumped_at)
|
||||
end
|
||||
|
||||
it "bumps the topic when a new version is made of the last post" do
|
||||
lambda {
|
||||
@last_post.revise(Fabricate(:moderator), 'updated contents')
|
||||
@topic.reload
|
||||
}.should change(@topic, :bumped_at)
|
||||
end
|
||||
|
||||
it "doesn't bump the topic when a post that isn't the last post receives a new version" do
|
||||
lambda {
|
||||
@earlier_post.revise(Fabricate(:moderator), 'updated contents')
|
||||
@topic.reload
|
||||
}.should_not change(@topic, :bumped_at)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'moderator posts' do
|
||||
before do
|
||||
@moderator = Fabricate(:moderator)
|
||||
@topic = Fabricate(:topic)
|
||||
@mod_post = @topic.add_moderator_post(@moderator, "Moderator did something. http://discourse.org", post_number: 999)
|
||||
end
|
||||
|
||||
it 'creates a moderator post' do
|
||||
@mod_post.should be_present
|
||||
end
|
||||
|
||||
it 'has the moderator action type' do
|
||||
@mod_post.post_type.should == Post::MODERATOR_ACTION
|
||||
end
|
||||
|
||||
it 'increases the moderator_posts count' do
|
||||
@topic.reload
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "inserts the post at the number we provided" do
|
||||
@mod_post.post_number.should == 999
|
||||
end
|
||||
|
||||
it "has the custom sort order we specified" do
|
||||
@mod_post.sort_order.should == 999
|
||||
end
|
||||
|
||||
it 'creates a topic link' do
|
||||
@topic.topic_links.count.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'update_status' do
|
||||
before do
|
||||
@topic = Fabricate(:topic, bumped_at: 1.hour.ago)
|
||||
@topic.reload
|
||||
@original_bumped_at = @topic.bumped_at.to_f
|
||||
@user = @topic.user
|
||||
end
|
||||
|
||||
context 'visibility' do
|
||||
context 'disable' do
|
||||
before do
|
||||
@topic.update_status('visible', false, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should not be visible' do
|
||||
@topic.should_not be_visible
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'enable' do
|
||||
before do
|
||||
@topic.update_attribute :visible, false
|
||||
@topic.update_status('visible', true, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should be visible' do
|
||||
@topic.should be_visible
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pinned' do
|
||||
context 'disable' do
|
||||
before do
|
||||
@topic.update_status('pinned', false, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should not be pinned' do
|
||||
@topic.should_not be_pinned
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
end
|
||||
|
||||
context 'enable' do
|
||||
before do
|
||||
@topic.update_attribute :pinned, false
|
||||
@topic.update_status('pinned', true, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should be pinned' do
|
||||
@topic.should be_pinned
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived' do
|
||||
context 'disable' do
|
||||
before do
|
||||
@topic.update_status('archived', false, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should not be pinned' do
|
||||
@topic.should_not be_archived
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
end
|
||||
|
||||
context 'enable' do
|
||||
before do
|
||||
@topic.update_attribute :archived, false
|
||||
@topic.update_status('archived', true, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should be archived' do
|
||||
@topic.should be_archived
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'closed' do
|
||||
context 'disable' do
|
||||
before do
|
||||
@topic.update_status('closed', false, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should not be pinned' do
|
||||
@topic.should_not be_closed
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
# We bump the topic when a topic is re-opened
|
||||
it "bumps the topic" do
|
||||
@topic.bumped_at.to_f.should_not == @original_bumped_at
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'enable' do
|
||||
before do
|
||||
@topic.update_attribute :closed, false
|
||||
@topic.update_status('closed', true, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should be closed' do
|
||||
@topic.should be_closed
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
@topic.moderator_posts_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't bump the topic" do
|
||||
@topic.bumped_at.to_f.should == @original_bumped_at
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe 'toggle_star' do
|
||||
|
||||
before do
|
||||
@topic = Fabricate(:topic)
|
||||
@user = @topic.user
|
||||
end
|
||||
|
||||
it 'triggers a forum topic user change with true' do
|
||||
# otherwise no chance the mock will work
|
||||
freeze_time do
|
||||
TopicUser.expects(:change).with(@user, @topic.id, starred: true, starred_at: DateTime.now)
|
||||
@topic.toggle_star(@user, true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'increases the star_count of the forum topic' do
|
||||
lambda {
|
||||
@topic.toggle_star(@user, true)
|
||||
@topic.reload
|
||||
}.should change(@topic, :star_count).by(1)
|
||||
end
|
||||
|
||||
it 'triggers the rate limiter' do
|
||||
Topic::FavoriteLimiter.any_instance.expects(:performed!)
|
||||
@topic.toggle_star(@user, true)
|
||||
end
|
||||
|
||||
describe 'removing a star' do
|
||||
before do
|
||||
@topic.toggle_star(@user, true)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'rolls back the rate limiter' do
|
||||
Topic::FavoriteLimiter.any_instance.expects(:rollback!)
|
||||
@topic.toggle_star(@user, false)
|
||||
end
|
||||
|
||||
it 'triggers a forum topic user change with false' do
|
||||
TopicUser.expects(:change).with(@user, @topic.id, starred: false, starred_at: nil)
|
||||
@topic.toggle_star(@user, false)
|
||||
end
|
||||
|
||||
it 'reduces the star_count' do
|
||||
lambda {
|
||||
@topic.toggle_star(@user, false)
|
||||
@topic.reload
|
||||
}.should change(@topic, :star_count).by(-1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'last_poster info' do
|
||||
|
||||
before do
|
||||
@user = Fabricate(:user)
|
||||
@post = Fabricate(:post, user: @user)
|
||||
@topic = @post.topic
|
||||
end
|
||||
|
||||
it 'initially has the last_post_user_id of the OP' do
|
||||
@topic.last_post_user_id.should == @user.id
|
||||
end
|
||||
|
||||
context 'after a second post' do
|
||||
before do
|
||||
@second_user = Fabricate(:coding_horror)
|
||||
@new_post = Fabricate(:post, topic: @topic, user: @second_user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'updates the last_post_user_id to the second_user' do
|
||||
@topic.last_post_user_id.should == @second_user.id
|
||||
end
|
||||
|
||||
it 'resets the last_posted_at back to the OP' do
|
||||
@topic.last_posted_at.to_i.should == @new_post.created_at.to_i
|
||||
end
|
||||
|
||||
it 'has a posted flag set for the second user' do
|
||||
topic_user = @second_user.topic_users.where(topic_id: @topic.id).first
|
||||
topic_user.posted?.should be_true
|
||||
end
|
||||
|
||||
|
||||
context 'after deleting that post' do
|
||||
|
||||
before do
|
||||
@new_post.destroy
|
||||
Topic.reset_highest(@topic.id)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'resets the last_poster_id back to the OP' do
|
||||
@topic.last_post_user_id.should == @user.id
|
||||
end
|
||||
|
||||
it 'resets the last_posted_at back to the OP' do
|
||||
@topic.last_posted_at.to_i.should == @post.created_at.to_i
|
||||
end
|
||||
|
||||
context 'topic_user' do
|
||||
before do
|
||||
@topic_user = @second_user.topic_users.where(topic_id: @topic.id).first
|
||||
end
|
||||
|
||||
it 'clears the posted flag for the second user' do
|
||||
@topic_user.posted?.should be_false
|
||||
end
|
||||
|
||||
it "sets the second user's last_read_post_number back to 1" do
|
||||
@topic_user.last_read_post_number.should == 1
|
||||
end
|
||||
|
||||
it "sets the second user's last_read_post_number back to 1" do
|
||||
@topic_user.seen_post_count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'with category' do
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
end
|
||||
|
||||
it "should not increase the topic_count with no category" do
|
||||
lambda { Fabricate(:topic, user: @category.user); @category.reload }.should_not change(@category, :topic_count)
|
||||
end
|
||||
|
||||
it "should increase the category's topic_count" do
|
||||
lambda { Fabricate(:topic, user: @category.user, category_id: @category.id); @category.reload }.should change(@category, :topic_count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'meta data' do
|
||||
let(:topic) { Fabricate(:topic, :meta_data => {hello: 'world'}) }
|
||||
|
||||
it 'allows us to create a topic with meta data' do
|
||||
topic.meta_data['hello'].should == 'world'
|
||||
end
|
||||
|
||||
context 'updating' do
|
||||
|
||||
context 'existing key' do
|
||||
before do
|
||||
topic.update_meta_data(hello: 'bane')
|
||||
end
|
||||
|
||||
it 'updates the key' do
|
||||
topic.meta_data['hello'].should == 'bane'
|
||||
end
|
||||
end
|
||||
|
||||
context 'new key' do
|
||||
before do
|
||||
topic.update_meta_data(city: 'gotham')
|
||||
end
|
||||
|
||||
it 'adds the new key' do
|
||||
topic.meta_data['city'].should == 'gotham'
|
||||
end
|
||||
|
||||
it 'still has the old key' do
|
||||
topic.meta_data['hello'].should == 'world'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'after create' do
|
||||
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
|
||||
it 'is a regular topic by default' do
|
||||
topic.archetype.should == Archetype.default
|
||||
end
|
||||
|
||||
it 'is not a best_of' do
|
||||
topic.has_best_of.should be_false
|
||||
end
|
||||
|
||||
it 'is not invisible' do
|
||||
topic.should be_visible
|
||||
end
|
||||
|
||||
it 'is not pinned' do
|
||||
topic.should_not be_pinned
|
||||
end
|
||||
|
||||
it 'is not closed' do
|
||||
topic.should_not be_closed
|
||||
end
|
||||
|
||||
it 'is not archived' do
|
||||
topic.should_not be_archived
|
||||
end
|
||||
|
||||
it 'has no moderator posts' do
|
||||
topic.moderator_posts_count.should == 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
context 'post' do
|
||||
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||
|
||||
it 'has the same archetype as the topic' do
|
||||
post.archetype.should == topic.archetype
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'versions' do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
|
||||
it "has version 1 by default" do
|
||||
topic.version.should == 1
|
||||
end
|
||||
|
||||
context 'changing title' do
|
||||
before do
|
||||
topic.title = "new title"
|
||||
topic.save
|
||||
end
|
||||
|
||||
it "creates a new version" do
|
||||
topic.version.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
context 'changing category' do
|
||||
let(:category) { Fabricate(:category) }
|
||||
|
||||
before do
|
||||
topic.change_category(category.name)
|
||||
end
|
||||
|
||||
it "creates a new version" do
|
||||
topic.version.should == 2
|
||||
end
|
||||
|
||||
context "removing a category" do
|
||||
before do
|
||||
topic.change_category(nil)
|
||||
end
|
||||
|
||||
it "creates a new version" do
|
||||
topic.version.should == 3
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'bumping the topic' do
|
||||
before do
|
||||
topic.bumped_at = 10.minutes.from_now
|
||||
topic.save
|
||||
end
|
||||
|
||||
it "doesn't craete a new version" do
|
||||
topic.version.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'change_category' do
|
||||
|
||||
before do
|
||||
@topic = Fabricate(:topic)
|
||||
@category = Fabricate(:category, user: @topic.user)
|
||||
@user = @topic.user
|
||||
end
|
||||
|
||||
describe 'without a previous category' do
|
||||
|
||||
it 'should not change the topic_count when not changed' do
|
||||
lambda { @topic.change_category(nil); @category.reload }.should_not change(@category, :topic_count)
|
||||
end
|
||||
|
||||
describe 'changed category' do
|
||||
before do
|
||||
@topic.change_category(@category.name)
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'changes the category' do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it 'increases the topic_count' do
|
||||
@category.topic_count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
it "doesn't change the category when it can't be found" do
|
||||
@topic.change_category('made up')
|
||||
@topic.category.should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with a previous category' do
|
||||
before do
|
||||
@topic.change_category(@category.name)
|
||||
@topic.reload
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'increases the topic_count' do
|
||||
@category.topic_count.should == 1
|
||||
end
|
||||
|
||||
it "doesn't change the topic_count when the value doesn't change" do
|
||||
lambda { @topic.change_category(@category.name); @category.reload }.should_not change(@category, :topic_count)
|
||||
end
|
||||
|
||||
it "doesn't reset the category when given a name that doesn't exist" do
|
||||
@topic.change_category('made up')
|
||||
@topic.category_id.should be_present
|
||||
end
|
||||
|
||||
describe 'to a different category' do
|
||||
before do
|
||||
@new_category = Fabricate(:category, user: @user, name: '2nd category')
|
||||
@topic.change_category(@new_category.name)
|
||||
@topic.reload
|
||||
@new_category.reload
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it "should increase the new category's topic count" do
|
||||
@new_category.topic_count.should == 1
|
||||
end
|
||||
|
||||
it "should lower the original category's topic count" do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when the category exists' do
|
||||
before do
|
||||
@topic.change_category(nil)
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it "resets the category" do
|
||||
@topic.category_id.should be_blank
|
||||
end
|
||||
|
||||
it "lowers the forum topic count" do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
199
spec/models/topic_user_spec.rb
Normal file
199
spec/models/topic_user_spec.rb
Normal file
@@ -0,0 +1,199 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicUser do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
|
||||
before do
|
||||
#mock time so we can test dates
|
||||
@now = DateTime.now.yesterday
|
||||
DateTime.expects(:now).at_least_once.returns(@now)
|
||||
@topic = Fabricate(:topic)
|
||||
@user = Fabricate(:coding_horror)
|
||||
end
|
||||
|
||||
describe 'notifications' do
|
||||
|
||||
it 'should be set to tracking if auto_track_topics is enabled' do
|
||||
@user.auto_track_topics_after_msecs = 0
|
||||
@user.save
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
end
|
||||
|
||||
it 'should reset regular topics to tracking topics if auto track is changed' do
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
@user.auto_track_topics_after_msecs = 0
|
||||
@user.save
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
end
|
||||
|
||||
it 'should be set to "regular" notifications, by default on non creators' do
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
end
|
||||
|
||||
it 'reason should reset when changed' do
|
||||
@topic.notify_muted!(@topic.user)
|
||||
TopicUser.get(@topic,@topic.user).notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when watched' do
|
||||
@topic.notify_watch!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when set to regular' do
|
||||
@topic.notify_regular!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when set to regular' do
|
||||
@topic.notify_muted!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::MUTED
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should watch topics a user created' do
|
||||
tu = TopicUser.get(@topic,@topic.user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_TOPIC
|
||||
end
|
||||
end
|
||||
|
||||
describe 'visited at' do
|
||||
before do
|
||||
TopicUser.track_visit!(@topic, @user)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
|
||||
end
|
||||
|
||||
it 'set upon initial visit' do
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
end
|
||||
|
||||
it 'updates upon repeat visit' do
|
||||
tomorrow = @now.tomorrow
|
||||
DateTime.expects(:now).returns(tomorrow)
|
||||
|
||||
TopicUser.track_visit!(@topic,@user)
|
||||
# reload is a no go
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.last_visited_at.to_i.should == tomorrow.to_i
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'read tracking' do
|
||||
before do
|
||||
@post = Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
TopicUser.update_last_read(@user, @topic.id, 1, 0)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
end
|
||||
|
||||
it 'should create a new record for a visit' do
|
||||
@topic_user.last_read_post_number.should == 1
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
end
|
||||
|
||||
it 'should update the record for repeat visit' do
|
||||
Fabricate(:post, topic: @topic, user: @user)
|
||||
TopicUser.update_last_read(@user, @topic.id, 2, 0)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
@topic_user.last_read_post_number.should == 2
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
end
|
||||
|
||||
context 'auto tracking' do
|
||||
before do
|
||||
Fabricate(:post, topic: @topic, user: @user)
|
||||
@new_user = Fabricate(:user, auto_track_topics_after_msecs: 1000)
|
||||
TopicUser.update_last_read(@new_user, @topic.id, 2, 0)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
end
|
||||
|
||||
it 'should automatically track topics you reply to' do
|
||||
post = Fabricate(:post, topic: @topic, user: @new_user)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
@topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_POST
|
||||
end
|
||||
|
||||
it 'should not automatically track topics you reply to and have set state manually' do
|
||||
Fabricate(:post, topic: @topic, user: @new_user)
|
||||
TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
@topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
end
|
||||
|
||||
it 'should automatically track topics after they are read for long enough' do
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
TopicUser.update_last_read(@new_user, @topic.id, 2, 1001)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
end
|
||||
|
||||
it 'should not automatically track topics after they are read for long enough if changed manually' do
|
||||
TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
|
||||
TopicUser.update_last_read(@new_user, @topic, 2, 1001)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'change a flag' do
|
||||
|
||||
it 'creates a forum topic user record' do
|
||||
lambda {
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
}.should change(TopicUser, :count).by(1)
|
||||
end
|
||||
|
||||
it "only inserts a row once, even on repeated calls" do
|
||||
lambda {
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
TopicUser.change(@user, @topic.id, starred: false)
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
}.should change(TopicUser, :count).by(1)
|
||||
end
|
||||
|
||||
describe 'after creating a row' do
|
||||
before do
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
@topic_user = TopicUser.where(user_id: @user.id, topic_id: @topic.id).first
|
||||
end
|
||||
|
||||
it 'has the correct starred value' do
|
||||
@topic_user.should be_starred
|
||||
end
|
||||
|
||||
it 'has a lookup' do
|
||||
TopicUser.lookup_for(@user, [@topic]).should be_present
|
||||
end
|
||||
|
||||
it 'has a key in the lookup for this forum topic' do
|
||||
TopicUser.lookup_for(@user, [@topic]).has_key?(@topic.id).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
9
spec/models/upload_spec.rb
Normal file
9
spec/models/upload_spec.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Upload do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
it { should validate_presence_of :original_filename }
|
||||
it { should validate_presence_of :filesize }
|
||||
end
|
||||
174
spec/models/user_action_spec.rb
Normal file
174
spec/models/user_action_spec.rb
Normal file
@@ -0,0 +1,174 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserAction do
|
||||
|
||||
it { should validate_presence_of :action_type }
|
||||
it { should validate_presence_of :user_id }
|
||||
|
||||
|
||||
describe 'lists' do
|
||||
|
||||
before do
|
||||
a = UserAction.new
|
||||
@post = Fabricate(:post)
|
||||
@user = Fabricate(:coding_horror)
|
||||
row = {
|
||||
action_type: UserAction::NEW_PRIVATE_MESSAGE,
|
||||
user_id: @user.id,
|
||||
acting_user_id: @user.id,
|
||||
target_topic_id: @post.topic_id,
|
||||
target_post_id: @post.id,
|
||||
}
|
||||
|
||||
UserAction.log_action!(row)
|
||||
|
||||
row[:action_type] = UserAction::GOT_PRIVATE_MESSAGE
|
||||
UserAction.log_action!(row)
|
||||
|
||||
row[:action_type] = UserAction::NEW_TOPIC
|
||||
UserAction.log_action!(row)
|
||||
|
||||
end
|
||||
|
||||
describe 'stats' do
|
||||
|
||||
let :mystats do
|
||||
UserAction.stats(@user.id,Guardian.new(@user))
|
||||
end
|
||||
|
||||
it 'should include non private message events' do
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::NEW_TOPIC)
|
||||
end
|
||||
|
||||
it 'should exclude private messages for non owners' do
|
||||
UserAction.stats(@user.id,Guardian.new).map{|r| r["action_type"].to_i}.should_not include(UserAction::NEW_PRIVATE_MESSAGE)
|
||||
end
|
||||
|
||||
it 'should not include got private messages for owners' do
|
||||
UserAction.stats(@user.id,Guardian.new).map{|r| r["action_type"].to_i}.should_not include(UserAction::GOT_PRIVATE_MESSAGE)
|
||||
end
|
||||
|
||||
it 'should include private messages for owners' do
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::NEW_PRIVATE_MESSAGE)
|
||||
end
|
||||
|
||||
it 'should include got private messages for owners' do
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::GOT_PRIVATE_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'stream' do
|
||||
|
||||
it 'should have 1 item for non owners' do
|
||||
UserAction.stream(user_id: @user.id, guardian: Guardian.new).count.should == 1
|
||||
end
|
||||
|
||||
it 'should have 3 items for non owners' do
|
||||
UserAction.stream(user_id: @user.id, guardian: @user.guardian).count.should == 3
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls the message bus observer' do
|
||||
MessageBusObserver.any_instance.expects(:after_create_user_action).with(instance_of(UserAction))
|
||||
Fabricate(:user_action)
|
||||
end
|
||||
|
||||
describe 'when user likes' do
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
@likee = @post.user
|
||||
@liker = Fabricate(:coding_horror)
|
||||
PostAction.act(@liker, @post, PostActionType.Types[:like])
|
||||
@liker_action = @liker.user_actions.where(action_type: UserAction::LIKE).first
|
||||
@likee_action = @likee.user_actions.where(action_type: UserAction::WAS_LIKED).first
|
||||
end
|
||||
|
||||
it 'should create a like action on the liker' do
|
||||
@liker_action.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should create a like action on the likee' do
|
||||
@likee_action.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a user posts a new topic' do
|
||||
before do
|
||||
@post = Fabricate(:old_post)
|
||||
end
|
||||
|
||||
describe 'topic action' do
|
||||
before do
|
||||
@action = @post.user.user_actions.where(action_type: UserAction::NEW_TOPIC).first
|
||||
end
|
||||
it 'should exist' do
|
||||
@action.should_not be_nil
|
||||
end
|
||||
it 'shoule have the correct date' do
|
||||
@action.created_at.should be_within(1).of(@post.topic.created_at)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it 'should not log a post user action' do
|
||||
@post.user.user_actions.where(action_type: UserAction::POST).first.should be_nil
|
||||
end
|
||||
|
||||
|
||||
describe 'when another user posts on the topic' do
|
||||
before do
|
||||
@other_user = Fabricate(:coding_horror)
|
||||
@mentioned = Fabricate(:admin)
|
||||
@response = Fabricate(:post, topic: @post.topic, user: @other_user, raw: "perhaps @#{@mentioned.username} knows how this works?")
|
||||
end
|
||||
|
||||
it 'should log a post action for the poster' do
|
||||
@response.user.user_actions.where(action_type: UserAction::POST).first.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should log a post action for the original poster' do
|
||||
@post.user.user_actions.where(action_type: UserAction::TOPIC_RESPONSE).first.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should log a mention for the mentioned' do
|
||||
@mentioned.user_actions.where(action_type: UserAction::MENTION).first.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should not log a double notification for a post edit' do
|
||||
@response.raw = "here it goes again"
|
||||
@response.save!
|
||||
@response.user.user_actions.where(action_type: UserAction::POST).count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when user bookmarks' do
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
@user = @post.user
|
||||
PostAction.act(@user, @post, PostActionType.Types[:bookmark])
|
||||
@action = @user.user_actions.where(action_type: UserAction::BOOKMARK).first
|
||||
end
|
||||
|
||||
it 'should create a bookmark action' do
|
||||
@action.action_type.should == UserAction::BOOKMARK
|
||||
end
|
||||
it 'should point to the correct post' do
|
||||
@action.target_post_id.should == @post.id
|
||||
end
|
||||
it 'should have the right acting_user' do
|
||||
@action.acting_user_id.should == @user.id
|
||||
end
|
||||
it 'should target the correct user' do
|
||||
@action.user_id.should == @user.id
|
||||
end
|
||||
it 'should nuke the action when unbookmarked' do
|
||||
PostAction.remove_act(@user, @post, PostActionType.Types[:bookmark])
|
||||
@user.user_actions.where(action_type: UserAction::BOOKMARK).first.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
77
spec/models/user_email_observer_spec.rb
Normal file
77
spec/models/user_email_observer_spec.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserEmailObserver do
|
||||
|
||||
context 'user_mentioned' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:notification) { Fabricate(:notification, user: user) }
|
||||
|
||||
it "enqueues a job for the email" do
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, type: :user_mentioned, user_id: notification.user_id, notification_id: notification.id)
|
||||
UserEmailObserver.send(:new).email_user_mentioned(notification)
|
||||
end
|
||||
|
||||
it "doesn't enqueue an email if the user has mention emails disabled" do
|
||||
user.expects(:email_direct?).returns(false)
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, has_entry(type: :user_mentioned)).never
|
||||
UserEmailObserver.send(:new).email_user_mentioned(notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'user_replied' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:notification) { Fabricate(:notification, user: user) }
|
||||
|
||||
it "enqueues a job for the email" do
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, type: :user_replied, user_id: notification.user_id, notification_id: notification.id)
|
||||
UserEmailObserver.send(:new).email_user_replied(notification)
|
||||
end
|
||||
|
||||
it "doesn't enqueue an email if the user has mention emails disabled" do
|
||||
user.expects(:email_direct?).returns(false)
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, has_entry(type: :user_replied)).never
|
||||
UserEmailObserver.send(:new).email_user_replied(notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'user_quoted' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:notification) { Fabricate(:notification, user: user) }
|
||||
|
||||
it "enqueues a job for the email" do
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, type: :user_quoted, user_id: notification.user_id, notification_id: notification.id)
|
||||
UserEmailObserver.send(:new).email_user_quoted(notification)
|
||||
end
|
||||
|
||||
it "doesn't enqueue an email if the user has mention emails disabled" do
|
||||
user.expects(:email_direct?).returns(false)
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, has_entry(type: :user_quoted)).never
|
||||
UserEmailObserver.send(:new).email_user_quoted(notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'email_user_invited_to_private_message' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:notification) { Fabricate(:notification, user: user) }
|
||||
|
||||
it "enqueues a job for the email" do
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, type: :user_invited_to_private_message, user_id: notification.user_id, notification_id: notification.id)
|
||||
UserEmailObserver.send(:new).email_user_invited_to_private_message(notification)
|
||||
end
|
||||
|
||||
it "doesn't enqueue an email if the user has mention emails disabled" do
|
||||
user.expects(:email_direct?).returns(false)
|
||||
Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, has_entry(type: :user_invited_to_private_message)).never
|
||||
UserEmailObserver.send(:new).email_user_invited_to_private_message(notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
8
spec/models/user_open_id_spec.rb
Normal file
8
spec/models/user_open_id_spec.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserOpenId do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should validate_presence_of :email }
|
||||
it { should validate_presence_of :url }
|
||||
end
|
||||
586
spec/models/user_spec.rb
Normal file
586
spec/models/user_spec.rb
Normal file
@@ -0,0 +1,586 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe User do
|
||||
|
||||
it { should have_many :posts }
|
||||
it { should have_many :notifications }
|
||||
it { should have_many :topic_users }
|
||||
it { should have_many :post_actions }
|
||||
it { should have_many :user_actions }
|
||||
it { should have_many :topics }
|
||||
it { should have_many :user_open_ids }
|
||||
it { should have_many :post_timings }
|
||||
it { should have_many :email_tokens }
|
||||
it { should have_many :views }
|
||||
it { should have_many :user_visits }
|
||||
it { should belong_to :approved_by }
|
||||
|
||||
it { should validate_presence_of :username }
|
||||
it { should validate_presence_of :email }
|
||||
|
||||
context '#update_view_counts' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context 'topics_entered' do
|
||||
context 'without any views' do
|
||||
it "doesn't increase the user's topics_entered" do
|
||||
lambda { User.update_view_counts; user.reload }.should_not change(user, :topics_entered)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a view' do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let!(:view) { View.create_for(topic, '127.0.0.1', user) }
|
||||
|
||||
it "adds one to the topics entered" do
|
||||
User.update_view_counts
|
||||
user.reload
|
||||
user.topics_entered.should == 1
|
||||
end
|
||||
|
||||
it "won't record a second view as a different topic" do
|
||||
View.create_for(topic, '127.0.0.1', user)
|
||||
User.update_view_counts
|
||||
user.reload
|
||||
user.topics_entered.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'posts_read_count' do
|
||||
context 'without any post timings' do
|
||||
it "doesn't increase the user's posts_read_count" do
|
||||
lambda { User.update_view_counts; user.reload }.should_not change(user, :posts_read_count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a post timing' do
|
||||
let!(:post) { Fabricate(:post) }
|
||||
let!(:post_timings) do
|
||||
PostTiming.record_timing(msecs: 1234, topic_id: post.topic_id, user_id: user.id, post_number: post.post_number)
|
||||
end
|
||||
|
||||
it "increases posts_read_count" do
|
||||
User.update_view_counts
|
||||
user.reload
|
||||
user.posts_read_count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
context '.enqueue_welcome_message' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it 'enqueues the system message' do
|
||||
Jobs.expects(:enqueue).with(:send_system_message, user_id: user.id, message_type: 'welcome_user')
|
||||
user.enqueue_welcome_message('welcome_user')
|
||||
end
|
||||
|
||||
it "doesn't enqueue the system message when the site settings disable it" do
|
||||
SiteSetting.expects(:send_welcome_message?).returns(false)
|
||||
Jobs.expects(:enqueue).with(:send_system_message, user_id: user.id, message_type: 'welcome_user').never
|
||||
user.enqueue_welcome_message('welcome_user')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.approve!' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
|
||||
it "generates a welcome message" do
|
||||
user.expects(:enqueue_welcome_message).with('welcome_approved')
|
||||
user.approve(admin)
|
||||
end
|
||||
|
||||
context 'after approval' do
|
||||
before do
|
||||
user.approve(admin)
|
||||
end
|
||||
|
||||
it 'marks the user as approved' do
|
||||
user.should be_approved
|
||||
end
|
||||
|
||||
it 'has the admin as the approved by' do
|
||||
user.approved_by.should == admin
|
||||
end
|
||||
|
||||
it 'has a value for approved_at' do
|
||||
user.approved_at.should be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe 'bookmark' do
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
end
|
||||
|
||||
it "creates a bookmark with the true parameter" do
|
||||
lambda {
|
||||
PostAction.act(@post.user, @post, PostActionType.Types[:bookmark])
|
||||
}.should change(PostAction, :count).by(1)
|
||||
end
|
||||
|
||||
describe 'when removing a bookmark' do
|
||||
before do
|
||||
PostAction.act(@post.user, @post, PostActionType.Types[:bookmark])
|
||||
end
|
||||
|
||||
it 'reduces the bookmark count of the post' do
|
||||
active = PostAction.where(deleted_at: nil)
|
||||
lambda {
|
||||
PostAction.remove_act(@post.user, @post, PostActionType.Types[:bookmark])
|
||||
}.should change(active, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'change_username' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context 'success' do
|
||||
let(:new_username) { "#{user.username}1234" }
|
||||
|
||||
before do
|
||||
@result = user.change_username(new_username)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@result.should be_true
|
||||
end
|
||||
|
||||
it 'should change the username' do
|
||||
user.reload
|
||||
user.username.should == new_username
|
||||
end
|
||||
|
||||
it 'should change the username_lower' do
|
||||
user.reload
|
||||
user.username_lower.should == new_username.downcase
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe 'new' do
|
||||
|
||||
subject { Fabricate.build(:user) }
|
||||
|
||||
it { should be_valid }
|
||||
it { should_not be_admin }
|
||||
it { should_not be_active }
|
||||
it { should_not be_approved }
|
||||
its(:approved_at) { should be_blank }
|
||||
its(:approved_by_id) { should be_blank }
|
||||
its(:email_digests) { should be_true }
|
||||
its(:email_private_messages) { should be_true }
|
||||
its(:email_direct ) { should be_true }
|
||||
its(:time_read) { should == 0}
|
||||
|
||||
# Default to digests after one week
|
||||
its(:digest_after_days) { should == 7 }
|
||||
|
||||
context 'after_save' do
|
||||
before do
|
||||
subject.save
|
||||
end
|
||||
|
||||
its(:email_tokens) { should be_present }
|
||||
its(:bio_cooked) { should be_present }
|
||||
its(:topics_entered) { should == 0 }
|
||||
its(:posts_read_count) { should == 0 }
|
||||
end
|
||||
end
|
||||
|
||||
describe "trust levels" do
|
||||
let(:user) { Fabricate(:user, trust_level: TrustLevel.Levels[:new]) }
|
||||
|
||||
it "sets to the default trust level setting" do
|
||||
SiteSetting.expects(:default_trust_level).returns(TrustLevel.Levels[:advanced])
|
||||
User.new.trust_level.should == TrustLevel.Levels[:advanced]
|
||||
end
|
||||
|
||||
describe 'has_trust_level' do
|
||||
|
||||
it "raises an error with an invalid level" do
|
||||
lambda { user.has_trust_level?(:wat) }.should raise_error
|
||||
end
|
||||
|
||||
it "is true for your basic level" do
|
||||
user.has_trust_level?(:new).should be_true
|
||||
end
|
||||
|
||||
it "is false for a higher level" do
|
||||
user.has_trust_level?(:moderator).should be_false
|
||||
end
|
||||
|
||||
it "is true if you exceed the level" do
|
||||
user.trust_level = TrustLevel.Levels[:advanced]
|
||||
user.has_trust_level?(:basic).should be_true
|
||||
end
|
||||
|
||||
it "is true for an admin even with a low trust level" do
|
||||
user.trust_level = TrustLevel.Levels[:new]
|
||||
user.admin = true
|
||||
user.has_trust_level?(:advanced).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'moderator' do
|
||||
it "isn't a moderator by default" do
|
||||
user.has_trust_level?(:moderator).should be_false
|
||||
end
|
||||
|
||||
it "is a moderator if the user level is moderator" do
|
||||
user.trust_level = TrustLevel.Levels[:moderator]
|
||||
user.has_trust_level?(:moderator).should be_true
|
||||
end
|
||||
|
||||
it "is a moderator if the user is an admin" do
|
||||
user.admin = true
|
||||
user.has_trust_level?(:moderator).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe 'temporary_key' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:temporary_key) { user.temporary_key}
|
||||
|
||||
it 'has a temporary key' do
|
||||
temporary_key.should be_present
|
||||
end
|
||||
|
||||
describe 'User#find_by_temporary_key' do
|
||||
|
||||
it 'can be used to find the user' do
|
||||
User.find_by_temporary_key(temporary_key).should == user
|
||||
end
|
||||
|
||||
it 'returns nil with an invalid key' do
|
||||
User.find_by_temporary_key('asdfasdf').should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'email_hash' do
|
||||
before do
|
||||
@user = Fabricate(:user)
|
||||
end
|
||||
|
||||
it 'should have a sane email hash' do
|
||||
@user.email_hash.should =~ /^[0-9a-f]{32}$/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'name heuristics' do
|
||||
it 'is able to guess a decent username from an email' do
|
||||
User.suggest_username('bob@bob.com').should == 'bob'
|
||||
end
|
||||
|
||||
it 'is able to guess a decent name from an email' do
|
||||
User.suggest_name('sam.saffron@gmail.com').should == 'Sam Saffron'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'username format' do
|
||||
it "should always be 3 chars or longer" do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.username = 'ss'
|
||||
@user.save.should == false
|
||||
end
|
||||
|
||||
it "should never end with a ." do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.username = 'sam.'
|
||||
@user.save.should == false
|
||||
end
|
||||
|
||||
it "should never contain spaces" do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.username = 'sam s'
|
||||
@user.save.should == false
|
||||
end
|
||||
|
||||
['Bad One', 'Giraf%fe', 'Hello!', '@twitter', 'me@example.com', 'no.dots', 'purple.', '.bilbo', '_nope', 'sa$sy'].each do |bad_nickname|
|
||||
it "should not allow username '#{bad_nickname}'" do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.username = bad_nickname
|
||||
@user.save.should == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'username uniqueness' do
|
||||
before do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.save!
|
||||
@codinghorror = Fabricate.build(:coding_horror)
|
||||
end
|
||||
|
||||
it "should not allow saving if username is reused" do
|
||||
@codinghorror.username = @user.username
|
||||
@codinghorror.save.should be_false
|
||||
end
|
||||
|
||||
it "should not allow saving if username is reused in different casing" do
|
||||
@codinghorror.username = @user.username.upcase
|
||||
@codinghorror.save.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context '.username_available?' do
|
||||
it "returns true for a username that is available" do
|
||||
User.username_available?('BruceWayne').should be_true
|
||||
end
|
||||
|
||||
it 'returns false when a username is taken' do
|
||||
User.username_available?(Fabricate(:user).username).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.suggest_username' do
|
||||
it 'corrects weird characters' do
|
||||
User.suggest_username("Darth%^Vadar").should == "Darth_Vadar"
|
||||
end
|
||||
|
||||
it 'adds 1 to an existing username' do
|
||||
user = Fabricate(:user)
|
||||
User.suggest_username(user.username).should == "#{user.username}1"
|
||||
end
|
||||
|
||||
it "adds numbers if it's too short" do
|
||||
User.suggest_username('a').should == 'a11'
|
||||
end
|
||||
|
||||
it "has a special case for me emails" do
|
||||
User.suggest_username('me@eviltrout.com').should == 'eviltrout'
|
||||
end
|
||||
|
||||
it "shortens very long suggestions" do
|
||||
User.suggest_username("myreallylongnameisrobinwardesquire").should == 'myreallylongnam'
|
||||
end
|
||||
|
||||
it "makes room for the digit added if the username is too long" do
|
||||
User.create(username: 'myreallylongnam', email: 'fake@discourse.org')
|
||||
User.suggest_username("myreallylongnam").should == 'myreallylongna1'
|
||||
end
|
||||
|
||||
it "removes leading character if it is not alphanumeric" do
|
||||
User.suggest_username("_myname").should == 'myname'
|
||||
end
|
||||
|
||||
it "removes trailing characters if they are invalid" do
|
||||
User.suggest_username("myname!^$=").should == 'myname'
|
||||
end
|
||||
|
||||
it "replace dots" do
|
||||
User.suggest_username("my.name").should == 'my_name'
|
||||
end
|
||||
|
||||
it "remove leading dots" do
|
||||
User.suggest_username(".myname").should == 'myname'
|
||||
end
|
||||
|
||||
it "remove trailing dots" do
|
||||
User.suggest_username("myname.").should == 'myname'
|
||||
end
|
||||
|
||||
it 'should handle typical facebook usernames' do
|
||||
User.suggest_username('roger.nelson.3344913').should == 'roger_nelson_33'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'passwords' do
|
||||
before do
|
||||
@user = Fabricate.build(:user)
|
||||
@user.password = "ilovepasta"
|
||||
@user.save!
|
||||
end
|
||||
|
||||
it "should have a valid password after the initial save" do
|
||||
@user.confirm_password?("ilovepasta").should be_true
|
||||
end
|
||||
|
||||
it "should not have an active account after initial save" do
|
||||
@user.active.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'changing bio' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
user.bio_raw = "**turtle power!**"
|
||||
user.save
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "should markdown the raw_bio and put it in cooked_bio" do
|
||||
user.bio_cooked.should == "<p><strong>turtle power!</strong></p>"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "previous_visit_at" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:active_user_rate_limit_secs).returns(0)
|
||||
end
|
||||
|
||||
it "should be blank on creation" do
|
||||
user.previous_visit_at.should be_nil
|
||||
end
|
||||
|
||||
describe "first time" do
|
||||
let!(:first_visit_date) { DateTime.now }
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(first_visit_date)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "should have no value" do
|
||||
user.previous_visit_at.should be_nil
|
||||
end
|
||||
|
||||
describe "another call right after" do
|
||||
before do
|
||||
# A different time, to make sure it doesn't change
|
||||
DateTime.stubs(:now).returns(10.minutes.from_now)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "still has no value" do
|
||||
user.previous_visit_at.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "second visit" do
|
||||
let!(:second_visit_date) { 2.hours.from_now }
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(second_visit_date)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "should have the previous visit value" do
|
||||
user.previous_visit_at.should == first_visit_date
|
||||
end
|
||||
|
||||
describe "third visit" do
|
||||
let!(:third_visit_date) { 5.hours.from_now }
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(third_visit_date)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "should have the second visit value" do
|
||||
user.previous_visit_at.should == second_visit_date
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "last_seen_at" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "should have a blank last seen on creation" do
|
||||
user.last_seen_at.should be_nil
|
||||
end
|
||||
|
||||
it "should have 0 for days_visited" do
|
||||
user.days_visited.should == 0
|
||||
end
|
||||
|
||||
describe 'with no previous values' do
|
||||
let!(:date) { DateTime.now }
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(date)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "updates last_seen_at" do
|
||||
user.last_seen_at.should == date
|
||||
end
|
||||
|
||||
it "should have 0 for days_visited" do
|
||||
user.reload
|
||||
user.days_visited.should == 1
|
||||
end
|
||||
|
||||
it "should log a user_visit with the date" do
|
||||
user.user_visits.first.visited_at.should == date.to_date
|
||||
end
|
||||
|
||||
context "called twice" do
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(date)
|
||||
user.update_last_seen!
|
||||
user.update_last_seen!
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "doesn't increase days_visited twice" do
|
||||
user.days_visited.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "after 3 days" do
|
||||
let!(:future_date) { 3.days.from_now }
|
||||
|
||||
before do
|
||||
DateTime.stubs(:now).returns(future_date)
|
||||
user.update_last_seen!
|
||||
end
|
||||
|
||||
it "should log a second visited_at record when we log an update later" do
|
||||
user.user_visits.count.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe '#create_for_email' do
|
||||
let(:subject) { User.create_for_email('test@email.com') }
|
||||
it { should be_present }
|
||||
its(:username) { should == 'test' }
|
||||
its(:name) { should == 'test'}
|
||||
it { should_not be_active }
|
||||
end
|
||||
|
||||
end
|
||||
4
spec/models/user_visit_spec.rb
Normal file
4
spec/models/user_visit_spec.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserVisit do
|
||||
end
|
||||
13
spec/models/view_spec.rb
Normal file
13
spec/models/view_spec.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe View do
|
||||
|
||||
it { should belong_to :parent }
|
||||
it { should belong_to :user }
|
||||
it { should validate_presence_of :parent_type }
|
||||
it { should validate_presence_of :parent_id }
|
||||
it { should validate_presence_of :ip }
|
||||
it { should validate_presence_of :viewed_at }
|
||||
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user