Initial release of Discourse

This commit is contained in:
Robin Ward
2013-02-05 14:16:51 -05:00
commit 21b5628528
2932 changed files with 143949 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
require 'spec_helper'
describe CategoryFeaturedTopic do
it { should belong_to :category }
it { should belong_to :topic }
end

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,5 @@
require 'spec_helper'
describe PostActionType do
end

View 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

View File

@@ -0,0 +1,8 @@
require 'spec_helper'
describe PostOneboxRender do
it { should belong_to :onebox_render }
it { should belong_to :post }
end

View 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
View 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: "![sherlock](http://bbc.co.uk/sherlock.jpg)")) }
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

View 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

View 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

View 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

View File

@@ -0,0 +1,6 @@
require 'spec_helper'
describe TopicAllowedUser do
it { should belong_to :user }
it { should belong_to :topic }
end

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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
View 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

View File

@@ -0,0 +1,4 @@
require 'spec_helper'
describe UserVisit do
end

13
spec/models/view_spec.rb Normal file
View 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