2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-09-05 08:59:27 +08:00

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,51 @@
# encoding: utf-8
require 'spec_helper'
require 'archetype'
describe Archetype do
context 'default archetype' do
it 'has an Archetype by default' do
Archetype.list.should be_present
end
it 'has an id of default' do
Archetype.list.first.id.should == Archetype.default
end
context 'duplicate' do
before do
@old_size = Archetype.list.size
Archetype.register(Archetype.default)
end
it 'does not add the same archetype twice' do
Archetype.list.size.should == @old_size
end
end
end
context 'register an archetype' do
before do
@list = Archetype.list.dup
Archetype.register('glados')
end
it 'has one more element' do
Archetype.list.size.should == @list.size + 1
end
it 'has a glados element' do
Archetype.list.find {|a| a.id == 'glados'}.should be_present
end
end
end

View file

@ -0,0 +1,72 @@
require 'spec_helper'
require 'category_list'
describe CategoryList do
let(:user) { Fabricate(:user) }
let(:category_list) { CategoryList.new(user) }
context "with no categories" do
it "has no categories" do
category_list.categories.should be_blank
end
context "with an uncateorized topic" do
let!(:topic) { Fabricate(:topic)}
let(:category) { category_list.categories.first }
it "has a category" do
category.should be_present
end
it "has the uncategorized label" do
category.name.should == SiteSetting.uncategorized_name
end
it "has the uncategorized slug" do
category.slug.should == SiteSetting.uncategorized_name
end
it "has one topic this week" do
category.topics_week.should == 1
end
it "contains the topic in featured_topics" do
category.featured_topics.should == [topic]
end
end
end
context "with a category" do
let(:topic_category) { Fabricate(:category) }
it "should not return empty categories (no featured topics)" do
category_list.categories.should be_blank
end
context "with a topic in a category" do
let!(:topic) { Fabricate(:topic, category: topic_category)}
let(:category) { category_list.categories.first }
it "should return the category" do
category.should be_present
end
it "returns the correct category" do
category.id.should == topic_category.id
end
it "should contain our topic" do
category.featured_topics.should == [topic]
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'spec_helper'
require 'content_buffer'
describe ContentBuffer do
it "handles deletion across lines properly" do
c = ContentBuffer.new("a\nbc\nc")
c.apply_transform!(start: {row: 0, col: 0}, finish: {col: 1, row: 1}, operation: :delete)
c.to_s.should == "c\nc"
end
it "handles deletion inside lines properly" do
c = ContentBuffer.new("hello world")
c.apply_transform!(start: {row: 0, col: 1}, finish: {col: 4, row: 0}, operation: :delete)
c.to_s.should == "ho world"
end
it "handles inserts inside lines properly" do
c = ContentBuffer.new("hello!")
c.apply_transform!(start: {row: 0, col: 5}, operation: :insert, text: " world")
c.to_s.should == "hello world!"
end
it "handles multiline inserts" do
c = ContentBuffer.new("hello!")
c.apply_transform!(start: {row: 0, col: 5}, operation: :insert, text: "\nworld")
c.to_s.should == "hello\nworld!"
end
end

View file

@ -0,0 +1,97 @@
require 'spec_helper'
require 'cooked_post_processor'
describe CookedPostProcessor do
context 'process_onebox' do
before do
post = Fabricate.build(:post_with_youtube)
post.id = 123
@cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
Oneboxer.expects(:onebox).with("http://www.youtube.com/watch?v=9bZkp7q19f0", post_id: 123, invalidate_oneboxes: true).returns('GANGNAM STYLE')
@cpp.post_process_oneboxes
end
it 'should be dirty' do
@cpp.should be_dirty
end
it 'inserts the onebox' do
@cpp.html.should == "GANGNAM STYLE"
end
end
context 'process_images' do
it "has no topic image if there isn't one in the post" do
@post = Fabricate(:post)
@post.topic.image_url.should be_blank
end
context 'with sized images in the post' do
before do
@topic = Fabricate(:topic)
@post = Fabricate.build(:post_with_image_url, topic: @topic, user: @topic.user)
@cpp = CookedPostProcessor.new(@post, :image_sizes => {'http://www.forumwarz.com/images/header/logo.png' => {'width' => 111, 'height' => 222}})
end
it "doesn't call image_dimensions because it knows the size" do
CookedPostProcessor.expects(:image_dimensions).never
@cpp.post_process_images
end
it "adds the width from the image sizes provided" do
@cpp.post_process_images
@cpp.html.should =~ /width=\"111\"/
end
end
context 'with unsized images in the post' do
before do
CookedPostProcessor.expects(:image_dimensions).returns([123, 456])
@post = Fabricate(:post_with_images)
end
it "adds a topic image if there's one in the post" do
@post.topic.reload
@post.topic.image_url.should == "/path/to/img.jpg"
end
it "adds the height and width to images that don't have them" do
@post.reload
@post.cooked.should =~ /width=\"123\" height=\"456\"/
end
end
end
context 'image_dimensions' do
it "returns unless called with a http or https url" do
CookedPostProcessor.image_dimensions('/tmp/image.jpg').should be_blank
end
context 'with valid url' do
before do
@url = 'http://www.forumwarz.com/images/header/logo.png'
end
it "doesn't call fastimage if image crawling is disabled" do
SiteSetting.expects(:crawl_images?).returns(false)
FastImage.expects(:size).never
CookedPostProcessor.image_dimensions(@url)
end
it "calls fastimage if image crawling is enabled" do
SiteSetting.expects(:crawl_images?).returns(true)
FastImage.expects(:size).with(@url)
CookedPostProcessor.image_dimensions(@url)
end
end
end
end

View file

@ -0,0 +1,43 @@
require 'spec_helper'
require 'discourse_plugin_registry'
describe DiscoursePluginRegistry do
let(:registry) { DiscoursePluginRegistry.new }
context '.register_css' do
before do
registry.register_css('hello.css')
end
it 'is returned by DiscoursePluginRegistry.stylesheets' do
registry.stylesheets.include?('hello.css').should be_true
end
it "won't add the same file twice" do
lambda { registry.register_css('hello.css') }.should_not change(registry.stylesheets, :size)
end
end
context '.register_js' do
before do
registry.register_js('hello.js')
end
it 'is returned by DiscoursePluginRegistry.javascripts' do
registry.javascripts.include?('hello.js').should be_true
end
it "won't add the same file twice" do
lambda { registry.register_js('hello.js') }.should_not change(registry.javascripts, :size)
end
end
context '.register_archetype' do
it "delegates archetypes to the Archetype component" do
Archetype.expects(:register).with('threaded', hello: 123)
registry.register_archetype('threaded', hello: 123)
end
end
end

View file

@ -0,0 +1,56 @@
require 'spec_helper'
require 'discourse'
describe Discourse do
before do
RailsMultisite::ConnectionManagement.stubs(:current_hostname).returns('foo.com')
end
context 'current_hostname' do
it 'returns the hostname from the current db connection' do
Discourse.current_hostname.should == 'foo.com'
end
end
context 'base_url' do
context 'when ssl is off' do
before do
SiteSetting.expects(:use_ssl?).returns(false)
end
it 'has a non-ssl base url' do
Discourse.base_url.should == "http://foo.com"
end
end
context 'when ssl is on' do
before do
SiteSetting.expects(:use_ssl?).returns(true)
end
it 'has a non-ssl base url' do
Discourse.base_url.should == "https://foo.com"
end
end
context 'with a non standard port specified' do
before do
SiteSetting.stubs(:port).returns(3000)
end
it "returns the non standart port in the base url" do
Discourse.base_url.should == "http://foo.com:3000"
end
end
end
end

View file

@ -0,0 +1,12 @@
require 'spec_helper'
require 'distributed_hash'
describe DiscoursePluginRegistry do
# it 'should sync the sets across instances' do
# h1 = DistributedHash.new(:hash)
# h2 = DistributedHash.new(:hash)
# h1[:hello] = "world"
# h2[:hello].should == "world"
# end
end

View file

@ -0,0 +1,119 @@
require 'spec_helper'
require 'email_sender'
describe EmailSender do
it "doesn't deliver mail when the message is nil" do
Mail::Message.any_instance.expects(:deliver).never
EmailSender.new(nil, :hello).send
end
it "doesn't deliver when the to address is nil" do
message = Mail::Message.new(body: 'hello')
message.expects(:deliver).never
EmailSender.new(message, :hello).send
end
it "doesn't deliver when the body is nil" do
message = Mail::Message.new(to: 'eviltrout@test.domain')
message.expects(:deliver).never
EmailSender.new(message, :hello).send
end
context 'with a valid message' do
let(:message) do
message = Mail::Message.new to: 'eviltrout@test.domain',
body: '**hello**'
message.stubs(:deliver)
message
end
let(:email_sender) { EmailSender.new(message, :valid_type) }
it 'calls deliver' do
message.expects(:deliver).once
email_sender.send
end
context 'email logs' do
before do
email_sender.send
@email_log = EmailLog.last
end
it 'creates an email log' do
@email_log.should be_present
end
it 'has the correct type' do
@email_log.email_type.should == 'valid_type'
end
it 'has the correct to_address' do
@email_log.to_address.should == 'eviltrout@test.domain'
end
it 'has no user_id' do
@email_log.user_id.should be_blank
end
end
context 'html' do
before do
email_sender.send
end
it 'makes the message multipart' do
message.should be_multipart
end
it 'has a html part' do
message.parts.detect {|p| p.content_type == "text/html; charset=UTF-8"}.should be_true
end
context 'html part' do
let(:html_part) { message.parts.detect {|p| p.content_type == "text/html; charset=UTF-8"} }
it 'has a html part' do
html_part.should be_present
end
it 'has run markdown on the body' do
html_part.body.to_s.should == "<p><strong>hello</strong></p>"
end
end
end
end
context 'with a user' do
let(:message) do
message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body'
message.stubs(:deliver)
message
end
let(:user) { Fabricate(:user) }
let(:email_sender) { EmailSender.new(message, :valid_type, user) }
before do
email_sender.send
@email_log = EmailLog.last
end
it 'should have the current user_id' do
@email_log.user_id.should == user.id
end
end
end

View file

@ -0,0 +1,22 @@
require 'spec_helper'
require 'email'
describe Email do
it 'should treat a good email as valid' do
Email.is_valid?('sam@sam.com').should be_true
end
it 'should treat a bad email as invalid' do
Email.is_valid?('sam@sam').should be_false
end
it 'should allow museum tld' do
Email.is_valid?('sam@nic.museum').should be_true
end
it 'should not think a word is an email' do
Email.is_valid?('sam').should be_false
end
end

View file

@ -0,0 +1,37 @@
require 'spec_helper'
require 'export/export'
describe Export do
describe '#current_schema_version' do
it "should return the latest migration version" do
Export.current_schema_version.should == User.exec_sql("select max(version) as max from schema_migrations")[0]["max"]
end
end
describe "models_included_in_export" do
it "should include the user model" do
Export.models_included_in_export.map(&:name).should include('User')
end
it "should not include the message bus model" do
Export.models_included_in_export.map(&:name).should_not include('MessageBus')
end
end
describe "is_export_running?" do
it "should return true when an export is in progress" do
$redis.stubs(:get).with(Export.export_running_key).returns('1')
Export.is_export_running?.should be_true
end
it "should return false when an export is not happening" do
$redis.stubs(:get).with(Export.export_running_key).returns('0')
Export.is_export_running?.should be_false
end
it "should return false when an export has never been run" do
$redis.stubs(:get).with(Export.export_running_key).returns(nil)
Export.is_export_running?.should be_false
end
end
end

View file

@ -0,0 +1,146 @@
require 'spec_helper'
require 'export/json_encoder'
describe Export::JsonEncoder do
describe "exported data" do
before do
@encoder = Export::JsonEncoder.new
@testIO = StringIO.new
@encoder.stubs(:json_output_stream).returns(@testIO)
@encoder.stubs(:tmp_directory).returns( File.join(Rails.root, 'tmp', 'json_encoder_spec') )
end
describe "write_schema_info" do
it "should write a schema section when given valid arguments" do
version = '20121216230719'
@encoder.write_schema_info( source: 'discourse', version: version )
@encoder.finish
json = JSON.parse( @testIO.string )
json.should have_key('schema')
json['schema']['source'].should == 'discourse'
json['schema']['version'].should == version
end
it "should raise an exception when its arguments are invalid" do
expect {
@encoder.write_schema_info({})
}.to raise_error(Export::SchemaArgumentsError)
end
end
describe "write_table" do
let(:table_name) { Topic.table_name }
let(:columns) { Topic.columns }
before do
@encoder.write_schema_info( source: 'discourse', version: '111' )
end
it "should yield a row count of 0 to the caller on the first iteration" do
yield_count = 0
@encoder.write_table(table_name, columns) do |row_count|
row_count.should == 0
yield_count += 1
break
end
yield_count.should == 1
end
it "should yield the number of rows I sent the first time on the second iteration" do
yield_count = 0
@encoder.write_table(table_name, columns) do |row_count|
yield_count += 1
if yield_count == 1
[[1, 'Hello'], [2, 'Yeah'], [3, 'Great']]
elsif yield_count == 2
row_count.should == 3
break
end
end
yield_count.should == 2
end
it "should stop yielding when it gets an empty array" do
yield_count = 0
@encoder.write_table(table_name, columns) do |row_count|
yield_count += 1
break if yield_count > 1
[]
end
yield_count.should == 1
end
it "should stop yielding when it gets nil" do
yield_count = 0
@encoder.write_table(table_name, columns) do |row_count|
yield_count += 1
break if yield_count > 1
nil
end
yield_count.should == 1
end
end
describe "exported data" do
before do
@encoder.write_schema_info( source: 'discourse', version: '20121216230719' )
end
it "should have a table count of 0 when no tables were exported" do
@encoder.finish
json = JSON.parse( @testIO.string )
json['schema']['table_count'].should == 0
end
it "should have a table count of 1 when one table was exported" do
@encoder.write_table(Topic.table_name, Topic.columns) { |row_count| [] }
@encoder.finish
json = JSON.parse( @testIO.string )
json['schema']['table_count'].should == 1
end
it "should have a table count of 3 when three tables were exported" do
@encoder.write_table(Topic.table_name, Topic.columns) { |row_count| [] }
@encoder.write_table(User.table_name, User.columns) { |row_count| [] }
@encoder.write_table(Post.table_name, Post.columns) { |row_count| [] }
@encoder.finish
json = JSON.parse( @testIO.string )
json['schema']['table_count'].should == 3
end
it "should have a row count of 0 when no rows were exported" do
@encoder.write_table(Notification.table_name, Notification.columns) { |row_count| [] }
@encoder.finish
json = JSON.parse( @testIO.string )
json[Notification.table_name]['row_count'].should == 0
end
it "should have a row count of 1 when one row was exported" do
@encoder.write_table(Notification.table_name, Notification.columns) do |row_count|
if row_count == 0
[['1409', '5', '1227', '', 't', '2012-12-07 19:59:56.691592', '2012-12-07 19:59:56.691592', '303', '16', '420']]
else
[]
end
end
@encoder.finish
json = JSON.parse( @testIO.string )
json[Notification.table_name]['row_count'].should == 1
end
it "should have a row count of 2 when two rows were exported" do
@encoder.write_table(Notification.table_name, Notification.columns) do |row_count|
if row_count == 0
[['1409', '5', '1227', '', 't', '2012-12-07 19:59:56.691592', '2012-12-07 19:59:56.691592', '303', '16', '420'],
['1408', '4', '1188', '', 'f', '2012-12-07 18:40:30.460404', '2012-12-07 18:40:30.460404', '304', '1', '421']]
else
[]
end
end
@encoder.finish
json = JSON.parse( @testIO.string )
json[Notification.table_name]['row_count'].should == 2
end
end
end
end

View file

@ -0,0 +1,728 @@
require 'spec_helper'
require 'guardian'
describe Guardian do
let(:user) { Fabricate(:user) }
let(:moderator) { Fabricate(:moderator) }
let(:admin) { Fabricate(:admin) }
let(:another_admin) { Fabricate(:another_admin) }
let(:coding_horror) { Fabricate(:coding_horror) }
let(:topic) { Fabricate(:topic, user: user) }
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
it 'can be created without a user (not logged in)' do
lambda { Guardian.new }.should_not raise_error
end
it 'can be instantiaed with a user instance' do
lambda { Guardian.new(user) }.should_not raise_error
end
describe 'post_can_act?' do
let(:post) { Fabricate(:post) }
let(:user) { Fabricate(:user) }
it "returns false when the user is nil" do
Guardian.new(nil).post_can_act?(post, :like).should be_false
end
it "returns false when the post is nil" do
Guardian.new(user).post_can_act?(nil, :like).should be_false
end
it "returns false when the topic is archived" do
post.topic.archived = true
Guardian.new(user).post_can_act?(post, :like).should be_false
end
it "returns false when liking yourself" do
Guardian.new(post.user).post_can_act?(post, :like).should be_false
end
it "returns false when you've already done it" do
Guardian.new(user).post_can_act?(post, :like, taken_actions: {PostActionType.Types[:like] => 1}).should be_false
end
it "returns false when you already flagged a post" do
Guardian.new(user).post_can_act?(post, :off_topic, taken_actions: {PostActionType.Types[:spam] => 1}).should be_false
end
describe "trust levels" do
it "returns true for a new user liking something" do
user.trust_level = TrustLevel.Levels[:new]
Guardian.new(user).post_can_act?(post, :like).should be_true
end
it "returns false for a new user flagging something as spam" do
user.trust_level = TrustLevel.Levels[:new]
Guardian.new(user).post_can_act?(post, :spam).should be_false
end
it "returns false for a new user flagging something as off topic" do
user.trust_level = TrustLevel.Levels[:new]
Guardian.new(user).post_can_act?(post, :off_topic).should be_false
end
end
end
describe 'can_send_private_message' do
let(:user) { Fabricate(:user) }
let(:another_user) { Fabricate(:user) }
it "returns false when the user is nil" do
Guardian.new(nil).can_send_private_message?(user).should be_false
end
it "returns false when the target user is nil" do
Guardian.new(user).can_send_private_message?(nil).should be_false
end
it "returns false when the target is the same as the user" do
Guardian.new(user).can_send_private_message?(user).should be_false
end
it "returns false when you are untrusted" do
user.trust_level = TrustLevel.Levels[:new]
Guardian.new(user).can_send_private_message?(another_user).should be_false
end
it "returns true to another user" do
Guardian.new(user).can_send_private_message?(another_user).should be_true
end
end
describe 'can_reply_as_new_topic' do
let(:user) { Fabricate(:user) }
let(:topic) { Fabricate(:topic) }
it "returns false for a non logged in user" do
Guardian.new(nil).can_reply_as_new_topic?(topic).should be_false
end
it "returns false for a nil topic" do
Guardian.new(user).can_reply_as_new_topic?(nil).should be_false
end
it "returns false for an untrusted user" do
user.trust_level = TrustLevel.Levels[:new]
Guardian.new(user).can_reply_as_new_topic?(topic).should be_false
end
it "returns true for a trusted user" do
Guardian.new(user).can_reply_as_new_topic?(topic).should be_true
end
end
describe 'can_see_post_actors?' do
let(:topic) { Fabricate(:topic, user: coding_horror)}
it 'returns false when the post is nil' do
Guardian.new(user).can_see_post_actors?(nil, PostActionType.Types[:like]).should be_false
end
it 'returns true for likes' do
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:like]).should be_true
end
it 'returns false for bookmarks' do
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:bookmark]).should be_false
end
it 'returns false for off-topic flags' do
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:off_topic]).should be_false
end
it 'returns false for spam flags' do
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:spam]).should be_false
end
it 'returns true for public votes' do
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:vote]).should be_true
end
it 'returns false for private votes' do
topic.expects(:has_meta_data_boolean?).with(:private_poll).returns(true)
Guardian.new(user).can_see_post_actors?(topic, PostActionType.Types[:vote]).should be_false
end
end
describe 'can_impersonate?' do
it 'returns false when the target is nil' do
Guardian.new(admin).can_impersonate?(nil).should be_false
end
it 'returns false when the user is nil' do
Guardian.new.can_impersonate?(user).should be_false
end
it "doesn't allow a non-admin to impersonate someone" do
Guardian.new(coding_horror).can_impersonate?(user).should be_false
end
it "doesn't allow an admin to impersonate themselves" do
Guardian.new(admin).can_impersonate?(admin).should be_false
end
it "doesn't allow an admin to impersonate another admin" do
Guardian.new(admin).can_impersonate?(another_admin).should be_false
end
it "allows an admin to impersonate a regular user" do
Guardian.new(admin).can_impersonate?(user).should be_true
end
it "allows an admin to impersonate a moderator" do
Guardian.new(admin).can_impersonate?(moderator).should be_true
end
end
describe 'can_invite_to?' do
let(:topic) { Fabricate(:topic) }
let(:user) { topic.user }
let(:moderator) { Fabricate(:moderator) }
it 'returns false with a nil user' do
Guardian.new(nil).can_invite_to?(topic).should be_false
end
it 'returns false with a nil object' do
Guardian.new(moderator).can_invite_to?(nil).should be_false
end
it 'returns true for a moderator to invite' do
Guardian.new(moderator).can_invite_to?(topic).should be_true
end
it 'returns false when the site requires approving users' do
SiteSetting.expects(:must_approve_users?).returns(true)
Guardian.new(moderator).can_invite_to?(topic).should be_false
end
it 'returns false for a regular user to invite' do
Guardian.new(user).can_invite_to?(topic).should be_false
end
end
describe 'can_see?' do
it 'returns false with a nil object' do
Guardian.new.can_see?(nil).should be_false
end
describe 'a Topic' do
it 'allows non logged in users to view topics' do
Guardian.new.can_see?(topic).should be_true
end
end
end
describe 'can_create?' do
describe 'a Category' do
it 'returns false when not logged in' do
Guardian.new.can_create?(Category).should be_false
end
it 'returns false when a regular user' do
Guardian.new(user).can_create?(Category).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_create?(Category).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_create?(Category).should be_true
end
end
describe 'a Post' do
it "is false when not logged in" do
Guardian.new.can_create?(Post, topic).should be_false
end
it 'is true for a regular user' do
Guardian.new(topic.user).can_create?(Post, topic).should be_true
end
it "is false when you can't see the topic" do
Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
Guardian.new(topic.user).can_create?(Post, topic).should be_false
end
context 'closed topic' do
before do
topic.closed = true
end
it "doesn't allow new posts from regular users" do
Guardian.new(topic.user).can_create?(Post, topic).should be_false
end
it 'allows editing of posts' do
Guardian.new(topic.user).can_edit?(post).should be_true
end
it "allows new posts from moderators" do
Guardian.new(moderator).can_create?(Post, topic).should be_true
end
it "allows new posts from admins" do
Guardian.new(admin).can_create?(Post, topic).should be_true
end
end
context 'archived topic' do
before do
topic.archived = true
end
context 'regular users' do
it "doesn't allow new posts from regular users" do
Guardian.new(coding_horror).can_create?(Post, topic).should be_false
end
it 'allows editing of posts' do
Guardian.new(coding_horror).can_edit?(post).should be_false
end
end
it "allows new posts from moderators" do
Guardian.new(moderator).can_create?(Post, topic).should be_true
end
it "allows new posts from admins" do
Guardian.new(admin).can_create?(Post, topic).should be_true
end
end
end
end
describe 'post_can_act?' do
it "isn't allowed on nil" do
Guardian.new(user).post_can_act?(nil, nil).should be_false
end
describe 'a Post' do
let (:guardian) do
Guardian.new(user)
end
it "isn't allowed when not logged in" do
Guardian.new(nil).post_can_act?(post,:vote).should be_false
end
it "is allowed as a regular user" do
guardian.post_can_act?(post,:vote).should be_true
end
it "doesn't allow voting if the user has an action from voting already" do
guardian.post_can_act?(post,:vote,taken_actions: {PostActionType.Types[:vote] => 1}).should be_false
end
it "allows voting if the user has performed a different action" do
guardian.post_can_act?(post,:vote,taken_actions: {PostActionType.Types[:like] => 1}).should be_true
end
it "isn't allowed on archived topics" do
topic.archived = true
Guardian.new(user).post_can_act?(post,:like).should be_false
end
describe 'multiple voting' do
it "isn't allowed if the user voted and the topic doesn't allow multiple votes" do
Topic.any_instance.expects(:has_meta_data_boolean?).with(:single_vote).returns(true)
Guardian.new(user).can_vote?(post, :voted_in_topic => true).should be_false
end
it "is allowed if the user voted and the topic doesn't allow multiple votes" do
Guardian.new(user).can_vote?(post, :voted_in_topic => false).should be_true
end
end
end
end
describe 'can_edit?' do
it 'returns false with a nil object' do
Guardian.new(user).can_edit?(nil).should be_false
end
describe 'a Post' do
it 'returns false when not logged in' do
Guardian.new.can_edit?(post).should be_false
end
it 'returns true if you want to edit your own post' do
Guardian.new(post.user).can_edit?(post).should be_true
end
it 'returns false if another regular user tries to edit your post' do
Guardian.new(coding_horror).can_edit?(post).should be_false
end
it 'returns true as a moderator' do
Guardian.new(moderator).can_edit?(post).should be_true
end
it 'returns true as an admin' do
Guardian.new(admin).can_edit?(post).should be_true
end
end
describe 'a Topic' do
it 'returns false when not logged in' do
Guardian.new.can_edit?(topic).should be_false
end
it 'returns true for editing your own post' do
Guardian.new(topic.user).can_edit?(topic).should be_true
end
it 'returns false as a regular user' do
Guardian.new(coding_horror).can_edit?(topic).should be_false
end
it 'returns true as a moderator' do
Guardian.new(moderator).can_edit?(topic).should be_true
end
it 'returns true as an admin' do
Guardian.new(admin).can_edit?(topic).should be_true
end
end
describe 'a Category' do
let(:category) { Fabricate(:category) }
it 'returns false when not logged in' do
Guardian.new.can_edit?(category).should be_false
end
it 'returns false as a regular user' do
Guardian.new(category.user).can_edit?(category).should be_false
end
it 'returns true as a moderator' do
Guardian.new(moderator).can_edit?(category).should be_true
end
it 'returns true as an admin' do
Guardian.new(admin).can_edit?(category).should be_true
end
end
describe 'a User' do
it 'returns false when not logged in' do
Guardian.new.can_edit?(user).should be_false
end
it 'returns false as a different user' do
Guardian.new(coding_horror).can_edit?(user).should be_false
end
it 'returns true when trying to edit yourself' do
Guardian.new(user).can_edit?(user).should be_true
end
it 'returns false as a moderator' do
Guardian.new(moderator).can_edit?(user).should be_false
end
it 'returns true as an admin' do
Guardian.new(admin).can_edit?(user).should be_true
end
end
end
context 'can_moderate?' do
it 'returns false with a nil object' do
Guardian.new(user).can_moderate?(nil).should be_false
end
context 'a Topic' do
it 'returns false when not logged in' do
Guardian.new.can_moderate?(topic).should be_false
end
it 'returns false when not a moderator' do
Guardian.new(user).can_moderate?(topic).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_moderate?(topic).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_moderate?(topic).should be_true
end
end
end
context 'can_see_flags?' do
it "returns false when there is no post" do
Guardian.new(moderator).can_see_flags?(nil).should be_false
end
it "returns false when there is no user" do
Guardian.new(nil).can_see_flags?(post).should be_false
end
it "allow regular uses to see flags" do
Guardian.new(user).can_see_flags?(post).should be_false
end
it "allows moderators to see flags" do
Guardian.new(moderator).can_see_flags?(post).should be_true
end
it "allows moderators to see flags" do
Guardian.new(admin).can_see_flags?(post).should be_true
end
end
context 'can_move_posts?' do
it 'returns false with a nil object' do
Guardian.new(user).can_move_posts?(nil).should be_false
end
context 'a Topic' do
it 'returns false when not logged in' do
Guardian.new.can_move_posts?(topic).should be_false
end
it 'returns false when not a moderator' do
Guardian.new(user).can_move_posts?(topic).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_move_posts?(topic).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_move_posts?(topic).should be_true
end
end
end
context 'can_delete?' do
it 'returns false with a nil object' do
Guardian.new(user).can_delete?(nil).should be_false
end
context 'a Topic' do
it 'returns false when not logged in' do
Guardian.new.can_delete?(topic).should be_false
end
it 'returns false when not a moderator' do
Guardian.new(user).can_delete?(topic).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_delete?(topic).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_delete?(topic).should be_true
end
end
context 'a Post' do
before do
post.post_number = 2
end
it 'returns false when not logged in' do
Guardian.new.can_delete?(post).should be_false
end
it 'returns false when not a moderator' do
Guardian.new(user).can_delete?(post).should be_false
end
it "returns false when it's the OP, even as a moderator" do
post.update_attribute :post_number, 1
Guardian.new(moderator).can_delete?(post).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_delete?(post).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_delete?(post).should be_true
end
end
context 'a Category' do
let(:category) { Fabricate(:category, user: moderator) }
it 'returns false when not logged in' do
Guardian.new.can_delete?(category).should be_false
end
it 'returns false when a regular user' do
Guardian.new(user).can_delete?(category).should be_false
end
it 'returns true when a moderator' do
Guardian.new(moderator).can_delete?(category).should be_true
end
it 'returns true when an admin' do
Guardian.new(admin).can_delete?(category).should be_true
end
it "can't be deleted if it has a forum topic" do
category.topic_count = 10
Guardian.new(moderator).can_delete?(category).should be_false
end
end
context 'a PostAction' do
let(:post_action) { PostAction.create(user_id: user.id, post_id: post.id, post_action_type_id: 1)}
it 'returns false when not logged in' do
Guardian.new.can_delete?(post_action).should be_false
end
it 'returns false when not the user who created it' do
Guardian.new(coding_horror).can_delete?(post_action).should be_false
end
it "returns false if the window has expired" do
post_action.created_at = 20.minutes.ago
SiteSetting.expects(:post_undo_action_window_mins).returns(10)
Guardian.new(user).can_delete?(post_action).should be_false
end
it "returns true if it's yours" do
Guardian.new(user).can_delete?(post_action).should be_true
end
end
end
context 'can_approve?' do
it "wont allow a non-logged in user to approve" do
Guardian.new.can_approve?(user).should be_false
end
it "wont allow a non-admin to approve a user" do
Guardian.new(coding_horror).can_approve?(user).should be_false
end
it "returns false when the user is already approved" do
user.approved = true
Guardian.new(admin).can_approve?(user).should be_false
end
it "allows an admin to approve a user" do
Guardian.new(admin).can_approve?(user).should be_true
end
it "allows a moderator to approve a user" do
Guardian.new(moderator).can_approve?(user).should be_true
end
end
context 'can_grant_admin?' do
it "wont allow a non logged in user to grant an admin's access" do
Guardian.new.can_grant_admin?(another_admin).should be_false
end
it "wont allow a regular user to revoke an admin's access" do
Guardian.new(user).can_grant_admin?(another_admin).should be_false
end
it 'wont allow an admin to grant their own access' do
Guardian.new(admin).can_grant_admin?(admin).should be_false
end
it "allows an admin to grant a regular user access" do
Guardian.new(admin).can_grant_admin?(user).should be_true
end
end
context 'can_revoke_admin?' do
it "wont allow a non logged in user to revoke an admin's access" do
Guardian.new.can_revoke_admin?(another_admin).should be_false
end
it "wont allow a regular user to revoke an admin's access" do
Guardian.new(user).can_revoke_admin?(another_admin).should be_false
end
it 'wont allow an admin to revoke their own access' do
Guardian.new(admin).can_revoke_admin?(admin).should be_false
end
it "allows an admin to revoke another admin's access" do
Guardian.new(admin).can_revoke_admin?(another_admin).should be_true
end
end
context "can_see_pending_invites_from?" do
it 'is false without a logged in user' do
Guardian.new(nil).can_see_pending_invites_from?(user).should be_false
end
it 'is false without a user to look at' do
Guardian.new(user).can_see_pending_invites_from?(nil).should be_false
end
it 'is true when looking at your own invites' do
Guardian.new(user).can_see_pending_invites_from?(user).should be_true
end
end
end

View file

@ -0,0 +1,42 @@
require 'spec_helper'
require 'image_sizer'
describe ImageSizer do
before do
SiteSetting.expects(:max_image_width).returns(500)
end
it 'returns the same dimensions if the width is less than the maximum' do
ImageSizer.resize(400, 200).should == [400, 200]
end
it 'returns nil if the width is nil' do
ImageSizer.resize(nil, 100).should be_nil
end
it 'returns nil if the height is nil' do
ImageSizer.resize(100, nil).should be_nil
end
it 'works with string parameters' do
ImageSizer.resize('100', '101').should == [100, 101]
end
describe 'when larger than the maximum' do
before do
@w, @h = ImageSizer.resize(600, 123)
end
it 'returns the maxmimum width if larger than the maximum' do
@w.should == 500
end
it 'resizes the height retaining the aspect ratio' do
@h.should == 102
end
end
end

View file

@ -0,0 +1,24 @@
require 'spec_helper'
require 'import/adapter/base'
describe Import::Adapter::Base do
describe 'the base implementation' do
let(:adapter) { Import::Adapter::Base.new }
describe 'apply_to_column_names' do
it 'should return the column names passed in' do
cols = ['first', 'second']
adapter.apply_to_column_names('table_name', cols).should == cols
end
end
describe 'apply_to_row' do
it 'should return the row passed in' do
row = [1,2,3,4]
adapter.apply_to_row('table_name', row).should == row
end
end
end
end

View file

@ -0,0 +1,66 @@
require 'spec_helper'
require 'import/import'
class AdapterX < Import::Adapter::Base; end
class Adapter1 < Import::Adapter::Base; end
class Adapter2 < Import::Adapter::Base; end
class Adapter3 < Import::Adapter::Base; end
describe Import do
describe "is_import_running?" do
it "should return true when an import is in progress" do
$redis.stubs(:get).with(Import.import_running_key).returns('1')
Import.is_import_running?.should be_true
end
it "should return false when an import is not happening" do
$redis.stubs(:get).with(Import.import_running_key).returns('0')
Import.is_import_running?.should be_false
end
it "should return false when an import has never been run" do
$redis.stubs(:get).with(Import.import_running_key).returns(nil)
Import.is_import_running?.should be_false
end
end
describe 'add_import_adapter' do
it "should return true" do
Import.clear_adapters
Import.add_import_adapter(AdapterX, '20130110121212', ['users']).should be_true
end
end
describe 'adapters_for_version' do
it "should return an empty Hash when there are no adapters" do
Import.clear_adapters
Import.adapters_for_version('1').should == {}
end
context 'when there are some adapters' do
before do
Import.clear_adapters
Import.add_import_adapter(Adapter1, '10', ['users'])
Import.add_import_adapter(Adapter2, '20', ['users'])
Import.add_import_adapter(Adapter3, '30', ['users'])
end
it "should return no adapters when the version is newer than all adapters" do
Import.adapters_for_version('31')['users'].should have(0).adapters
end
it "should return adapters that are newer than the given version" do
Import.adapters_for_version('12')['users'].should have(2).adapters
Import.adapters_for_version('22')['users'].should have(1).adapters
end
it "should return the adapters in order" do
adapters = Import.adapters_for_version('1')['users']
adapters[0].should be_a(Adapter1)
adapters[1].should be_a(Adapter2)
adapters[2].should be_a(Adapter3)
end
end
end
end

View file

@ -0,0 +1,62 @@
require 'spec_helper'
require 'import/json_decoder'
describe Import::JsonDecoder do
describe "start" do
context "given valid arguments" do
before do
@version = '20121201205642'
@export_data = {
schema: { source: 'discourse', version: @version},
categories: {
fields: Category.columns.map(&:name),
rows: [
["3", "entertainment", "AB9364", "155", nil, nil, nil, nil, "19", "2012-07-12 18:55:56.355932", "2012-07-12 18:55:56.355932", "1186", "17", "0", "0", "entertainment"],
["4", "question", "AB9364", "164", nil, nil, nil, nil, "1", "2012-07-12 18:55:56.355932", "2012-07-12 18:55:56.355932", "1186", "1", "0", "0", "question"]
]
},
notifications: {
fields: Notification.columns.map(&:name),
rows: [
["1416", "2", "1214", "{\"topic_title\":\"UI: Where did the 'Create a Topic' button go?\",\"display_username\":\"Lowell Heddings\"}", "t", "2012-12-09 18:05:09.862898", "2012-12-09 18:05:09.862898", "394", "2", nil],
["1415", "2", "1187", "{\"topic_title\":\"Jenkins Config.xml\",\"display_username\":\"Sam\"}", "t", "2012-12-08 10:11:17.599724", "2012-12-08 10:11:17.599724", "392", "3", nil]
]
}
}
@testIO = StringIO.new(@export_data.to_json, 'r')
@decoder = Import::JsonDecoder.new('json_decoder_spec.json.gz')
@decoder.stubs(:input_stream).returns(@testIO)
@valid_args = { callbacks: { schema_info: stub_everything, table_data: stub_everything } }
end
it "should call the schema_info callback before sending table data" do
callback_sequence = sequence('callbacks')
@valid_args[:callbacks][:schema_info].expects(:call).in_sequence(callback_sequence)
@valid_args[:callbacks][:table_data].expects(:call).in_sequence(callback_sequence).at_least_once
@decoder.start( @valid_args )
end
it "should call the schema_info callback with source and version parameters when export data is from discourse" do
@valid_args[:callbacks][:schema_info].expects(:call).with do |arg|
arg.should have_key(:source)
arg.should have_key(:version)
arg[:source].should == @export_data[:schema][:source]
arg[:version].should == @export_data[:schema][:version]
end
@decoder.start( @valid_args )
end
it "should call the table_data callback at least once for each table in the export file" do
@valid_args[:callbacks][:table_data].expects(:call).with('categories', @export_data[:categories][:fields], anything, anything).at_least_once
@valid_args[:callbacks][:table_data].expects(:call).with('notifications', @export_data[:notifications][:fields], anything, anything).at_least_once
@decoder.start( @valid_args )
end
end
context "given invalid arguments" do
end
end
end

View file

@ -0,0 +1,12 @@
require 'spec_helper'
require 'jobs'
describe Jobs::CalculateViewCounts do
it "delegates to User" do
User.expects(:update_view_counts)
Jobs::CalculateViewCounts.new.execute({})
end
end

View file

@ -0,0 +1,61 @@
require 'spec_helper'
require 'jobs'
describe Jobs::EnqueueDigestEmails do
describe '#target_users' do
context 'disabled digests' do
let!(:user_no_digests) { Fabricate(:user, email_digests: false, last_emailed_at: 8.days.ago, last_seen_at: 10.days.ago) }
it "doesn't return users with email disabled" do
Jobs::EnqueueDigestEmails.new.target_users.include?(user_no_digests).should be_false
end
end
context 'recently emailed' do
let!(:user_emailed_recently) { Fabricate(:user, last_emailed_at: 6.days.ago) }
it "doesn't return users who have been emailed recently" do
Jobs::EnqueueDigestEmails.new.target_users.include?(user_emailed_recently).should be_false
end
end
context 'visited the site today' do
let!(:user_visited_today) { Fabricate(:user, last_seen_at: 6.days.ago) }
it "doesn't return users who have been emailed recently" do
Jobs::EnqueueDigestEmails.new.target_users.include?(user_visited_today).should be_false
end
end
context 'regular users' do
let!(:user) { Fabricate(:user) }
it "returns the user" do
Jobs::EnqueueDigestEmails.new.target_users.should == [user]
end
end
end
describe '#execute' do
let(:user) { Fabricate(:user) }
before do
Jobs::EnqueueDigestEmails.any_instance.expects(:target_users).returns([user])
end
it "enqueues the digest email job" do
Jobs.expects(:enqueue).with(:user_email, type: :digest, user_id: user.id)
Jobs::EnqueueDigestEmails.new.execute({})
end
end
end

View file

@ -0,0 +1,190 @@
require 'spec_helper'
describe Jobs::Exporter do
before do
Jobs::Exporter.any_instance.stubs(:log).returns(true)
Jobs::Exporter.any_instance.stubs(:create_tar_file).returns(true)
Export::JsonEncoder.any_instance.stubs(:tmp_directory).returns( File.join(Rails.root, 'tmp', 'exporter_spec') )
Discourse.stubs(:enable_maintenance_mode).returns(true)
Discourse.stubs(:disable_maintenance_mode).returns(true)
end
describe "execute" do
context 'when no export or import is running' do
before do
@testIO = StringIO.new
Export::JsonEncoder.any_instance.stubs(:json_output_stream).returns(@testIO)
Jobs::Exporter.any_instance.stubs(:ordered_models_for_export).returns([])
Export.stubs(:is_export_running?).returns(false)
Export.stubs(:is_import_running?).returns(false)
@exporter_args = {}
end
it "should indicate that an export is now running" do
Export.expects(:set_export_started)
Jobs::Exporter.new.execute( @exporter_args )
end
it "should indicate that an export is not running after it's done" do
Export.expects(:set_export_is_not_running)
Jobs::Exporter.new.execute( @exporter_args )
end
it "should put the site in maintenance mode when it starts" do
encoder = stub_everything
Export::JsonEncoder.stubs(:new).returns(encoder)
seq = sequence('export-sequence')
Discourse.expects(:enable_maintenance_mode).in_sequence(seq).at_least_once
encoder.expects(:write_schema_info).in_sequence(seq).at_least_once
Jobs::Exporter.new.execute( @exporter_args )
end
it "should take the site out of maintenance mode when it ends" do
encoder = stub_everything
Export::JsonEncoder.stubs(:new).returns(encoder)
seq = sequence('export-sequence')
encoder.expects(:write_schema_info).in_sequence(seq).at_least_once
Discourse.expects(:disable_maintenance_mode).in_sequence(seq).at_least_once
Jobs::Exporter.new.execute( @exporter_args )
end
describe "without specifying a format" do
it "should use json as the default" do
Export::JsonEncoder.expects(:new).returns( stub_everything )
Jobs::Exporter.new.execute( @exporter_args.reject { |key, val| key == :format } )
end
end
describe "specifying an invalid format" do
it "should raise an exception and not flag that an export has started" do
Jobs::Exporter.expects(:set_export_started).never
expect {
Jobs::Exporter.new.execute( @exporter_args.merge( format: :interpretive_dance ) )
}.to raise_error(Export::FormatInvalidError)
end
end
context "using json format" do
before do
@exporter_args = {format: :json}
end
it "should export metadata" do
version = '201212121212'
encoder = stub_everything
encoder.expects(:write_schema_info).with do |arg|
arg[:source].should == 'discourse'
arg[:version].should == version
end
Export::JsonEncoder.stubs(:new).returns(encoder)
Export.stubs(:current_schema_version).returns(version)
Jobs::Exporter.new.execute( @exporter_args )
end
describe "exporting tables" do
before do
# Create some real database records
@user1, @user2 = Fabricate(:user), Fabricate(:user)
@topic1 = Fabricate(:topic, user: @user1)
@topic2 = Fabricate(:topic, user: @user2)
@topic3 = Fabricate(:topic, user: @user1)
@post1 = Fabricate(:post, topic: @topic1, user: @user1)
@post1 = Fabricate(:post, topic: @topic3, user: @user1)
@reply1 = Fabricate(:basic_reply, user: @user2, topic: @topic3)
@reply2 = Fabricate(:basic_reply, user: @user1, topic: @topic1)
@reply3 = Fabricate(:basic_reply, user: @user1, topic: @topic3)
end
it "should export all rows from the topics table in ascending id order" do
Jobs::Exporter.any_instance.stubs(:ordered_models_for_export).returns([Topic])
Jobs::Exporter.new.execute( @exporter_args )
json = JSON.parse( @testIO.string )
json.should have_key('topics')
json['topics'].should have_key('rows')
json['topics']['rows'].should have(3).rows
json['topics']['rows'][0][0].to_i.should == @topic1.id
json['topics']['rows'][1][0].to_i.should == @topic2.id
json['topics']['rows'][2][0].to_i.should == @topic3.id
end
it "should export all rows from the post_replies table in ascending order by post_id, reply_id" do
# because post_replies doesn't have an id column, so order by one of its indexes
Jobs::Exporter.any_instance.stubs(:ordered_models_for_export).returns([PostReply])
Jobs::Exporter.new.execute( @exporter_args )
json = JSON.parse( @testIO.string )
json.should have_key('post_replies')
json['post_replies'].should have_key('rows')
json['post_replies']['rows'].should have(3).rows
json['post_replies']['rows'][0][1].to_i.should == @reply2.id
json['post_replies']['rows'][1][1].to_i.should == @reply1.id
json['post_replies']['rows'][2][1].to_i.should == @reply3.id
end
it "should export column names for each table" do
Jobs::Exporter.any_instance.stubs(:ordered_models_for_export).returns([Topic, TopicUser, PostReply])
Jobs::Exporter.new.execute( @exporter_args )
json = JSON.parse( @testIO.string )
json['topics'].should have_key('fields')
json['topic_users'].should have_key('fields')
json['post_replies'].should have_key('fields')
json['topics']['fields'].should == Topic.columns.map(&:name)
json['topic_users']['fields'].should == TopicUser.columns.map(&:name)
json['post_replies']['fields'].should == PostReply.columns.map(&:name)
end
end
end
context "when it finishes successfully" do
context "and no user was given" do
it "should not send a notification to anyone" do
expect {
Jobs::Exporter.new.execute( @exporter_args )
}.to_not change { Notification.count }
end
end
context "and a user was given" do
before do
@user = Fabricate(:user)
@admin = Fabricate(:admin)
end
it "should send a notification to the user who started the export" do
expect {
Jobs::Exporter.new.execute( @exporter_args.merge( user_id: @user.id ) )
}.to change { Notification.count }.by(1)
end
end
end
end
context 'when an export is already running' do
before do
Export.expects(:is_export_running?).returns(true)
end
it "should not start an export and raise an exception" do
Export.expects(:set_export_started).never
Jobs::Exporter.any_instance.expects(:start_export).never
expect {
Jobs::Exporter.new.execute({})
}.to raise_error(Export::ExportInProgressError)
end
end
context 'when an import is running' do
before do
Import.expects(:is_import_running?).returns(true)
end
it "should not start an export and raise an exception" do
Export.expects(:set_export_started).never
Jobs::Exporter.any_instance.expects(:start_export).never
expect {
Jobs::Exporter.new.execute({})
}.to raise_error(Import::ImportInProgressError)
end
end
end
end

View file

@ -0,0 +1,44 @@
require 'spec_helper'
require 'jobs/process_post'
describe Jobs::FeatureTopicUsers do
it "raises an error without a topic_id" do
lambda { Jobs::FeatureTopicUsers.new.execute({}) }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error with a missing topic_id" do
lambda { Jobs::FeatureTopicUsers.new.execute(topic_id: 123) }.should raise_error(Discourse::InvalidParameters)
end
context 'with a topic' do
let!(:post) { Fabricate(:post) }
let(:topic) { post.topic }
let!(:coding_horror) { Fabricate(:coding_horror) }
let!(:evil_trout) { Fabricate(:evil_trout) }
let!(:second_post) { Fabricate(:post, topic: topic, user: coding_horror)}
let!(:third_post) { Fabricate(:post, topic: topic, user: evil_trout)}
it "won't feature the OP" do
Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
topic.reload.featured_user_ids.include?(topic.user_id).should be_false
end
it "features the second poster" do
Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
topic.reload.featured_user_ids.include?(coding_horror.id).should be_true
end
it "will not feature the second poster if we supply their post to be ignored" do
Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id, except_post_id: second_post.id)
topic.reload.featured_user_ids.include?(coding_horror.id).should be_false
end
it "won't feature the last poster" do
Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
topic.reload.featured_user_ids.include?(evil_trout.id).should be_false
end
end
end

View file

@ -0,0 +1,541 @@
require 'spec_helper'
describe Jobs::Importer do
def stub_schema_changes
Jobs::Importer.any_instance.stubs(:create_backup_schema).returns( true )
Jobs::Importer.any_instance.stubs(:backup_and_setup_table).returns( true )
end
def stub_data_loading
Jobs::Importer.any_instance.stubs(:set_schema_info).returns( true )
Jobs::Importer.any_instance.stubs(:load_table).returns( true )
Jobs::Importer.any_instance.stubs(:create_indexes).returns( true )
end
before do
Discourse.stubs(:enable_maintenance_mode).returns(true)
Discourse.stubs(:disable_maintenance_mode).returns(true)
Jobs::Importer.any_instance.stubs(:log).returns(true)
Jobs::Importer.any_instance.stubs(:extract_uploads).returns(true)
Jobs::Importer.any_instance.stubs(:extract_files).returns(true)
Jobs::Importer.any_instance.stubs(:tmp_directory).returns( File.join(Rails.root, 'tmp', 'importer_spec') )
@importer_args = { filename: 'importer_spec.json.gz' }
end
context "SiteSetting to enable imports" do
it "should exist" do
SiteSetting.all_settings.detect {|s| s[:setting] == :allow_import }.should be_present
end
it "should default to false" do
SiteSetting.allow_import?.should be_false
end
end
context 'when import is disabled' do
before do
stub_schema_changes
stub_data_loading
Import::JsonDecoder.stubs(:new).returns( stub_everything )
SiteSetting.stubs(:allow_import).returns(false)
end
describe "execute" do
it "should raise an error" do
expect {
Jobs::Importer.new.execute( @importer_args )
}.to raise_error(Import::ImportDisabledError)
end
it "should not start an import" do
Import::JsonDecoder.expects(:new).never
Jobs::Importer.any_instance.expects(:backup_tables).never
Discourse.expects(:enable_maintenance_mode).never
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
end
end
context 'when import is enabled' do
before do
SiteSetting.stubs(:allow_import).returns(true)
end
describe "execute" do
before do
stub_data_loading
end
shared_examples_for "when import should not be started" do
it "should not start an import" do
Import::JsonDecoder.expects(:new).never
Jobs::Importer.any_instance.expects(:backup_tables).never
Jobs::Importer.new.execute( @invalid_args ) rescue nil
end
it "should not put the site in maintenance mode" do
Discourse.expects(:enable_maintenance_mode).never
Jobs::Importer.new.execute( @invalid_args ) rescue nil
end
end
context "when an import is already running" do
before do
Import::JsonDecoder.stubs(:new).returns( stub_everything )
Import.stubs(:is_import_running?).returns( true )
end
it "should raise an error" do
expect {
Jobs::Importer.new.execute( @importer_args )
}.to raise_error(Import::ImportInProgressError)
end
it_should_behave_like "when import should not be started"
end
context "when an export is running" do
before do
Export.stubs(:is_export_running?).returns( true )
end
it "should raise an error" do
expect {
Jobs::Importer.new.execute( @importer_args )
}.to raise_error(Export::ExportInProgressError)
end
it_should_behave_like "when import should not be started"
end
context "when no export or import are running" do
before do
Import.stubs(:is_import_running?).returns( false )
Export.stubs(:is_export_running?).returns( false )
end
it "without specifying a format should use json as the default format" do
stub_schema_changes
Import::JsonDecoder.expects(:new).returns( stub_everything )
Jobs::Importer.new.execute( @importer_args.reject { |key, val| key == :format } )
end
it "when specifying json as the format it should use json" do
stub_schema_changes
Import::JsonDecoder.expects(:new).returns( stub_everything )
Jobs::Importer.new.execute( @importer_args.merge(format: :json) )
end
context "when specifying an invalid format" do
before do
stub_schema_changes
@invalid_args = @importer_args.merge( format: :smoke_signals )
end
it "should raise an error" do
expect {
Jobs::Importer.new.execute( @invalid_args )
}.to raise_error(Import::FormatInvalidError)
end
it_should_behave_like "when import should not be started"
end
context "when filename is not given" do
before do
stub_schema_changes
@invalid_args = @importer_args.reject { |k,v| k == :filename }
end
it "should raise an error" do
expect {
Jobs::Importer.new.execute( @invalid_args )
}.to raise_error(Import::FilenameMissingError)
end
it_should_behave_like "when import should not be started"
end
context "before loading data into tables" do
before do
Import::JsonDecoder.stubs(:new).returns( stub_everything )
stub_data_loading
end
shared_examples_for "a successful call to execute" do
it "should make a backup of the users table" do
Jobs::Importer.any_instance.stubs(:ordered_models_for_import).returns([User])
Jobs::Importer.new.execute(@importer_args)
User.exec_sql_row_count("SELECT table_name FROM information_schema.tables WHERE table_schema = 'backup' AND table_name = 'users'").should == 1
end
it "should have a users table that's empty" do
@user1 = Fabricate(:user)
User.count.should == 1
Jobs::Importer.any_instance.stubs(:ordered_models_for_import).returns([User])
Jobs::Importer.new.execute(@importer_args)
User.count.should == 0
end
it "should indicate that an import is running when it starts" do
Import.expects(:set_import_started)
Jobs::Importer.new.execute(@importer_args)
end
it "should indicate that an import is running when it's done" do
Import.expects(:set_import_is_not_running)
Jobs::Importer.new.execute(@importer_args)
end
it "should put the site in maintenance mode" do
seq = sequence('call sequence')
Import.is_import_running?.should be_false
Discourse.expects(:enable_maintenance_mode).in_sequence(seq).at_least_once
Jobs::Importer.any_instance.expects(:backup_tables).in_sequence(seq).at_least_once
Jobs::Importer.any_instance.expects(:load_data).in_sequence(seq).at_least_once
# fails here
Jobs::Importer.new.execute( @importer_args )
end
it "should take the site out of maintenance mode when it's done" do
seq = sequence('call sequence')
Jobs::Importer.any_instance.expects(:backup_tables).in_sequence(seq).at_least_once
Jobs::Importer.any_instance.expects(:load_data).in_sequence(seq).at_least_once
Discourse.expects(:disable_maintenance_mode).in_sequence(seq).at_least_once
Jobs::Importer.new.execute( @importer_args )
end
end
context "the first time an import is run" do
it_should_behave_like "a successful call to execute"
end
context "the second time an import is run" do
before do
Jobs::Importer.new.execute(@importer_args)
end
it_should_behave_like "a successful call to execute"
end
end
#
# Import notifications don't work from the rake task. Why is activerecord inserting an "id" value of NULL?
#
# PG::Error: ERROR: null value in column "id" violates not-null constraint
# : INSERT INTO "topic_allowed_users" ("created_at", "id", "topic_id", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id"
#
# context "when it finishes successfully" do
# before do
# stub_schema_changes
# Import::JsonDecoder.stubs(:new).returns( stub_everything )
# end
# context "and no user was given" do
# it "should not send a notification to anyone" do
# expect {
# Jobs::Importer.new.execute( @importer_args )
# }.to_not change { Notification.count }
# end
# end
# context "and a user was given" do
# before do
# @user = Fabricate(:user)
# @admin = Fabricate(:admin)
# end
# it "should send a notification to the user who started the import" do
# expect {
# Jobs::Importer.new.execute( @importer_args.merge( user_id: @user.id ) )
# }.to change { Notification.count }.by(1)
# end
# end
# end
end
end
describe "set_schema_info" do
context "when source is Discourse" do
before do
@current_version = '20121216230719'
Export.stubs(:current_schema_version).returns(@current_version)
@valid_args = { source: 'discourse', version: @current_version, table_count: Export.models_included_in_export.size }
end
it "succeeds when receiving the current schema version" do
Jobs::Importer.new.set_schema_info( @valid_args ).should be_true
end
it "succeeds when receiving an older schema version" do
Jobs::Importer.new.set_schema_info( @valid_args.merge( version: "#{@current_version.to_i - 1}") ).should be_true
end
it "raises an error if version is not given" do
expect {
Jobs::Importer.new.set_schema_info( @valid_args.reject {|key, val| key == :version} )
}.to raise_error(ArgumentError)
end
it "raises an error when receiving a newer schema version" do
expect {
Jobs::Importer.new.set_schema_info( @valid_args.merge( version: "#{@current_version.to_i + 1}") )
}.to raise_error(Import::UnsupportedSchemaVersion)
end
it "raises an error when it doesn't get the number of tables it expects" do
expect {
Jobs::Importer.new.set_schema_info( @valid_args.merge( table_count: 2 ) )
}.to raise_error(Import::WrongTableCountError)
end
end
it "raises an error when it receives an unsupported source" do
expect {
Jobs::Importer.new.set_schema_info( source: 'digg' )
}.to raise_error(Import::UnsupportedExportSource)
end
end
describe "load_table" do
before do
stub_schema_changes
@valid_field_list = ["id", "notification_type", "user_id", "data", "read", "created_at", "updated_at", "topic_id", "post_number", "post_action_id"]
@valid_notifications_row_data = [
['1409', '5', '1227', '', 't', '2012-12-07 19:59:56.691592', '2012-12-07 19:59:56.691592', '303', '16', '420'],
['1408', '4', '1188', '', 'f', '2012-12-07 18:40:30.460404', '2012-12-07 18:40:30.460404', '304', '1', '421']
]
end
context "when export data is at the current scheam version" do
before do
Import.stubs(:adapters_for_version).returns({})
end
context "with good data" do
it "should add rows to the notifcations table given valid row data" do
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
Notification.count.should == @valid_notifications_row_data.length
end
it "should successfully load rows with double quote literals in the values" do
@valid_notifications_row_data[0][3] = "{\"topic_title\":\"Errors, errbit and you!\",\"display_username\":\"Coding Horror\"}"
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
Notification.count.should == @valid_notifications_row_data.length
end
it "should successfully load rows with single quote literals in the values" do
@valid_notifications_row_data[0][3] = "{\"topic_title\":\"Bacon's Delicious, Am I Right\",\"display_username\":\"Celine Dion\"}"
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
Notification.count.should == @valid_notifications_row_data.length
end
it "should succesfully load rows with null values" do
@valid_notifications_row_data[0][7] = nil
@valid_notifications_row_data[1][9] = nil
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
Notification.count.should == @valid_notifications_row_data.length
end
it "should successfully load rows with question marks in the values" do
@valid_notifications_row_data[0][3] = "{\"topic_title\":\"Who took my sandwich?\",\"display_username\":\"Lunchless\"}"
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
Notification.count.should == @valid_notifications_row_data.length
end
end
context "with fewer than the expected number of fields for a table" do
before do
@short_field_list = ["id", "notification_type", "user_id", "data", "read", "created_at", "updated_at", "topic_id", "post_number"]
@short_notifications_row_data = [
['1409', '5', '1227', '', 't', '2012-12-07 19:59:56.691592', '2012-12-07 19:59:56.691592', '303', '16'],
['1408', '4', '1188', '', 'f', '2012-12-07 18:40:30.460404', '2012-12-07 18:40:30.460404', '304', '1']
]
end
it "should not raise an error" do
expect {
Jobs::Importer.new.load_table('notifications', @short_field_list, @short_notifications_row_data, @short_notifications_row_data.size)
}.to_not raise_error
end
end
context "with more than the expected number of fields for a table" do
before do
@too_long_field_list = ["id", "notification_type", "user_id", "data", "read", "created_at", "updated_at", "topic_id", "post_number", "post_action_id", "extra_col"]
@too_long_notifications_row_data = [
['1409', '5', '1227', '', 't', '2012-12-07 19:59:56.691592', '2012-12-07 19:59:56.691592', '303', '16', '420', 'extra'],
['1408', '4', '1188', '', 'f', '2012-12-07 18:40:30.460404', '2012-12-07 18:40:30.460404', '304', '1', '421', 'extra']
]
end
it "should raise an error" do
expect {
Jobs::Importer.new.load_table('notifications', @too_long_field_list, @too_long_notifications_row_data, @too_long_notifications_row_data.size)
}.to raise_error(Import::WrongFieldCountError)
end
end
context "with an unrecognized table name" do
it "should not raise an error" do
expect {
Jobs::Importer.new.load_table('pork_chops', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
}.to_not raise_error
end
it "should report a warning" do
Jobs::Importer.any_instance.expects(:add_warning).once
Jobs::Importer.new.load_table('pork_chops', @valid_field_list, @valid_notifications_row_data, @valid_notifications_row_data.size)
end
end
end
context "when import adapters are needed" do
before do
@version = (Export.current_schema_version.to_i - 1).to_s
Export.stubs(:current_schema_version).returns( @version )
end
it "should apply the adapter" do
@adapter = mock('adapter', apply_to_column_names: @valid_field_list, apply_to_row: @valid_notifications_row_data[0])
Import.expects(:adapters_for_version).at_least_once.returns({'notifications' => [@adapter]})
Jobs::Importer.new.load_table('notifications', @valid_field_list, @valid_notifications_row_data[0,1], 1)
end
end
end
describe "create_indexes" do
before do
Import::JsonDecoder.stubs(:new).returns( stub_everything )
Jobs::Importer.any_instance.stubs(:set_schema_info).returns( true )
Jobs::Importer.any_instance.stubs(:load_table).returns( true )
end
it "should create the same indexes on the new tables" do
Jobs::Importer.any_instance.stubs(:ordered_models_for_import).returns([Topic])
expect {
Jobs::Importer.new.execute( @importer_args )
}.to_not change{ Topic.exec_sql("SELECT indexname FROM pg_indexes WHERE tablename = 'topics' and schemaname = 'public';").map {|x| x['indexname']}.sort }
end
it "should create primary keys" do
Jobs::Importer.any_instance.stubs(:ordered_models_for_import).returns([User])
Jobs::Importer.new.execute( @importer_args )
User.connection.primary_key('users').should_not be_nil
end
end
describe "rollback" do
it "should not get called if format parameter is invalid" do
stub_data_loading
Jobs::Importer.any_instance.stubs(:start_import).raises(Import::FormatInvalidError)
Jobs::Importer.any_instance.expects(:rollback).never
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
context "when creating the backup schema fails" do
it "should not call rollback" do
stub_data_loading
Jobs::Importer.any_instance.stubs(:create_backup_schema).raises(RuntimeError)
Jobs::Importer.any_instance.expects(:rollback).never
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
end
shared_examples_for "a case when rollback is needed" do
before do
Jobs::Importer.any_instance.stubs(:ordered_models_for_import).returns([User])
@user1, @user2 = Fabricate(:user), Fabricate(:user)
@user_row1 = User.connection.select_rows("select * from users order by id DESC limit 1")
@user_row1[0] = '11111' # change the id
@export_data = {
schema: { source: 'discourse', version: '20121201205642'},
users: {
fields: User.columns.map(&:name),
rows: [ *@user_row1 ]
}
}
@testIO = StringIO.new(@export_data.to_json, 'r')
Import::JsonDecoder.any_instance.stubs(:input_stream).returns(@testIO)
end
it "should call rollback" do
Jobs::Importer.any_instance.expects(:rollback).once
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
it "should restore the data" do
expect {
Jobs::Importer.new.execute( @importer_args ) rescue nil
}.to_not change { User.count }
users = User.all
users.should include(@user1)
users.should include(@user2)
end
it "should take the site out of maintenance mode" do
Discourse.expects(:disable_maintenance_mode).at_least_once
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
end
context "when backing up a table fails" do
it "should not call rollback" do # because the transaction will rollback automatically
stub_data_loading
Jobs::Importer.any_instance.stubs(:backup_and_setup_table).raises(ActiveRecord::StatementInvalid)
Jobs::Importer.any_instance.expects(:rollback).never
Jobs::Importer.new.execute( @importer_args ) rescue nil
end
end
context "when export source is invalid" do
before do
Jobs::Importer.any_instance.stubs(:set_schema_info).raises(Import::UnsupportedExportSource)
end
it_should_behave_like "a case when rollback is needed"
end
context "when schema version is not supported" do
before do
Jobs::Importer.any_instance.stubs(:set_schema_info).raises(Import::UnsupportedSchemaVersion)
end
it_should_behave_like "a case when rollback is needed"
end
context "when schema info in export file is invalid for some other reason" do
before do
Jobs::Importer.any_instance.stubs(:set_schema_info).raises(ArgumentError)
end
it_should_behave_like "a case when rollback is needed"
end
context "when loading a table fails" do
before do
Jobs::Importer.any_instance.stubs(:load_table).raises(ActiveRecord::StatementInvalid)
end
it_should_behave_like "a case when rollback is needed"
end
context "when creating indexes fails" do
before do
Jobs::Importer.any_instance.stubs(:create_indexes).raises(ActiveRecord::StatementInvalid)
end
it_should_behave_like "a case when rollback is needed"
end
context "when table count is wrong" do
before do
Jobs::Importer.any_instance.stubs(:set_schema_info).raises(Import::WrongTableCountError)
end
it_should_behave_like "a case when rollback is needed"
end
context "when field count for a table is wrong" do
before do
Jobs::Importer.any_instance.stubs(:load_table).raises(Import::WrongFieldCountError)
end
it_should_behave_like "a case when rollback is needed"
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'spec_helper'
require 'jobs'
describe Jobs::InviteEmail do
context '.execute' do
it 'raises an error when the invite_id is missing' do
lambda { Jobs::InviteEmail.new.execute({}) }.should raise_error(Discourse::InvalidParameters)
end
context 'with an invite id' do
let (:mailer) { Mail::Message.new(to: 'eviltrout@test.domain') }
let (:invite) { Fabricate(:invite) }
it 'delegates to the test mailer' do
EmailSender.any_instance.expects(:send)
InviteMailer.expects(:send_invite).with(invite).returns(mailer)
Jobs::InviteEmail.new.execute(invite_id: invite.id)
end
end
end
end

View file

@ -0,0 +1,17 @@
require 'spec_helper'
require 'jobs'
describe Jobs::Base do
it 'delegates the process call to execute' do
Jobs::Base.any_instance.expects(:execute).with('hello' => 'world')
Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true)
end
it 'converts to an indifferent access hash' do
Jobs::Base.any_instance.expects(:execute).with(instance_of(HashWithIndifferentAccess))
Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true)
end
end

View file

@ -0,0 +1,36 @@
require 'spec_helper'
require 'jobs/process_post'
describe Jobs::NotifyMovedPosts do
it "raises an error without post_ids" do
lambda { Jobs::NotifyMovedPosts.new.execute(moved_by_id: 1234) }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error without moved_by_id" do
lambda { Jobs::NotifyMovedPosts.new.execute(post_ids: [1,2,3]) }.should raise_error(Discourse::InvalidParameters)
end
context 'with post ids' do
let!(:p1) { Fabricate(:post) }
let!(:p2) { Fabricate(:post, user: Fabricate(:evil_trout), topic: p1.topic) }
let!(:p3) { Fabricate(:post, user: p1.user, topic: p1.topic) }
let(:admin) { Fabricate(:admin) }
let(:moved_post_notifications) { Notification.where(notification_type: Notification.Types[:moved_post]) }
it "should create two notifications" do
lambda { Jobs::NotifyMovedPosts.new.execute(post_ids: [p1.id, p2.id, p3.id], moved_by_id: admin.id) }.should change(moved_post_notifications, :count).by(2)
end
context 'when moved by one of the posters' do
it "create one notifications, because the poster is the mover" do
lambda { Jobs::NotifyMovedPosts.new.execute(post_ids: [p1.id, p2.id, p3.id], moved_by_id: p1.user_id) }.should change(moved_post_notifications, :count).by(1)
end
end
end
end

View file

@ -0,0 +1,37 @@
require 'spec_helper'
require 'jobs/process_post'
describe Jobs::ProcessPost do
it "returns when the post cannot be found" do
lambda { Jobs::ProcessPost.new.perform(post_id: 1, sync_exec: true) }.should_not raise_error
end
context 'with a post' do
before do
@post = Fabricate(:post)
end
it 'calls process on a CookedPostProcessor' do
CookedPostProcessor.any_instance.expects(:post_process).once
Jobs::ProcessPost.new.execute(post_id: @post.id)
end
it 'updates the html if the dirty flag is true' do
CookedPostProcessor.any_instance.expects(:dirty?).returns(true)
CookedPostProcessor.any_instance.expects(:html).returns('test')
Post.any_instance.expects(:update_column).with(:cooked, 'test').once
Jobs::ProcessPost.new.execute(post_id: @post.id)
end
it "doesn't update the cooked content if dirty is false" do
CookedPostProcessor.any_instance.expects(:dirty?).returns(false)
Post.any_instance.expects(:update_column).never
Jobs::ProcessPost.new.execute(post_id: @post.id)
end
end
end

View file

@ -0,0 +1,26 @@
require 'spec_helper'
require 'jobs/send_system_message'
describe Jobs::SendSystemMessage do
it "raises an error without a user_id" do
lambda { Jobs::SendSystemMessage.new.execute(message_type: 'welcome_invite') }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error without a message_type" do
lambda { Jobs::SendSystemMessage.new.execute(user_id: 1234) }.should raise_error(Discourse::InvalidParameters)
end
context 'with valid parameters' do
let(:user) { Fabricate(:user) }
it "should call SystemMessage.create" do
SystemMessage.any_instance.expects(:create).with('welcome_invite')
Jobs::SendSystemMessage.new.execute(user_id: user.id, message_type: 'welcome_invite')
end
end
end

View file

@ -0,0 +1,27 @@
require 'spec_helper'
require 'jobs'
describe Jobs::TestEmail do
context '.execute' do
it 'raises an error when the address is missing' do
lambda { Jobs::TestEmail.new.execute({}) }.should raise_error(Discourse::InvalidParameters)
end
context 'with an address' do
let (:mailer) { Mail::Message.new(to: 'eviltrout@test.domain') }
it 'delegates to the test mailer' do
EmailSender.any_instance.expects(:send)
TestMailer.expects(:send_test).with('eviltrout@test.domain').returns(mailer)
Jobs::TestEmail.new.execute(to_address: 'eviltrout@test.domain')
end
end
end
end

View file

@ -0,0 +1,97 @@
require 'spec_helper'
require 'jobs'
describe Jobs::UserEmail do
before do
SiteSetting.stubs(:email_time_window_mins).returns(10)
end
let(:user) { Fabricate(:user, last_seen_at: 11.minutes.ago ) }
let(:mailer) { Mail::Message.new(to: user.email) }
it "raises an error when there is no user" do
lambda { Jobs::UserEmail.new.execute(type: :digest) }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when there is no type" do
lambda { Jobs::UserEmail.new.execute(user_id: user.id) }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when the type doesn't exist" do
lambda { Jobs::UserEmail.new.execute(type: :no_method, user_id: user.id) }.should raise_error(Discourse::InvalidParameters)
end
it "doesn't call the mailer when the user is missing" do
UserNotifications.expects(:digest).never
Jobs::UserEmail.new.execute(type: :digest, user_id: 1234)
end
context 'to_address' do
it 'overwrites a to_address when present' do
UserNotifications.expects(:authorize_email).returns(mailer)
EmailSender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :authorize_email, user_id: user.id, to_address: 'jake@adventuretime.ooo')
mailer.to.should == ['jake@adventuretime.ooo']
end
end
context "recently seen" do
let(:post) { Fabricate(:post, user: user) }
it "doesn't send an email to a user that's been recently seen" do
user.update_column(:last_seen_at, 9.minutes.ago)
EmailSender.any_instance.expects(:send).never
Jobs::UserEmail.new.execute(type: :private_message, user_id: user.id, post_id: post.id)
end
end
context 'args' do
it 'passes a token as an argument when a token is present' do
UserNotifications.expects(:forgot_password).with(user, {email_token: 'asdfasdf'}).returns(mailer)
EmailSender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, email_token: 'asdfasdf')
end
context "post" do
let(:post) { Fabricate(:post, user: user) }
it 'passes a post as an argument when a post_id is present' do
UserNotifications.expects(:private_message).with(user, {post: post}).returns(mailer)
EmailSender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :private_message, user_id: user.id, post_id: post.id)
end
it "doesn't send the email if you've seen the post" do
EmailSender.any_instance.expects(:send).never
PostTiming.record_timing(topic_id: post.topic_id, user_id: user.id, post_number: post.post_number, msecs: 6666)
Jobs::UserEmail.new.execute(type: :private_message, user_id: user.id, post_id: post.id)
end
end
context 'notification' do
let!(:notification) { Fabricate(:notification, user: user)}
it 'passes a notification as an argument when a notification_id is present' do
EmailSender.any_instance.expects(:send)
UserNotifications.expects(:user_mentioned).with(user, notification: notification).returns(mailer)
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
end
it "doesn't send the email if the notification has been seen" do
EmailSender.any_instance.expects(:send).never
notification.update_column(:read, true)
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
end
end
end
end

View file

@ -0,0 +1,79 @@
require 'spec_helper'
require 'jobs'
describe Jobs do
describe 'enqueue' do
describe 'when queue_jobs is true' do
before do
SiteSetting.expects(:queue_jobs?).returns(true)
end
it 'enqueues a job in sidekiq' do
Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, post_id: 1, current_site_id: 'default')
Jobs.enqueue(:process_post, post_id: 1)
end
it "does not pass current_site_id when 'all_sites' is present" do
Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, post_id: 1)
Jobs.enqueue(:process_post, post_id: 1, all_sites: true)
end
it "doesn't execute the job" do
Sidekiq::Client.stubs(:enqueue)
Jobs::ProcessPost.any_instance.expects(:perform).never
Jobs.enqueue(:process_post, post_id: 1)
end
it "should enqueue with the correct database id when the current_site_id option is given" do
Sidekiq::Client.expects(:enqueue).with do |arg1, arg2|
arg2[:current_site_id] == 'test_db' and arg2[:sync_exec].nil?
end
Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db')
end
end
describe 'when queue_jobs is false' do
before do
SiteSetting.expects(:queue_jobs?).returns(false)
end
it "doesn't enqueue in sidekiq" do
Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, {}).never
Jobs.enqueue(:process_post, post_id: 1)
end
it "executes the job right away" do
Jobs::ProcessPost.any_instance.expects(:perform).with(post_id: 1, sync_exec: true, current_site_id: "default")
Jobs.enqueue(:process_post, post_id: 1)
end
context 'and current_site_id option is given and does not match the current connection' do
before do
Sidekiq::Client.stubs(:enqueue)
Jobs::ProcessPost.any_instance.stubs(:execute).returns(true)
end
it 'should not execute the job' do
Jobs::ProcessPost.any_instance.expects(:execute).never
Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') rescue nil
end
it 'should raise an exception' do
expect {
Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db')
}.to raise_error(ArgumentError)
end
it 'should not connect to the given database' do
RailsMultisite::ConnectionManagement.expects(:establish_connection).never
Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') rescue nil
end
end
end
end
end

View file

@ -0,0 +1,64 @@
require 'spec_helper'
require_dependency 'mothership'
describe Mothership do
describe '#nickname_available?' do
it 'should return true when nickname is available and no suggestion' do
RestClient.stubs(:get).returns( {success: 'OK', available: true}.to_json )
Mothership.nickname_available?('MacGyver').should == [true, nil]
end
it 'should return false and a suggestion when nickname is not available' do
RestClient.stubs(:get).returns( {success: 'OK', available: false, suggestion: 'MacGyver1'}.to_json )
available, suggestion = Mothership.nickname_available?('MacGyver')
available.should be_false
suggestion.should_not be_nil
end
# How to handle connect errors? timeout? 401? 403? 429?
end
describe '#nickname_match?' do
it 'should return true when it is a match and no suggestion' do
RestClient.stubs(:get).returns( {success: 'OK', match: true, available: false}.to_json )
Mothership.nickname_match?('MacGyver', 'macg@example.com').should == [true, false, nil]
end
it 'should return false and a suggestion when it is not a match and the nickname is not available' do
RestClient.stubs(:get).returns( {success: 'OK', match: false, available: false, suggestion: 'MacGyver1'}.to_json )
match, available, suggestion = Mothership.nickname_match?('MacGyver', 'macg@example.com')
match.should be_false
available.should be_false
suggestion.should_not be_nil
end
it 'should return false and no suggestion when it is not a match and the nickname is available' do
RestClient.stubs(:get).returns( {success: 'OK', match: false, available: true}.to_json )
match, available, suggestion = Mothership.nickname_match?('MacGyver', 'macg@example.com')
match.should be_false
available.should be_true
suggestion.should be_nil
end
end
describe '#register_nickname' do
it 'should return true when registration succeeds' do
RestClient.stubs(:post).returns( {success: 'OK'}.to_json )
Mothership.register_nickname('MacGyver', 'macg@example.com').should be_true
end
it 'should return raise an exception when registration fails' do
RestClient.stubs(:post).returns( {failed: -200}.to_json )
expect {
Mothership.register_nickname('MacGyver', 'macg@example.com')
}.to raise_error(Mothership::NicknameUnavailable)
end
end
describe '#current_discourse_version' do
it 'should return the latest version of discourse' do
RestClient.stubs(:get).returns( {success: 'OK', version: 1.0}.to_json )
Mothership.current_discourse_version().should == 1.0
end
end
end

View file

@ -0,0 +1,132 @@
require 'spec_helper'
require 'oneboxer'
describe Oneboxer do
# A class to help us test
class DummyOnebox < Oneboxer::BaseOnebox
matcher /^https?:\/\/dummy.localhost/
def onebox
"dummy!"
end
end
before do
Oneboxer.add_onebox DummyOnebox
@dummy_onebox_url = "http://dummy.localhost/dummy-object"
end
it 'should have matchers set up by default' do
Oneboxer.matchers.should be_present
end
context 'find onebox for url' do
it 'returns blank with an unknown url' do
Oneboxer.onebox_for_url('http://asdfasdfasdfasdf.asdf').should be_blank
end
it 'returns something when matched' do
Oneboxer.onebox_for_url(@dummy_onebox_url).should be_present
end
it 'returns an instance of our class when matched' do
Oneboxer.onebox_for_url(@dummy_onebox_url).kind_of?(DummyOnebox).should be_true
end
end
context 'without caching' do
it 'calls the onebox method of our matched class' do
Oneboxer.onebox_nocache(@dummy_onebox_url).should == 'dummy!'
end
end
context 'with caching' do
context 'initial cache is empty' do
it 'has no OneboxRender records' do
OneboxRender.count.should == 0
end
it 'calls the onebox_nocache method if there is no cache record yet' do
Oneboxer.expects(:onebox_nocache).with(@dummy_onebox_url).once
Oneboxer.onebox(@dummy_onebox_url)
end
end
context 'caching result' do
before do
@post = Fabricate(:post)
@result = Oneboxer.onebox(@dummy_onebox_url, post_id: @post.id)
@onebox_render = OneboxRender.where(url: @dummy_onebox_url).first
end
it "returns the correct result" do
@result.should == 'dummy!'
end
it "created a OneboxRender record with the url" do
@onebox_render.should be_present
end
it "created a OneboxRender record with the url" do
@onebox_render.url.should == @dummy_onebox_url
end
it "associated the render with a post" do
@onebox_render.posts.should == [@post]
end
it "has an expires_at value" do
@onebox_render.expires_at.should be_present
end
it "doesn't call onebox_nocache on a cache hit" do
Oneboxer.expects(:onebox_nocache).never
Oneboxer.onebox(@dummy_onebox_url).should == 'dummy!'
end
context 'invalidating cache' do
it "deletes the onebox render" do
Oneboxer.expects(:onebox_nocache).once.returns('new cache value!')
Oneboxer.onebox(@dummy_onebox_url, invalidate_oneboxes: true).should == 'new cache value!'
end
end
end
end
context 'each_onebox_link' do
before do
@html = "<a href='http://discourse.org' class='onebox'>Discourse Link</a>"
end
it 'yields each url and element when given a string' do
result = Oneboxer.each_onebox_link(@html) do |url, element|
element.is_a?(Hpricot::Elem).should be_true
url.should == 'http://discourse.org'
end
result.kind_of?(Hpricot::Doc).should be_true
end
it 'yields each url and element when given a doc' do
doc = Hpricot(@html)
Oneboxer.each_onebox_link(doc) do |url, element|
element.is_a?(Hpricot::Elem).should be_true
url.should == 'http://discourse.org'
end
end
end
end

View file

@ -0,0 +1,103 @@
require 'spec_helper'
require 'post_creator'
describe PostCreator do
let(:user) { Fabricate(:user) }
it 'raises an error without a raw value' do
lambda { PostCreator.new(user, {}) }.should raise_error(Discourse::InvalidParameters)
end
context 'new topic' do
let(:category) { Fabricate(:category, user: user) }
let(:basic_topic_params) { {title: 'hello world', raw: 'my name is fred', archetype_id: 1} }
let(:image_sizes) { {'http://an.image.host/image.jpg' => {'width' => 111, 'height' => 222}} }
let(:creator) { PostCreator.new(user, basic_topic_params) }
let(:creator_with_category) { PostCreator.new(user, basic_topic_params.merge(category: category.name )) }
let(:creator_with_meta_data) { PostCreator.new(user, basic_topic_params.merge(meta_data: {hello: 'world'} )) }
let(:creator_with_image_sizes) { PostCreator.new(user, basic_topic_params.merge(image_sizes: image_sizes)) }
it 'ensures the user can create the topic' do
Guardian.any_instance.expects(:can_create?).with(Topic,nil).returns(false)
lambda { creator.create }.should raise_error(Discourse::InvalidAccess)
end
context 'success' do
it 'creates a topic' do
lambda { creator.create }.should change(Topic, :count).by(1)
end
it 'returns a post' do
creator.create.is_a?(Post).should be_true
end
it 'extracts links from the post' do
TopicLink.expects(:extract_from).with(instance_of(Post))
creator.create
end
it 'assigns a category when supplied' do
creator_with_category.create.topic.category.should == category
end
it 'adds meta data from the post' do
creator_with_meta_data.create.topic.meta_data['hello'].should == 'world'
end
it 'passes the image sizes through' do
Post.any_instance.expects(:image_sizes=).with(image_sizes)
creator_with_image_sizes.create
end
end
end
context 'existing topic' do
let!(:topic) { Fabricate(:topic, user: user) }
let(:creator) { PostCreator.new(user, raw: 'test reply', topic_id: topic.id, reply_to_post_number: 4) }
it 'ensures the user can create the post' do
Guardian.any_instance.expects(:can_create?).with(Post, topic).returns(false)
lambda { creator.create }.should raise_error(Discourse::InvalidAccess)
end
context 'success' do
it 'should create the post' do
lambda { creator.create }.should change(Post, :count).by(1)
end
it "doesn't create a topic" do
lambda { creator.create }.should_not change(Topic, :count)
end
it "passes through the reply_to_post_number" do
creator.create.reply_to_post_number.should == 4
end
end
end
context 'private message' do
let(:target_user1) { Fabricate(:coding_horror) }
let(:target_user2) { Fabricate(:moderator) }
let(:post) do
PostCreator.create(user, title: 'hi there',
raw: 'this is my awesome message',
archetype: Archetype.private_message,
target_usernames: [target_user1.username, target_user2.username].join(','))
end
it 'has the right archetype' do
post.topic.archetype.should == Archetype.private_message
end
it 'has the right count (me and 2 other users)' do
post.topic.topic_allowed_users.count.should == 3
end
end
end

View file

@ -0,0 +1,143 @@
require 'spec_helper'
require 'pretty_text'
describe PrettyText do
describe "Cooking" do
it "should support github style code blocks" do
PrettyText.cook("```
test
```").should == "<pre><code class=\"lang-auto\">test \n</code></pre>"
end
it "should support quoting [] " do
PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"][sam][/quote]").should =~ /\[sam\]/
end
it "produces a quote even with new lines in it" do
PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]").should == "<p></p><aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n <div class=\"quote-controls\"></div>\n <img width=\"20\" height=\"20\" src=\"/users/eviltrout/avatar/40?__ws=http%3A%2F%2Ftest.localhost\" class=\"avatar \" title=\"\">\n EvilTrout\n said:\n </div>\n <blockquote>ddd</blockquote>\n</aside><p></p>"
end
it "should produce a quote" do
PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]").should == "<p></p><aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n <div class=\"quote-controls\"></div>\n <img width=\"20\" height=\"20\" src=\"/users/eviltrout/avatar/40?__ws=http%3A%2F%2Ftest.localhost\" class=\"avatar \" title=\"\">\n EvilTrout\n said:\n </div>\n <blockquote>ddd</blockquote>\n</aside><p></p>"
end
it "trims spaces on quote params" do
PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]").should == "<p></p><aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n <div class=\"quote-controls\"></div>\n <img width=\"20\" height=\"20\" src=\"/users/eviltrout/avatar/40?__ws=http%3A%2F%2Ftest.localhost\" class=\"avatar \" title=\"\">\n EvilTrout\n said:\n </div>\n <blockquote>ddd</blockquote>\n</aside><p></p>"
end
it "should handle 3 mentions in a row" do
PrettyText.cook('@hello @hello @hello').should == "<p><span class=\"mention\">@hello</span> <span class=\"mention\">@hello</span> <span class=\"mention\">@hello</span></p>"
end
it "should not do weird @ mention stuff inside a pre block" do
PrettyText.cook("```
a @test
```").should == "<pre><code class=\"lang-auto\">a @test \n</code></pre>"
end
it "should sanitize the html" do
PrettyText.cook("<script>alert(42)</script>").should == "alert(42)"
end
it "should escape html within the code block" do
PrettyText.cook("```text
<header>hello</header>
```").should == "<pre><code class=\"text\">&lt;header&gt;hello&lt;/header&gt; \n</code></pre>"
end
it "should support language choices" do
PrettyText.cook("```ruby
test
```").should == "<pre><code class=\"ruby\">test \n</code></pre>"
end
it 'should decorate @mentions' do
PrettyText.cook("Hello @eviltrout").should == "<p>Hello <span class=\"mention\">@eviltrout</span></p>"
end
it 'should allow for @mentions to have punctuation' do
PrettyText.cook("hello @bob's @bob,@bob; @bob\"").should ==
"<p>hello <span class=\"mention\">@bob</span>'s <span class=\"mention\">@bob</span>,<span class=\"mention\">@bob</span>; <span class=\"mention\">@bob</span>\"</p>"
end
it 'should add spoiler tags' do
PrettyText.cook("[spoiler]hello[/spoiler]").should == "<p><span class=\"spoiler\">hello</span></p>"
end
it "should only detect ``` at the begining of lines" do
PrettyText.cook(" ```\n hello\n ```")
.should == "<pre><code>```\nhello\n```\n</code></pre>"
end
end
describe "Excerpt" do
it "should preserve links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should == "<a href='http://cnn.com'>cnn</a>"
end
it "should dump images" do
PrettyText.excerpt("<img src='http://cnn.com/a.gif'>",100).should == "[image]"
end
it "should keep alt tags" do
PrettyText.excerpt("<img src='http://cnn.com/a.gif' alt='car' title='my big car'>",100).should == "[car]"
end
it "should keep title tags" do
PrettyText.excerpt("<img src='http://cnn.com/a.gif' title='car'>",100).should == "[car]"
end
it "should deal with special keys properly" do
PrettyText.excerpt("<pre><b></pre>",100).should == ""
end
it "should truncate stuff properly" do
PrettyText.excerpt("hello world",5).should == "hello&hellip;"
end
it "should insert a space between to Ps" do
PrettyText.excerpt("<p>a</p><p>b</p>",5).should == "a b "
end
it "should strip quotes" do
PrettyText.excerpt("<aside class='quote'><p>a</p><p>b</p></aside>boom",5).should == "boom"
end
it "should not count the surrounds of a link" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should == "<a href='http://cnn.com'>cnn</a>"
end
it "should truncate links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should == "<a href='http://cnn.com'>cn&hellip;</a>"
end
it "should be able to extract links" do
PrettyText.extract_links("<a href='http://cnn.com'>http://bla.com</a>").to_a.should == ["http://cnn.com"]
end
it "should not preserve tags in code blocks" do
PrettyText.excerpt("<pre><code class='handlebars'>&lt;h3&gt;Hours&lt;/h3&gt;</code></pre>",100).should == "&lt;h3&gt;Hours&lt;/h3&gt;"
end
it "should handle nil" do
PrettyText.excerpt(nil,100).should == ''
end
end
describe "apply cdn" do
it "should detect bare links to images and apply a CDN" do
PrettyText.apply_cdn("<a href='/hello.png'>hello</a><img src='/a.jpeg'>","http://a.com").should ==
"<a href=\"http://a.com/hello.png\">hello</a><img src=\"http://a.com/a.jpeg\">"
end
it "should not touch non images" do
PrettyText.apply_cdn("<a href='/hello'>hello</a>","http://a.com").should ==
"<a href=\"/hello\">hello</a>"
end
end
end

View file

@ -0,0 +1,48 @@
require 'spec_helper'
require 'promotion'
describe Promotion do
context "new user" do
let(:user) { Fabricate(:user, trust_level: TrustLevel.Levels[:new])}
let(:promotion) { Promotion.new(user) }
it "doesn't raise an error with a nil user" do
-> { Promotion.new(nil).review }.should_not raise_error
end
context 'that has done nothing' do
let!(:result) { promotion.review }
it "returns false" do
result.should be_false
end
it "has not changed the user's trust level" do
user.trust_level.should == TrustLevel.Levels[:new]
end
end
context "that has done the requisite things" do
before do
user.topics_entered = SiteSetting.basic_requires_topics_entered
user.posts_read_count = SiteSetting.basic_requires_read_posts
user.time_read = SiteSetting.basic_requires_time_spent_mins * 60
@result = promotion.review
end
it "returns true" do
@result.should be_true
end
it "has upgraded the user to basic" do
user.trust_level.should == TrustLevel.Levels[:basic]
end
end
end
end

View file

@ -0,0 +1,103 @@
require 'spec_helper'
require 'rate_limiter'
describe RateLimiter do
let(:user) { Fabricate(:user) }
let(:rate_limiter) { RateLimiter.new(user, "peppermint-butler", 2, 60) }
context 'disabled' do
before do
RateLimiter.stubs(:disabled?).returns(true)
rate_limiter.performed!
rate_limiter.performed!
end
it "returns true for can_perform?" do
rate_limiter.can_perform?.should be_true
end
it "doesn't raise an error on performed!" do
lambda { rate_limiter.performed! }.should_not raise_error
end
end
context 'enabled' do
before do
RateLimiter.stubs(:disabled?).returns(false)
rate_limiter.clear!
end
context 'never done' do
it "should perform right away" do
rate_limiter.can_perform?.should be_true
end
it "performs without an error" do
lambda { rate_limiter.performed! }.should_not raise_error
end
end
context "multiple calls" do
before do
rate_limiter.performed!
rate_limiter.performed!
end
it "returns false for can_perform when the limit has been hit" do
rate_limiter.can_perform?.should be_false
end
it "raises an error the third time called" do
lambda { rate_limiter.performed! }.should raise_error
end
context "as an admin/moderator" do
it "returns true for can_perform if the user is an admin" do
user.admin = true
rate_limiter.can_perform?.should be_true
end
it "doesn't raise an error when an admin performs the task" do
user.admin = true
lambda { rate_limiter.performed! }.should_not raise_error
end
it "returns true for can_perform if the user is a mod" do
user.trust_level = TrustLevel.Levels[:moderator]
rate_limiter.can_perform?.should be_true
end
it "doesn't raise an error when a moderator performs the task" do
user.trust_level = TrustLevel.Levels[:moderator]
lambda { rate_limiter.performed! }.should_not raise_error
end
end
context "rollback!" do
before do
rate_limiter.rollback!
end
it "returns true for can_perform since there is now room" do
rate_limiter.can_perform?.should be_true
end
it "raises no error now that there is room" do
lambda { rate_limiter.performed! }.should_not raise_error
end
end
end
end
end

View file

@ -0,0 +1,49 @@
require 'spec_helper'
require 'score_calculator'
describe ScoreCalculator do
before do
@post = Fabricate(:post, reads: 111)
@topic = @post.topic
end
context 'with weightings' do
before do
ScoreCalculator.new(reads: 3).calculate
@post.reload
end
it 'takes the supplied weightings into effect' do
@post.score.should == 333
end
end
context 'best_of' do
it "won't update the site settings when the site settings don't match" do
ScoreCalculator.new(reads: 3).calculate
@topic.reload
@topic.has_best_of.should be_false
end
it "removes the best_of flag if the topic no longer qualifies" do
@topic.update_column(:has_best_of, true)
ScoreCalculator.new(reads: 3).calculate
@topic.reload
@topic.has_best_of.should be_false
end
it "won't update the site settings when the site settings don't match" do
SiteSetting.expects(:best_of_likes_required).returns(0)
SiteSetting.expects(:best_of_posts_required).returns(1)
SiteSetting.expects(:best_of_score_threshold).returns(100)
ScoreCalculator.new(reads: 3).calculate
@topic.reload
@topic.has_best_of.should be_true
end
end
end

View file

@ -0,0 +1,182 @@
require 'spec_helper'
require 'search'
describe Search do
def first_of_type(results, type)
return nil if results.blank?
results.each do |r|
return r[:results].first if r[:type] == type
end
nil
end
context 'post indexing observer' do
before do
@category = Fabricate(:category, name: 'america')
@topic = Fabricate(:topic, title: 'sam test', category: @category)
@post = Fabricate(:post, topic: @topic, raw: 'this <b>fun test</b> <img src="bla" title="my image">')
@indexed = Topic.exec_sql("select search_data from posts_search where id = #{@post.id}").first["search_data"]
end
it "should include body in index" do
@indexed.should =~ /fun/
end
it "should include title in index" do
@indexed.should =~ /sam/
end
it "should include category in index" do
@indexed.should =~ /america/
end
it "should pick up on title updates" do
@topic.title = "harpi"
@topic.save!
@indexed = Topic.exec_sql("select search_data from posts_search where id = #{@post.id}").first["search_data"]
@indexed.should =~ /harpi/
end
end
context 'user indexing observer' do
before do
@user = Fabricate(:user, username: 'fred', name: 'bob jones')
@indexed = User.exec_sql("select search_data from users_search where id = #{@user.id}").first["search_data"]
end
it "should pick up on username" do
@indexed.should =~ /fred/
end
it "should pick up on name" do
@indexed.should =~ /jone/
end
end
context 'category indexing observer' do
before do
@category = Fabricate(:category, name: 'america')
@indexed = Topic.exec_sql("select search_data from categories_search where id = #{@category.id}").first["search_data"]
end
it "should pick up on name" do
@indexed.should =~ /america/
end
end
it 'returns something blank on a nil search' do
ActiveRecord::Base.expects(:exec_sql).never
Search.query(nil).should be_blank
end
it 'escapes non alphanumeric characters' do
ActiveRecord::Base.expects(:exec_sql).never
Search.query(':!$').should be_blank
end
it 'works when given two terms with spaces' do
lambda { Search.query('evil trout') }.should_not raise_error
end
context 'users' do
let!(:user) { Fabricate(:user) }
let(:result) { first_of_type(Search.query('bruce'), 'user') }
it 'returns a result' do
result.should be_present
end
it 'has the display name as the title' do
result['title'].should == user.username
end
it 'has the avatar_template is there so it can hand it to the client' do
result['avatar_template'].should_not be_nil
end
it 'has a url for the record' do
result['url'].should == "/users/#{user.username_lower}"
end
end
context 'topics' do
let!(:topic) { Fabricate(:topic) }
context 'searching the OP' do
let!(:post) { Fabricate(:post, topic: topic, user: topic.user) }
let(:result) { first_of_type(Search.query('hello'), 'topic') }
it 'returns a result' do
result.should be_present
end
it 'has the topic title' do
result['title'].should == topic.title
end
it 'has a url for the post' do
result['url'].should == topic.relative_url
end
end
end
context 'categories' do
let!(:category) { Fabricate(:category) }
let(:result) { first_of_type(Search.query('amazing'), 'category') }
it 'returns a result' do
result.should be_present
end
it 'has the category name' do
result['title'].should == category.name
end
it 'has a url for the topic' do
result['url'].should == "/category/#{category.slug}"
end
end
context 'type_filter' do
let!(:user) { Fabricate(:user, username: 'amazing', email: 'amazing@amazing.com') }
let!(:category) { Fabricate(:category, name: 'amazing category', user: user) }
context 'user filter' do
let(:results) { Search.query('amazing', 'user') }
it "returns a user result" do
results.detect {|r| r[:type] == 'user'}.should be_present
end
it "returns no category results" do
results.detect {|r| r[:type] == 'category'}.should be_blank
end
end
context 'category filter' do
let(:results) { Search.query('amazing', 'category') }
it "returns a user result" do
results.detect {|r| r[:type] == 'user'}.should be_blank
end
it "returns no category results" do
results.detect {|r| r[:type] == 'category'}.should be_present
end
end
end
end

View file

@ -0,0 +1,32 @@
# encoding: utf-8
require 'spec_helper'
require 'slug'
describe Slug do
it 'replaces spaces with hyphens' do
Slug.for("hello world").should == 'hello-world'
end
it 'changes accented characters' do
Slug.for('àllo').should == 'allo'
end
it 'removes symbols' do
Slug.for('evil#trout').should == 'eviltrout'
end
it 'handles a.b.c properly' do
Slug.for("a.b.c").should == "a-b-c"
end
it 'handles double dots right' do
Slug.for("a....b.....c").should == "a-b-c"
end
end

View file

@ -0,0 +1,35 @@
# encoding: utf-8
require 'spec_helper'
require_dependency 'sql_builder'
describe SqlBuilder do
before do
@builder = SqlBuilder.new("select * from (select :a A union all select :b) as X /*where*/ /*order_by*/ /*limit*/ /*offset*/")
end
it "should allow for 1 param exec" do
@builder.exec(a: 1, b: 2).values[0][0].should == '1'
end
it "should allow for a single where" do
@builder.where(":a = 1")
@builder.exec(a: 1, b: 2).values[0][0].should == '1'
end
it "should allow where chaining" do
@builder.where(":a = 1")
@builder.where("2 = 1")
@builder.exec(a: 1, b: 2).to_a.length.should == 0
end
it "should allow order by" do
@builder.order_by("A desc").limit(1)
.exec(a:1, b:2).values[0][0].should == "2"
end
it "should allow offset" do
@builder.order_by("A desc").offset(1)
.exec(a:1, b:2).values[0][0].should == "1"
end
end

View file

@ -0,0 +1,43 @@
require 'spec_helper'
require 'system_message'
describe SystemMessage do
let!(:admin) { Fabricate(:admin) }
context 'send' do
let(:user) { Fabricate(:user) }
let(:system_message) { SystemMessage.new(user) }
let(:post) { system_message.create(:welcome_invite) }
let(:topic) { post.topic }
it 'should create a post' do
post.should be_present
end
it 'should be a private message' do
topic.should be_private_message
end
it 'should be visible by the user' do
topic.allowed_users.include?(user).should be_true
end
end
context '#system_user' do
it 'returns the user specified by the site setting system_username' do
SiteSetting.stubs(:system_username).returns(admin.username)
SystemMessage.system_user.should == admin
end
it 'returns the first admin user otherwise' do
SiteSetting.stubs(:system_username).returns(nil)
SystemMessage.system_user.should == admin
end
end
end

View file

@ -0,0 +1,260 @@
require 'spec_helper'
require 'topic_view'
describe TopicQuery do
let!(:user) { Fabricate(:coding_horror) }
let(:creator) { Fabricate(:user) }
let(:topic_query) { TopicQuery.new(user) }
let(:moderator) { Fabricate(:moderator) }
let(:admin) { Fabricate(:moderator) }
context 'a bunch of topics' do
let!(:regular_topic) { Fabricate(:topic, title: 'regular', user: creator, bumped_at: 15.minutes.ago) }
let!(:pinned_topic) { Fabricate(:topic, title: 'pinned', user: creator, pinned: true, bumped_at: 10.minutes.ago) }
let!(:archived_topic) { Fabricate(:topic, title: 'archived', user: creator, archived: true, bumped_at: 6.minutes.ago) }
let!(:invisible_topic) { Fabricate(:topic, title: 'invisible', user: creator, visible: false, bumped_at: 5.minutes.ago) }
let!(:closed_topic) { Fabricate(:topic, title: 'closed', user: creator, closed: true, bumped_at: 1.minute.ago) }
context 'list_popular' do
let(:topics) { topic_query.list_popular.topics }
it "returns the topics in the correct order" do
topics.should == [pinned_topic, closed_topic, archived_topic, regular_topic]
end
it "includes the invisible topic if you're a moderator" do
TopicQuery.new(moderator).list_popular.topics.include?(invisible_topic).should be_true
end
it "includes the invisible topic if you're an admin" do
TopicQuery.new(admin).list_popular.topics.include?(invisible_topic).should be_true
end
end
end
context 'categorized' do
let(:category) { Fabricate(:category) }
let!(:topic_no_cat) { Fabricate(:topic) }
let!(:topic_in_cat) { Fabricate(:topic, category: category) }
it "returns the topic without a category when filtering uncategorized" do
topic_query.list_uncategorized.topics.should == [topic_no_cat]
end
it "returns the topic with a category when filtering by category" do
topic_query.list_category(category).topics.should == [topic_in_cat]
end
it "returns nothing when filtering by another category" do
topic_query.list_category(Fabricate(:category, name: 'new cat')).topics.should be_blank
end
end
context 'unread / read topics' do
context 'with no data' do
it "has no read topics" do
topic_query.list_unread.topics.should be_blank
end
it "has no unread topics" do
topic_query.list_unread.topics.should be_blank
end
it "has an unread count of 0" do
topic_query.unread_count.should == 0
end
end
context 'with read data' do
let!(:partially_read) { Fabricate(:post, user: creator).topic }
let!(:fully_read) { Fabricate(:post, user: creator).topic }
before do
TopicUser.update_last_read(user, partially_read.id, 0, 0)
TopicUser.update_last_read(user, fully_read.id, 1, 0)
end
context 'list_unread' do
it 'contains no topics' do
topic_query.list_unread.topics.should == []
end
it "returns 0 as the unread count" do
topic_query.unread_count.should == 0
end
end
context 'user with auto_track_topics list_unread' do
before do
user.auto_track_topics_after_msecs = 0
user.save
end
it 'only contains the partially read topic' do
topic_query.list_unread.topics.should == [partially_read]
end
it "returns 1 as the unread count" do
topic_query.unread_count.should == 1
end
end
context 'list_read' do
it 'contain both topics ' do
topic_query.list_read.topics.should =~ [fully_read, partially_read]
end
end
end
end
context 'list_favorited' do
let(:topic) { Fabricate(:topic) }
it "returns no results when the user hasn't favorited anything" do
topic_query.list_favorited.topics.should be_blank
end
context 'with a favorited topic' do
before do
topic.toggle_star(user, true)
end
it "returns the topic after it has been favorited" do
topic_query.list_favorited.topics.should == [topic]
end
end
end
context 'list_new' do
context 'without a new topic' do
it "has an new_count of 0" do
topic_query.new_count.should == 0
end
it "has no new topics" do
topic_query.list_new.topics.should be_blank
end
end
context 'with a new topic' do
let!(:new_topic) { Fabricate(:topic, user: creator, bumped_at: 10.minutes.ago) }
let(:topics) { topic_query.list_new.topics }
it "contains the new topic" do
topics.should == [new_topic]
end
context "muted topics" do
before do
new_topic.notify_muted!(user)
end
it "returns an empty set" do
topics.should be_blank
end
context 'un-muted' do
before do
new_topic.notify_tracking!(user)
end
it "returns the topic again" do
topics.should == [new_topic]
end
end
end
end
end
context 'list_posted' do
let(:topics) { topic_query.list_posted.topics }
it "returns blank when there are no posted topics" do
topics.should be_blank
end
context 'created topics' do
let!(:created_topic) { Fabricate(:post, user: user).topic }
it "includes the created topic" do
topics.include?(created_topic).should be_true
end
end
context "topic you've posted in" do
let(:other_users_topic) { Fabricate(:post, user: creator).topic }
let!(:your_post) { Fabricate(:post, user: user, topic: other_users_topic )}
it "includes the posted topic" do
topics.include?(other_users_topic).should be_true
end
end
end
context 'suggested_for' do
context 'when anonymous' do
let(:topic) { Fabricate(:topic) }
let!(:new_topic) { Fabricate(:post, user: creator).topic }
it "should return the new topic" do
TopicQuery.new.list_suggested_for(topic).topics.should == [new_topic]
end
end
context 'when logged in' do
let(:topic) { Fabricate(:topic) }
let(:suggested_topics) { topic_query.list_suggested_for(topic).topics.map{|t| t.id} }
it "should return empty results when there is nothing to find" do
suggested_topics.should be_blank
end
context 'with some existing topics' do
let!(:partially_read) { Fabricate(:post, user: creator).topic }
let!(:new_topic) { Fabricate(:post, user: creator).topic }
let!(:fully_read) { Fabricate(:post, user: creator).topic }
before do
user.auto_track_topics_after_msecs = 0
user.save
TopicUser.update_last_read(user, partially_read.id, 0, 0)
TopicUser.update_last_read(user, fully_read.id, 1, 0)
end
it "won't return new or fully read if there are enough partially read topics" do
SiteSetting.stubs(:suggested_topics).returns(1)
suggested_topics.should == [partially_read.id]
end
it "won't fully read if there are enough partially read topics and new topics" do
SiteSetting.stubs(:suggested_topics).returns(2)
suggested_topics.should == [partially_read.id, new_topic.id]
end
it "returns unread, then new, then random" do
SiteSetting.stubs(:suggested_topics).returns(3)
suggested_topics.should == [partially_read.id, new_topic.id, fully_read.id]
end
end
end
end
end

View file

@ -0,0 +1,234 @@
require 'spec_helper'
require 'topic_view'
describe TopicView do
let(:topic) { Fabricate(:topic) }
let(:coding_horror) { Fabricate(:coding_horror) }
let(:first_poster) { topic.user }
let!(:p1) { Fabricate(:post, topic: topic, user: first_poster )}
let!(:p2) { Fabricate(:post, topic: topic, user: coding_horror )}
let!(:p3) { Fabricate(:post, topic: topic, user: first_poster )}
let(:topic_view) { TopicView.new(topic.id, coding_horror) }
it "raises a not found error if the topic doesn't exist" do
lambda { TopicView.new(1231232, coding_horror) }.should raise_error(Discourse::NotFound)
end
it "raises an error if the user can't see the topic" do
Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
lambda { topic_view }.should raise_error(Discourse::InvalidAccess)
end
it "raises NotLoggedIn if the user isn't logged in and is trying to view a private message" do
Topic.any_instance.expects(:private_message?).returns(true)
lambda { TopicView.new(topic.id, nil) }.should raise_error(Discourse::NotLoggedIn)
end
context '.posts_count' do
it 'returns the two posters with their counts' do
topic_view.posts_count.to_a.should =~ [[first_poster.id, 2], [coding_horror.id, 1]]
end
end
context '.participants' do
it 'returns the two participants hashed by id' do
topic_view.participants.to_a.should =~ [[first_poster.id, first_poster], [coding_horror.id, coding_horror]]
end
end
context '.all_post_actions' do
it 'is blank at first' do
topic_view.all_post_actions.should be_blank
end
it 'returns the like' do
PostAction.act(coding_horror, p1, PostActionType.Types[:like])
topic_view.all_post_actions[p1.id][PostActionType.Types[:like]].should be_present
end
end
it 'allows admins to see deleted posts' do
p3.destroy
admin = Fabricate(:admin)
topic_view = TopicView.new(topic.id, admin)
topic_view.posts.count.should == 3
end
it 'does not allow non admins to see deleted posts' do
p3.destroy
topic_view.posts.count.should == 2
end
# Sam: disabled for now, we only need this for poss, if we do, roll it into topic
# having to walk every post action is not really a good idea
#
# context '.voted_in_topic?' do
# it "is false when the user hasn't voted" do
# topic_view.voted_in_topic?.should be_false
# end
# it "is true when the user has voted for a post" do
# PostAction.act(coding_horror, p1, PostActionType.Types[:vote])
# topic_view.voted_in_topic?.should be_true
# end
# end
context '.post_action_visibility' do
it "is allows users to see likes" do
topic_view.post_action_visibility.include?(PostActionType.Types[:like]).should be_true
end
end
context '.read?' do
it 'is unread with no logged in user' do
TopicView.new(topic.id).read?(1).should be_false
end
it 'makes posts as unread by default' do
topic_view.read?(1).should be_false
end
it 'knows a post is read when it has a PostTiming' do
PostTiming.create(topic: topic, user: coding_horror, post_number: 1, msecs: 1000)
topic_view.read?(1).should be_true
end
end
context '.topic_user' do
it 'returns nil when there is no user' do
TopicView.new(topic.id, nil).topic_user.should be_blank
end
it 'returns a record once the user has some data' do
TopicView.new(topic.id, coding_horror).topic_user.should be_present
end
end
context '.posts' do
context 'near a post_number' do
context 'with a valid post_number' do
before do
topic.reload
topic_view.filter_posts_near(p2.post_number)
end
it 'returns posts around a post number' do
topic_view.posts.should == [p1, p2, p3]
end
it 'has a min of the 1st post number' do
topic_view.min.should == p1.post_number
end
it 'has a max of the 3rd post number' do
topic_view.max.should == p3.post_number
end
it 'is the inital load' do
topic_view.should be_initial_load
end
end
end
context 'before a post_number' do
before do
topic_view.filter_posts_before(p3.post_number)
end
it 'returns posts before a post number' do
topic_view.posts.should == [p2, p1]
end
it 'has a min of the 1st post number' do
topic_view.min.should == p1.post_number
end
it 'has a max of the 2nd post number' do
topic_view.max.should == p2.post_number
end
it "isn't the inital load" do
topic_view.should_not be_initial_load
end
end
context 'after a post_number' do
before do
topic_view.filter_posts_after(p1.post_number)
end
it 'returns posts after a post number' do
topic_view.posts.should == [p2, p3]
end
it 'has a min of the 1st post number' do
topic_view.min.should == p1.post_number
end
it 'has a max of 3' do
topic_view.max.should == 3
end
it "isn't the inital load" do
topic_view.should_not be_initial_load
end
end
end
context 'post range' do
context 'without gaps' do
before do
SiteSetting.stubs(:posts_per_page).returns(20)
TopicView.any_instance.stubs(:post_numbers).returns((1..50).to_a)
end
it 'returns the first a full page if the post number is 1' do
topic_view.post_range(1).should == [1, 20]
end
it 'returns 4 posts above and 16 below' do
topic_view.post_range(20).should == [15, 34]
end
it "returns 20 previous results if we ask for the last post" do
topic_view.post_range(50).should == [30, 50]
end
it "returns 20 previous results we would overlap the last post" do
topic_view.post_range(40).should == [30, 50]
end
end
context 'with gaps' do
before do
SiteSetting.stubs(:posts_per_page).returns(20)
post_numbers = ((1..20).to_a << [100, 105] << (110..150).to_a).flatten
TopicView.any_instance.stubs(:post_numbers).returns(post_numbers)
end
it "will return posts even if the post required is missing" do
topic_view.post_range(80).should == [16, 122]
end
it "works at the end of gapped post numbers" do
topic_view.post_range(140).should == [130, 150]
end
it "works well past the end of the post numbers" do
topic_view.post_range(2000).should == [130, 150]
end
end
end
end

View file

@ -0,0 +1,64 @@
require 'spec_helper'
require 'unread'
describe Unread do
before do
@topic = Fabricate(:topic, posts_count: 13, highest_post_number: 13)
@topic_user = TopicUser.get(@topic, @topic.user)
@topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::TRACKING)
@topic_user.notification_level = TopicUser::NotificationLevel::TRACKING
@unread = Unread.new(@topic, @topic_user)
end
describe 'unread_posts' do
it 'should have 0 unread posts if the user has seen all posts' do
@topic_user.stubs(:last_read_post_number).returns(13)
@topic_user.stubs(:seen_post_count).returns(13)
@unread.unread_posts.should == 0
end
it 'should have 6 unread posts if the user has seen all but 6 posts' do
@topic_user.stubs(:last_read_post_number).returns(5)
@topic_user.stubs(:seen_post_count).returns(11)
@unread.unread_posts.should == 6
end
it 'should have 0 unread posts if the user has seen more posts than exist (deleted)' do
@topic_user.stubs(:last_read_post_number).returns(100)
@topic_user.stubs(:seen_post_count).returns(13)
@unread.unread_posts.should == 0
end
end
describe 'new_posts' do
it 'should have 0 new posts if the user has read all posts' do
@topic_user.stubs(:last_read_post_number).returns(13)
@unread.new_posts.should == 0
end
it 'returns 0 when the topic is the same length as when you last saw it' do
@topic_user.stubs(:seen_post_count).returns(13)
@unread.new_posts.should == 0
end
it 'has 3 new posts if the user has read 10 posts' do
@topic_user.stubs(:seen_post_count).returns(10)
@unread.new_posts.should == 3
end
it 'has 0 new posts if the user has read 10 posts but is not tracking' do
@topic_user.stubs(:seen_post_count).returns(10)
@topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::REGULAR)
@unread.new_posts.should == 0
end
it 'has 0 new posts if the user read more posts than exist (deleted)' do
@topic_user.stubs(:seen_post_count).returns(16)
@unread.new_posts.should == 0
end
end
end

View file

@ -0,0 +1,20 @@
require 'spec_helper'
describe Admin::AdminController do
context 'index' do
it 'needs you to be logged in' do
lambda { xhr :get, :index }.should raise_error(Discourse::NotLoggedIn)
end
it "raises an error if you aren't an admin" do
user = log_in
xhr :get, :index
response.should be_forbidden
end
end
end

View file

@ -0,0 +1,37 @@
require 'spec_helper'
describe Admin::EmailLogsController do
it "is a subclass of AdminController" do
(Admin::EmailLogsController < Admin::AdminController).should be_true
end
let!(:user) { log_in(:admin) }
context '.index' do
before do
xhr :get, :index
end
subject { response }
it { should be_success }
end
context '.test' do
it 'raises an error without the email parameter' do
lambda { xhr :post, :test }.should raise_error(Discourse::InvalidParameters)
end
context 'with an email address' do
it 'enqueues a test email job' do
Jobs.expects(:enqueue).with(:test_email, to_address: 'eviltrout@test.domain')
xhr :post, :test, email_address: 'eviltrout@test.domain'
end
end
end
end

View file

@ -0,0 +1,57 @@
require 'spec_helper'
describe Admin::ExportController do
it "is a subclass of AdminController" do
(Admin::ExportController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
describe "create" do
it "should start an export job" do
Jobs::Exporter.any_instance.expects(:execute).returns(true)
xhr :post, :create
end
it "should return a job id" do
job_id = 'abc123'
Jobs.stubs(:enqueue).returns( job_id )
xhr :post, :create
json = JSON.parse(response.body)
json.should have_key('job_id')
json['job_id'].should == job_id
end
shared_examples_for "when export should not be started" do
it "should return an error" do
xhr :post, :create
json = JSON.parse(response.body)
json['failed'].should_not be_nil
json['message'].should_not be_nil
end
it "should not start an export job" do
Jobs::Exporter.any_instance.expects(:start_export).never
xhr :post, :create
end
end
context "when an export is already running" do
before do
Export.stubs(:is_export_running?).returns( true )
end
it_should_behave_like "when export should not be started"
end
context "when an import is currently running" do
before do
Import.stubs(:is_import_running?).returns( true )
end
it_should_behave_like "when export should not be started"
end
end
end
end

View file

@ -0,0 +1,37 @@
require 'spec_helper'
describe Admin::FlagsController do
it "is a subclass of AdminController" do
(Admin::FlagsController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
context 'index' do
it 'returns empty json when nothing is flagged' do
xhr :get, :index
data = ::JSON.parse(response.body)
data["users"].should == []
data["posts"].should == []
end
it 'returns a valid json payload when some thing is flagged' do
p = Fabricate(:post)
u = Fabricate(:user)
PostAction.act(u, p, PostActionType.Types[:spam])
xhr :get, :index
data = ::JSON.parse(response.body)
data["users"].length == 2
data["posts"].length == 1
end
end
end
end

View file

@ -0,0 +1,68 @@
require 'spec_helper'
describe Admin::ImpersonateController do
it "is a subclass of AdminController" do
(Admin::ImpersonateController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
let!(:admin) { log_in(:admin) }
let(:user) { Fabricate(:user) }
context 'index' do
it 'returns success' do
xhr :get, :index
response.should be_success
end
end
context 'create' do
it 'requires a username_or_email parameter' do
lambda { xhr :put, :create }.should raise_error(Discourse::InvalidParameters)
end
it 'returns 404 when that user does not exist' do
xhr :post, :create, username_or_email: 'hedonismbot'
response.status.should == 404
end
it "raises an invalid access error if the user can't be impersonated" do
Guardian.any_instance.expects(:can_impersonate?).with(user).returns(false)
xhr :post, :create, username_or_email: user.email
response.should be_forbidden
end
context 'success' do
it "changes the current user session id" do
xhr :post, :create, username_or_email: user.username
session[:current_user_id].should == user.id
end
it "returns success" do
xhr :post, :create, username_or_email: user.email
response.should be_success
end
it "also works with an email address" do
xhr :post, :create, username_or_email: user.email
session[:current_user_id].should == user.id
end
it "also works with a name" do
xhr :post, :create, username_or_email: user.name
session[:current_user_id].should == user.id
end
end
end
end
end

View file

@ -0,0 +1,42 @@
require 'spec_helper'
describe Admin::SiteCustomizationsController do
it "is a subclass of AdminController" do
(Admin::UsersController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
context ' .index' do
it 'returns success' do
xhr :get, :index
response.should be_success
end
it 'returns JSON' do
xhr :get, :index
::JSON.parse(response.body).should be_present
end
end
context ' .create' do
it 'returns success' do
xhr :post, :create, site_customization: {name: 'my test name'}
response.should be_success
end
it 'returns json' do
xhr :post, :create, site_customization: {name: 'my test name'}
::JSON.parse(response.body).should be_present
end
end
end
end

View file

@ -0,0 +1,43 @@
require 'spec_helper'
describe Admin::SiteSettingsController do
it "is a subclass of AdminController" do
(Admin::SiteSettingsController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
context 'index' do
it 'returns success' do
xhr :get, :index
response.should be_success
end
it 'returns JSON' do
xhr :get, :index
::JSON.parse(response.body).should be_present
end
end
context 'update' do
it 'requires a value parameter' do
lambda { xhr :put, :update, id: 'test_setting' }.should raise_error(Discourse::InvalidParameters)
end
it 'sets the value when the param is present' do
SiteSetting.expects(:'test_setting=').with('hello').once
xhr :put, :update, id: 'test_setting', value: 'hello'
end
end
end
end

View file

@ -0,0 +1,118 @@
require 'spec_helper'
describe Admin::UsersController do
it "is a subclass of AdminController" do
(Admin::UsersController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
context '.index' do
it 'returns success' do
xhr :get, :index
response.should be_success
end
it 'returns JSON' do
xhr :get, :index
::JSON.parse(response.body).should be_present
end
end
context '.show' do
it 'returns success' do
xhr :get, :show, id: @user.username
response.should be_success
end
end
context '.approve_bulk' do
let(:evil_trout) { Fabricate(:evil_trout) }
it "does nothing without uesrs" do
User.any_instance.expects(:approve).never
xhr :put, :approve_bulk
end
it "won't approve the user when not allowed" do
Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(false)
User.any_instance.expects(:approve).never
xhr :put, :approve_bulk, users: [evil_trout.id]
end
it "approves the user when permitted" do
Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(true)
User.any_instance.expects(:approve).once
xhr :put, :approve_bulk, users: [evil_trout.id]
end
end
context '.approve' do
let(:evil_trout) { Fabricate(:evil_trout) }
it "raises an error when the user doesn't have permission" do
Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(false)
xhr :put, :approve, user_id: evil_trout.id
response.should be_forbidden
end
it 'calls approve' do
User.any_instance.expects(:approve).with(@user)
xhr :put, :approve, user_id: evil_trout.id
end
end
context '.revoke_admin' do
before do
@another_admin = Fabricate(:another_admin)
end
it 'raises an error unless the user can revoke access' do
Guardian.any_instance.expects(:can_revoke_admin?).with(@another_admin).returns(false)
xhr :put, :revoke_admin, user_id: @another_admin.id
response.should be_forbidden
end
it 'updates the admin flag' do
xhr :put, :revoke_admin, user_id: @another_admin.id
@another_admin.reload
@another_admin.should_not be_admin
end
end
context '.grant_admin' do
before do
@another_user = Fabricate(:coding_horror)
end
it "raises an error when the user doesn't have permission" do
Guardian.any_instance.expects(:can_grant_admin?).with(@another_user).returns(false)
xhr :put, :grant_admin, user_id: @another_user.id
response.should be_forbidden
end
it "returns a 404 if the username doesn't exist" do
xhr :put, :grant_admin, user_id: 123123
response.should be_forbidden
end
it 'updates the admin flag' do
xhr :put, :grant_admin, user_id: @another_user.id
@another_user.reload
@another_user.should be_admin
end
end
end
end

View file

@ -0,0 +1,55 @@
require 'spec_helper'
require_dependency 'version'
describe Admin::VersionsController do
before do
RestClient.stubs(:get).returns( {success: 'OK', version: '1.2.33'}.to_json )
end
it "is a subclass of AdminController" do
(Admin::VersionsController < Admin::AdminController).should be_true
end
context 'while logged in as an admin' do
before do
@user = log_in(:admin)
end
describe 'show' do
context 'when discourse_org_access_key is set' do
before do
SiteSetting.stubs(:discourse_org_access_key).returns('asdf')
end
subject { xhr :get, :show }
it { should be_success }
it 'should return the currently available version' do
json = JSON.parse(subject.body)
json['latest_version'].should == '1.2.33'
end
it "should return the installed version" do
json = JSON.parse(subject.body)
json['installed_version'].should == Discourse::VERSION::STRING
end
end
context 'when discourse_org_access_key is blank' do
subject { xhr :get, :show }
it { should be_success }
it 'should return the installed version as the currently available version' do
json = JSON.parse(subject.body)
json['latest_version'].should == Discourse::VERSION::STRING
end
it "should return the installed version" do
json = JSON.parse(subject.body)
json['installed_version'].should == Discourse::VERSION::STRING
end
end
end
end
end

View file

@ -0,0 +1,147 @@
require 'spec_helper'
describe CategoriesController do
describe 'create' do
it 'requires the user to be logged in' do
lambda { xhr :post, :create }.should raise_error(Discourse::NotLoggedIn)
end
describe 'logged in' do
before do
@user = log_in(:moderator)
end
it "raises an exception when they don't have permission to create it" do
Guardian.any_instance.expects(:can_create?).with(Category, nil).returns(false)
xhr :post, :create, name: 'hello', color: '#ff0'
response.should be_forbidden
end
it 'raises an exception when the name is missing' do
lambda { xhr :post, :create, color: '#ff0' }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an exception when the color is missing' do
lambda { xhr :post, :create, name: 'hello' }.should raise_error(Discourse::InvalidParameters)
end
describe 'failure' do
before do
@category = Fabricate(:category, user: @user)
xhr :post, :create, name: @category.name, color: '#ff0'
end
it { should_not respond_with(:success) }
it 'returns errors on a duplicate category name' do
response.code.to_i.should == 422
end
end
describe 'success' do
before do
xhr :post, :create, name: 'hello', color: '#ff0'
end
it 'creates a category' do
Category.count.should == 1
end
it { should respond_with(:success) }
end
end
end
describe 'destroy' do
it 'requires the user to be logged in' do
lambda { xhr :delete, :destroy, id: 'category'}.should raise_error(Discourse::NotLoggedIn)
end
describe 'logged in' do
before do
@user = log_in
@category = Fabricate(:category, user: @user)
end
it "raises an exception if they don't have permission to delete it" do
Guardian.any_instance.expects(:can_delete_category?).returns(false)
xhr :delete, :destroy, id: @category.slug
response.should be_forbidden
end
it "deletes the record" do
Guardian.any_instance.expects(:can_delete_category?).returns(true)
lambda { xhr :delete, :destroy, id: @category.slug}.should change(Category, :count).by(-1)
end
end
end
describe 'update' do
it 'requires the user to be logged in' do
lambda { xhr :put, :update, id: 'category'}.should raise_error(Discourse::NotLoggedIn)
end
describe 'logged in' do
before do
@user = log_in(:moderator)
@category = Fabricate(:category, user: @user)
end
it "raises an exception if they don't have permission to edit it" do
Guardian.any_instance.expects(:can_edit?).returns(false)
xhr :put, :update, id: @category.slug, name: 'hello', color: '#ff0'
response.should be_forbidden
end
it "requires a name" do
lambda { xhr :put, :update, id: @category.slug, color: '#fff' }.should raise_error(Discourse::InvalidParameters)
end
it "requires a color" do
lambda { xhr :put, :update, id: @category.slug, name: 'asdf'}.should raise_error(Discourse::InvalidParameters)
end
describe 'failure' do
before do
@other_category = Fabricate(:category, name: 'Other', user: @user )
xhr :put, :update, id: @category.id, name: @other_category.name, color: '#ff0'
end
it 'returns errors on a duplicate category name' do
response.should_not be_success
end
it 'returns errors on a duplicate category name' do
response.code.to_i.should == 422
end
end
describe 'success' do
before do
xhr :put, :update, id: @category.id, name: 'science', color: '#000'
@category.reload
end
it 'updates the name' do
@category.name.should == 'science'
end
it 'updates the color' do
@category.color.should == '#000'
end
end
end
end
end

View file

@ -0,0 +1,64 @@
require 'spec_helper'
describe ClicksController do
context 'create' do
context 'missing params' do
it 'raises an error without the url param' do
lambda { xhr :get, :track, post_id: 123 }.should raise_error(Discourse::InvalidParameters)
end
it "redirects to the url even without the topic_id or post_id params" do
xhr :get, :track, url: 'http://google.com'
response.should redirect_to("http://google.com")
end
end
context 'correct params' do
before do
request.stubs(:remote_ip).returns('192.168.0.1')
end
context 'with a post_id' do
it 'calls create_from' do
TopicLinkClick.expects(:create_from).with(url: 'http://discourse.org', post_id: 123, ip: '192.168.0.1')
xhr :get, :track, url: 'http://discourse.org', post_id: 123
response.should redirect_to("http://discourse.org")
end
it 'redirects to the url' do
TopicLinkClick.stubs(:create_from)
xhr :get, :track, url: 'http://discourse.org', post_id: 123
response.should redirect_to("http://discourse.org")
end
it 'will pass the user_id to create_from' do
TopicLinkClick.expects(:create_from).with(url: 'http://discourse.org', post_id: 123, ip: '192.168.0.1')
xhr :get, :track, url: 'http://discourse.org', post_id: 123
response.should redirect_to("http://discourse.org")
end
it "doesn't redirect with the redirect=false param" do
TopicLinkClick.expects(:create_from).with(url: 'http://discourse.org', post_id: 123, ip: '192.168.0.1')
xhr :get, :track, url: 'http://discourse.org', post_id: 123, redirect: 'false'
response.should_not be_redirect
end
end
context 'with a topic_id' do
it 'calls create_from' do
TopicLinkClick.expects(:create_from).with(url: 'http://discourse.org', topic_id: 789, ip: '192.168.0.1')
xhr :get, :track, url: 'http://discourse.org', topic_id: 789
response.should redirect_to("http://discourse.org")
end
end
end
end
end

View file

@ -0,0 +1,22 @@
require 'spec_helper'
describe DraftController do
it 'requires you to be logged in' do
lambda { post :update }.should raise_error(Discourse::NotLoggedIn)
end
it 'saves a draft on update' do
user = log_in
post :update, draft_key: 'xyz', data: 'my data', sequence: 0
Draft.get(user, 'xyz', 0).should == 'my data'
end
it 'destroys drafts when required' do
user = log_in
Draft.set(user, 'xxx', 0, 'hi')
delete :destroy, draft_key: 'xxx', sequence: 0
Draft.get(user, 'xxx', 0).should be_nil
end
end

View file

@ -0,0 +1,99 @@
require 'spec_helper'
describe EmailController do
context '.preferences_redirect' do
it 'requires you to be logged in' do
lambda { get :preferences_redirect }.should raise_error(Discourse::NotLoggedIn)
end
context 'when logged in' do
let!(:user) { log_in }
it 'redirects to your user preferences' do
get :preferences_redirect
response.should redirect_to("/users/#{user.username}/preferences")
end
end
end
context '.resubscribe' do
let(:user) { Fabricate(:user, email_digests: false) }
context 'with a valid key' do
before do
get :resubscribe, key: user.temporary_key
user.reload
end
it 'subscribes the user' do
user.email_digests.should be_true
end
end
end
context '.unsubscribe' do
let(:user) { Fabricate(:user) }
context 'with a valid key' do
before do
get :unsubscribe, key: user.temporary_key
user.reload
end
it 'unsubscribes the user' do
user.email_digests.should be_false
end
it "does not set the not_found instance variable" do
assigns(:not_found).should be_blank
end
end
context 'when logged in as a different user' do
let!(:logged_in_user) { log_in(:coding_horror) }
before do
get :unsubscribe, key: user.temporary_key
user.reload
end
it 'does not unsubscribe the user' do
user.email_digests.should be_true
end
it 'sets not found' do
assigns(:not_found).should be_true
end
end
context 'when logged in as the keyed user' do
before do
log_in_user(user)
get :unsubscribe, key: user.temporary_key
user.reload
end
it 'unsubscribes the user' do
user.email_digests.should be_false
end
it "doesn't set not found" do
assigns(:not_found).should be_blank
end
end
it "sets not_found when the key didn't match anything" do
get :unsubscribe, key: 'asdfasdf'
assigns(:not_found).should be_true
end
end
end

View file

@ -0,0 +1,82 @@
require 'spec_helper'
describe ExcerptController do
describe 'show' do
it 'raises an error without the url param' do
lambda { xhr :get, :show }.should raise_error(Discourse::InvalidParameters)
end
it 'returns 404 with a non-existant url' do
xhr :get, :show, url: 'http://madeup.com/url'
response.status.should == 404
end
it 'returns 404 from an invalid url' do
xhr :get, :show, url: 'asdfasdf'
response.status.should == 404
end
describe 'user excerpt' do
before do
@user = Fabricate(:user)
@url = "http://test.host/users/#{@user.username}"
xhr :get, :show, url: @url
end
it 'returns a valid status' do
response.should be_success
end
it 'returns an excerpt type for the forum topic' do
parsed = JSON.parse(response.body)
parsed['type'].should == 'User'
end
end
describe 'forum topic excerpt' do
before do
@post = Fabricate(:post)
@url = "http://test.host#{@post.topic.relative_url}"
xhr :get, :show, url: @url
end
it 'returns a valid status' do
response.should be_success
end
it 'returns an excerpt type for the forum topic' do
parsed = JSON.parse(response.body)
parsed['type'].should == 'Post'
end
end
describe 'post excerpt' do
before do
@post = Fabricate(:post)
@url = "http://test.host#{@post.topic.relative_url}/1"
xhr :get, :show, url: @url
end
it 'returns a valid status' do
response.should be_success
end
it 'returns an excerpt type for the forum topic' do
parsed = JSON.parse(response.body)
parsed['type'].should == 'Post'
end
end
end
end

View file

@ -0,0 +1,151 @@
require 'spec_helper'
describe InvitesController do
context '.destroy' do
it 'requires you to be logged in' do
lambda {
delete :destroy, email: 'jake@adventuretime.ooo'
}.should raise_error(Discourse::NotLoggedIn)
end
context 'while logged in' do
let!(:user) { log_in }
let!(:invite) { Fabricate(:invite, invited_by: user) }
let(:another_invite) { Fabricate(:invite, email: 'anotheremail@address.com') }
it 'raises an error when the email is missing' do
lambda { delete :destroy }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when the email cannot be found" do
lambda { delete :destroy, email: 'finn@adventuretime.ooo' }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an error when the invite is not yours' do
lambda { delete :destroy, email: another_invite.email }.should raise_error(Discourse::InvalidParameters)
end
it "destroys the invite" do
Invite.any_instance.expects(:destroy)
delete :destroy, email: invite.email
end
end
end
context '.show' do
context 'with an invalid invite id' do
before do
get :show, id: "doesn't exist"
end
it "redirects to the root" do
response.should redirect_to("/")
end
it "should not change the session" do
session[:current_user_id].should be_blank
end
end
context 'with a deleted invite' do
let(:topic) { Fabricate(:topic) }
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
let(:deleted_invite) { invite.destroy; invite }
before do
get :show, id: deleted_invite.invite_key
end
it "redirects to the root" do
response.should redirect_to("/")
end
it "should not change the session" do
session[:current_user_id].should be_blank
end
end
context 'with a valid invite id' do
let(:topic) { Fabricate(:topic) }
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
it 'redeems the invite' do
Invite.any_instance.expects(:redeem)
get :show, id: invite.invite_key
end
context 'when redeem returns a user' do
let(:user) { Fabricate(:coding_horror) }
context 'success' do
before do
Invite.any_instance.expects(:redeem).returns(user)
get :show, id: invite.invite_key
end
it 'logs in the user' do
session[:current_user_id].should == user.id
end
it 'redirects to the first topic the user was invited to' do
response.should redirect_to(topic.relative_url)
end
end
context 'welcome message' do
before do
Invite.any_instance.stubs(:redeem).returns(user)
Jobs.expects(:enqueue).with(:invite_email, has_key(:invite_id))
end
it 'sends a welcome message if set' do
user.send_welcome_message = true
user.expects(:enqueue_welcome_message).with('welcome_invite')
get :show, id: invite.invite_key
end
it "doesn't send a welcome message if not set" do
user.expects(:enqueue_welcome_message).with('welcome_invite').never
get :show, id: invite.invite_key
end
end
context 'access_required' do
it "doesn't set a cookie for access if there is no access required" do
SiteSetting.expects(:restrict_access?).returns(false)
Invite.any_instance.expects(:redeem).returns(user)
get :show, id: invite.invite_key
cookies[:_access].should be_blank
end
it "sets the cookie when access is required" do
SiteSetting.expects(:restrict_access?).returns(true)
SiteSetting.expects(:access_password).returns('adventure time!')
Invite.any_instance.expects(:redeem).returns(user)
get :show, id: invite.invite_key
cookies[:_access].should == 'adventure time!'
end
end
end
end
end
end

View file

@ -0,0 +1,87 @@
require 'spec_helper'
describe ListController do
# we need some data
before do
@user = Fabricate(:coding_horror)
@post = Fabricate(:post, :user => @user)
end
context 'index' do
before do
xhr :get, :index
end
it { should respond_with(:success) }
end
context 'category' do
context 'in a category' do
let(:category) { Fabricate(:category) }
it "raises an invalid access error when the user can't see the category" do
Guardian.any_instance.expects(:can_see?).with(category).returns(false)
xhr :get, :category, category: category.slug
response.should be_forbidden
end
context 'with access to see the category' do
before do
xhr :get, :category, category: category.slug
end
it { should respond_with(:success) }
end
end
context 'uncategorized' do
it "doesn't check access to see the category, since we didn't provide one" do
Guardian.any_instance.expects(:can_see?).never
xhr :get, :category, category: SiteSetting.uncategorized_name
end
it "responds with success" do
xhr :get, :category, category: SiteSetting.uncategorized_name
response.should be_success
end
end
end
context 'favorited' do
it 'raises an error when not logged in' do
lambda { xhr :get, :favorited }.should raise_error(Discourse::NotLoggedIn)
end
context 'when logged in' do
before do
log_in_user(@user)
xhr :get, :favorited
end
it { should respond_with(:success) }
end
end
context 'read' do
it 'raises an error when not logged in' do
lambda { xhr :get, :read }.should raise_error(Discourse::NotLoggedIn)
end
context 'when logged in' do
before do
log_in_user(@user)
xhr :get, :read
end
it { should respond_with(:success) }
end
end
end

View file

@ -0,0 +1,22 @@
require 'spec_helper'
describe NotificationsController do
context 'when logged in' do
let!(:user) { log_in }
before do
xhr :get, :index
end
subject { response }
it { should be_success }
end
context 'when not logged in' do
it 'should raise an error' do
lambda { xhr :get, :index }.should raise_error(Discourse::NotLoggedIn)
end
end
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
describe OneboxController do
it 'asks the oneboxer for the preview' do
Oneboxer.expects(:preview).with('http://google.com')
xhr :get, :show, url: 'http://google.com'
end
it 'invalidates the cache if refresh is passed' do
Oneboxer.expects(:invalidate).with('http://google.com')
xhr :get, :show, url: 'http://google.com', refresh: true
end
end

View file

@ -0,0 +1,127 @@
require 'spec_helper'
describe PostActionsController do
describe 'create' do
it 'requires you to be logged in' do
lambda { xhr :post, :create }.should raise_error(Discourse::NotLoggedIn)
end
describe 'logged in' do
before do
@user = log_in
@post = Fabricate(:post, user: Fabricate(:coding_horror))
end
it 'raises an error when the id is missing' do
lambda { xhr :post, :create, post_action_type_id: PostActionType.Types[:like] }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an error when the post_action_type_id index is missing' do
lambda { xhr :post, :create, id: @post.id }.should raise_error(Discourse::InvalidParameters)
end
it "fails when the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_see?).with(@post).returns(false)
xhr :post, :create, id: @post.id, post_action_type_id: PostActionType.Types[:like]
response.should be_forbidden
end
it "fails when the user doesn't have permission to perform that action" do
Guardian.any_instance.expects(:post_can_act?).with(@post, :like).returns(false)
xhr :post, :create, id: @post.id, post_action_type_id: PostActionType.Types[:like]
response.should be_forbidden
end
it 'allows us to create an post action on a post' do
PostAction.expects(:act).once.with(@user, @post, PostActionType.Types[:like], nil)
xhr :post, :create, id: @post.id, post_action_type_id: PostActionType.Types[:like]
end
end
end
context 'destroy' do
let(:post) { Fabricate(:post, user: Fabricate(:coding_horror)) }
it 'requires you to be logged in' do
lambda { xhr :delete, :destroy, id: post.id }.should raise_error(Discourse::NotLoggedIn)
end
context 'logged in' do
let!(:user) { log_in }
it 'raises an error when the post_action_type_id is missing' do
lambda { xhr :delete, :destroy, id: post.id }.should raise_error(Discourse::InvalidParameters)
end
it "returns 404 when the post action type doesn't exist for that user" do
xhr :delete, :destroy, id: post.id, post_action_type_id: 1
response.code.should == '404'
end
context 'with a post_action record ' do
let!(:post_action) { PostAction.create(user_id: user.id, post_id: post.id, post_action_type_id: 1)}
it 'returns success' do
xhr :delete, :destroy, id: post.id, post_action_type_id: 1
response.should be_success
end
it 'deletes the action' do
xhr :delete, :destroy, id: post.id, post_action_type_id: 1
PostAction.exists?(user_id: user.id, post_id: post.id, post_action_type_id: 1, deleted_at: nil).should be_false
end
it 'ensures it can be deleted' do
Guardian.any_instance.expects(:can_delete?).with(post_action).returns(false)
xhr :delete, :destroy, id: post.id, post_action_type_id: 1
response.should be_forbidden
end
end
end
end
describe 'users' do
let!(:post) { Fabricate(:post, user: log_in) }
it 'raises an error without an id' do
lambda {
xhr :get, :users, post_action_type_id: PostActionType.Types[:like]
}.should raise_error(Discourse::InvalidParameters)
end
it 'raises an error without a post action type' do
lambda {
xhr :get, :users, id: post.id
}.should raise_error(Discourse::InvalidParameters)
end
it "fails when the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_see?).with(post).returns(false)
xhr :get, :users, id: post.id, post_action_type_id: PostActionType.Types[:like]
response.should be_forbidden
end
it 'raises an error when the post action type cannot be seen' do
Guardian.any_instance.expects(:can_see_post_actors?).with(instance_of(Topic), PostActionType.Types[:like]).returns(false)
xhr :get, :users, id: post.id, post_action_type_id: PostActionType.Types[:like]
response.should be_forbidden
end
it 'succeeds' do
xhr :get, :users, id: post.id, post_action_type_id: PostActionType.Types[:like]
response.should be_success
end
end
end

View file

@ -0,0 +1,278 @@
require 'spec_helper'
describe PostsController do
describe 'show' do
let(:post) { Fabricate(:post, user: log_in) }
it 'ensures the user can see the post' do
Guardian.any_instance.expects(:can_see?).with(post).returns(false)
xhr :get, :show, id: post.id
response.should be_forbidden
end
it 'suceeds' do
xhr :get, :show, id: post.id
response.should be_success
end
end
describe 'versions' do
it 'raises an exception when not logged in' do
lambda { xhr :get, :versions, post_id: 123 }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let(:post) { Fabricate(:post, user: log_in) }
it "raises an error if the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_see?).with(post).returns(false)
xhr :get, :versions, post_id: post.id
response.should be_forbidden
end
it 'renders JSON' do
xhr :get, :versions, post_id: post.id
::JSON.parse(response.body).should be_present
end
end
end
describe 'delete a post' do
it 'raises an exception when not logged in' do
lambda { xhr :delete, :destroy, id: 123 }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let(:post) { Fabricate(:post, user: log_in(:moderator), post_number: 2) }
it "raises an error when the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_delete?).with(post).returns(false)
xhr :delete, :destroy, id: post.id
response.should be_forbidden
end
it "deletes the post" do
Post.any_instance.expects(:destroy)
xhr :delete, :destroy, id: post.id
end
it "updates the highest read data for the forum" do
Topic.expects(:reset_highest).with(post.topic_id)
xhr :delete, :destroy, id: post.id
end
end
end
describe 'destroy_many' do
it 'raises an exception when not logged in' do
lambda { xhr :delete, :destroy_many, post_ids: [123, 345] }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let!(:poster) { log_in(:moderator) }
let!(:post1) { Fabricate(:post, user: poster, post_number: 2) }
let!(:post2) { Fabricate(:post, topic_id: post1.topic_id, user: poster, post_number: 3) }
it "raises invalid parameters no post_ids" do
lambda { xhr :delete, :destroy_many }.should raise_error(Discourse::InvalidParameters)
end
it "raises invalid parameters with missing ids" do
lambda { xhr :delete, :destroy_many, post_ids: [12345] }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when the user doesn't have permission to delete the posts" do
Guardian.any_instance.expects(:can_delete?).with(instance_of(Post)).returns(false)
xhr :delete, :destroy_many, post_ids: [post1.id, post2.id]
response.should be_forbidden
end
it "deletes the post" do
Post.any_instance.expects(:destroy).twice
xhr :delete, :destroy_many, post_ids: [post1.id, post2.id]
end
it "updates the highest read data for the forum" do
Topic.expects(:reset_highest)
xhr :delete, :destroy_many, post_ids: [post1.id, post2.id]
end
end
end
describe 'edit a post' do
it 'raises an exception when not logged in' do
lambda { xhr :put, :update, id: 2 }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let(:post) { Fabricate(:post, user: log_in) }
let(:update_params) do
{id: post.id,
post: {raw: 'edited body'},
image_sizes: {'http://image.com/image.jpg' => {'width' => 123, 'height' => 456}}}
end
it 'passes the image sizes through' do
Post.any_instance.expects(:image_sizes=)
xhr :put, :update, update_params
end
it "raises an error when the post parameter is missing" do
update_params.delete(:post)
lambda {
xhr :put, :update, update_params
}.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_edit?).with(post).returns(false)
xhr :put, :update, update_params
response.should be_forbidden
end
it "calls revise with valid parameters" do
Post.any_instance.expects(:revise).with(post.user, 'edited body')
xhr :put, :update, update_params
end
it "extracts links from the new body" do
TopicLink.expects(:extract_from).with(post)
xhr :put, :update, update_params
end
end
end
describe 'bookmark a post' do
it 'raises an exception when not logged in' do
lambda { xhr :put, :bookmark, post_id: 2 }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let(:post) { Fabricate(:post, user: log_in) }
it "raises an error if the user doesn't have permission to see the post" do
Guardian.any_instance.expects(:can_see?).with(post).returns(false)
xhr :put, :bookmark, post_id: post.id, bookmarked: 'true'
response.should be_forbidden
end
it 'creates a bookmark' do
PostAction.expects(:act).with(post.user, post, PostActionType.Types[:bookmark])
xhr :put, :bookmark, post_id: post.id, bookmarked: 'true'
end
it 'removes a bookmark' do
PostAction.expects(:remove_act).with(post.user, post, PostActionType.Types[:bookmark])
xhr :put, :bookmark, post_id: post.id
end
end
end
describe 'creating a post' do
it 'raises an exception when not logged in' do
lambda { xhr :post, :create }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let!(:user) { log_in }
let(:new_post) { Fabricate.build(:post, user: user) }
it "raises an exception without a post parameter" do
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
end
it 'calls the post creator' do
PostCreator.any_instance.expects(:create).returns(new_post)
xhr :post, :create, post: {raw: 'test'}
response.should be_success
end
it 'returns JSON of the post' do
PostCreator.any_instance.expects(:create).returns(new_post)
xhr :post, :create, post: {raw: 'test'}
::JSON.parse(response.body).should be_present
end
context "parameters" do
let(:post_creator) { mock }
before do
post_creator.expects(:create).returns(new_post)
post_creator.stubs(:errors).returns(nil)
end
it "passes raw through" do
PostCreator.expects(:new).with(user, has_entries(raw: 'hello')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}
end
it "passes title through" do
PostCreator.expects(:new).with(user, has_entries(title: 'new topic title')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}, title: 'new topic title'
end
it "passes topic_id through" do
PostCreator.expects(:new).with(user, has_entries(topic_id: '1234')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello', topic_id: 1234}
end
it "passes archetype through" do
PostCreator.expects(:new).with(user, has_entries(archetype: 'private_message')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}, archetype: 'private_message'
end
it "passes category through" do
PostCreator.expects(:new).with(user, has_entries(category: 'cool')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello', category: 'cool'}
end
it "passes target_usernames through" do
PostCreator.expects(:new).with(user, has_entries(target_usernames: 'evil,trout')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}, target_usernames: 'evil,trout'
end
it "passes reply_to_post_number through" do
PostCreator.expects(:new).with(user, has_entries(reply_to_post_number: '6789')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello', reply_to_post_number: 6789}
end
it "passes image_sizes through" do
PostCreator.expects(:new).with(user, has_entries(image_sizes: 'test')).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}, image_sizes: 'test'
end
it "passes meta_data through" do
PostCreator.expects(:new).with(user, has_entries(meta_data: {'xyz' => 'abc'})).returns(post_creator)
xhr :post, :create, post: {raw: 'hello'}, meta_data: {xyz: 'abc'}
end
end
end
end
end

View file

@ -0,0 +1,56 @@
require 'spec_helper'
describe RequestAccessController do
context '.new' do
it "sets a default return path" do
get :new
assigns(:return_path).should == "/"
end
it "assigns the return path we provide" do
get :new, return_path: '/asdf'
assigns(:return_path).should == "/asdf"
end
end
context '.create' do
context 'without an invalid password' do
before do
post :create, password: 'asdf'
end
it "adds a flash" do
flash[:error].should be_present
end
it "doesn't set the cookie" do
cookies[:_access].should be_blank
end
end
context 'with a valid password' do
before do
SiteSetting.stubs(:access_password).returns 'test password'
post :create, password: 'test password', return_path: '/the-path'
end
it 'creates the cookie' do
cookies[:_access].should == 'test password'
end
it 'redirects to the return path' do
response.should redirect_to('/the-path')
end
it 'sets no flash error' do
flash[:error].should be_blank
end
end
end
end

View file

@ -0,0 +1,16 @@
require 'spec_helper'
describe SearchController do
it 'performs the query' do
Search.expects(:query).with('test', nil)
xhr :get, :query, term: 'test'
end
it 'performs the query with a filter' do
Search.expects(:query).with('test', 'topic')
xhr :get, :query, term: 'test', type_filter: 'topic'
end
end

View file

@ -0,0 +1,130 @@
require 'spec_helper'
describe SessionController do
describe '.create' do
let(:user) { Fabricate(:user) }
it "raises an error when the login isn't present" do
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
end
describe 'invalid password' do
it "should return an error with an invalid password" do
xhr :post, :create, login: user.username, password: 'sssss'
::JSON.parse(response.body)['error'].should be_present
end
end
describe 'success by username' do
before do
xhr :post, :create, login: user.username, password: 'myawesomepassword'
user.reload
end
it 'sets a session id' do
session[:current_user_id].should == user.id
end
it 'gives the user an auth token' do
user.auth_token.should be_present
end
it 'sets a cookie with the auth token' do
cookies[:_t].should == user.auth_token
end
end
describe 'strips leading @ symbol' do
before do
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
user.reload
end
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
describe 'also allow login by email' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
end
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
describe "when the site requires approval of users" do
before do
SiteSetting.expects(:must_approve_users?).returns(true)
end
context 'with an unapproved user' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
end
end
end
end
describe '.destroy' do
before do
@user = log_in
xhr :delete, :destroy, id: @user.username
end
it 'removes the session variable' do
session[:current_user_id].should be_blank
end
it 'removes the auth token cookie' do
cookies[:_t].should be_blank
end
end
describe '.forgot_password' do
it 'raises an error without a username parameter' do
lambda { xhr :post, :forgot_password }.should raise_error(Discourse::InvalidParameters)
end
context 'for a non existant username' do
it "doesn't generate a new token for a made up username" do
lambda { xhr :post, :forgot_password, username: 'made_up'}.should_not change(EmailToken, :count)
end
it "doesn't enqueue an email" do
Jobs.expects(:enqueue).with(:user_mail, anything).never
xhr :post, :forgot_password, username: 'made_up'
end
end
context 'for an existing username' do
let(:user) { Fabricate(:user) }
it "generates a new token for a made up username" do
lambda { xhr :post, :forgot_password, username: user.username}.should change(EmailToken, :count)
end
it "enqueues an email" do
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :forgot_password, user_id: user.id))
xhr :post, :forgot_password, username: user.username
end
end
end
end

View file

@ -0,0 +1,27 @@
require 'spec_helper'
describe StaticController do
context "with a static file that's present" do
before do
xhr :get, :show, :id => 'faq'
end
it 'renders the static file if present' do
response.should be_success
end
it "renders the file" do
response.should render_template('faq')
end
end
context "with a missing file" do
it "should respond 404" do
xhr :get, :show, :id => 'does-not-exist'
response.response_code.should == 404
end
end
end

View file

@ -0,0 +1,419 @@
require 'spec_helper'
describe TopicsController do
context 'move_posts' do
it 'needs you to be logged in' do
lambda { xhr :post, :move_posts, topic_id: 111, title: 'blah', post_ids: [1,2,3] }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
let!(:user) { log_in(:moderator) }
let(:p1) { Fabricate(:post, user: user) }
let(:topic) { p1.topic }
it "raises an error without a title" do
lambda { xhr :post, :move_posts, topic_id: topic.id, post_ids: [1,2,3] }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error without postIds" do
lambda { xhr :post, :move_posts, topic_id: topic.id, title: 'blah' }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error when the user doesn't have permission to move the posts" do
Guardian.any_instance.expects(:can_move_posts?).returns(false)
xhr :post, :move_posts, topic_id: topic.id, title: 'blah', post_ids: [1,2,3]
response.should be_forbidden
end
context 'success' do
let(:p2) { Fabricate(:post, user: user) }
before do
Topic.any_instance.expects(:move_posts).with(user, 'blah', [p2.id]).returns(topic)
xhr :post, :move_posts, topic_id: topic.id, title: 'blah', post_ids: [p2.id]
end
it "returns success" do
response.should be_success
end
it "has a JSON response" do
::JSON.parse(response.body)['success'].should be_true
end
it "has a url" do
::JSON.parse(response.body)['url'].should be_present
end
end
context 'failure' do
let(:p2) { Fabricate(:post, user: user) }
before do
Topic.any_instance.expects(:move_posts).with(user, 'blah', [p2.id]).returns(nil)
xhr :post, :move_posts, topic_id: topic.id, title: 'blah', post_ids: [p2.id]
end
it "returns success" do
response.should be_success
end
it "has success in the JSON" do
::JSON.parse(response.body)['success'].should be_false
end
it "has a url" do
::JSON.parse(response.body)['url'].should be_blank
end
end
end
end
context 'status' do
it 'needs you to be logged in' do
lambda { xhr :put, :status, topic_id: 1, status: 'visible', enabled: true }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@user = log_in(:moderator)
@topic = Fabricate(:topic, user: @user)
end
it "raises an exception if you can't change it" do
Guardian.any_instance.expects(:can_moderate?).with(@topic).returns(false)
xhr :put, :status, topic_id: @topic.id, status: 'visible', enabled: 'true'
response.should be_forbidden
end
it 'requires the status parameter' do
lambda { xhr :put, :status, topic_id: @topic.id, enabled: true }.should raise_error(Discourse::InvalidParameters)
end
it 'requires the enabled parameter' do
lambda { xhr :put, :status, topic_id: @topic.id, status: 'visible' }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an error with a status not in the whitelist' do
lambda { xhr :put, :status, topic_id: @topic.id, status: 'title', enabled: 'true' }.should raise_error(Discourse::InvalidParameters)
end
it 'calls update_status on the forum topic with false' do
Topic.any_instance.expects(:update_status).with('closed', false, @user)
xhr :put, :status, topic_id: @topic.id, status: 'closed', enabled: 'false'
end
it 'calls update_status on the forum topic with true' do
Topic.any_instance.expects(:update_status).with('closed', true, @user)
xhr :put, :status, topic_id: @topic.id, status: 'closed', enabled: 'true'
end
end
end
context 'delete_timings' do
it 'needs you to be logged in' do
lambda { xhr :delete, :destroy_timings, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
end
context 'when logged in' do
before do
@user = log_in
@topic = Fabricate(:topic, user: @user)
@topic_user = TopicUser.get(@topic, @topic.user)
end
it 'deletes the forum topic user record' do
PostTiming.expects(:destroy_for).with(@user.id, @topic.id)
xhr :delete, :destroy_timings, topic_id: @topic.id
end
end
end
describe 'mute/unmute' do
it 'needs you to be logged in' do
lambda { xhr :put, :mute, topic_id: 99}.should raise_error(Discourse::NotLoggedIn)
end
it 'needs you to be logged in' do
lambda { xhr :put, :unmute, topic_id: 99}.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@topic = Fabricate(:topic, user: log_in)
end
it "changes the user's starred flag when the parameter is present" do
Topic.any_instance.expects(:toggle_mute).with(@topic.user, true)
xhr :put, :mute, topic_id: @topic.id, starred: 'true'
end
it "removes the user's starred flag when the parameter is not true" do
Topic.any_instance.expects(:toggle_mute).with(@topic.user, false)
xhr :put, :unmute, topic_id: @topic.id, starred: 'false'
end
end
end
describe 'star' do
it 'needs you to be logged in' do
lambda { xhr :put, :star, topic_id: 1, starred: true }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@topic = Fabricate(:topic, user: log_in)
end
it "ensures the user can see the topic" do
Guardian.any_instance.expects(:can_see?).with(@topic).returns(false)
xhr :put, :star, topic_id: @topic.id, starred: 'true'
response.should be_forbidden
end
it "changes the user's starred flag when the parameter is present" do
Topic.any_instance.expects(:toggle_star).with(@topic.user, true)
xhr :put, :star, topic_id: @topic.id, starred: 'true'
end
it "removes the user's starred flag when the parameter is not true" do
Topic.any_instance.expects(:toggle_star).with(@topic.user, false)
xhr :put, :star, topic_id: @topic.id, starred: 'false'
end
end
end
describe 'delete' do
it "won't allow us to delete a topic when we're not logged in" do
lambda { xhr :delete, :destroy, id: 1 }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@topic = Fabricate(:topic, user: log_in)
end
describe 'without access' do
it "raises an exception when the user doesn't have permission to delete the topic" do
Guardian.any_instance.expects(:can_delete?).with(@topic).returns(false)
xhr :delete, :destroy, id: @topic.id
response.should be_forbidden
end
end
describe 'with permission' do
before do
Guardian.any_instance.expects(:can_delete?).with(@topic).returns(true)
end
it 'succeeds' do
xhr :delete, :destroy, id: @topic.id
response.should be_success
end
it 'deletes the topic' do
xhr :delete, :destroy, id: @topic.id
Topic.exists?(id: @topic_id).should be_false
end
end
end
end
describe 'show' do
let(:topic) { Fabricate(:post).topic }
let!(:p1) { Fabricate(:post, user: topic.user) }
let!(:p2) { Fabricate(:post, user: topic.user) }
it 'shows a topic correctly' do
xhr :get, :show, id: topic.id
response.should be_success
end
it 'records a view' do
lambda { xhr :get, :show, id: topic.id }.should change(View, :count).by(1)
end
it 'tracks a visit for all html requests' do
current_user = log_in(:coding_horror)
TopicUser.expects(:track_visit!).with(topic, current_user)
get :show, id: topic.id
end
context 'consider for a promotion' do
let!(:user) { log_in(:coding_horror) }
let(:promotion) do
result = mock
Promotion.stubs(:new).with(user).returns(result)
result
end
it "reviews the user for a promotion if they're new" do
user.update_column(:trust_level, TrustLevel.Levels[:new])
promotion.expects(:review)
get :show, id: topic.id
end
it "doesn't reviews the user for a promotion if they're basic" do
promotion.expects(:review).never
get :show, id: topic.id
end
end
context 'filters' do
it 'grabs first page when no post number is selected' do
TopicView.any_instance.expects(:filter_posts_paged).with(0)
xhr :get, :show, id: topic.id
end
it 'delegates a post_number param to TopicView#filter_posts_near' do
TopicView.any_instance.expects(:filter_posts_near).with(p2.post_number)
xhr :get, :show, id: topic.id, post_number: p2.post_number
end
it 'delegates a posts_after param to TopicView#filter_posts_after' do
TopicView.any_instance.expects(:filter_posts_after).with(p1.post_number)
xhr :get, :show, id: topic.id, posts_after: p1.post_number
end
it 'delegates a posts_before param to TopicView#filter_posts_before' do
TopicView.any_instance.expects(:filter_posts_before).with(p2.post_number)
xhr :get, :show, id: topic.id, posts_before: p2.post_number
end
end
end
describe 'update' do
it "won't allow us to update a topic when we're not logged in" do
lambda { xhr :put, :update, topic_id: 1, slug: 'xyz' }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@topic = Fabricate(:topic, user: log_in)
end
describe 'without permission' do
it "raises an exception when the user doesn't have permission to update the topic" do
Guardian.any_instance.expects(:can_edit?).with(@topic).returns(false)
xhr :put, :update, topic_id: @topic.id, slug: @topic.title
response.should be_forbidden
end
end
describe 'with permission' do
before do
Guardian.any_instance.expects(:can_edit?).with(@topic).returns(true)
end
it 'succeeds' do
xhr :put, :update, topic_id: @topic.id, slug: @topic.title
response.should be_success
end
it 'allows a change of title' do
xhr :put, :update, topic_id: @topic.id, slug: @topic.title, title: 'new title'
@topic.reload
@topic.title.should == 'new title'
end
it 'triggers a change of category' do
Topic.any_instance.expects(:change_category).with('incredible')
xhr :put, :update, topic_id: @topic.id, slug: @topic.title, category: 'incredible'
end
end
end
end
describe 'invite' do
it "won't allow us to invite toa topic when we're not logged in" do
lambda { xhr :post, :invite, topic_id: 1, email: 'jake@adventuretime.ooo' }.should raise_error(Discourse::NotLoggedIn)
end
describe 'when logged in' do
before do
@topic = Fabricate(:topic, user: log_in)
end
it 'requires an email parameter' do
lambda { xhr :post, :invite, topic_id: @topic.id }.should raise_error(Discourse::InvalidParameters)
end
describe 'without permission' do
it "raises an exception when the user doesn't have permission to invite to the topic" do
Guardian.any_instance.expects(:can_invite_to?).with(@topic).returns(false)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
response.should be_forbidden
end
end
describe 'with permission' do
before do
Guardian.any_instance.expects(:can_invite_to?).with(@topic).returns(true)
end
context 'when it returns an invite' do
before do
Topic.any_instance.expects(:invite_by_email).with(@topic.user, 'jake@adventuretime.ooo').returns(Invite.new)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
end
it 'should succeed' do
response.should be_success
end
it 'returns success JSON' do
::JSON.parse(response.body).should == {'success' => 'OK'}
end
end
context 'when it fails and returns nil' do
before do
Topic.any_instance.expects(:invite_by_email).with(@topic.user, 'jake@adventuretime.ooo').returns(nil)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
end
it 'should succeed' do
response.should_not be_success
end
it 'returns success JSON' do
::JSON.parse(response.body).should == {'failed' => 'FAILED'}
end
end
end
end
end
end

View file

@ -0,0 +1,635 @@
require 'spec_helper'
describe UsersController do
describe '.show' do
let!(:user) { log_in }
it 'returns success' do
xhr :get, :show, username: user.username
response.should be_success
end
it "returns not found when the username doesn't exist" do
xhr :get, :show, username: 'madeuppity'
response.should_not be_success
end
it "raises an error on invalid access" do
Guardian.any_instance.expects(:can_see?).with(user).returns(false)
xhr :get, :show, username: user.username
response.should be_forbidden
end
end
describe '.user_preferences_redirect' do
it 'requires the user to be logged in' do
lambda { get :user_preferences_redirect }.should raise_error(Discourse::NotLoggedIn)
end
it "redirects to their profile when logged in" do
user = log_in
get :user_preferences_redirect
response.should redirect_to("/users/#{user.username_lower}/preferences")
end
end
describe '.authorize_email' do
context 'invalid token' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(nil)
get :authorize_email, token: 'asdfasdf'
end
it 'return success' do
response.should be_success
end
it 'sets a flash error' do
flash[:error].should be_present
end
end
context 'valid token' do
let(:user) { Fabricate(:user) }
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
get :authorize_email, token: 'asdfasdf'
end
it 'returns success' do
response.should be_success
end
it "doesn't set an error" do
flash[:error].should be_blank
end
it 'logs in as the user' do
session[:current_user_id].should be_present
end
end
end
describe '.activate_account' do
context 'invalid token' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(nil)
get :activate_account, token: 'asdfasdf'
end
it 'return success' do
response.should be_success
end
it 'sets a flash error' do
flash[:error].should be_present
end
end
context 'valid token' do
let(:user) { Fabricate(:user) }
context 'welcome message' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
end
it 'enqueues a welcome message if the user object indicates so' do
user.send_welcome_message = true
user.expects(:enqueue_welcome_message).with('welcome_user')
get :activate_account, token: 'asdfasdf'
end
it "doesn't enqueue the welcome message if the object returns false" do
user.send_welcome_message = false
user.expects(:enqueue_welcome_message).with('welcome_user').never
get :activate_account, token: 'asdfasdf'
end
end
context 'reponse' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
get :activate_account, token: 'asdfasdf'
end
it 'returns success' do
response.should be_success
end
it "doesn't set an error" do
flash[:error].should be_blank
end
it 'logs in as the user' do
session[:current_user_id].should be_present
end
it "doesn't set @needs_approval" do
assigns[:needs_approval].should be_blank
end
end
context 'must_approve_users' do
before do
SiteSetting.expects(:must_approve_users?).returns(true)
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
get :activate_account, token: 'asdfasdf'
end
it 'returns success' do
response.should be_success
end
it 'sets @needs_approval' do
assigns[:needs_approval].should be_present
end
it "doesn't set an error" do
flash[:error].should be_blank
end
it "doesn't log the user in" do
session[:current_user_id].should be_blank
end
end
end
end
describe '.change_email' do
let(:new_email) { 'bubblegum@adventuretime.ooo' }
it "requires you to be logged in" do
lambda { xhr :put, :change_email, username: 'asdf', email: new_email }.should raise_error(Discourse::NotLoggedIn)
end
context 'when logged in' do
let!(:user) { log_in }
it 'raises an error without an email parameter' do
lambda { xhr :put, :change_email, username: user.username }.should raise_error(Discourse::InvalidParameters)
end
it "raises an error if you can't edit the user" do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
xhr :put, :change_email, username: user.username, email: new_email
response.should be_forbidden
end
context 'when the new email address is taken' do
let!(:other_user) { Fabricate(:coding_horror) }
it 'raises an error' do
lambda { xhr :put, :change_email, username: user.username, email: other_user.email }.should raise_error(Discourse::InvalidParameters)
end
end
context 'success' do
it 'has an email token' do
lambda { xhr :put, :change_email, username: user.username, email: new_email }.should change(EmailToken, :count)
end
it 'enqueues an email authorization' do
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :authorize_email, user_id: user.id, to_address: new_email))
xhr :put, :change_email, username: user.username, email: new_email
end
end
end
end
describe '.password_reset' do
let(:user) { Fabricate(:user) }
context 'invalid token' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(nil)
get :password_reset, token: 'asdfasdf'
end
it 'return success' do
response.should be_success
end
it 'sets a flash error' do
flash[:error].should be_present
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
end
end
context 'valid token' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
get :password_reset, token: 'asdfasdf'
end
it 'returns success' do
response.should be_success
end
it "doesn't set an error" do
flash[:error].should be_blank
end
end
context 'submit change' do
before do
EmailToken.expects(:confirm).with('asdfasdf').returns(user)
end
it "logs in the user" do
put :password_reset, token: 'asdfasdf', password: 'newpassword'
session[:current_user_id].should be_present
end
it "doesn't log in the user when not approved" do
SiteSetting.expects(:must_approve_users?).returns(true)
put :password_reset, token: 'asdfasdf', password: 'newpassword'
session[:current_user_id].should be_blank
end
end
end
describe '.create' do
before do
@user = Fabricate.build(:user)
@user.password = "strongpassword"
Mothership.stubs(:register_nickname).returns([true, nil])
end
context 'when creating a non active user (unconfirmed email)' do
it 'should enqueue a signup email' do
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :signup))
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
end
it "doesn't send a welcome email" do
User.any_instance.expects(:enqueue_welcome_message).with('welcome_user').never
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
end
end
context 'when creating an active user (confirmed email)' do
before do
User.any_instance.stubs(:active?).returns(true)
end
it 'should enqueue a signup email' do
User.any_instance.expects(:enqueue_welcome_message).with('welcome_user')
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
end
it "should be logged in" do
User.any_instance.expects(:enqueue_welcome_message)
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
session[:current_user_id].should be_present
end
it "returns true in the active part of the JSON" do
User.any_instance.expects(:enqueue_welcome_message)
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
::JSON.parse(response.body)['active'].should == true
end
context 'when approving of users is required' do
before do
SiteSetting.expects(:must_approve_users).returns(true)
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
end
it "doesn't return active in the JSON" do
::JSON.parse(response.body)['active'].should == false
end
end
end
context 'after success' do
before do
xhr :post, :create, :name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email
end
it 'should succeed' do
should respond_with(:success)
end
it 'has the proper JSON' do
json = JSON::parse(response.body)
json["success"].should be_true
end
it 'should not result in an active account' do
User.where(username: @user.username).first.active.should be_false
end
end
end
context '.username' do
it 'raises an error when not logged in' do
lambda { xhr :put, :username, username: 'somename' }.should raise_error(Discourse::NotLoggedIn)
end
context 'while logged in' do
let!(:user) { log_in }
let(:new_username) { "#{user.username}1234" }
it 'raises an error without a new_username param' do
lambda { xhr :put, :username, username: user.username }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an error when you don\'t have permission to change the user' do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
xhr :put, :username, username: user.username, new_username: new_username
response.should be_forbidden
end
it 'raises an error when change_username fails' do
User.any_instance.expects(:change_username).with(new_username).returns(false)
lambda { xhr :put, :username, username: user.username, new_username: new_username }.should raise_error(Discourse::InvalidParameters)
end
it 'should succeed when the change_username returns true' do
User.any_instance.expects(:change_username).with(new_username).returns(true)
xhr :put, :username, username: user.username, new_username: new_username
response.should be_success
end
end
end
context '.check_username' do
before do
Mothership.stubs(:nickname_available?).returns([true, nil])
end
it 'raises an error without a username parameter' do
lambda { xhr :get, :check_username }.should raise_error(Discourse::InvalidParameters)
end
shared_examples_for 'when username is unavailable locally' do
it 'should return success' do
response.should be_success
end
it 'should return available as false in the JSON' do
::JSON.parse(response.body)['available'].should be_false
end
it 'should return a suggested username' do
::JSON.parse(response.body)['suggestion'].should be_present
end
end
shared_examples_for 'when username is available everywhere' do
it 'should return success' do
response.should be_success
end
it 'should return available in the JSON' do
::JSON.parse(response.body)['available'].should be_true
end
end
context 'when call_mothership is disabled' do
before do
SiteSetting.stubs(:call_mothership?).returns(false)
Mothership.expects(:nickname_available?).never
Mothership.expects(:nickname_match?).never
end
context 'available everywhere' do
before do
xhr :get, :check_username, username: 'BruceWayne'
end
it_should_behave_like 'when username is available everywhere'
end
context 'available locally but not globally' do
before do
xhr :get, :check_username, username: 'BruceWayne'
end
it_should_behave_like 'when username is available everywhere'
end
context 'unavailable locally but available globally' do
let!(:user) { Fabricate(:user) }
before do
xhr :get, :check_username, username: user.username
end
it_should_behave_like 'when username is unavailable locally'
end
context 'unavailable everywhere' do
let!(:user) { Fabricate(:user) }
before do
xhr :get, :check_username, username: user.username
end
it_should_behave_like 'when username is unavailable locally'
end
end
context 'when call_mothership is enabled' do
before do
SiteSetting.stubs(:call_mothership?).returns(true)
end
context 'available locally and globally' do
before do
Mothership.stubs(:nickname_available?).returns([true, nil])
Mothership.stubs(:nickname_match?).returns([false, true, nil]) # match = false, available = true, suggestion = nil
end
shared_examples_for 'check_username when nickname is available everywhere' do
it 'should return success' do
response.should be_success
end
it 'should return available in the JSON' do
::JSON.parse(response.body)['available'].should be_true
end
it 'should return global_match false in the JSON' do
::JSON.parse(response.body)['global_match'].should be_false
end
end
context 'and email is not given' do
before do
xhr :get, :check_username, username: 'BruceWayne'
end
it_should_behave_like 'check_username when nickname is available everywhere'
end
context 'and email is given' do
before do
xhr :get, :check_username, username: 'BruceWayne', email: 'brucie@gmail.com'
end
it_should_behave_like 'check_username when nickname is available everywhere'
end
end
shared_examples_for 'when email is needed to check nickname match' do
it 'should return success' do
response.should be_success
end
it 'should return available as false in the JSON' do
::JSON.parse(response.body)['available'].should be_false
end
it 'should not return a suggested username' do
::JSON.parse(response.body)['suggestion'].should_not be_present
end
end
context 'available locally but not globally' do
before do
Mothership.stubs(:nickname_available?).returns([false, 'suggestion'])
end
context 'email param is not given' do
before do
xhr :get, :check_username, username: 'BruceWayne'
end
it_should_behave_like 'when email is needed to check nickname match'
end
context 'email param is an empty string' do
before do
xhr :get, :check_username, username: 'BruceWayne', email: ''
end
it_should_behave_like 'when email is needed to check nickname match'
end
context 'email matches global nickname' do
before do
Mothership.stubs(:nickname_match?).returns([true, false, nil])
xhr :get, :check_username, username: 'BruceWayne', email: 'brucie@example.com'
end
it_should_behave_like 'when username is available everywhere'
it 'should indicate a global match' do
::JSON.parse(response.body)['global_match'].should be_true
end
end
context 'email does not match global nickname' do
before do
Mothership.stubs(:nickname_match?).returns([false, false, 'suggestion'])
xhr :get, :check_username, username: 'BruceWayne', email: 'brucie@example.com'
end
it_should_behave_like 'when username is unavailable locally'
it 'should not indicate a global match' do
::JSON.parse(response.body)['global_match'].should be_false
end
end
end
context 'unavailable locally and globally' do
let!(:user) { Fabricate(:user) }
before do
Mothership.stubs(:nickname_available?).returns([false, 'suggestion'])
xhr :get, :check_username, username: user.username
end
it_should_behave_like 'when username is unavailable locally'
end
context 'unavailable locally and available globally' do
let!(:user) { Fabricate(:user) }
before do
Mothership.stubs(:nickname_available?).returns([true, nil])
xhr :get, :check_username, username: user.username
end
it_should_behave_like 'when username is unavailable locally'
end
end
context 'when discourse_org_access_key is wrong' do
before do
SiteSetting.stubs(:call_mothership?).returns(true)
Mothership.stubs(:nickname_available?).raises(RestClient::Forbidden)
Mothership.stubs(:nickname_match?).raises(RestClient::Forbidden)
end
it 'should return an error message' do
xhr :get, :check_username, username: 'horsie'
json = JSON.parse(response.body)
json['errors'].should_not be_nil
json['errors'][0].should_not be_nil
end
end
end
describe '.invited' do
let(:user) { Fabricate(:user) }
it 'returns success' do
xhr :get, :invited, username: user.username
response.should be_success
end
end
describe '.update' do
context 'not logged in' do
it 'raises an error when not logged in' do
lambda { xhr :put, :update, username: 'somename' }.should raise_error(Discourse::NotLoggedIn)
end
end
context 'logged in' do
let!(:user) { log_in }
context 'without a token' do
it 'should ensure you can update the user' do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
put :update, username: user.username
response.should be_forbidden
end
context 'as a user who can edit the user' do
before do
put :update, username: user.username, bio_raw: 'brand new bio'
user.reload
end
it 'updates the user' do
user.bio_raw.should == 'brand new bio'
end
it 'returns json success' do
response.should be_success
end
end
end
end
end
end

View file

@ -0,0 +1,4 @@
Fabricator(:category) do
name 'Amazing Category'
user
end

View file

@ -0,0 +1,9 @@
Fabricator(:incoming_link) do
url 'http://localhost:3000/t/pinball/76/6'
referer 'https://twitter.com/evil_trout'
end
Fabricator(:incoming_link_not_topic, from: :incoming_link) do
url 'http://localhost:3000/made-up-url'
referer 'https://twitter.com/evil_trout'
end

View file

@ -0,0 +1,4 @@
Fabricator(:invite) do
invited_by(fabricator: :user)
email 'iceking@ADVENTURETIME.ooo'
end

View file

@ -0,0 +1,18 @@
Fabricator(:notification) do
notification_type Notification.Types[:mentioned]
data '{"poison":"ivy","killer":"croc"}'
user
topic {|attrs| Fabricate(:topic, user: attrs[:user] ) }
end
Fabricator(:quote_notification, from: :notification) do
notification_type Notification.Types[:quoted]
user
topic {|attrs| Fabricate(:topic, user: attrs[:user] ) }
end
Fabricator(:private_message_notification, from: :notification) do
notification_type Notification.Types[:private_message]
user
topic {|attrs| Fabricate(:topic, user: attrs[:user] ) }
end

View file

@ -0,0 +1,74 @@
Fabricator(:post) do
user
topic {|attrs| Fabricate(:topic, user: attrs[:user] ) }
raw "Hello world"
end
Fabricator(:post_with_youtube, from: :post) do
cooked '<a href="http://www.youtube.com/watch?v=9bZkp7q19f0" class="onebox" target="_blank">http://www.youtube.com/watch?v=9bZkp7q19f0</a>'
end
Fabricator(:old_post, from: :post) do
topic {|attrs| Fabricate(:topic, user: attrs[:user], created_at: (DateTime.now - 100) ) }
created_at (DateTime.now - 100)
end
Fabricator(:moderator_post, from: :post) do
user
topic {|attrs| Fabricate(:topic, user: attrs[:user] ) }
post_type Post::MODERATOR_ACTION
raw "Hello world"
end
Fabricator(:post_with_images, from: :post) do
raw "
<img src='/path/to/img.jpg' height='50' width='50'>
![Alt text](/second_image.jpg)
"
end
Fabricator(:post_with_image_url, from: :post) do
cooked "
<img src=\"http://www.forumwarz.com/images/header/logo.png\">
"
end
Fabricator(:basic_reply, from: :post) do
user(:coding_horror)
reply_to_post_number 1
topic
raw 'this reply has no quotes'
end
Fabricator(:reply, from: :post) do
user(:coding_horror)
topic
raw '
[quote="Evil Trout, post:1"]hello[/quote]
Hmmm!
'
end
Fabricator(:multi_quote_reply, from: :post) do
user(:coding_horror)
topic
raw '
[quote="Evil Trout, post:1"]post1 quote[/quote]
Aha!
[quote="Evil Trout, post:2"]post2 quote[/quote]
Neat-o
'
end
Fabricator(:post_with_external_links, from: :post) do
user
topic
raw "
Here's a link to twitter: http://twitter.com
And a link to google: http://google.com
And a markdown link: [forumwarz](http://forumwarz.com)
And a markdown link with a period after it [codinghorror](http://www.codinghorror.com/blog).
"
end

View file

@ -0,0 +1,17 @@
Fabricator(:topic) do
user
title { sequence(:title) { |i| "Test topic #{i}" } }
end
Fabricator(:topic_allowed_user) do
end
Fabricator(:private_message_topic, from: :topic) do
user
title { sequence(:title) { |i| "Private Message #{i}" } }
archetype "private_message"
topic_allowed_users{|t| [
Fabricate.build(:topic_allowed_user, user_id: t[:user].id),
Fabricate.build(:topic_allowed_user, user_id: Fabricate(:coding_horror).id)
]}
end

View file

@ -0,0 +1,6 @@
Fabricator(:user_action) do
user
action_type UserAction::STAR
end

View file

@ -0,0 +1,51 @@
Fabricator(:user) do
name 'Bruce Wayne'
username { sequence(:username) { |i| "bruce#{i}" } }
email { sequence(:email) { |i| "bruce#{i}@wayne.com" } }
password 'myawesomepassword'
trust_level TrustLevel.Levels[:basic]
bio_raw "I'm batman!"
end
Fabricator(:coding_horror, from: :user) do
name 'Coding Horror'
username 'CodingHorror'
email 'jeff@somewhere.com'
password 'mymoreawesomepassword'
end
Fabricator(:evil_trout, from: :user) do
name 'Evil Trout'
username 'eviltrout'
email 'eviltrout@somewhere.com'
password 'imafish'
end
Fabricator(:walter_white, from: :user) do
name 'Walter White'
username 'heisenberg'
email 'wwhite@bluemeth.com'
password 'letscook'
end
Fabricator(:moderator, from: :user) do
name 'A. Moderator'
username 'moderator'
email 'moderator@discourse.org'
trust_level TrustLevel.Levels[:moderator]
end
Fabricator(:admin, from: :user) do
name 'Anne Admin'
username 'anne'
email 'anne@discourse.org'
admin true
end
Fabricator(:another_admin, from: :user) do
name 'Anne Admin the 2nd'
username 'anne2'
email 'anne2@discourse.org'
admin true
end

View file

@ -0,0 +1,25 @@
require 'spec_helper'
describe "i18n integrity checks" do
it 'should have an i18n key for all trust levels' do
TrustLevel.all.each do |ts|
ts.name.should_not =~ /translation missing/
end
end
it "needs an i18n key (description) for each Site Setting" do
SiteSetting.all_settings.each do |s|
next if s[:setting] =~ /^test/
s[:description].should_not =~ /translation missing/
end
end
it "needs an i18n key (notification_types) for each Notification type" do
Notification.Types.keys.each do |type|
I18n.t("notification_types.#{type}").should_not =~ /translation missing/
end
end
end

View file

@ -0,0 +1,138 @@
describe "Discourse.BBCode", ->
format = Discourse.BBCode.format
describe 'default replacer', ->
describe "simple tags", ->
it "bolds text", ->
expect(format("[b]strong[/b]")).toBe("<span class='bbcode-b'>strong</span>")
it "italics text", ->
expect(format("[i]emphasis[/i]")).toBe("<span class='bbcode-i'>emphasis</span>")
it "underlines text", ->
expect(format("[u]underlined[/u]")).toBe("<span class='bbcode-u'>underlined</span>")
it "strikes-through text", ->
expect(format("[s]strikethrough[/s]")).toBe("<span class='bbcode-s'>strikethrough</span>")
it "makes code into pre", ->
expect(format("[code]\nx++\n[/code]")).toBe("<pre>\nx++\n</pre>")
it "supports spoiler tags", ->
expect(format("[spoiler]it's a sled[/spoiler]")).toBe("<span class=\"spoiler\">it's a sled</span>")
it "links images", ->
expect(format("[img]http://eviltrout.com/eviltrout.png[/img]")).toBe("<img src=\"http://eviltrout.com/eviltrout.png\">")
it "supports [url] without a title", ->
expect(format("[url]http://bettercallsaul.com[/url]")).toBe("<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>")
it "supports [email] without a title", ->
expect(format("[email]eviltrout@mailinator.com[/email]")).toBe("<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>")
describe "lists", ->
it "creates an ul", ->
expect(format("[ul][li]option one[/li][/ul]")).toBe("<ul><li>option one</li></ul>")
it "creates an ol", ->
expect(format("[ol][li]option one[/li][/ol]")).toBe("<ol><li>option one</li></ol>")
describe "color", ->
it "supports [color=] with a short hex value", ->
expect(format("[color=#00f]blue[/color]")).toBe("<span style=\"color: #00f\">blue</span>")
it "supports [color=] with a long hex value", ->
expect(format("[color=#ffff00]yellow[/color]")).toBe("<span style=\"color: #ffff00\">yellow</span>")
it "supports [color=] with an html color", ->
expect(format("[color=red]red[/color]")).toBe("<span style=\"color: red\">red</span>")
it "it performs a noop on invalid input", ->
expect(format("[color=javascript:alert('wat')]noop[/color]")).toBe("noop")
describe "tags with arguments", ->
it "supports [size=]", ->
expect(format("[size=35]BIG[/size]")).toBe("<span class=\"bbcode-size-35\">BIG</span>")
it "supports [url] with a title", ->
expect(format("[url=http://bettercallsaul.com]better call![/url]")).toBe("<a href=\"http://bettercallsaul.com\">better call!</a>")
it "supports [email] with a title", ->
expect(format("[email=eviltrout@mailinator.com]evil trout[/email]")).toBe("<a href=\"mailto:eviltrout@mailinator.com\">evil trout</a>")
describe "more complicated", ->
it "can nest tags", ->
expect(format("[u][i]abc[/i][/u]")).toBe("<span class='bbcode-u'><span class='bbcode-i'>abc</span></span>")
it "can bold two things on the same line", ->
expect(format("[b]first[/b] [b]second[/b]")).toBe("<span class='bbcode-b'>first</span> <span class='bbcode-b'>second</span>")
describe 'email environment', ->
describe "simple tags", ->
it "bolds text", ->
expect(format("[b]strong[/b]", environment: 'email')).toBe("<b>strong</b>")
it "italics text", ->
expect(format("[i]emphasis[/i]", environment: 'email')).toBe("<i>emphasis</i>")
it "underlines text", ->
expect(format("[u]underlined[/u]", environment: 'email')).toBe("<u>underlined</u>")
it "strikes-through text", ->
expect(format("[s]strikethrough[/s]", environment: 'email')).toBe("<s>strikethrough</s>")
it "makes code into pre", ->
expect(format("[code]\nx++\n[/code]", environment: 'email')).toBe("<pre>\nx++\n</pre>")
it "supports spoiler tags", ->
expect(format("[spoiler]it's a sled[/spoiler]", environment: 'email')).toBe("<span style='background-color: #000'>it's a sled</span>")
it "links images", ->
expect(format("[img]http://eviltrout.com/eviltrout.png[/img]", environment: 'email')).toBe("<img src=\"http://eviltrout.com/eviltrout.png\">")
it "supports [url] without a title", ->
expect(format("[url]http://bettercallsaul.com[/url]", environment: 'email')).toBe("<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>")
it "supports [email] without a title", ->
expect(format("[email]eviltrout@mailinator.com[/email]", environment: 'email')).toBe("<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>")
describe "lists", ->
it "creates an ul", ->
expect(format("[ul][li]option one[/li][/ul]", environment: 'email')).toBe("<ul><li>option one</li></ul>")
it "creates an ol", ->
expect(format("[ol][li]option one[/li][/ol]", environment: 'email')).toBe("<ol><li>option one</li></ol>")
describe "color", ->
it "supports [color=] with a short hex value", ->
expect(format("[color=#00f]blue[/color]", environment: 'email')).toBe("<span style=\"color: #00f\">blue</span>")
it "supports [color=] with a long hex value", ->
expect(format("[color=#ffff00]yellow[/color]", environment: 'email')).toBe("<span style=\"color: #ffff00\">yellow</span>")
it "supports [color=] with an html color", ->
expect(format("[color=red]red[/color]", environment: 'email')).toBe("<span style=\"color: red\">red</span>")
it "it performs a noop on invalid input", ->
expect(format("[color=javascript:alert('wat')]noop[/color]", environment: 'email')).toBe("noop")
describe "tags with arguments", ->
it "supports [size=]", ->
expect(format("[size=35]BIG[/size]", environment: 'email')).toBe("<span style=\"font-size: 35px\">BIG</span>")
it "supports [url] with a title", ->
expect(format("[url=http://bettercallsaul.com]better call![/url]", environment: 'email')).toBe("<a href=\"http://bettercallsaul.com\">better call!</a>")
it "supports [email] with a title", ->
expect(format("[email=eviltrout@mailinator.com]evil trout[/email]", environment: 'email')).toBe("<a href=\"mailto:eviltrout@mailinator.com\">evil trout</a>")

View file

@ -0,0 +1,17 @@
describe "Discourse.KeyValueStore", ->
describe "Setting values", ->
store = Discourse.KeyValueStore
store.init("test")
it "able to get the value back from the store", ->
store.set(key: "bob", value: "uncle")
expect(store.get("bob")).toBe("uncle")
it "able to nuke the store", ->
store.set(key: "bob1", value: "uncle")
store.abandonLocal()
localStorage["a"] = 1
expect(store.get("bob1")).toBe(undefined)
expect(localStorage["a"]).toBe("1")

View file

@ -0,0 +1,24 @@
describe "Discourse.MessageBus", ->
describe "Web Sockets", ->
bus = Discourse.MessageBus
bus.start()
# PENDING: Fix to allow these to run in jasmine-guard
#it "is able to get a response from the echo server", ->
# response = null
# bus.send("/echo", "hello world", (r) -> response = r)
# # give it some time to spin up
# waitsFor((-> response == "hello world"),"gotEcho",500)
#it "should get responses from broadcast channel", ->
# response = null
# # note /message_bus/broadcast is dev only
# bus.subscribe("/animals", (r) -> response = r)
# $.ajax
# url: '/message-bus/broadcast'
# data: {channel: "/animals", data: "kitten"}
# cache: false
# waitsFor((-> response == "kitten"),"gotBroadcast", 500)

View file

@ -0,0 +1,81 @@
describe "PreloadStore", ->
beforeEach ->
PreloadStore.store('bane', 'evil')
describe "contains", ->
it "returns false for a key that doesn't exist", ->
expect(PreloadStore.contains('joker')).toBe(false)
it "returns true for a stored key", ->
expect(PreloadStore.contains('bane')).toBe(true)
describe 'getStatic', ->
it "returns undefined if the key doesn't exist", ->
expect(PreloadStore.getStatic('joker')).toBe(undefined)
it "returns the the key if it exists", ->
expect(PreloadStore.getStatic('bane')).toBe('evil')
it "removes the key after being called", ->
PreloadStore.getStatic('bane')
expect(PreloadStore.getStatic('bane')).toBe(undefined)
describe 'get', ->
it "returns a promise that resolves to undefined", ->
done = storeResult = null
PreloadStore.get('joker').then (result) ->
done = true
storeResult = result
waitsFor (-> return done), "Promise never resolved", 1000
runs -> expect(storeResult).toBe(undefined)
it "returns a promise that resolves to the result of the finder", ->
done = storeResult = null
finder = -> 'evil'
PreloadStore.get('joker', finder).then (result) ->
done = true
storeResult = result
waitsFor (-> return done), "Promise never resolved", 1000
runs -> expect(storeResult).toBe('evil')
it "returns a promise that resolves to the result of the finder's promise", ->
done = storeResult = null
finder = ->
promise = new RSVP.Promise
promise.resolve('evil')
promise
PreloadStore.get('joker', finder).then (result) ->
done = true
storeResult = result
waitsFor (-> return done), "Promise never resolved", 1000
runs -> expect(storeResult).toBe('evil')
it "returns a promise that resolves to the result of the finder's rejected promise", ->
done = storeResult = null
finder = ->
promise = new RSVP.Promise
promise.reject('evil')
promise
PreloadStore.get('joker', finder).then null, (rejectedResult) ->
done = true
storeResult = rejectedResult
waitsFor (-> return done), "Promise never rejected", 1000
runs -> expect(storeResult).toBe('evil')
it "returns a promise that resolves to 'evil'", ->
done = storeResult = null
PreloadStore.get('bane').then (result) ->
done = true
storeResult = result
waitsFor (-> return done), "Promise never resolved", 1000
runs -> expect(storeResult).toBe('evil')

View file

@ -0,0 +1,3 @@
/*
*/

11
spec/javascripts/spec.js Normal file
View file

@ -0,0 +1,11 @@
//= require env
//= require jquery
//= require external/jquery.ui.widget.js
//= require external/handlebars-1.0.rc.2.js
//= require preload_store
//= require_tree ../../app/assets/javascripts/external
//= require_tree ../../app/assets/javascripts/discourse/components
//= require_tree ../../app/assets/javascripts/discourse/templates
//= require_tree .

View file

@ -0,0 +1,82 @@
describe "Discourse.Utilities", ->
describe "Cooking", ->
cook = (contents, opts) ->
opts = opts || {}
opts.mentionLookup = opts.mentionLookup || (() -> false)
Discourse.Utilities.cook(contents, opts)
it "surrounds text with paragraphs", ->
expect(cook("hello")).toBe("<p>hello</p>")
it "automatically handles trivial newlines", ->
expect(cook("1\n2\n3")).toBe("<p>1 <br>\n2 <br>\n3</p>")
it "handles quotes properly", ->
cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {topicId: 2, lookupAvatar: (name) -> "#{name}"})
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n bob\n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>")
it "includes no avatar if none is found", ->
cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {topicId: 2, lookupAvatar: (name) -> null})
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n \n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>")
describe "Links", ->
it "allows links to contain query params", ->
expect(cook("Youtube: http://www.youtube.com/watch?v=1MrpeBRkM5A")).toBe('<p>Youtube: <a href="http://www.youtube.com/watch?v=1MrpeBRkM5A">http://www.youtube.com/watch?v=1MrpeBRkM5A</a></p>')
it "escapes double underscores in URLs", ->
expect(cook("Derpy: http://derp.com?__test=1")).toBe('<p>Derpy: <a href="http://derp.com?%5F%5Ftest=1">http://derp.com?__test=1</a></p>')
it "autolinks something that begins with www", ->
expect(cook("Atwood: www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">www.codinghorror.com</a></p>')
it "autolinks a URL with http://www", ->
expect(cook("Atwood: http://www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">http://www.codinghorror.com</a></p>')
it "autolinks a URL", ->
expect(cook("EvilTrout: http://eviltrout.com")).toBe('<p>EvilTrout: <a href="http://eviltrout.com">http://eviltrout.com</a></p>')
it "supports markdown style links", ->
expect(cook("here is [an example](http://twitter.com)")).toBe('<p>here is <a href="http://twitter.com">an example</a></p>')
it "autolinks a URL with parentheses (like Wikipedia)", ->
expect(cook("Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)")).toBe('<p>Batman: <a href="http://en.wikipedia.org/wiki/The_Dark_Knight_(film)">http://en.wikipedia.org/wiki/The_Dark_Knight_(film)</a></p>')
describe "Mentioning", ->
it "translates mentions to links", ->
expect(cook("Hello @sam", {mentionLookup: (->true)})).toBe("<p>Hello <a href='/users/sam' class='mention'>@sam</a></p>")
it "adds a mention class", ->
expect(cook("Hello @EvilTrout")).toBe("<p>Hello <span class='mention'>@EvilTrout</span></p>")
it "won't add mention class to an email address", ->
expect(cook("robin@email.host")).toBe("<p>robin@email.host</p>")
it "won't be affected by email addresses that have a number before the @ symbol", ->
expect(cook("hanzo55@yahoo.com")).toBe("<p>hanzo55@yahoo.com</p>")
it "supports a @mention at the beginning of a post", ->
expect(cook("@EvilTrout yo")).toBe("<p><span class='mention'>@EvilTrout</span> yo</p>")
# Oneboxing functionality
describe "Oneboxing", ->
it "doesn't onebox a link within a list", ->
expect(cook("- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org")).not.toMatch(/onebox/)
it "adds a onebox class to a link on its own line", ->
expect(cook("http://test.com")).toMatch(/onebox/)
it "supports multiple links", ->
expect(cook("http://test.com\nhttp://test2.com")).toMatch(/onebox[\s\S]+onebox/m)
it "doesn't onebox links that have trailing text", ->
expect(cook("http://test.com bob")).not.toMatch(/onebox/)
it "works with links that have underscores in them", ->
expect(cook("http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street")).toBe("<p><a href=\"http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street\" class=\"onebox\" target=\"_blank\">http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street</a></p>")

View file

@ -0,0 +1,16 @@
require "spec_helper"
describe InviteMailer do
describe "send_invite" do
let(:invite) { Fabricate(:invite) }
subject { InviteMailer.send_invite(invite) }
its(:to) { should == [invite.email] }
its(:subject) { should be_present }
its(:body) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
end
end

View file

@ -0,0 +1,15 @@
require "spec_helper"
describe TestMailer do
describe "send_test" do
subject { TestMailer.send_test('marcheline@adventuretime.ooo') }
its(:to) { should == ['marcheline@adventuretime.ooo'] }
its(:subject) { should be_present }
its(:body) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
end
end

View file

@ -0,0 +1,60 @@
require "spec_helper"
describe UserNotifications do
let(:user) { Fabricate(:user) }
describe ".signup" do
subject { UserNotifications.signup(user) }
its(:to) { should == [user.email] }
its(:subject) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
its(:body) { should be_present }
end
describe ".forgot_password" do
subject { UserNotifications.forgot_password(user) }
its(:to) { should == [user.email] }
its(:subject) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
its(:body) { should be_present }
end
describe '.daily_digest' do
subject { UserNotifications.digest(user) }
context "without new topics" do
its(:to) { should be_blank }
end
context "with new topics" do
before do
Topic.expects(:new_topics).returns([Fabricate(:topic, user: Fabricate(:coding_horror))])
end
its(:to) { should == [user.email] }
its(:subject) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
its(:body) { should be_present }
end
end
describe '.user_mentioned' do
let(:post) { Fabricate(:post, user: user) }
let(:notification) do
Fabricate(:notification, user: user, topic: post.topic, post_number: post.post_number )
end
subject { UserNotifications.user_mentioned(user, notification: notification, post: notification.post) }
its(:to) { should == [user.email] }
its(:subject) { should be_present }
its(:from) { should == [SiteSetting.notification_email] }
its(:body) { should be_present }
end
end

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

Some files were not shown because too many files have changed in this diff Show more