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:
commit
21b5628528
2932 changed files with 143949 additions and 0 deletions
51
spec/components/archetype_spec.rb
Normal file
51
spec/components/archetype_spec.rb
Normal 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
|
||||
|
72
spec/components/category_list_spec.rb
Normal file
72
spec/components/category_list_spec.rb
Normal 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
|
29
spec/components/content_buffer_spec.rb
Normal file
29
spec/components/content_buffer_spec.rb
Normal 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
|
97
spec/components/cooked_post_processor_spec.rb
Normal file
97
spec/components/cooked_post_processor_spec.rb
Normal 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
|
43
spec/components/discourse_plugin_registry_spec.rb
Normal file
43
spec/components/discourse_plugin_registry_spec.rb
Normal 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
|
56
spec/components/discourse_spec.rb
Normal file
56
spec/components/discourse_spec.rb
Normal 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
|
||||
|
12
spec/components/distributed_hash_spec.rb
Normal file
12
spec/components/distributed_hash_spec.rb
Normal 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
|
119
spec/components/email_sender_spec.rb
Normal file
119
spec/components/email_sender_spec.rb
Normal 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
|
22
spec/components/email_spec.rb
Normal file
22
spec/components/email_spec.rb
Normal 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
|
37
spec/components/export/export_spec.rb
Normal file
37
spec/components/export/export_spec.rb
Normal 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
|
146
spec/components/export/json_encoder_spec.rb
Normal file
146
spec/components/export/json_encoder_spec.rb
Normal 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
|
728
spec/components/guardian_spec.rb
Normal file
728
spec/components/guardian_spec.rb
Normal 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
|
||||
|
42
spec/components/image_sizer_spec.rb
Normal file
42
spec/components/image_sizer_spec.rb
Normal 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
|
24
spec/components/import/adapter/base_spec.rb
Normal file
24
spec/components/import/adapter/base_spec.rb
Normal 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
|
66
spec/components/import/import_spec.rb
Normal file
66
spec/components/import/import_spec.rb
Normal 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
|
62
spec/components/import/json_decoder_spec.rb
Normal file
62
spec/components/import/json_decoder_spec.rb
Normal 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
|
12
spec/components/jobs/calculate_view_counts_spec.rb
Normal file
12
spec/components/jobs/calculate_view_counts_spec.rb
Normal 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
|
61
spec/components/jobs/enqueue_digest_emails_spec.rb
Normal file
61
spec/components/jobs/enqueue_digest_emails_spec.rb
Normal 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
|
||||
|
190
spec/components/jobs/exporter_spec.rb
Normal file
190
spec/components/jobs/exporter_spec.rb
Normal 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
|
44
spec/components/jobs/feature_topic_users_spec.rb
Normal file
44
spec/components/jobs/feature_topic_users_spec.rb
Normal 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
|
541
spec/components/jobs/importer_spec.rb
Normal file
541
spec/components/jobs/importer_spec.rb
Normal 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
|
29
spec/components/jobs/invite_email_spec.rb
Normal file
29
spec/components/jobs/invite_email_spec.rb
Normal 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
|
||||
|
17
spec/components/jobs/jobs_base_spec.rb
Normal file
17
spec/components/jobs/jobs_base_spec.rb
Normal 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
|
||||
|
36
spec/components/jobs/notify_moved_posts_spec.rb
Normal file
36
spec/components/jobs/notify_moved_posts_spec.rb
Normal 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
|
37
spec/components/jobs/process_post_spec.rb
Normal file
37
spec/components/jobs/process_post_spec.rb
Normal 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
|
26
spec/components/jobs/send_system_message_spec.rb
Normal file
26
spec/components/jobs/send_system_message_spec.rb
Normal 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
|
27
spec/components/jobs/test_email_spec.rb
Normal file
27
spec/components/jobs/test_email_spec.rb
Normal 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
|
||||
|
97
spec/components/jobs/user_email_spec.rb
Normal file
97
spec/components/jobs/user_email_spec.rb
Normal 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
|
||||
|
79
spec/components/jobs_spec.rb
Normal file
79
spec/components/jobs_spec.rb
Normal 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
|
||||
|
64
spec/components/mothership_spec.rb
Normal file
64
spec/components/mothership_spec.rb
Normal 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
|
132
spec/components/oneboxer_spec.rb
Normal file
132
spec/components/oneboxer_spec.rb
Normal 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
|
||||
|
||||
|
103
spec/components/post_creator_spec.rb
Normal file
103
spec/components/post_creator_spec.rb
Normal 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
|
||||
|
143
spec/components/pretty_text_spec.rb
Normal file
143
spec/components/pretty_text_spec.rb
Normal 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\"><header>hello</header> \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…"
|
||||
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…</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'><h3>Hours</h3></code></pre>",100).should == "<h3>Hours</h3>"
|
||||
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
|
48
spec/components/promotion_spec.rb
Normal file
48
spec/components/promotion_spec.rb
Normal 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
|
103
spec/components/rate_limiter_spec.rb
Normal file
103
spec/components/rate_limiter_spec.rb
Normal 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
|
49
spec/components/score_calculator_spec.rb
Normal file
49
spec/components/score_calculator_spec.rb
Normal 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
|
182
spec/components/search_spec.rb
Normal file
182
spec/components/search_spec.rb
Normal 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
|
||||
|
32
spec/components/slug_spec.rb
Normal file
32
spec/components/slug_spec.rb
Normal 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
|
||||
|
35
spec/components/sql_builder_spec.rb
Normal file
35
spec/components/sql_builder_spec.rb
Normal 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
|
43
spec/components/system_message_spec.rb
Normal file
43
spec/components/system_message_spec.rb
Normal 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
|
260
spec/components/topic_query_spec.rb
Normal file
260
spec/components/topic_query_spec.rb
Normal 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
|
234
spec/components/topic_view_spec.rb
Normal file
234
spec/components/topic_view_spec.rb
Normal 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
|
||||
|
64
spec/components/unread_spec.rb
Normal file
64
spec/components/unread_spec.rb
Normal 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
|
20
spec/controllers/admin/admin_controller_spec.rb
Normal file
20
spec/controllers/admin/admin_controller_spec.rb
Normal 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
|
37
spec/controllers/admin/email_logs_controller_spec.rb
Normal file
37
spec/controllers/admin/email_logs_controller_spec.rb
Normal 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
|
57
spec/controllers/admin/export_controller_spec.rb
Normal file
57
spec/controllers/admin/export_controller_spec.rb
Normal 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
|
37
spec/controllers/admin/flags_controller_spec.rb
Normal file
37
spec/controllers/admin/flags_controller_spec.rb
Normal 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
|
||||
|
68
spec/controllers/admin/impersonate_controller_spec.rb
Normal file
68
spec/controllers/admin/impersonate_controller_spec.rb
Normal 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
|
|
@ -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
|
43
spec/controllers/admin/site_settings_controller_spec.rb
Normal file
43
spec/controllers/admin/site_settings_controller_spec.rb
Normal 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
|
118
spec/controllers/admin/users_controller_spec.rb
Normal file
118
spec/controllers/admin/users_controller_spec.rb
Normal 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
|
55
spec/controllers/admin/versions_controller_spec.rb
Normal file
55
spec/controllers/admin/versions_controller_spec.rb
Normal 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
|
147
spec/controllers/categories_controller_spec.rb
Normal file
147
spec/controllers/categories_controller_spec.rb
Normal 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
|
64
spec/controllers/clicks_controller_spec.rb
Normal file
64
spec/controllers/clicks_controller_spec.rb
Normal 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
|
22
spec/controllers/draft_controller_spec.rb
Normal file
22
spec/controllers/draft_controller_spec.rb
Normal 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
|
99
spec/controllers/email_controller_spec.rb
Normal file
99
spec/controllers/email_controller_spec.rb
Normal 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
|
82
spec/controllers/excerpt_controller_spec.rb
Normal file
82
spec/controllers/excerpt_controller_spec.rb
Normal 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
|
151
spec/controllers/invites_controller_spec.rb
Normal file
151
spec/controllers/invites_controller_spec.rb
Normal 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
|
87
spec/controllers/list_controller_spec.rb
Normal file
87
spec/controllers/list_controller_spec.rb
Normal 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
|
22
spec/controllers/notifications_controller_spec.rb
Normal file
22
spec/controllers/notifications_controller_spec.rb
Normal 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
|
15
spec/controllers/onebox_controller_spec.rb
Normal file
15
spec/controllers/onebox_controller_spec.rb
Normal 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
|
127
spec/controllers/post_actions_controller_spec.rb
Normal file
127
spec/controllers/post_actions_controller_spec.rb
Normal 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
|
278
spec/controllers/posts_controller_spec.rb
Normal file
278
spec/controllers/posts_controller_spec.rb
Normal 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
|
56
spec/controllers/request_access_controller_spec.rb
Normal file
56
spec/controllers/request_access_controller_spec.rb
Normal 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
|
16
spec/controllers/search_controller_spec.rb
Normal file
16
spec/controllers/search_controller_spec.rb
Normal 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
|
130
spec/controllers/session_controller_spec.rb
Normal file
130
spec/controllers/session_controller_spec.rb
Normal 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
|
27
spec/controllers/static_controller_spec.rb
Normal file
27
spec/controllers/static_controller_spec.rb
Normal 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
|
419
spec/controllers/topics_controller_spec.rb
Normal file
419
spec/controllers/topics_controller_spec.rb
Normal 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
|
635
spec/controllers/users_controller_spec.rb
Normal file
635
spec/controllers/users_controller_spec.rb
Normal 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
|
4
spec/fabricators/category_fabricator.rb
Normal file
4
spec/fabricators/category_fabricator.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
Fabricator(:category) do
|
||||
name 'Amazing Category'
|
||||
user
|
||||
end
|
9
spec/fabricators/incoming_link_fabricator.rb
Normal file
9
spec/fabricators/incoming_link_fabricator.rb
Normal 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
|
4
spec/fabricators/invite_fabricator.rb
Normal file
4
spec/fabricators/invite_fabricator.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
Fabricator(:invite) do
|
||||
invited_by(fabricator: :user)
|
||||
email 'iceking@ADVENTURETIME.ooo'
|
||||
end
|
18
spec/fabricators/notification_fabricator.rb
Normal file
18
spec/fabricators/notification_fabricator.rb
Normal 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
|
74
spec/fabricators/post_fabricator.rb
Normal file
74
spec/fabricators/post_fabricator.rb
Normal 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'>
|
||||

|
||||
"
|
||||
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
|
17
spec/fabricators/topic_fabricator.rb
Normal file
17
spec/fabricators/topic_fabricator.rb
Normal 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
|
6
spec/fabricators/user_action_fabricator.rb
Normal file
6
spec/fabricators/user_action_fabricator.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
Fabricator(:user_action) do
|
||||
|
||||
user
|
||||
action_type UserAction::STAR
|
||||
|
||||
end
|
51
spec/fabricators/user_fabricator.rb
Normal file
51
spec/fabricators/user_fabricator.rb
Normal 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
|
||||
|
25
spec/integrity/i18n_spec.rb
Normal file
25
spec/integrity/i18n_spec.rb
Normal 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
|
138
spec/javascripts/bbcode_spec.js.coffee
Normal file
138
spec/javascripts/bbcode_spec.js.coffee
Normal 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>")
|
||||
|
17
spec/javascripts/key_value_store_spec.js.coffee
Normal file
17
spec/javascripts/key_value_store_spec.js.coffee
Normal 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")
|
24
spec/javascripts/message_bus_spec.js.coffee
Normal file
24
spec/javascripts/message_bus_spec.js.coffee
Normal 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)
|
81
spec/javascripts/preload_store_spec.js.coffee
Normal file
81
spec/javascripts/preload_store_spec.js.coffee
Normal 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')
|
3
spec/javascripts/spec.css
Normal file
3
spec/javascripts/spec.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
/*
|
||||
|
||||
*/
|
11
spec/javascripts/spec.js
Normal file
11
spec/javascripts/spec.js
Normal 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 .
|
82
spec/javascripts/utilities_spec.js.coffee
Normal file
82
spec/javascripts/utilities_spec.js.coffee
Normal 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>")
|
16
spec/mailers/invite_mailer_spec.rb
Normal file
16
spec/mailers/invite_mailer_spec.rb
Normal 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
|
15
spec/mailers/test_mailer_spec.rb
Normal file
15
spec/mailers/test_mailer_spec.rb
Normal 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
|
60
spec/mailers/user_notifications_spec.rb
Normal file
60
spec/mailers/user_notifications_spec.rb
Normal 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
|
9
spec/models/category_featured_topic_spec.rb
Normal file
9
spec/models/category_featured_topic_spec.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe CategoryFeaturedTopic do
|
||||
|
||||
it { should belong_to :category }
|
||||
it { should belong_to :topic }
|
||||
|
||||
end
|
||||
|
26
spec/models/category_featured_user_spec.rb
Normal file
26
spec/models/category_featured_user_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe CategoryFeaturedUser do
|
||||
|
||||
it { should belong_to :category }
|
||||
it { should belong_to :user }
|
||||
|
||||
|
||||
context 'featuring users' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
CategoryFeaturedUser.feature_users_in(@category)
|
||||
end
|
||||
|
||||
it 'has a featured user' do
|
||||
CategoryFeaturedUser.count.should_not == 0
|
||||
end
|
||||
|
||||
it 'returns the user via the category association' do
|
||||
@category.featured_users.should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
175
spec/models/category_spec.rb
Normal file
175
spec/models/category_spec.rb
Normal file
|
@ -0,0 +1,175 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Category do
|
||||
|
||||
it { should validate_presence_of :name }
|
||||
|
||||
it 'validates uniqueness of name' do
|
||||
Fabricate(:category)
|
||||
should validate_uniqueness_of(:name)
|
||||
end
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should have_many :topics }
|
||||
it { should have_many :category_featured_topics }
|
||||
it { should have_many :featured_topics }
|
||||
|
||||
describe "uncategorized name" do
|
||||
|
||||
let(:category) { Fabricate.build(:category, name: SiteSetting.uncategorized_name) }
|
||||
|
||||
it "is invalid to create a category with the reserved name" do
|
||||
category.should_not be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "short name" do
|
||||
let!(:category) { Fabricate(:category, name: 'xx') }
|
||||
|
||||
it "creates the category" do
|
||||
category.should be_present
|
||||
end
|
||||
|
||||
it 'has one topic' do
|
||||
Topic.where(category_id: category.id).count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'caching' do
|
||||
|
||||
it "invalidates the site cache on creation" do
|
||||
Site.expects(:invalidate_cache).once
|
||||
Fabricate(:category)
|
||||
end
|
||||
|
||||
it "invalidates the site cache on update" do
|
||||
cat = Fabricate(:category)
|
||||
Site.expects(:invalidate_cache).once
|
||||
cat.update_attributes(name: 'new name')
|
||||
end
|
||||
|
||||
it "invalidates the site cache on destroy" do
|
||||
cat = Fabricate(:category)
|
||||
Site.expects(:invalidate_cache).once
|
||||
cat.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after create' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
@topic = @category.topic
|
||||
end
|
||||
|
||||
it 'creates a slug' do
|
||||
@category.slug.should == 'amazing-category'
|
||||
end
|
||||
|
||||
it 'has one topic' do
|
||||
Topic.where(category_id: @category).count.should == 1
|
||||
end
|
||||
|
||||
it 'creates a topic post' do
|
||||
@topic.should be_present
|
||||
end
|
||||
|
||||
it 'points back to itself' do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it 'is an invisible topic' do
|
||||
@topic.should_not be_visible
|
||||
end
|
||||
|
||||
it 'is an undeletable topic' do
|
||||
Guardian.new(@category.user).can_delete?(@topic).should be_false
|
||||
end
|
||||
|
||||
it 'should have one post' do
|
||||
@topic.posts.count.should == 1
|
||||
end
|
||||
|
||||
it 'should have an excerpt' do
|
||||
@category.excerpt.should be_present
|
||||
end
|
||||
|
||||
it 'should have a topic url' do
|
||||
@category.topic_url.should be_present
|
||||
end
|
||||
|
||||
describe "trying to change the category topic's category" do
|
||||
|
||||
before do
|
||||
@new_cat = Fabricate(:category, name: '2nd Category', user: @category.user)
|
||||
@topic.change_category(@new_cat.name)
|
||||
@topic.reload
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'still has 0 forum topics' do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
|
||||
it "didn't change the category" do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it "didn't change the category's forum topic" do
|
||||
@category.topic.should == @topic
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'destroy' do
|
||||
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
@category_id = @category.id
|
||||
@topic_id = @category.topic_id
|
||||
@category.destroy
|
||||
end
|
||||
|
||||
it 'deletes the category' do
|
||||
Category.exists?(id: @category_id).should be_false
|
||||
end
|
||||
|
||||
it 'deletes the forum topic' do
|
||||
Topic.exists?(id: @topic_id).should be_false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'update_stats' do
|
||||
|
||||
# We're going to test with one topic. That's enough for stats!
|
||||
before do
|
||||
@category = Fabricate(:category)
|
||||
|
||||
# Create a non-invisible category to make sure count is 1
|
||||
@topic = Fabricate(:topic, user: @category.user, category: @category)
|
||||
|
||||
Category.update_stats
|
||||
@category.reload
|
||||
end
|
||||
|
||||
it 'updates topics_week' do
|
||||
@category.topics_week.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_month' do
|
||||
@category.topics_month.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_year' do
|
||||
@category.topics_year.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
14
spec/models/draft_sequence_spec.rb
Normal file
14
spec/models/draft_sequence_spec.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe DraftSequence do
|
||||
it 'should produce next sequence for a key' do
|
||||
u = Fabricate(:user)
|
||||
DraftSequence.next!(u, 'test').should == 1
|
||||
DraftSequence.next!(u, 'test').should == 2
|
||||
end
|
||||
|
||||
it 'should return 0 by default' do
|
||||
u = Fabricate(:user)
|
||||
DraftSequence.current(u, 'test').should == 0
|
||||
end
|
||||
end
|
81
spec/models/draft_spec.rb
Normal file
81
spec/models/draft_spec.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Draft do
|
||||
before do
|
||||
@user = Fabricate(:user)
|
||||
end
|
||||
it "can get a draft by user" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.get(@user, "test", 0).should == "data"
|
||||
end
|
||||
|
||||
it "uses the user id and key correctly" do
|
||||
Draft.set(@user, "test", 0,"data")
|
||||
Draft.get(Fabricate(:coding_horror), "test", 0).should be_nil
|
||||
end
|
||||
|
||||
it "should overwrite draft data correctly" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.set(@user, "test", 0, "new data")
|
||||
Draft.get(@user, "test", 0).should == "new data"
|
||||
end
|
||||
|
||||
it "should clear drafts on request" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.clear(@user, "test", 0)
|
||||
Draft.get(@user, "test", 0).should be_nil
|
||||
end
|
||||
|
||||
it "should disregard old draft if sequence decreases" do
|
||||
Draft.set(@user, "test", 0, "data")
|
||||
Draft.set(@user, "test", 1, "hello")
|
||||
Draft.set(@user, "test", 0, "foo")
|
||||
Draft.get(@user, "test", 0).should be_nil
|
||||
Draft.get(@user, "test", 1).should == "hello"
|
||||
end
|
||||
|
||||
|
||||
context 'key expiry' do
|
||||
it 'nukes new topic draft after a topic is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_TOPIC, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u)
|
||||
s = DraftSequence.current(u, Draft::NEW_TOPIC)
|
||||
Draft.get(u, Draft::NEW_TOPIC, s).should be_nil
|
||||
end
|
||||
|
||||
it 'nukes new pm draft after a pm is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_PRIVATE_MESSAGE, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u, archetype: Archetype.private_message)
|
||||
s = DraftSequence.current(t.user, Draft::NEW_PRIVATE_MESSAGE)
|
||||
Draft.get(u, Draft::NEW_PRIVATE_MESSAGE, s).should be_nil
|
||||
end
|
||||
|
||||
it 'does not nuke new topic draft after a pm is created' do
|
||||
u = Fabricate(:user)
|
||||
Draft.set(u, Draft::NEW_TOPIC, 0, 'my draft')
|
||||
t = Fabricate(:topic, user: u, archetype: Archetype.private_message)
|
||||
s = DraftSequence.current(t.user, Draft::NEW_TOPIC)
|
||||
Draft.get(u, Draft::NEW_TOPIC, s).should == 'my draft'
|
||||
end
|
||||
|
||||
it 'nukes the post draft when a post is created' do
|
||||
p = Fabricate(:post)
|
||||
Draft.set(p.user, p.topic.draft_key, 0,'hello')
|
||||
Fabricate(:post, topic: p.topic, user: p.user)
|
||||
Draft.get(p.user, p.topic.draft_key, DraftSequence.current(p.user, p.topic.draft_key)).should be_nil
|
||||
end
|
||||
|
||||
it 'nukes the post draft when a post is revised' do
|
||||
p = Fabricate(:post)
|
||||
Draft.set(p.user, p.topic.draft_key, 0,'hello')
|
||||
p.revise(p.user, 'another test')
|
||||
s = DraftSequence.current(p.user, p.topic.draft_key)
|
||||
Draft.get(p.user, p.topic.draft_key, s).should be_nil
|
||||
end
|
||||
|
||||
it 'increases the sequence number when a post is revised' do
|
||||
end
|
||||
end
|
||||
end
|
24
spec/models/email_log_spec.rb
Normal file
24
spec/models/email_log_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EmailLog do
|
||||
|
||||
it { should belong_to :user }
|
||||
|
||||
it { should validate_presence_of :to_address }
|
||||
it { should validate_presence_of :email_type }
|
||||
|
||||
|
||||
context 'after_create with user' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it 'updates the last_emailed_at value for the user' do
|
||||
lambda {
|
||||
user.email_logs.create(email_type: 'blah', to_address: user.email)
|
||||
user.reload
|
||||
}.should change(user, :last_emailed_at)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
128
spec/models/email_token_spec.rb
Normal file
128
spec/models/email_token_spec.rb
Normal file
|
@ -0,0 +1,128 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EmailToken do
|
||||
|
||||
it { should validate_presence_of :user_id }
|
||||
it { should validate_presence_of :email }
|
||||
it { should belong_to :user }
|
||||
|
||||
|
||||
context '#create' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:original_token) { user.email_tokens.first }
|
||||
let!(:email_token) { user.email_tokens.create(email: 'bubblegum@adevnturetime.ooo') }
|
||||
|
||||
it 'should create the email token' do
|
||||
email_token.should be_present
|
||||
end
|
||||
|
||||
it 'is valid' do
|
||||
email_token.should be_valid
|
||||
end
|
||||
|
||||
it 'has a token' do
|
||||
email_token.token.should be_present
|
||||
end
|
||||
|
||||
it 'is not confirmed' do
|
||||
email_token.should_not be_confirmed
|
||||
end
|
||||
|
||||
it 'is not expired' do
|
||||
email_token.should_not be_expired
|
||||
end
|
||||
|
||||
it 'marks the older token as expired' do
|
||||
original_token.reload
|
||||
original_token.should be_expired
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
context '#confirm' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:email_token) { user.email_tokens.first }
|
||||
|
||||
it 'returns nil with a nil token' do
|
||||
EmailToken.confirm(nil).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil with a made up token' do
|
||||
EmailToken.confirm(EmailToken.generate_token).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil unless the token is the right length' do
|
||||
EmailToken.confirm('a').should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil when a token is expired' do
|
||||
email_token.update_column(:expired, true)
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
it 'returns nil when a token is older than a specific time' do
|
||||
EmailToken.expects(:valid_after).returns(1.week.ago)
|
||||
email_token.update_column(:created_at, 2.weeks.ago)
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
context 'taken email address' do
|
||||
|
||||
before do
|
||||
@other_user = Fabricate(:coding_horror)
|
||||
email_token.update_attribute :email, @other_user.email
|
||||
end
|
||||
|
||||
it 'returns nil when the email has been taken since the token has been generated' do
|
||||
EmailToken.confirm(email_token.token).should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'welcome message' do
|
||||
it 'sends a welcome message when the user is activated' do
|
||||
user = EmailToken.confirm(email_token.token)
|
||||
user.send_welcome_message.should be_true
|
||||
end
|
||||
|
||||
context "when using the code a second time" do
|
||||
before do
|
||||
EmailToken.confirm(email_token.token)
|
||||
end
|
||||
|
||||
it "doesn't send the welcome message" do
|
||||
user = EmailToken.confirm(email_token.token)
|
||||
user.send_welcome_message.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
|
||||
let!(:confirmed_user) { EmailToken.confirm(email_token.token) }
|
||||
|
||||
it "returns the correct user" do
|
||||
confirmed_user.should == user
|
||||
end
|
||||
|
||||
it 'marks the user as active' do
|
||||
confirmed_user.reload
|
||||
confirmed_user.should be_active
|
||||
end
|
||||
|
||||
it 'marks the token as confirmed' do
|
||||
email_token.reload
|
||||
email_token.should be_confirmed
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
58
spec/models/error_log_spec.rb
Normal file
58
spec/models/error_log_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
require 'spec_helper'
|
||||
describe ErrorLog do
|
||||
|
||||
def boom
|
||||
raise "boom"
|
||||
end
|
||||
|
||||
def exception
|
||||
begin
|
||||
boom
|
||||
rescue => e
|
||||
return e
|
||||
end
|
||||
end
|
||||
|
||||
def controller
|
||||
DraftController.new
|
||||
end
|
||||
|
||||
def request
|
||||
ActionController::TestRequest.new(:host => 'test')
|
||||
end
|
||||
|
||||
describe "add_row!" do
|
||||
it "creates a non empty file on first call" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.add_row!(hello: "world")
|
||||
File.exists?(ErrorLog.filename).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "logging data" do
|
||||
it "is able to read the data it writes" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
i = 0
|
||||
ErrorLog.each do |h|
|
||||
i += 1
|
||||
end
|
||||
i.should == 2
|
||||
end
|
||||
|
||||
it "is able to skip rows" do
|
||||
ErrorLog.clear_all!
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
ErrorLog.report!(exception, controller, request, nil)
|
||||
i = 0
|
||||
ErrorLog.skip(3) do |h|
|
||||
i += 1
|
||||
end
|
||||
i.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
end
|
75
spec/models/incoming_link_spec.rb
Normal file
75
spec/models/incoming_link_spec.rb
Normal file
|
@ -0,0 +1,75 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe IncomingLink do
|
||||
|
||||
it { should belong_to :topic }
|
||||
it { should validate_presence_of :url }
|
||||
|
||||
it { should ensure_length_of(:referer).is_at_least(3).is_at_most(1000) }
|
||||
it { should ensure_length_of(:domain).is_at_least(1).is_at_most(100) }
|
||||
|
||||
describe 'local topic link' do
|
||||
|
||||
it 'should validate properly' do
|
||||
Fabricate.build(:incoming_link).should be_valid
|
||||
end
|
||||
|
||||
describe 'saving local link' do
|
||||
|
||||
before do
|
||||
@post = Fabricate(:post)
|
||||
@topic = @post.topic
|
||||
@incoming_link = IncomingLink.create(url: "/t/slug/#{@topic.id}/#{@post.post_number}",
|
||||
referer: "http://twitter.com")
|
||||
end
|
||||
|
||||
describe 'incoming link counts' do
|
||||
it "increases the post's incoming link count" do
|
||||
lambda { @incoming_link.save; @post.reload }.should change(@post, :incoming_link_count).by(1)
|
||||
end
|
||||
|
||||
it "increases the topic's incoming link count" do
|
||||
lambda { @incoming_link.save; @topic.reload }.should change(@topic, :incoming_link_count).by(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'after save' do
|
||||
before do
|
||||
@incoming_link.save
|
||||
end
|
||||
|
||||
it 'has a domain' do
|
||||
@incoming_link.domain.should == "twitter.com"
|
||||
end
|
||||
|
||||
it 'has the topic_id' do
|
||||
@incoming_link.topic_id.should == @topic.id
|
||||
end
|
||||
|
||||
it 'has the post_number' do
|
||||
@incoming_link.post_number.should == @post.post_number
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe 'non-topic url' do
|
||||
|
||||
before do
|
||||
@link = Fabricate(:incoming_link_not_topic)
|
||||
end
|
||||
|
||||
it 'has no topic_id' do
|
||||
@link.topic_id.should be_blank
|
||||
end
|
||||
|
||||
it 'has no post_number' do
|
||||
@link.topic_id.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
286
spec/models/invite_spec.rb
Normal file
286
spec/models/invite_spec.rb
Normal file
|
@ -0,0 +1,286 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Invite do
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should have_many :topic_invites }
|
||||
it { should belong_to :invited_by }
|
||||
it { should have_many :topics }
|
||||
it { should validate_presence_of :email }
|
||||
it { should validate_presence_of :invited_by_id }
|
||||
|
||||
context 'user validators' do
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:invite) { Invite.create(email: user.email, invited_by: coding_horror) }
|
||||
|
||||
it "should not allow an invite with the same email as an existing user" do
|
||||
invite.should_not be_valid
|
||||
end
|
||||
|
||||
it "should not allow a user to invite themselves" do
|
||||
invite.email_already_exists.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '#create' do
|
||||
|
||||
context 'saved' do
|
||||
subject { Fabricate(:invite) }
|
||||
its(:invite_key) { should be_present }
|
||||
its(:email_already_exists) { should be_false }
|
||||
|
||||
it 'should store a lower case version of the email' do
|
||||
subject.email.should == "iceking@adventuretime.ooo"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'to a topic' do
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let(:inviter) { topic.user }
|
||||
|
||||
context 'email' do
|
||||
it 'enqueues a job to email the invite' do
|
||||
Jobs.expects(:enqueue).with(:invite_email, has_key(:invite_id))
|
||||
topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'destroyed' do
|
||||
it "can invite the same user after their invite was destroyed" do
|
||||
invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
invite.destroy
|
||||
invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
invite.should be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'after created' do
|
||||
before do
|
||||
@invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
|
||||
it 'belongs to the topic' do
|
||||
topic.invites.should == [@invite]
|
||||
end
|
||||
|
||||
it 'has a topic' do
|
||||
@invite.topics.should == [topic]
|
||||
end
|
||||
|
||||
it 'is pending in the invite list for the creator' do
|
||||
InvitedList.new(inviter).pending.should == [@invite]
|
||||
end
|
||||
|
||||
|
||||
context 'when added by another user' do
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let(:new_invite) { topic.invite_by_email(coding_horror, 'iceking@adventuretime.ooo') }
|
||||
|
||||
it 'returns a different invite' do
|
||||
new_invite.should_not == @invite
|
||||
end
|
||||
|
||||
it 'has a different key' do
|
||||
new_invite.invite_key.should_not == @invite.invite_key
|
||||
end
|
||||
|
||||
it 'has the topic relationship' do
|
||||
new_invite.topics.should == [topic]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding a duplicate' do
|
||||
it 'returns the original invite' do
|
||||
topic.invite_by_email(inviter, 'iceking@adventuretime.ooo').should == @invite
|
||||
end
|
||||
|
||||
it 'matches case insensitively' do
|
||||
topic.invite_by_email(inviter, 'ICEKING@adventuretime.ooo').should == @invite
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding to another topic' do
|
||||
let!(:another_topic) { Fabricate(:topic, user: topic.user) }
|
||||
|
||||
before do
|
||||
@new_invite = another_topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
||||
end
|
||||
|
||||
it 'should be the same invite' do
|
||||
@new_invite.should == @invite
|
||||
end
|
||||
|
||||
it 'belongs to the new topic' do
|
||||
another_topic.invites.should == [@invite]
|
||||
end
|
||||
|
||||
it 'has references to both topics' do
|
||||
@invite.topics.should =~ [topic, another_topic]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'an existing user' do
|
||||
let(:topic) { Fabricate(:topic, archetype: Archetype.private_message) }
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let!(:invite) { topic.invite_by_email(topic.user, coding_horror.email) }
|
||||
|
||||
it "doesn't create an invite" do
|
||||
invite.should be_blank
|
||||
end
|
||||
|
||||
it "gives the user permission to access the topic" do
|
||||
topic.allowed_users.include?(coding_horror).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context '.redeem' do
|
||||
|
||||
let(:invite) { Fabricate(:invite) }
|
||||
|
||||
it 'creates a notification for the invitee' do
|
||||
lambda { invite.redeem }.should change(Notification, :count)
|
||||
end
|
||||
|
||||
it 'wont redeem an expired invite' do
|
||||
SiteSetting.expects(:invite_expiry_days).returns(10)
|
||||
invite.update_column(:created_at, 20.days.ago)
|
||||
invite.redeem.should be_blank
|
||||
end
|
||||
|
||||
it 'wont redeem a deleted invite' do
|
||||
invite.destroy
|
||||
invite.redeem.should be_blank
|
||||
end
|
||||
|
||||
context 'invite trust levels' do
|
||||
|
||||
it "returns the trust level in default_invitee_trust_level" do
|
||||
SiteSetting.stubs(:default_invitee_trust_level).returns(TrustLevel.Levels[:experienced])
|
||||
invite.redeem.trust_level.should == TrustLevel.Levels[:experienced]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'simple invite' do
|
||||
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'returns a user record' do
|
||||
user.is_a?(User).should be_true
|
||||
end
|
||||
|
||||
it 'wants us to send a welcome message' do
|
||||
user.send_welcome_message.should be_true
|
||||
end
|
||||
|
||||
it 'has the default_invitee_trust_level' do
|
||||
user.trust_level.should == SiteSetting.default_invitee_trust_level
|
||||
end
|
||||
|
||||
context 'after redeeming' do
|
||||
before do
|
||||
invite.reload
|
||||
end
|
||||
|
||||
it 'no longer in the pending list for that user' do
|
||||
InvitedList.new(invite.invited_by).pending.should be_blank
|
||||
end
|
||||
|
||||
it 'is redeeemed in the invite list for the creator' do
|
||||
InvitedList.new(invite.invited_by).redeemed.should == [invite]
|
||||
end
|
||||
|
||||
it 'has set the user_id attribute' do
|
||||
invite.user.should == user
|
||||
end
|
||||
|
||||
it 'returns true for redeemed' do
|
||||
invite.should be_redeemed
|
||||
end
|
||||
|
||||
context 'again' do
|
||||
it 'will not redeem twice' do
|
||||
invite.redeem.should == user
|
||||
end
|
||||
|
||||
it "doesn't want us to send a welcome message" do
|
||||
invite.redeem.send_welcome_message.should be_false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'invited to topics' do
|
||||
let!(:topic) { Fabricate(:private_message_topic) }
|
||||
let!(:invite) { topic.invite(topic.user, 'jake@adventuretime.ooo')}
|
||||
|
||||
context 'redeem topic invite' do
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'can see the private topic' do
|
||||
Guardian.new(user).can_see?(topic).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'invited by another user to the same topic' do
|
||||
let(:coding_horror) { User.where(username: 'CodingHorror').first }
|
||||
let!(:another_invite) { topic.invite(coding_horror, 'jake@adventuretime.ooo') }
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'invited by another user to a different topic' do
|
||||
let(:coding_horror) { User.where(username: 'CodingHorror').first }
|
||||
let(:another_topic) { Fabricate(:topic, archetype: "private_message", user: coding_horror) }
|
||||
let!(:another_invite) { another_topic.invite(coding_horror, 'jake@adventuretime.ooo') }
|
||||
let!(:user) { invite.redeem }
|
||||
|
||||
it 'adds the user to the topic_users of the first topic' do
|
||||
topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'adds the user to the topic_users of the second topic' do
|
||||
another_topic.allowed_users.include?(user).should be_true
|
||||
end
|
||||
|
||||
it 'does not redeem the second invite' do
|
||||
another_invite.reload
|
||||
another_invite.should_not be_redeemed
|
||||
end
|
||||
|
||||
context 'if they redeem the other invite afterwards' do
|
||||
|
||||
before do
|
||||
@result = another_invite.redeem
|
||||
end
|
||||
|
||||
it 'returns the same user' do
|
||||
@result.should == user
|
||||
end
|
||||
|
||||
it 'marks the second invite as redeemed' do
|
||||
another_invite.reload
|
||||
another_invite.should be_redeemed
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
18
spec/models/message_bus_observer_spec.rb
Normal file
18
spec/models/message_bus_observer_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MessageBusObserver do
|
||||
|
||||
context 'after create topic' do
|
||||
|
||||
after do
|
||||
@topic = Fabricate(:topic)
|
||||
end
|
||||
|
||||
it 'publishes the topic to the list' do
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
111
spec/models/notification_spec.rb
Normal file
111
spec/models/notification_spec.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Notification do
|
||||
|
||||
it { should validate_presence_of :notification_type }
|
||||
it { should validate_presence_of :data }
|
||||
|
||||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
|
||||
describe 'unread counts' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context 'a regular notification' do
|
||||
it 'increases unread_notifications' do
|
||||
lambda { Fabricate(:notification, user: user); user.reload }.should change(user, :unread_notifications)
|
||||
end
|
||||
|
||||
it "doesn't increase unread_private_messages" do
|
||||
lambda { Fabricate(:notification, user: user); user.reload }.should_not change(user, :unread_private_messages)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a private message' do
|
||||
it "doesn't increase unread_notifications" do
|
||||
lambda { Fabricate(:private_message_notification, user: user); user.reload }.should_not change(user, :unread_notifications)
|
||||
end
|
||||
|
||||
it "increases unread_private_messages" do
|
||||
lambda { Fabricate(:private_message_notification, user: user); user.reload }.should change(user, :unread_private_messages)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'message bus' do
|
||||
|
||||
it 'updates the notification count on create' do
|
||||
MessageBusObserver.any_instance.expects(:refresh_notification_count).with(instance_of(Notification))
|
||||
Fabricate(:notification)
|
||||
end
|
||||
|
||||
context 'destroy' do
|
||||
|
||||
let!(:notification) { Fabricate(:notification) }
|
||||
|
||||
it 'updates the notification count on destroy' do
|
||||
MessageBusObserver.any_instance.expects(:refresh_notification_count).with(instance_of(Notification))
|
||||
notification.destroy
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe '@mention' do
|
||||
|
||||
it "calls email_user_mentioned on creating a notification" do
|
||||
UserEmailObserver.any_instance.expects(:email_user_mentioned).with(instance_of(Notification))
|
||||
Fabricate(:notification)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '@mention' do
|
||||
it "calls email_user_quoted on creating a quote notification" do
|
||||
UserEmailObserver.any_instance.expects(:email_user_quoted).with(instance_of(Notification))
|
||||
Fabricate(:quote_notification)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'private message' do
|
||||
before do
|
||||
@topic = Fabricate(:private_message_topic)
|
||||
@post = Fabricate(:post, :topic => @topic, :user => @topic.user)
|
||||
@target = @post.topic.topic_allowed_users.reject{|a| a.user_id == @post.user_id}[0].user
|
||||
end
|
||||
|
||||
it 'should create a private message notification' do
|
||||
@target.notifications.first.notification_type.should == Notification.Types[:private_message]
|
||||
end
|
||||
|
||||
it 'should not add a pm notification for the creator' do
|
||||
@post.user.unread_notifications.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '.post' do
|
||||
|
||||
let(:post) { Fabricate(:post) }
|
||||
let!(:notification) { Fabricate(:notification, user: post.user, topic: post.topic, post_number: post.post_number) }
|
||||
|
||||
it 'returns the post' do
|
||||
notification.post.should == post
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'data' do
|
||||
let(:notification) { Fabricate.build(:notification) }
|
||||
|
||||
it 'should have a data hash' do
|
||||
notification.data_hash.should be_present
|
||||
end
|
||||
|
||||
it 'should have the data within the json' do
|
||||
notification.data_hash[:poison].should == 'ivy'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue