diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e590127d13d..5f71482907f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -10,7 +10,7 @@ on Discourse with: 1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads 2. Install Vagrant: https://www.vagrantup.com/ 3. Open a terminal -4. Clone the project: `git@github.com:discourse/discourse.git` +4. Clone the project: `git clone git@github.com:discourse/discourse.git` 5. Enter the project directory: `cd discourse` ### Using Vagrant @@ -51,7 +51,7 @@ bundle exec rails server In a few seconds, rails will start server pages. To access them, open a web browser to http://localhost:4000 - if it all worked you should see discourse! Congratulations, you are ready to start working! -You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changed. +You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changes. ### Guard + Rspec @@ -71,6 +71,12 @@ Wait a minute while it runs all our unit tests. Once it has completed, live relo ### Sending Email +Mail is sent asynchronously by Sidekiq, so you'll need to have sidekiq running to process jobs. Run it with this command: + +``` +bundle exec sidekiq +``` + Mailcatcher is used to avoid the whole issue of actually sending emails: https://github.com/sj26/mailcatcher To start mailcatcher, run the following command in the vagrant image: diff --git a/Gemfile.lock b/Gemfile.lock index 9e24d9e1bbd..5b15df96bb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,34 +63,34 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.11) - actionpack (= 3.2.11) + actionmailer (3.2.12) + actionpack (= 3.2.12) mail (~> 2.4.4) - actionpack (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) + actionpack (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.4) - rack (~> 1.4.0) + rack (~> 1.4.5) rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) - activemodel (3.2.11) - activesupport (= 3.2.11) + activemodel (3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) - activerecord (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) + activerecord (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) arel (~> 3.0.2) tzinfo (~> 0.3.29) activerecord-postgres-hstore (0.7.1) rails rake - activeresource (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - activesupport (3.2.11) + activeresource (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) + activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) acts_as_paranoid (0.4.1) @@ -194,7 +194,7 @@ GEM jquery-rails (2.2.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.7.6) + json (1.7.7) koala (1.6.0) addressable (~> 2.2) faraday (~> 0.8) @@ -208,10 +208,10 @@ GEM treetop (~> 1.4.8) metaclass (0.0.1) method_source (0.8.1) - mime-types (1.20.1) + mime-types (1.21) mocha (0.10.5) metaclass (~> 0.0.1) - multi_json (1.5.0) + multi_json (1.5.1) multipart-post (1.1.5) mustache (0.99.4) net-scp (1.0.4) @@ -239,17 +239,17 @@ GEM rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.11) - actionmailer (= 3.2.11) - actionpack (= 3.2.11) - activerecord (= 3.2.11) - activeresource (= 3.2.11) - activesupport (= 3.2.11) + rails (3.2.12) + actionmailer (= 3.2.12) + actionpack (= 3.2.12) + activerecord (= 3.2.12) + activeresource (= 3.2.12) + activesupport (= 3.2.12) bundler (~> 1.0) - railties (= 3.2.11) - railties (3.2.11) - actionpack (= 3.2.11) - activesupport (= 3.2.11) + railties (= 3.2.12) + railties (3.2.12) + actionpack (= 3.2.12) + activesupport (= 3.2.12) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) @@ -258,7 +258,7 @@ GEM rb-fsevent (0.9.3) rb-inotify (0.8.8) ffi (>= 0.5.0) - rdoc (3.12) + rdoc (3.12.1) json (~> 1.4) redis (3.0.2) redis-actionpack (3.2.3) diff --git a/app/assets/javascripts/discourse/components/autocomplete.js.coffee b/app/assets/javascripts/discourse/components/autocomplete.js.coffee index bc6e3dabd96..dc13f2bc187 100644 --- a/app/assets/javascripts/discourse/components/autocomplete.js.coffee +++ b/app/assets/javascripts/discourse/components/autocomplete.js.coffee @@ -84,9 +84,11 @@ ul = div.find('ul') selectedOption = 0 markSelected() + ul.find('li').click -> selectedOption = ul.find('li').index(this) completeTerm(autocompleteOptions[selectedOption]) + false pos = null if isInput diff --git a/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars b/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars index b61798b62ad..550f7118f91 100644 --- a/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars @@ -1,5 +1,4 @@

{{i18n private_message_info.title}}

-

{{{i18n private_message_info.description}}}

{{#each content.allowed_users}}
diff --git a/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee b/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee index 4c9d4c604ee..f2b41fc8bf9 100644 --- a/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee +++ b/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee @@ -38,6 +38,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend @addObject Discourse.ButtonView.createWithMixins classNames: ['btn', 'btn-primary', 'create'] + attributeBindings: ['disabled'] text: (-> archetype = @get('controller.content.archetype') return customTitle if customTitle = @get("parentView.replyButtonText#{archetype.capitalize()}") @@ -46,6 +47,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend renderIcon: (buffer) -> buffer.push("") click: -> @get('controller').reply() helpKey: 'topic.reply.help' + disabled: !@get('controller.content.can_create_post') unless topic.get('isPrivateMessage') @addObject Discourse.DropdownButtonView.createWithMixins diff --git a/app/assets/stylesheets/application/topic.css.scss b/app/assets/stylesheets/application/topic.css.scss index a63f9235393..6b5a30472e8 100644 --- a/app/assets/stylesheets/application/topic.css.scss +++ b/app/assets/stylesheets/application/topic.css.scss @@ -387,7 +387,7 @@ kbd { z-index: 1; } button { - padding: 0; + padding: 0 1px; cursor: pointer; z-index: 1000; position: absolute; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d01c3ab3481..db118b323c0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -66,12 +66,12 @@ class ApplicationController < ActionController::Base # for now do a simple remap, we may look at cleaner ways of doing the render raise ActiveRecord::RecordNotFound else - render file: 'public/404', layout: false, status: 404 + render file: 'public/404', formats: [:html], layout: false, status: 404 end end rescue_from Discourse::InvalidAccess do - render file: 'public/403', layout: false, status: 403 + render file: 'public/403', formats: [:html], layout: false, status: 403 end def store_preloaded(key, json) diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 6aef4abba2a..b8f4c7ff972 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -22,9 +22,14 @@ class SessionController < ApplicationController # If their password is correct if @user.confirm_password?(params[:password]) - log_on_user(@user) - render_serialized(@user, UserSerializer) - return + if @user.email_confirmed? + log_on_user(@user) + render_serialized(@user, UserSerializer) + return + else + render :json => {error: I18n.t("login.not_activated")} + return + end end end diff --git a/app/helpers/common_helper.rb b/app/helpers/common_helper.rb new file mode 100644 index 00000000000..83ee98efa5b --- /dev/null +++ b/app/helpers/common_helper.rb @@ -0,0 +1,7 @@ +module CommonHelper + def render_google_analytics_code + if Rails.env == "production" && SiteSetting.ga_tracking_code.present? + render :partial => "common/google_analytics" + end + end +end \ No newline at end of file diff --git a/app/helpers/topics_helper.rb b/app/helpers/topics_helper.rb new file mode 100644 index 00000000000..978b118d5b1 --- /dev/null +++ b/app/helpers/topics_helper.rb @@ -0,0 +1,10 @@ +module TopicsHelper + + def render_topic_title(topic) + link_to(topic.title,topic.relative_url) + end + + def render_topic_next_page_link(topic, next_page) + link_to("next page", "#{topic.relative_url}?page=#{next_page}") + end +end diff --git a/app/models/post.rb b/app/models/post.rb index 066cac96ba7..496224570c3 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -342,19 +342,21 @@ class Post < ActiveRecord::Base # This calculates the geometric mean of the post timings and stores it along with # each post. def self.calculate_avg_time - exec_sql("UPDATE posts - SET avg_time = (x.gmean / 1000) - FROM (SELECT post_timings.topic_id, - post_timings.post_number, - round(exp(avg(ln(msecs)))) AS gmean - FROM post_timings - INNER JOIN posts AS p2 - ON p2.post_number = post_timings.post_number - AND p2.topic_id = post_timings.topic_id - AND p2.user_id <> post_timings.user_id - GROUP BY post_timings.topic_id, post_timings.post_number) AS x - WHERE x.topic_id = posts.topic_id - AND x.post_number = posts.post_number") + retry_lock_error do + exec_sql("UPDATE posts + SET avg_time = (x.gmean / 1000) + FROM (SELECT post_timings.topic_id, + post_timings.post_number, + round(exp(avg(ln(msecs)))) AS gmean + FROM post_timings + INNER JOIN posts AS p2 + ON p2.post_number = post_timings.post_number + AND p2.topic_id = post_timings.topic_id + AND p2.user_id <> post_timings.user_id + GROUP BY post_timings.topic_id, post_timings.post_number) AS x + WHERE x.topic_id = posts.topic_id + AND x.post_number = posts.post_number") + end end before_save do diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index da3f5be4f27..d4215ae8128 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -47,6 +47,10 @@ class TopicLink < ActiveRecord::Base internal = true route = Rails.application.routes.recognize_path(parsed.path) + + # We aren't interested in tracking internal links to users + next if route[:controller] == 'users' + topic_id = route[:topic_id] post_number = route[:post_number] || 1 end diff --git a/app/models/user.rb b/app/models/user.rb index 2f5593f1877..41dac47d71c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -380,6 +380,10 @@ class User < ActiveRecord::Base end end + def email_confirmed? + email_tokens.where(email: self.email, confirmed: true).present? + end + protected diff --git a/app/views/common/_discourse_javascript.html.erb b/app/views/common/_discourse_javascript.html.erb new file mode 100644 index 00000000000..8c54a574241 --- /dev/null +++ b/app/views/common/_discourse_javascript.html.erb @@ -0,0 +1,25 @@ + <%- if mini_profiler_enabled? %> + <%- Rack::MiniProfiler.step "application" do %> + <%= javascript_include_tag "application" %> + <%-end%> + + <%- Rack::MiniProfiler.step "admin" do %> + <%= javascript_include_tag "admin"%> + <%-end%> + <%- else %> + <%= javascript_include_tag "application" %> + <%- if admin? %> + <%= javascript_include_tag "admin"%> + <%- end %> + <%- end%> + + \ No newline at end of file diff --git a/app/views/common/_discourse_stylesheet.html.erb b/app/views/common/_discourse_stylesheet.html.erb new file mode 100644 index 00000000000..6f81b70913f --- /dev/null +++ b/app/views/common/_discourse_stylesheet.html.erb @@ -0,0 +1,12 @@ + <%- unless SiteCustomization.override_default_style(session[:preview_style]) %> + <%=stylesheet_link_tag "application"%> + <%- end %> + + <%- if mini_profiler_enabled? %> + <%- Rack::MiniProfiler.step "stylsheet" do%> + <%= stylesheet_link_tag "admin"%> + <%-end%> + <%- elsif admin? %> + <%= stylesheet_link_tag "admin"%> + <%-end%> + <%=SiteCustomization.custom_stylesheet(session[:preview_style])%> \ No newline at end of file diff --git a/app/views/common/_google_analytics.html.erb b/app/views/common/_google_analytics.html.erb new file mode 100644 index 00000000000..79f2414e848 --- /dev/null +++ b/app/views/common/_google_analytics.html.erb @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/app/views/common/_special_font_face.html.erb b/app/views/common/_special_font_face.html.erb new file mode 100644 index 00000000000..bad6846bf53 --- /dev/null +++ b/app/views/common/_special_font_face.html.erb @@ -0,0 +1,31 @@ + + <%# + The fonts are loaded outside of the stylesheet so that we can dynamically change + the path. This is to get around CDN caching on the Origin: + + https://forums.aws.amazon.com/thread.jspa?threadID=114646 + %> + +<% font_domain = "#{request.protocol}#{request.host_with_port}" %> + + \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 106abd5f98d..f314b2819a0 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,48 +19,11 @@ <%= javascript_include_tag "preload_store" %> - <%# - The fonts are loaded outside of the stylesheet so that we can dynamically change - the path. This is to get around CDN caching on the Origin: - https://forums.aws.amazon.com/thread.jspa?threadID=114646 - %> + <%= render :partial => "common/special_font_face" %> + <%= render :partial => "common/discourse_stylesheet" %> - <%- font_domain = "#{request.protocol}#{request.host_with_port}" %> - - - <%- unless SiteCustomization.override_default_style(session[:preview_style]) %> - <%=stylesheet_link_tag "application"%> - <%- end %> - - <%- if mini_profiler_enabled? %> - <%- Rack::MiniProfiler.step "stylsheet" do%> - <%= stylesheet_link_tag "admin"%> - <%-end%> - <%- elsif admin? %> - <%= stylesheet_link_tag "admin"%> - <%-end%> - <%=SiteCustomization.custom_stylesheet(session[:preview_style])%> <%=csrf_meta_tags%> @@ -98,48 +61,8 @@
- <%- if mini_profiler_enabled? %> - <%- Rack::MiniProfiler.step "application" do %> - <%= javascript_include_tag "application" %> - <%-end%> - - <%- Rack::MiniProfiler.step "admin" do %> - <%= javascript_include_tag "admin"%> - <%-end%> - <%- else %> - <%= javascript_include_tag "application" %> - <%- if admin? %> - <%= javascript_include_tag "admin"%> - <%- end %> - <%- end%> - - - - <%- if Rails.env == "production" and SiteSetting.ga_tracking_code.present? %> - - <%-end%> + <%= render :partial => "common/discourse_javascript" %> + <%= render_google_analytics_code %> diff --git a/app/views/topics/show.html.erb b/app/views/topics/show.html.erb index 09b8906fe29..869cd21c099 100644 --- a/app/views/topics/show.html.erb +++ b/app/views/topics/show.html.erb @@ -1,5 +1,6 @@

- <%= @topic_view.topic.title %> + <%= render_topic_title(@topic_view.topic) %> +

<% (@post ? [@post] : @topic_view.posts).each do |post| %>
@@ -12,7 +13,7 @@ <% if @next_page%>

- next page + <%= render_topic_next_page_link(@topic_view.topic, @next_page) %>

<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 26dae763371..e6a0203b993 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -596,7 +596,6 @@ en: private_message_info: title: "Private Conversation" - description: "Participants in this private conversation" invite: "Invite Others..." email: 'Email' @@ -1039,6 +1038,7 @@ en: wait_approval: "Thanks for signing up. We will notify you when your account has been approved." active: "Your account is active and ready." activate_email: "You're almost done! We sent an activation email to %{email}. Please follow the instructions in the email to activate your account." + not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account." errors: "Failed to create account: %{errors}" not_available: "Not available. Try %{suggestion}?" diff --git a/lib/current_user.rb b/lib/current_user.rb index aa73a963aff..312288bb106 100644 --- a/lib/current_user.rb +++ b/lib/current_user.rb @@ -21,9 +21,9 @@ module CurrentUser @not_logged_in = session[:current_user_id].blank? if @current_user @current_user.update_last_seen! - if @current_user.ip_address != request.remote_ip + if (@current_user.ip_address != request.remote_ip) and request.remote_ip.present? @current_user.ip_address = request.remote_ip - User.exec_sql('update users set ip_address = ? where id = ?', request.remote_ip, @current_user.id) + @current_user.update_column(:ip_address, request.remote_ip) end end @current_user diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index a56862a67e9..8f7abca4c8d 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -9,10 +9,21 @@ class DiscoursePluginRegistry attr_accessor :stylesheets end - def register_js(filename, options={}) - self.class.javascripts ||= Set.new - self.class.server_side_javascripts ||= Set.new + # Default accessor values + # + def self.stylesheets + @stylesheets ||= Set.new + end + + def self.javascripts + @javascripts ||= Set.new + end + + def self.server_side_javascripts + @server_side_javascripts ||= Set.new + end + def register_js(filename, options={}) # If we have a server side option, add that too. self.class.server_side_javascripts << options[:server_side] if options[:server_side].present? @@ -20,12 +31,11 @@ class DiscoursePluginRegistry end def register_css(filename) - self.class.stylesheets ||= Set.new self.class.stylesheets << filename end def stylesheets - self.class.stylesheets || Set.new + self.class.stylesheets end def register_archetype(name, options={}) @@ -33,17 +43,17 @@ class DiscoursePluginRegistry end def server_side_javascripts - self.class.javascripts || Set.new + self.class.javascripts end def javascripts - self.class.javascripts || Set.new + self.class.javascripts end def self.clear - self.stylesheets = Set.new - self.server_side_javascripts = Set.new - self.javascripts = Set.new + self.stylesheets = nil + self.server_side_javascripts = nil + self.javascripts = nil end def self.setup(plugin_class) @@ -52,6 +62,4 @@ class DiscoursePluginRegistry plugin.setup end - - end diff --git a/lib/freedom_patches/active_record_base.rb b/lib/freedom_patches/active_record_base.rb index 110e2b56610..41ae0e91710 100644 --- a/lib/freedom_patches/active_record_base.rb +++ b/lib/freedom_patches/active_record_base.rb @@ -15,6 +15,24 @@ class ActiveRecord::Base ActiveRecord::Base.exec_sql(*args) end + + # Executes the given block +retries+ times (or forever, if explicitly given nil), + # catching and retrying SQL Deadlock errors. + # + # Thanks to: http://stackoverflow.com/a/7427186/165668 + # + def self.retry_lock_error(retries=5, &block) + begin + yield + rescue ActiveRecord::StatementInvalid => e + if e.message =~ /Deadlock found when trying to get lock/ and (retries.nil? || retries > 0) + retry_lock_error(retries ? retries - 1 : nil, &block) + else + raise e + end + end + end + # Support for psql. If we want to support multiple RDBMs in the future we can # split this. def exec_sql_row_count(*args) diff --git a/spec/components/discourse_plugin_registry_spec.rb b/spec/components/discourse_plugin_registry_spec.rb index fe064e6bced..ea2ad312ca5 100644 --- a/spec/components/discourse_plugin_registry_spec.rb +++ b/spec/components/discourse_plugin_registry_spec.rb @@ -4,6 +4,27 @@ require 'discourse_plugin_registry' describe DiscoursePluginRegistry do let(:registry) { DiscoursePluginRegistry.new } + + context '#stylesheets' do + it 'defaults to an empty Set' do + DiscoursePluginRegistry.stylesheets = nil + DiscoursePluginRegistry.stylesheets.should == Set.new + end + end + + context '#javascripts' do + it 'defaults to an empty Set' do + DiscoursePluginRegistry.javascripts = nil + DiscoursePluginRegistry.javascripts.should == Set.new + end + end + + context '#server_side_javascripts' do + it 'defaults to an empty Set' do + DiscoursePluginRegistry.server_side_javascripts = nil + DiscoursePluginRegistry.server_side_javascripts.should == Set.new + end + end context '.register_css' do before do diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index 45f312e1e52..59be7306925 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -6,77 +6,93 @@ describe SessionController 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 + context 'when email is confirmed' do before do - xhr :post, :create, login: user.username, password: 'myawesomepassword' - user.reload + token = user.email_tokens.where(email: user.email).first + EmailToken.confirm(token.token) end - it 'sets a session id' do - session[:current_user_id].should == user.id + it "raises an error when the login isn't present" do + lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters) end - it 'gives the user an auth token' do - user.auth_token.should be_present + 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 - it 'sets a cookie with the auth token' do - cookies[:_t].should == user.auth_token - end - end + describe 'success by username' do + before do + xhr :post, :create, login: user.username, password: 'myawesomepassword' + user.reload + end - describe 'strips leading @ symbol' do - before do - xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword' - user.reload + 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 - it 'sets a session id' do - session[:current_user_id].should == user.id - end - end + describe 'strips leading @ symbol' do + before do + xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword' + user.reload + end - describe 'also allow login by email' do - before do - xhr :post, :create, login: user.email, password: 'myawesomepassword' + it 'sets a session id' do + session[:current_user_id].should == user.id + end 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 + describe 'also allow login by email' 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 + 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 + context 'when email has not been confirmed' 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 + + it 'returns an error message' do + ::JSON.parse(response.body)['error'].should be_present + end + end end describe '.destroy' do diff --git a/spec/models/topic_link_spec.rb b/spec/models/topic_link_spec.rb index 3d992f980b8..b158ccf8628 100644 --- a/spec/models/topic_link_spec.rb +++ b/spec/models/topic_link_spec.rb @@ -44,125 +44,139 @@ describe TopicLink do end - describe 'domain-less link' do - let(:post) { @topic.posts.create(user: @user, raw: "hello") } - let!(:link) do - TopicLink.extract_from(post) - @topic.topic_links.first - end - - it 'is extracted' do - link.should be_present - end - - it 'has the correct domain' do - link.domain.should == test_uri.host - end - - it "is not destroyed when we call extract from again" do - TopicLink.extract_from(post) - link.reload - link.should be_present - end - - end - describe 'internal links' do - before do - @other_topic = Fabricate(:topic, user: @user) - @other_post = @other_topic.posts.create(user: @user, raw: "some content") - - @url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}" - - @topic.posts.create(user: @user, raw: 'initial post') - @post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}") - - TopicLink.extract_from(@post) - - @link = @topic.topic_links.first - end - - it 'extracted the link' do - @link.should be_present - end - - it 'is set to internal' do - @link.should be_internal - end - - it 'has the correct url' do - @link.url.should == @url - end - - it 'has the extracted domain' do - @link.domain.should == test_uri.host - end - - it 'should have the id of the linked forum' do - @link.link_topic_id == @other_topic.id - end - - it 'should not be the reflection' do - @link.should_not be_reflection - end - - describe 'reflection in the other topic' do - + context 'topic link' do before do - @reflection = @other_topic.topic_links.first + @other_topic = Fabricate(:topic, user: @user) + @other_post = @other_topic.posts.create(user: @user, raw: "some content") + + @url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}" + + @topic.posts.create(user: @user, raw: 'initial post') + @post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}") + + TopicLink.extract_from(@post) + + @link = @topic.topic_links.first end - it 'exists' do - @reflection.should be_present + it 'extracted the link' do + @link.should be_present end - it 'is a reflection' do - @reflection.should be_reflection - end - - it 'has a post_id' do - @reflection.post_id.should be_present - end - - it 'has the correct host' do - @reflection.domain.should == test_uri.host + it 'is set to internal' do + @link.should be_internal end it 'has the correct url' do - @reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}" + @link.url.should == @url end - it 'links to the original forum topic' do - @reflection.link_topic_id.should == @topic.id + it 'has the extracted domain' do + @link.domain.should == test_uri.host end - it 'links to the original post' do - @reflection.link_post_id.should == @post.id + it 'should have the id of the linked forum' do + @link.link_topic_id == @other_topic.id end - it 'has the user id of the original link' do - @reflection.user_id.should == @link.user_id + it 'should not be the reflection' do + @link.should_not be_reflection + end + + describe 'reflection in the other topic' do + + before do + @reflection = @other_topic.topic_links.first + end + + it 'exists' do + @reflection.should be_present + end + + it 'is a reflection' do + @reflection.should be_reflection + end + + it 'has a post_id' do + @reflection.post_id.should be_present + end + + it 'has the correct host' do + @reflection.domain.should == test_uri.host + end + + it 'has the correct url' do + @reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}" + end + + it 'links to the original forum topic' do + @reflection.link_topic_id.should == @topic.id + end + + it 'links to the original post' do + @reflection.link_post_id.should == @post.id + end + + it 'has the user id of the original link' do + @reflection.user_id.should == @link.user_id + end + end + + context 'removing a link' do + + before do + @post.revise(@post.user, "no more linkies") + TopicLink.extract_from(@post) + end + + it 'should remove the link' do + @topic.topic_links.where(post_id: @post.id).should be_blank + end + + it 'should remove the reflected link' do + @reflection = @other_topic.topic_links.should be_blank + end + + end + + end + + context "link to a user on discourse" do + let(:post) { @topic.posts.create(user: @user, raw: "user") } + before do + TopicLink.extract_from(post) + end + + it 'does not extract a link' do + @topic.topic_links.should be_blank end end - context 'removing a link' do + context "link to a discourse resource like a FAQ" do + let(:post) { @topic.posts.create(user: @user, raw: "faq link here") } + before do + TopicLink.extract_from(post) + end + + it 'does not extract a link' do + @topic.topic_links.should be_present + end + end + + context "@mention links" do + let(:post) { @topic.posts.create(user: @user, raw: "Hey @#{@user.username_lower}") } before do - @post.revise(@post.user, "no more linkies") - TopicLink.extract_from(@post) + TopicLink.extract_from(post) end - it 'should remove the link' do - @topic.topic_links.where(post_id: @post.id).should be_blank + it 'does not extract a link' do + @topic.topic_links.should be_blank end - - it 'should remove the reflected link' do - @reflection = @other_topic.topic_links.should be_blank - end - - end - + end + end describe 'internal link from pm' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 9f04c9e0217..3e518ce38ef 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -617,4 +617,30 @@ describe User do it { should_not be_active } end + describe 'email_confirmed?' do + let(:user) { Fabricate(:user) } + + context 'when email has not been confirmed yet' do + it 'should return false' do + user.email_confirmed?.should be_false + end + end + + context 'when email has been confirmed' do + it 'should return true' do + token = user.email_tokens.where(email: user.email).first + EmailToken.confirm(token.token) + user.email_confirmed?.should be_true + end + end + + context 'when user has no email tokens for some reason' do + it 'should return false' do + user.email_tokens.each {|t| t.destroy} + user.reload + user.email_confirmed?.should be_false + end + end + end + end