Merge branch 'master' of github.com:discourse/discourse

This commit is contained in:
Neil Lalonde 2013-07-05 10:57:05 -04:00
commit 7bcf1c5aaa
32 changed files with 402 additions and 174 deletions

View File

@ -13,18 +13,6 @@ Discourse.URL = Em.Object.createWithMixins({
// Used for matching a /more URL // Used for matching a /more URL
MORE_REGEXP: /\/more$/, MORE_REGEXP: /\/more$/,
/**
@private
Get a handle on the application's router. Note that currently it uses `__container__` which is not
advised but there is no other way to access the router.
@method router
**/
router: function() {
return Discourse.__container__.lookup('router:main');
}.property(),
/** /**
Browser aware replaceState. Will only be invoked if the browser supports it. Browser aware replaceState. Will only be invoked if the browser supports it.
@ -70,11 +58,65 @@ Discourse.URL = Em.Object.createWithMixins({
path = path.replace(rootURL, ''); path = path.replace(rootURL, '');
} }
/* // TODO: Extract into rules we can inject into the URL handler
If the URL is in the topic form, /t/something/:topic_id/:post_number if (this.navigatedToHome(oldPath, path)) { return; }
then we want to apply some special logic. If the post_number changes within the if (this.navigatedToListMore(oldPath, path)) { return; }
same topic, use replaceState and instruct our controller to load more posts. if (this.navigatedToHome(oldPath, path)) { return; }
*/
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
var router = this.get('router');
router.router.updateURL(path);
return router.handleURL(path);
},
/**
Replaces the query parameters in the URL. Use no parameters to clear them.
@method replaceQueryParams
**/
queryParams: Em.computed.alias('router.location.queryParams'),
/**
Redirect to a URL.
This has been extracted so it can be tested.
@method redirectTo
**/
redirectTo: function(url) {
window.location = Discourse.getURL(url);
},
/**
@private
If we're viewing more topics, scroll to where we were previously.
@method navigatedToListMore
@param {String} oldPath the previous path we were on
@param {String} path the path we're navigating to
**/
navigatedToListMore: function(oldPath, path) {
// If we transition from a /more path, scroll to the top
if (this.MORE_REGEXP.exec(oldPath) && (oldPath.indexOf(path) === 0)) {
window.scrollTo(0, 0);
}
return false;
},
/**
@private
If the URL is in the topic form, /t/something/:topic_id/:post_number
then we want to apply some special logic. If the post_number changes within the
same topic, use replaceState and instruct our controller to load more posts.
@method navigatedToPost
@param {String} oldPath the previous path we were on
@param {String} path the path we're navigating to
**/
navigatedToPost: function(oldPath, path) {
var newMatches = this.TOPIC_REGEXP.exec(path), var newMatches = this.TOPIC_REGEXP.exec(path),
newTopicId = newMatches ? newMatches[2] : null; newTopicId = newMatches ? newMatches[2] : null;
@ -99,28 +141,33 @@ Discourse.URL = Em.Object.createWithMixins({
}); });
// Abort routing, we have replaced our state. // Abort routing, we have replaced our state.
return; return true;
} }
} }
// If we transition from a /more path, scroll to the top return false;
if (this.MORE_REGEXP.exec(oldPath) && (oldPath.indexOf(path) === 0)) {
window.scrollTo(0, 0);
}
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
var router = this.get('router');
router.router.updateURL(path);
return router.handleURL(path);
}, },
/** /**
Replaces the query parameters in the URL. Use no parameters to clear them. @private
@method replaceQueryParams Handle the custom case of routing to the root path from itself.
@param {String} oldPath the previous path we were on
@param {String} path the path we're navigating to
**/ **/
queryParams: Em.computed.alias('router.location.queryParams'), navigatedToHome: function(oldPath, path) {
var defaultFilter = "/" + Discourse.ListController.filters[0];
if (path === "/" && (oldPath === "/" || oldPath === defaultFilter)) {
// Refresh our list
this.controllerFor('list').refresh();
return true;
}
return false;
},
/** /**
@private @private
@ -137,13 +184,27 @@ Discourse.URL = Em.Object.createWithMixins({
/** /**
@private @private
Redirect to a URL. Get a handle on the application's router. Note that currently it uses `__container__` which is not
This has been extracted so it can be tested. advised but there is no other way to access the router.
@method redirectTo @property router
**/ **/
redirectTo: function(url) { router: function() {
window.location = Discourse.getURL(url); return Discourse.__container__.lookup('router:main');
}.property(),
/**
@private
Get a controller. Note that currently it uses `__container__` which is not
advised but there is no other way to access the router.
@method controllerFor
@param {String} name the name of the controller
**/
controllerFor: function(name) {
return Discourse.__container__.lookup('controller:' + name);
} }
}); });

View File

@ -172,6 +172,11 @@ Discourse.Utilities = {
return false; return false;
} else if (files.length > 0) { } else if (files.length > 0) {
var upload = files[0]; var upload = files[0];
// ensures that new users can upload image
if (Discourse.User.current('trust_level') === 0 && Discourse.SiteSettings.newuser_max_images === 0) {
bootbox.alert(Em.String.i18n('post.errors.upload_not_allowed_for_new_user'));
return false;
}
// if the image was pasted, sets its name to a default one // if the image was pasted, sets its name to a default one
if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; } if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; }
// check that the uploaded file is authorized // check that the uploaded file is authorized

View File

@ -24,6 +24,19 @@ Discourse.ListController = Discourse.Controller.extend({
}); });
}.property(), }.property(),
/**
Refresh our current topic list
@method refresh
**/
refresh: function() {
var listTopicsController = this.get('controllers.listTopics');
listTopicsController.set('model.loaded', false);
this.load(this.get('filterMode')).then(function (topicList) {
listTopicsController.set('model', topicList);
});
},
/** /**
Load a list based on a filter Load a list based on a filter

View File

@ -20,7 +20,7 @@ Discourse.StaticController = Discourse.Controller.extend({
text = text[1]; text = text[1];
this.set('content', text); this.set('content', text);
} else { } else {
return Discourse.ajax(path + ".json", {dataType: 'html'}).then(function (result) { return Discourse.ajax(path).then(function (result) {
staticController.set('content', result); staticController.set('content', result);
}); });
} }

View File

@ -17,13 +17,18 @@ Discourse.UserStream = Discourse.Model.extend({
}.observes('filter'), }.observes('filter'),
findItems: function() { findItems: function() {
var url = Discourse.getURL("/user_actions?offset=") + this.get('itemsLoaded') + "&username=" + (this.get('user.username_lower')); var me = this;
if(this.get("loading")) { return; }
this.set("loading",true);
var url = Discourse.getURL("/user_actions.json?offset=") + this.get('itemsLoaded') + "&username=" + (this.get('user.username_lower'));
if (this.get('filter')) { if (this.get('filter')) {
url += "&filter=" + (this.get('filter')); url += "&filter=" + (this.get('filter'));
} }
var stream = this; var stream = this;
return Discourse.ajax(url, {cache: 'false'}).then( function(result) { return Discourse.ajax(url, {cache: 'false'}).then( function(result) {
me.set("loading",false);
if (result && result.user_actions) { if (result && result.user_actions) {
var copy = Em.A(); var copy = Em.A();
_.each(result.user_actions,function(action) { _.each(result.user_actions,function(action) {
@ -33,7 +38,7 @@ Discourse.UserStream = Discourse.Model.extend({
stream.get('content').pushObjects(copy); stream.get('content').pushObjects(copy);
stream.set('itemsLoaded', stream.get('itemsLoaded') + result.user_actions.length); stream.set('itemsLoaded', stream.get('itemsLoaded') + result.user_actions.length);
} }
}); }, function(){ me.set("loading", false); });
} }
}); });

View File

@ -31,7 +31,7 @@ Discourse.FilteredListRoute = Discourse.Route.extend({
var listTopicsController = this.controllerFor('listTopics'); var listTopicsController = this.controllerFor('listTopics');
listController.set('filterMode', this.filter); listController.set('filterMode', this.filter);
var listContent = listTopicsController.get('content'); var listContent = listTopicsController.get('model');
if (listContent) { if (listContent) {
listContent.set('loaded', false); listContent.set('loaded', false);
} }
@ -39,7 +39,7 @@ Discourse.FilteredListRoute = Discourse.Route.extend({
listController.load(this.filter).then(function(topicList) { listController.load(this.filter).then(function(topicList) {
listController.set('category', null); listController.set('category', null);
listController.set('canCreateTopic', topicList.get('can_create_topic')); listController.set('canCreateTopic', topicList.get('can_create_topic'));
listTopicsController.set('content', topicList); listTopicsController.set('model', topicList);
}); });
} }
}); });

View File

@ -255,7 +255,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
@method updatePosition @method updatePosition
**/ **/
updatePosition: function() { updatePosition: function() {
this.processSeenPosts(); this.processSeenPosts();
var offset = window.pageYOffset || $('html').scrollTop(); var offset = window.pageYOffset || $('html').scrollTop();
@ -277,7 +276,10 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
// Dock the counter if necessary // Dock the counter if necessary
var $lastPost = $('article[data-post-id=' + topic.get('postStream.lastPostId') + "]"); var $lastPost = $('article[data-post-id=' + topic.get('postStream.lastPostId') + "]");
var lastPostOffset = $lastPost.offset(); var lastPostOffset = $lastPost.offset();
if (!lastPostOffset) { return; } if (!lastPostOffset) {
this.set('controller.dockedCounter', false);
return;
}
this.set('controller.dockedCounter', (offset >= (lastPostOffset.top + $lastPost.height()) - $(window).height())); this.set('controller.dockedCounter', (offset >= (lastPostOffset.top + $lastPost.height()) - $(window).height()));
}, },

View File

@ -140,9 +140,22 @@ class Users::OmniauthCallbacksController < ApplicationController
def create_or_sign_on_user_using_cas(auth_token) def create_or_sign_on_user_using_cas(auth_token)
logger.error "authtoken #{auth_token}" logger.error "authtoken #{auth_token}"
email = "#{auth_token[:extra][:user]}@#{SiteSetting.cas_domainname}"
email = auth_token[:info][:email] if auth_token[:info]
email ||= if SiteSetting.cas_domainname.present?
"#{auth_token[:extra][:user]}@#{SiteSetting.cas_domainname}"
else
auth_token[:extra][:user]
end
username = auth_token[:extra][:user] username = auth_token[:extra][:user]
name = auth_token["uid"]
name = if auth_token[:info] && auth_token[:info][:name]
auth_token[:info][:name]
else
auth_token["uid"]
end
cas_user_id = auth_token["uid"] cas_user_id = auth_token["uid"]
session[:authentication] = { session[:authentication] = {

View File

@ -3,7 +3,6 @@ class IncomingLink < ActiveRecord::Base
belongs_to :user belongs_to :user
validates :url, presence: true validates :url, presence: true
validate :referer_valid validate :referer_valid
before_validation :extract_domain before_validation :extract_domain
@ -40,7 +39,8 @@ class IncomingLink < ActiveRecord::Base
# Internal: Extract the domain from link. # Internal: Extract the domain from link.
def extract_domain def extract_domain
if referer.present? if referer.present?
self.domain = URI.parse(referer).host self.domain = URI.parse(self.referer).host
self.referer = nil unless self.domain
end end
end end
@ -50,6 +50,7 @@ class IncomingLink < ActiveRecord::Base
parsed = URI.parse(url) parsed = URI.parse(url)
begin begin
# TODO achieve same thing with no exception
params = Rails.application.routes.recognize_path(parsed.path) params = Rails.application.routes.recognize_path(parsed.path)
self.topic_id = params[:topic_id] self.topic_id = params[:topic_id]
self.post_number = params[:post_number] self.post_number = params[:post_number]

View File

@ -205,7 +205,7 @@ class SiteSetting < ActiveRecord::Base
setting(:max_word_length, 30) setting(:max_word_length, 30)
setting(:newuser_max_links, 2) setting(:newuser_max_links, 2)
setting(:newuser_max_images, 0) client_setting(:newuser_max_images, 0)
setting(:newuser_spam_host_threshold, 3) setting(:newuser_spam_host_threshold, 3)

View File

@ -34,6 +34,11 @@ class Upload < ActiveRecord::Base
return unless width > SiteSetting.auto_link_images_wider_than return unless width > SiteSetting.auto_link_images_wider_than
return if has_thumbnail? return if has_thumbnail?
thumbnail = OptimizedImage.create_for(self, width, height) thumbnail = OptimizedImage.create_for(self, width, height)
# TODO: @regis we may want to do the more cleanly, create_for may change the dimensions
# this avoids a duplicate key, it should be done more cleanly, this is cheating
thumbnail.width = width
thumbnail.height = height
optimized_images << thumbnail if thumbnail optimized_images << thumbnail if thumbnail
end end

View File

@ -6,6 +6,7 @@ development:
pool: 5 pool: 5
timeout: 5000 timeout: 5000
host_names: host_names:
# /!\ Make sure you *also* restart sidekiq if you change this setting /!\
- "localhost" - "localhost"
# Warning: The database defined as "test" will be erased and # Warning: The database defined as "test" will be erased and
@ -29,6 +30,7 @@ production:
pool: 5 pool: 5
timeout: 5000 timeout: 5000
host_names: host_names:
# /!\ Make sure you *also* restart sidekiq if you change this setting /!\
- production.localhost # Update this to be the domain of your production site - production.localhost # Update this to be the domain of your production site
profile: profile:

View File

@ -8,6 +8,7 @@ production:
timeout: 5000 timeout: 5000
# db_id: 0 # database ID if hosting multiple sites # db_id: 0 # database ID if hosting multiple sites
host_names: host_names:
# /!\ Make sure you *also* restart sidekiq if you change this setting /!\
- production.localhost # Update this to be the domain of your production site - production.localhost # Update this to be the domain of your production site
test: test:

View File

@ -746,6 +746,7 @@ en:
upload_too_large: "Sorry, the file you are trying to upload is too big (maximum size is {{max_size_kb}}kb), please resize it and try again." upload_too_large: "Sorry, the file you are trying to upload is too big (maximum size is {{max_size_kb}}kb), please resize it and try again."
too_many_uploads: "Sorry, you can only upload one file at a time." too_many_uploads: "Sorry, you can only upload one file at a time."
upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extension: {{authorized_extensions}})." upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extension: {{authorized_extensions}})."
upload_not_allowed_for_new_user: "Sorry, new users can not upload images."
abandon: "Are you sure you want to abandon your post?" abandon: "Are you sure you want to abandon your post?"

View File

@ -729,6 +729,7 @@ fr:
upload_too_large: "Désolé, le fichier que vous êtes en train d'envoyer est trop grand (maximum {{max_size_kb}}Kb). Merci de le redimensionner et de réessayer." upload_too_large: "Désolé, le fichier que vous êtes en train d'envoyer est trop grand (maximum {{max_size_kb}}Kb). Merci de le redimensionner et de réessayer."
too_many_uploads: "Désolé, vous ne pouvez envoyer qu'un seul fichier à la fois." too_many_uploads: "Désolé, vous ne pouvez envoyer qu'un seul fichier à la fois."
upload_not_authorized: "Désole, le fichier que vous êtes en train d'uploader n'est pas autorisé (extensions autorisées : {{authorized_extensions}})." upload_not_authorized: "Désole, le fichier que vous êtes en train d'uploader n'est pas autorisé (extensions autorisées : {{authorized_extensions}})."
upload_not_allowed_for_new_user: "Désolé, les nouveaux utilisateurs ne peuvent pas uploader d'images."
abandon: "Voulez-vous vraiment abandonner ce message ?" abandon: "Voulez-vous vraiment abandonner ce message ?"

View File

@ -2,9 +2,9 @@ module Jobs
class CloseTopic < Jobs::Base class CloseTopic < Jobs::Base
def execute(args) def execute(args)
topic = Topic.find(args[:topic_id]) topic = Topic.where(id: args[:topic_id]).first
if topic and topic.auto_close_at and !topic.closed? and !topic.deleted_at if topic and topic.auto_close_at and !topic.closed? and !topic.deleted_at
closer = User.find(args[:user_id]) closer = User.where(id: args[:user_id]).first
if Guardian.new(closer).can_moderate?(topic) if Guardian.new(closer).can_moderate?(topic)
topic.update_status('autoclosed', true, closer) topic.update_status('autoclosed', true, closer)
end end

View File

@ -0,0 +1,50 @@
require 'open-uri'
desc 'Creates the integration fixtures. Requires a development instance running.'
task 'integration:create_fixtures' => :environment do
fixtures = {
list: ["/latest.json", "/categories.json", "/category/bug.json"],
topic: ["/t/280.json"],
user: ["/users/eviltrout.json", "/user_actions.json?offset=0&username=eviltrout"],
static: ["/faq", '/tos', '/privacy']
}
fixtures.each do |type, urls|
filename = "#{Rails.root}/test/javascripts/fixtures/#{type}_fixtures.js"
content = "/*jshint maxlen:10000000 */\n"
urls.each do |url|
http_result = fake_xhr("http://localhost:3000#{url}")
# If the result is not JSON, convert it to JSON
begin
parsed = ::JSON.parse(http_result)
rescue
http_result = http_result.to_json
end
content << "Discourse.URL_FIXTURES[\"#{url}\"] = #{http_result};\n"
end
File.write(filename, content)
end
end
def fake_xhr(url)
uri = URI(url)
result = nil
Net::HTTP.start(uri.host, uri.port) do |http|
request = Net::HTTP::Get.new uri
request.add_field "X-Requested-With", "XMLHttpRequest"
response = http.request(request)
result = response.body.force_encoding("UTF-8")
end
result
end

View File

@ -8,34 +8,39 @@ task 'posts:refresh_oneboxes' => :environment do
rebake_posts invalidate_oneboxes: true rebake_posts invalidate_oneboxes: true
end end
def rebake_post(post,opts)
cooked = post.cook(
post.raw,
topic_id: post.topic_id,
invalidate_oneboxes: opts.fetch(:invalidate_oneboxes, false)
)
if cooked != post.cooked
Post.exec_sql(
'update posts set cooked = ? where id = ?', cooked, post.id
)
post.cooked = cooked
putc "#"
else
putc "."
end
TopicLink.extract_from post
# make sure we trigger the post process
post.trigger_post_process
rescue => e
puts "\n\nFailed to bake topic_id #{post.topic_id} post_id #{post.id} #{e}\n#{e.backtrace.join("\n")} \n\n"
end
def rebake_posts(opts = {}) def rebake_posts(opts = {})
RailsMultisite::ConnectionManagement.each_connection do |db| RailsMultisite::ConnectionManagement.each_connection do |db|
puts "Re baking post markdown for #{db} , changes are denoted with # , no change with ." puts "Re baking post markdown for #{db} , changes are denoted with # , no change with ."
total = Post.select([ total = 0
Post.select([
:id, :user_id, :cooked, :raw, :topic_id, :post_number :id, :user_id, :cooked, :raw, :topic_id, :post_number
]).inject(0) do |total, post| ]).each do |post|
cooked = post.cook( rebake_post(post,opts)
post.raw,
topic_id: post.topic_id,
invalidate_oneboxes: opts.fetch(:invalidate_oneboxes, false)
)
if cooked != post.cooked
Post.exec_sql(
'update posts set cooked = ? where id = ?', cooked, post.id
)
post.cooked = cooked
putc "#"
else
putc "."
end
TopicLink.extract_from post
# make sure we trigger the post process
post.trigger_post_process
total += 1 total += 1
end end

View File

@ -8,16 +8,16 @@ describe Jobs::CloseTopic do
it 'closes a topic that is set to auto-close' do it 'closes a topic that is set to auto-close' do
topic = Fabricate.build(:topic, auto_close_at: Time.zone.now, user: admin) topic = Fabricate.build(:topic, auto_close_at: Time.zone.now, user: admin)
topic.expects(:update_status).with('autoclosed', true, admin) topic.expects(:update_status).with('autoclosed', true, admin)
Topic.stubs(:find).returns(topic) Topic.stubs(:where).returns([topic])
User.stubs(:find).returns(admin) User.stubs(:where).returns([admin])
Jobs::CloseTopic.new.execute( topic_id: 123, user_id: 234 ) Jobs::CloseTopic.new.execute( topic_id: 123, user_id: 234 )
end end
shared_examples_for "cases when CloseTopic does nothing" do shared_examples_for "cases when CloseTopic does nothing" do
it 'does nothing to the topic' do it 'does nothing to the topic' do
topic.expects(:update_status).never topic.expects(:update_status).never
Topic.stubs(:find).returns(topic) Topic.stubs(:where).returns([topic])
User.stubs(:find).returns(admin) User.stubs(:where).returns([admin])
Jobs::CloseTopic.new.execute( topic_id: 123, user_id: 234 ) Jobs::CloseTopic.new.execute( topic_id: 123, user_id: 234 )
end end
end end

View File

@ -3,7 +3,49 @@ require 'spec_helper'
describe Users::OmniauthCallbacksController do describe Users::OmniauthCallbacksController do
let(:auth) { {info: {email: 'eviltrout@made.up.email', name: 'Robin Ward', uid: 123456789}, "extra" => {"raw_info" => {} } } } let(:auth) { {info: {email: 'eviltrout@made.up.email', name: 'Robin Ward', uid: 123456789}, "extra" => {"raw_info" => {} } } }
let(:cas_auth) {{ uid: "caluser2", extra: {user: "caluser2"} } } let(:cas_auth) { { 'uid' => 'casuser', extra: { user: 'casuser'} } }
shared_examples_for "an authenticaton provider" do |provider|
context "when #{provider} logins are disabled" do
before do
SiteSetting.stubs("enable_#{provider}_logins?").returns(false)
end
it "fails" do
get :complete, provider: provider
response.should_not be_success
end
end
context "when #{provider} logins are enabled" do
before do
SiteSetting.stubs("enable_#{provider}_logins?").returns(true)
end
it "succeeds" do
get :complete, provider: provider
response.should be_success
end
context "and 'invite only' site setting is enabled" do
before do
SiteSetting.stubs(:invite_only?).returns(true)
end
it "informs the user they are awaiting approval" do
xhr :get, :complete, provider: provider, format: :json
expect(
JSON.parse(response.body)['awaiting_approval']
).to be_true
end
end
end
end
describe 'invalid provider' do describe 'invalid provider' do
it "fails" do it "fails" do
@ -20,29 +62,8 @@ describe Users::OmniauthCallbacksController do
request.env["omniauth.auth"] = auth request.env["omniauth.auth"] = auth
end end
it "fails when twitter logins are disabled" do it_behaves_like "an authenticaton provider", 'twitter'
SiteSetting.stubs(:enable_twitter_logins?).returns(false)
get :complete, provider: 'twitter'
response.should_not be_success
end
it "succeeds when twitter logins are enabled" do
SiteSetting.stubs(:enable_twitter_logins?).returns(true)
get :complete, provider: 'twitter'
response.should be_success
end
context "when 'invite only' site setting is enabled" do
before { SiteSetting.stubs(:invite_only?).returns(true) }
it 'informs the user they are awaiting approval' do
xhr :get, :complete, provider: 'twitter', format: :json
expect(
JSON.parse(response.body)['awaiting_approval']
).to be_true
end
end
end end
describe 'facebook' do describe 'facebook' do
@ -51,17 +72,7 @@ describe Users::OmniauthCallbacksController do
request.env["omniauth.auth"] = auth request.env["omniauth.auth"] = auth
end end
it "fails when facebook logins are disabled" do it_behaves_like "an authenticaton provider", 'facebook'
SiteSetting.stubs(:enable_facebook_logins?).returns(false)
get :complete, provider: 'facebook'
response.should_not be_success
end
it "succeeds when facebook logins are enabled" do
SiteSetting.stubs(:enable_facebook_logins?).returns(true)
get :complete, provider: 'facebook'
response.should be_success
end
end end
@ -71,16 +82,47 @@ describe Users::OmniauthCallbacksController do
request.env["omniauth.auth"] = cas_auth request.env["omniauth.auth"] = cas_auth
end end
it "fails when cas logins are disabled" do it_behaves_like "an authenticaton provider", 'cas'
SiteSetting.stubs(:enable_cas_logins?).returns(false)
get :complete, provider: 'cas' describe "extracted user data" do
response.should_not be_success before do
end SiteSetting.stubs(:enable_cas_logins?).returns(true)
end
subject {
xhr :get, :complete, provider: 'cas', format: :json
OpenStruct.new(JSON.parse(response.body))
}
context "when no user infos are returned by cas" do
its(:username) { should eq 'casuser' }
its(:name) { should eq 'casuser' }
its(:email) { should eq 'casuser' } # No cas_domainname configured!
context "when cas_domainname is configured" do
before do
SiteSetting.stubs(:cas_domainname).returns("example.com")
end
its(:email) { should eq 'casuser@example.com' }
end
end
context "when user infos are returned by cas" do
before do
request.env["omniauth.auth"] = cas_auth.merge({
info: {
name: 'Proper Name',
email: 'public@example.com'
}
})
end
its(:username) { should eq 'casuser' }
its(:name) { should eq 'Proper Name' }
its(:email) { should eq 'public@example.com' }
end
it "succeeds when cas logins are enabled" do
SiteSetting.stubs(:enable_cas_logins?).returns(true)
get :complete, provider: 'cas'
response.should be_success
end end
end end
@ -93,31 +135,11 @@ describe Users::OmniauthCallbacksController do
end end
describe "google" do describe "google" do
it "fails when google logins are disabled" do it_behaves_like "an authenticaton provider", 'google'
SiteSetting.stubs(:enable_google_logins?).returns(false)
get :complete, provider: 'google'
response.should_not be_success
end
it "succeeds when google logins are enabled" do
SiteSetting.stubs(:enable_google_logins?).returns(true)
get :complete, provider: 'google'
response.should be_success
end
end end
describe "yahoo" do describe "yahoo" do
it "fails when yahoo logins are disabled" do it_behaves_like "an authenticaton provider", 'yahoo'
SiteSetting.stubs(:enable_yahoo_logins?).returns(false)
get :complete, provider: 'yahoo'
response.should_not be_success
end
it "succeeds when yahoo logins are enabled" do
SiteSetting.stubs(:enable_yahoo_logins?).returns(true)
get :complete, provider: 'yahoo'
response.should be_success
end
end end
end end
@ -128,17 +150,7 @@ describe Users::OmniauthCallbacksController do
request.env["omniauth.auth"] = auth request.env["omniauth.auth"] = auth
end end
it "fails when github logins are disabled" do it_behaves_like "an authenticaton provider", 'github'
SiteSetting.stubs(:enable_github_logins?).returns(false)
get :complete, provider: 'github'
response.should_not be_success
end
it "succeeds when github logins are enabled" do
SiteSetting.stubs(:enable_github_logins?).returns(true)
get :complete, provider: 'github'
response.should be_success
end
end end
@ -148,17 +160,7 @@ describe Users::OmniauthCallbacksController do
request.env["omniauth.auth"] = auth request.env["omniauth.auth"] = auth
end end
it "fails when persona logins are disabled" do it_behaves_like "an authenticaton provider", 'persona'
SiteSetting.stubs(:enable_persona_logins?).returns(false)
get :complete, provider: 'persona'
response.should_not be_success
end
it "succeeds when persona logins are enabled" do
SiteSetting.stubs(:enable_persona_logins?).returns(true)
get :complete, provider: 'persona'
response.should be_success
end
end end

View File

@ -48,6 +48,10 @@ describe IncomingLink do
TestRequest.new(env) TestRequest.new(env)
end end
it "does not explode with bad referer" do
IncomingLink.add(req('http://sam.com','file:///Applications/Install/75067ABC-C9D1-47B7-8ACE-76AEDE3911B2/Install/'))
end
it "does nothing if referer is empty" do it "does nothing if referer is empty" do
IncomingLink.expects(:create).never IncomingLink.expects(:create).never
IncomingLink.add(req('http://somesite.com')) IncomingLink.add(req('http://somesite.com'))

View File

@ -23,6 +23,15 @@ test("uploading one file", function() {
ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.too_many_uploads'))); ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.too_many_uploads')));
}); });
test("new user", function() {
Discourse.SiteSettings.newuser_max_images = 0;
this.stub(Discourse.User, 'current').withArgs("trust_level").returns(0);
this.stub(bootbox, "alert");
ok(!validUpload([1]));
ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.upload_not_allowed_for_new_user')));
});
test("ensures an authorized upload", function() { test("ensures an authorized upload", function() {
var html = { name: "unauthorized.html" }; var html = { name: "unauthorized.html" };
var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", "); var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", ");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
function integration(name) { function integration(name) {
module(name, { module("Integration: " + name, {
setup: function() { setup: function() {
sinon.stub(Discourse.ScrollingDOMMethods, "bindOnScroll"); sinon.stub(Discourse.ScrollingDOMMethods, "bindOnScroll");
sinon.stub(Discourse.ScrollingDOMMethods, "unbindOnScroll"); sinon.stub(Discourse.ScrollingDOMMethods, "unbindOnScroll");

View File

@ -1,24 +1,29 @@
integration("List Topics"); integration("List Topics");
test("Default List", function() { test("Default List", function() {
expect(2);
visit("/").then(function() { visit("/").then(function() {
expect(2);
ok(exists("#topic-list"), "The list of topics was rendered"); ok(exists("#topic-list"), "The list of topics was rendered");
ok(exists('#topic-list .topic-list-item'), "has topics"); ok(exists('#topic-list .topic-list-item'), "has topics");
}); });
});
test("List one Category", function() {
expect(2);
visit("/category/bug").then(function() {
ok(exists("#topic-list"), "The list of topics was rendered");
ok(exists('#topic-list .topic-list-item'), "has topics");
});
}); });
test("Categories List", function() { test("Categories List", function() {
expect(1);
visit("/categories").then(function() { visit("/categories").then(function() {
expect(1);
ok(exists('.category-list-item'), "has a list of categories"); ok(exists('.category-list-item'), "has a list of categories");
}); });
}); });

View File

@ -0,0 +1,22 @@
integration("Static");
test("Faq", function() {
expect(1);
visit("/faq").then(function() {
ok(exists(".body-page"), "The content is present");
});
});
test("Terms of Service", function() {
expect(1);
visit("/tos").then(function() {
ok(exists(".body-page"), "The content is present");
});
});
test("Privacy", function() {
expect(1);
visit("/privacy").then(function() {
ok(exists(".body-page"), "The content is present");
});
});

View File

@ -0,0 +1,12 @@
integration("User");
test("Profile", function() {
visit("/users/eviltrout").then(function() {
expect(2);
ok(exists(".user-heading"), "The heading is rendered");
ok(exists("#user-stream"), "The stream is rendered");
});
});

View File

@ -1,6 +1,6 @@
integration("View Topic"); integration("View Topic");
test("View a Topic", function() { test("Enter a Topic", function() {
visit("/t/internationalization-localization/280").then(function() { visit("/t/internationalization-localization/280").then(function() {
expect(2); expect(2);

View File

@ -70,6 +70,7 @@ d.write('<style>#ember-testing-container { position: absolute; background: white
Discourse.rootElement = '#ember-testing'; Discourse.rootElement = '#ember-testing';
Discourse.setupForTesting(); Discourse.setupForTesting();
Discourse.injectTestHelpers(); Discourse.injectTestHelpers();
Discourse.bindDOMEvents();
Discourse.Router.map(function() { Discourse.Router.map(function() {