From e3618bdbecd9b5d86eb6d2c8c256ba3fcdf05189 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 17 Jan 2015 14:14:12 +0000 Subject: [PATCH] Add support for multiple email addresses per user (#4244). git-svn-id: http://svn.redmine.org/redmine/trunk@13886 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/email_addresses_controller.rb | 105 +++++++++++++ app/controllers/users_controller.rb | 2 +- app/helpers/email_addresses_helper.rb | 38 +++++ app/helpers/users_helper.rb | 6 + app/models/document.rb | 4 + app/models/email_address.rb | 54 +++++++ app/models/mail_handler.rb | 2 +- app/models/mailer.rb | 71 ++++++--- app/models/message.rb | 4 + app/models/news.rb | 23 ++- app/models/principal.rb | 12 +- app/models/user.rb | 54 +++++-- app/models/wiki_content.rb | 8 +- app/views/email_addresses/_index.html.erb | 26 ++++ app/views/email_addresses/index.html.erb | 2 + app/views/email_addresses/index.js.erb | 3 + app/views/my/account.html.erb | 1 + app/views/settings/_authentication.html.erb | 2 + app/views/users/edit.html.erb | 1 + config/initializers/10-patches.rb | 3 +- config/locales/en.yml | 6 + config/locales/fr.yml | 6 + config/routes.rb | 1 + config/settings.yml | 4 + .../20150113194759_create_email_addresses.rb | 12 ++ ...20150113211532_populate_email_addresses.rb | 14 ++ .../20150113213922_remove_users_mail.rb | 9 ++ ...13955_add_email_addresses_user_id_index.rb | 9 ++ public/images/email.png | Bin 0 -> 641 bytes public/images/email_add.png | Bin 0 -> 761 bytes public/images/email_disabled.png | Bin 0 -> 386 bytes public/javascripts/application.js | 4 +- public/stylesheets/application.css | 4 +- test/fixtures/email_addresses.yml | 57 +++++++ test/fixtures/users.yml | 43 +++--- test/functional/admin_controller_test.rb | 2 +- test/functional/documents_controller_test.rb | 2 +- .../email_addresses_controller_test.rb | 144 ++++++++++++++++++ test/functional/issues_controller_test.rb | 2 +- .../issues_custom_fields_visibility_test.rb | 2 +- .../mail_handler_controller_test.rb | 2 +- test/functional/messages_controller_test.rb | 2 +- test/functional/my_controller_test.rb | 2 +- test/functional/news_controller_test.rb | 2 +- test/functional/projects_controller_test.rb | 2 +- .../repositories_bazaar_controller_test.rb | 2 +- .../repositories_controller_test.rb | 2 +- .../repositories_cvs_controller_test.rb | 2 +- .../repositories_darcs_controller_test.rb | 2 +- ...repositories_filesystem_controller_test.rb | 2 +- .../repositories_git_controller_test.rb | 2 +- .../repositories_mercurial_controller_test.rb | 2 +- ...repositories_subversion_controller_test.rb | 2 +- test/functional/users_controller_test.rb | 2 +- test/functional/wiki_controller_test.rb | 2 +- test/integration/account_test.rb | 2 +- test/integration/api_test/users_test.rb | 2 +- test/integration/issues_test.rb | 2 +- test/integration/users_test.rb | 2 +- test/unit/document_test.rb | 2 +- test/unit/issue_test.rb | 2 +- test/unit/mail_handler_test.rb | 11 ++ test/unit/mailer_test.rb | 34 ++++- test/unit/user_test.rb | 45 +++++- test/unit/watcher_test.rb | 2 +- 65 files changed, 762 insertions(+), 114 deletions(-) create mode 100644 app/controllers/email_addresses_controller.rb create mode 100644 app/helpers/email_addresses_helper.rb create mode 100644 app/models/email_address.rb create mode 100644 app/views/email_addresses/_index.html.erb create mode 100644 app/views/email_addresses/index.html.erb create mode 100644 app/views/email_addresses/index.js.erb create mode 100644 db/migrate/20150113194759_create_email_addresses.rb create mode 100644 db/migrate/20150113211532_populate_email_addresses.rb create mode 100644 db/migrate/20150113213922_remove_users_mail.rb create mode 100644 db/migrate/20150113213955_add_email_addresses_user_id_index.rb create mode 100644 public/images/email.png create mode 100644 public/images/email_add.png create mode 100644 public/images/email_disabled.png create mode 100644 test/fixtures/email_addresses.yml create mode 100644 test/functional/email_addresses_controller_test.rb diff --git a/app/controllers/email_addresses_controller.rb b/app/controllers/email_addresses_controller.rb new file mode 100644 index 000000000..373be00a0 --- /dev/null +++ b/app/controllers/email_addresses_controller.rb @@ -0,0 +1,105 @@ +# Redmine - project management software +# Copyright (C) 2006-2015 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class EmailAddressesController < ApplicationController + before_filter :find_user, :require_admin_or_current_user + before_filter :find_email_address, :only => [:update, :destroy] + + def index + @addresses = @user.email_addresses.order(:id).where(:is_default => false).to_a + @address ||= EmailAddress.new + end + + def create + saved = false + if @user.email_addresses.count <= Setting.max_additional_emails.to_i + @address = EmailAddress.new(:user => @user, :is_default => false) + attrs = params[:email_address] + if attrs.is_a?(Hash) + @address.address = attrs[:address].to_s + end + saved = @address.save + end + + respond_to do |format| + format.html { + if saved + redirect_to user_email_addresses_path(@user) + else + index + render :action => 'index' + end + } + format.js { + @address = nil if saved + index + render :action => 'index' + } + end + end + + def update + if params[:notify].present? + @address.notify = params[:notify].to_s + end + @address.save + + respond_to do |format| + format.html { + redirect_to user_email_addresses_path(@user) + } + format.js { + @address = nil + index + render :action => 'index' + } + end + end + + def destroy + @address.destroy + + respond_to do |format| + format.html { + redirect_to user_email_addresses_path(@user) + } + format.js { + @address = nil + index + render :action => 'index' + } + end + end + + private + + def find_user + @user = User.find(params[:user_id]) + end + + def find_email_address + @address = @user.email_addresses.where(:is_default => false).find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def require_admin_or_current_user + unless @user == User.current + require_admin + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b57ee8deb..5958e36f8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -41,7 +41,7 @@ class UsersController < ApplicationController @status = params[:status] || 1 - scope = User.logged.status(@status) + scope = User.logged.status(@status).preload(:email_address) scope = scope.like(params[:name]) if params[:name].present? scope = scope.in_group(params[:group_id]) if params[:group_id].present? diff --git a/app/helpers/email_addresses_helper.rb b/app/helpers/email_addresses_helper.rb new file mode 100644 index 000000000..f397e9907 --- /dev/null +++ b/app/helpers/email_addresses_helper.rb @@ -0,0 +1,38 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2015 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module EmailAddressesHelper + + # Returns a link to enable or disable notifications for the address + def toggle_email_address_notify_link(address) + if address.notify? + link_to image_tag('email.png'), + user_email_address_path(address.user, address, :notify => '0'), + :method => :put, + :title => l(:label_disable_notifications), + :remote => true + else + link_to image_tag('email_disabled.png'), + user_email_address_path(address.user, address, :notify => '1'), + :method => :put, + :title => l(:label_enable_notifications), + :remote => true + end + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 1bd1d9a91..2ba41fa62 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -42,6 +42,12 @@ module UsersHelper end end + def additional_emails_link(user) + if user.email_addresses.count > 1 || Setting.max_additional_emails.to_i > 0 + link_to l(:label_email_address_plural), user_email_addresses_path(@user), :class => 'icon icon-email-add', :remote => true + end + end + def user_settings_tabs tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} diff --git a/app/models/document.rb b/app/models/document.rb index f73f8ed63..29f9031fc 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -60,6 +60,10 @@ class Document < ActiveRecord::Base @updated_on end + def notified_users + project.notified_users.reject {|user| !visible?(user)} + end + private def send_notification diff --git a/app/models/email_address.rb b/app/models/email_address.rb new file mode 100644 index 000000000..01fd75b1a --- /dev/null +++ b/app/models/email_address.rb @@ -0,0 +1,54 @@ +# Redmine - project management software +# Copyright (C) 2006-2015 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class EmailAddress < ActiveRecord::Base + belongs_to :user + attr_protected :id + + after_update :destroy_tokens + after_destroy :destroy_tokens + + validates_presence_of :address + validates_format_of :address, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true + validates_length_of :address, :maximum => User::MAIL_LENGTH_LIMIT, :allow_nil => true + validates_uniqueness_of :address, :case_sensitive => false, + :if => Proc.new {|email| email.address_changed? && email.address.present?} + + def address=(arg) + write_attribute(:address, arg.to_s.strip) + end + + def destroy + if is_default? + false + else + super + end + end + + private + + # Delete all outstanding password reset tokens on email change. + # This helps to keep the account secure in case the associated email account + # was compromised. + def destroy_tokens + if address_changed? || destroyed? + tokens = ['recovery'] + Token.where(:user_id => user_id, :action => tokens).delete_all + end + end +end diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index cff50e6af..825fdf546 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -306,7 +306,7 @@ class MailHandler < ActionMailer::Base if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} unless addresses.empty? - User.active.where('LOWER(mail) IN (?)', addresses).each do |w| + User.active.having_mail(addresses).each do |w| obj.add_watcher(w) end end diff --git a/app/models/mailer.rb b/app/models/mailer.rb index bc279b691..a859c039b 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -39,8 +39,8 @@ class Mailer < ActionMailer::Base @issue = issue @users = to_users + cc_users @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) - mail :to => to_users.map(&:mail), - :cc => cc_users.map(&:mail), + mail :to => to_users, + :cc => cc_users, :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" end @@ -71,8 +71,8 @@ class Mailer < ActionMailer::Base @journal = journal @journal_details = journal.visible_details(@users.first) @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") - mail :to => to_users.map(&:mail), - :cc => cc_users.map(&:mail), + mail :to => to_users, + :cc => cc_users, :subject => s end @@ -95,7 +95,7 @@ class Mailer < ActionMailer::Base @issues_url = url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort => 'due_date:asc') - mail :to => user.mail, + mail :to => user, :subject => l(:mail_subject_reminder, :count => issues.size, :days => days) end @@ -109,7 +109,7 @@ class Mailer < ActionMailer::Base @author = User.current @document = document @document_url = url_for(:controller => 'documents', :action => 'show', :id => document) - mail :to => document.recipients, + mail :to => document.notified_users, :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" end @@ -127,15 +127,15 @@ class Mailer < ActionMailer::Base when 'Project' added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) added_to = "#{l(:label_project)}: #{container}" - recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} + recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)} when 'Version' added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) added_to = "#{l(:label_version)}: #{container.name}" - recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} + recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)} when 'Document' added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) added_to = "#{l(:label_document)}: #{container.title}" - recipients = container.recipients + recipients = container.notified_users end redmine_headers 'Project' => container.project.identifier @attachments = attachments @@ -157,8 +157,8 @@ class Mailer < ActionMailer::Base references news @news = news @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :cc => news.cc_for_added_news, + mail :to => news.notified_users, + :cc => news.notified_watchers_for_added_news, :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}" end @@ -176,8 +176,8 @@ class Mailer < ActionMailer::Base @news = news @comment = comment @news_url = url_for(:controller => 'news', :action => 'show', :id => news) - mail :to => news.recipients, - :cc => news.watcher_recipients, + mail :to => news.notified_users, + :cc => news.notified_watchers, :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}" end @@ -192,8 +192,8 @@ class Mailer < ActionMailer::Base @author = message.author message_id message references message.root - recipients = message.recipients - cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) + recipients = message.notified_users + cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients) @message = message @message_url = url_for(message.event_url) mail :to => recipients, @@ -211,8 +211,8 @@ class Mailer < ActionMailer::Base 'Wiki-Page-Id' => wiki_content.page.id @author = wiki_content.author message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients - recipients + recipients = wiki_content.notified_users + cc = wiki_content.page.wiki.notified_watchers - recipients @wiki_content = wiki_content @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, @@ -232,8 +232,8 @@ class Mailer < ActionMailer::Base 'Wiki-Page-Id' => wiki_content.page.id @author = wiki_content.author message_id wiki_content - recipients = wiki_content.recipients - cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients + recipients = wiki_content.notified_users + cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients @wiki_content = wiki_content @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, @@ -267,7 +267,7 @@ class Mailer < ActionMailer::Base # Mailer.account_activation_request(user).deliver => sends an email to all active administrators def account_activation_request(user) # Send the email to all active administrators - recipients = User.active.where(:admin => true).collect { |u| u.mail }.compact + recipients = User.active.where(:admin => true) @user = user @url = url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, @@ -378,12 +378,20 @@ class Mailer < ActionMailer::Base 'From' => Setting.mail_from, 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" + # Replaces users with their email addresses + [:to, :cc, :bcc].each do |key| + if headers[key].present? + headers[key] = self.class.email_addresses(headers[key]) + end + end + # Removes the author from the recipients and cc # if the author does not want to receive notifications # about what the author do if @author && @author.logged? && @author.pref.no_self_notified - headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) - headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) + addresses = @author.mails + headers[:to] -= addresses if headers[:to].is_a?(Array) + headers[:cc] -= addresses if headers[:cc].is_a?(Array) end if @author && @author.logged? @@ -447,6 +455,25 @@ class Mailer < ActionMailer::Base end end + # Returns an array of email addresses to notify by + # replacing users in arg with their notified email addresses + # + # Example: + # Mailer.email_addresses(users) + # => ["foo@example.net", "bar@example.net"] + def self.email_addresses(arg) + arr = Array.wrap(arg) + mails = arr.reject {|a| a.is_a? Principal} + users = arr - mails + if users.any? + mails += EmailAddress. + where(:user_id => users.map(&:id)). + where("is_default = ? OR notify = ?", true, true). + pluck(:address) + end + mails + end + private # Appends a Redmine header field (name is prepended with 'X-Redmine-') diff --git a/app/models/message.rb b/app/models/message.rb index 5ca8d7ea7..de7ebaab5 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -103,6 +103,10 @@ class Message < ActiveRecord::Base usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project))) end + def notified_users + project.notified_users.reject {|user| !visible?(user)} + end + private def add_author_as_watcher diff --git a/app/models/news.rb b/app/models/news.rb index e0793d9a8..b8bc35f39 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -54,20 +54,29 @@ class News < ActiveRecord::Base user.allowed_to?(:comment_news, project) end + def notified_users + project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)} + end + def recipients - project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)}.map(&:mail) + notified_users.map(&:mail) end - # Returns the email addresses that should be cc'd when a new news is added - def cc_for_added_news - cc = [] + # Returns the users that should be cc'd when a new news is added + def notified_watchers_for_added_news + watchers = [] if m = project.enabled_module('news') - cc = m.notified_watchers + watchers = m.notified_watchers unless project.is_public? - cc = cc.select {|user| project.users.include?(user)} + watchers = watchers.select {|user| project.users.include?(user)} end end - cc.map(&:mail) + watchers + end + + # Returns the email addresses that should be cc'd when a new news is added + def cc_for_added_news + notified_watchers_for_added_news.map(&:mail) end # returns latest news for projects visible by user diff --git a/app/models/principal.rb b/app/models/principal.rb index 7e87091f3..355e8907e 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -68,7 +68,8 @@ class Principal < ActiveRecord::Base where({}) else pattern = "%#{q}%" - sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))" params = {:p => pattern} if q =~ /^(.+)\s+(.+)$/ a, b = "#{$1}%", "#{$2}%" @@ -108,6 +109,14 @@ class Principal < ActiveRecord::Base to_s end + def mail=(*args) + nil + end + + def mail + nil + end + def visible?(user=User.current) Principal.visible(user).where(:id => id).first == self end @@ -145,7 +154,6 @@ class Principal < ActiveRecord::Base self.hashed_password ||= '' self.firstname ||= '' self.lastname ||= '' - self.mail ||= '' true end end diff --git a/app/models/user.rb b/app/models/user.rb index b680fe52d..2175d0682 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -81,6 +81,8 @@ class User < Principal has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token' has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token' + has_one :email_address, lambda {where :is_default => true}, :autosave => true + has_many :email_addresses, :dependent => :delete_all belongs_to :auth_source scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } @@ -96,15 +98,12 @@ class User < Principal LOGIN_LENGTH_LIMIT = 60 MAIL_LENGTH_LIMIT = 60 - validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } + validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false - validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false # Login must contain letters, numbers, underscores only validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT validates_length_of :firstname, :lastname, :maximum => 30 - validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true - validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validate :validate_password_length validate do @@ -113,6 +112,7 @@ class User < Principal end end + before_validation :instantiate_email_address before_create :set_mail_notification before_save :generate_password_if_needed, :update_hashed_password before_destroy :remove_references_before_destroy @@ -127,6 +127,14 @@ class User < Principal where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) } scope :sorted, lambda { order(*User.fields_for_order_statement)} + scope :having_mail, lambda {|arg| + addresses = Array.wrap(arg).map {|a| a.to_s.downcase} + if addresses.any? + joins(:email_addresses).where("LOWER(address) IN (?)", addresses).uniq + else + none + end + } def set_mail_notification self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? @@ -152,8 +160,21 @@ class User < Principal base_reload(*args) end + def mail + email_address.try(:address) + end + def mail=(arg) - write_attribute(:mail, arg.to_s.strip) + email = email_address || build_email_address + email.address = arg + end + + def mail_changed? + email_address.try(:address_changed?) + end + + def mails + email_addresses.pluck(:address) end def self.find_or_initialize_by_identity_url(url) @@ -421,7 +442,7 @@ class User < Principal # Makes find_by_mail case-insensitive def self.find_by_mail(mail) - where("LOWER(mail) = ?", mail.to_s.downcase).first + having_mail(mail).first end # Returns true if the default admin account can no longer be used @@ -669,7 +690,7 @@ class User < Principal def self.anonymous anonymous_user = AnonymousUser.first if anonymous_user.nil? - anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) + anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0) raise 'Unable to create the anonymous user.' if anonymous_user.new_record? end anonymous_user @@ -699,6 +720,10 @@ class User < Principal end end + def instantiate_email_address + email_address || build_email_address + end + private def generate_password_if_needed @@ -708,16 +733,13 @@ class User < Principal end end - # Delete all outstanding password reset tokens on password or email change. + # Delete all outstanding password reset tokens on password change. # Delete the autologin tokens on password change to prohibit session leakage. # This helps to keep the account secure in case the associated email account # was compromised. def destroy_tokens - tokens = [] - tokens |= ['recovery', 'autologin'] if hashed_password_changed? - tokens |= ['recovery'] if mail_changed? - - if tokens.any? + if hashed_password_changed? + tokens = ['recovery', 'autologin'] Token.where(:user_id => id, :action => tokens).delete_all end end @@ -779,6 +801,7 @@ class AnonymousUser < User def logged?; false end def admin; false end def name(*args); I18n.t(:label_user_anonymous) end + def mail=(*args); nil end def mail; nil end def time_zone; nil end def rss_key; nil end @@ -804,4 +827,9 @@ class AnonymousUser < User def destroy false end + + protected + + def instantiate_email_address + end end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 90947d589..992c7da34 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -41,11 +41,13 @@ class WikiContent < ActiveRecord::Base page.nil? ? [] : page.attachments end + def notified_users + project.notified_users.reject {|user| !visible?(user)} + end + # Returns the mail addresses of users that should be notified def recipients - notified = project.notified_users - notified.reject! {|user| !visible?(user)} - notified.collect(&:mail) + notified_users.collect(&:mail) end # Return true if the content is the current page content diff --git a/app/views/email_addresses/_index.html.erb b/app/views/email_addresses/_index.html.erb new file mode 100644 index 000000000..644cd759b --- /dev/null +++ b/app/views/email_addresses/_index.html.erb @@ -0,0 +1,26 @@ +<% if @addresses.present? %> + + <% @addresses.each do |address| %> + "> + + + + <% end %> +
+ <%= toggle_email_address_notify_link(address) %> + <%= delete_link user_email_address_path(@user, address), :remote => true %> +
+<% end %> + +<% unless @addresses.size >= Setting.max_additional_emails.to_i %> +
+ <%= form_for @address, :url => user_email_addresses_path(@user), :remote => true do |f| %> +

<%= l(:label_email_address_add) %>

+ <%= error_messages_for @address %> +

+ <%= f.text_field :address, :size => 40 %> + <%= submit_tag l(:button_add) %> +

+ <% end %> +
+<% end %> diff --git a/app/views/email_addresses/index.html.erb b/app/views/email_addresses/index.html.erb new file mode 100644 index 000000000..7de1d37d1 --- /dev/null +++ b/app/views/email_addresses/index.html.erb @@ -0,0 +1,2 @@ +

<%= @user.name %>

+<%= render :partial => 'email_addresses/index' %> diff --git a/app/views/email_addresses/index.js.erb b/app/views/email_addresses/index.js.erb new file mode 100644 index 000000000..2a7147f36 --- /dev/null +++ b/app/views/email_addresses/index.js.erb @@ -0,0 +1,3 @@ +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'email_addresses/index') %>'); +showModal('ajax-modal', '600px', '<%= escape_javascript l(:label_email_address_plural) %>'); +$('#email_address_address').focus(); diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 0ce223b0a..cadf0e830 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -1,4 +1,5 @@
+<%= additional_emails_link(@user) %> <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %> <%= call_hook(:view_my_account_contextual, :user => @user)%>
diff --git a/app/views/settings/_authentication.html.erb b/app/views/settings/_authentication.html.erb index d190fab66..77b5afced 100644 --- a/app/views/settings/_authentication.html.erb +++ b/app/views/settings/_authentication.html.erb @@ -16,6 +16,8 @@

<%= setting_check_box :lost_password, :label => :label_password_lost %>

+

<%= setting_text_field :max_additional_emails, :size => 6 %>

+

<%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

<%= setting_check_box :rest_api_enabled %>

diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index d46521730..67b57f802 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -1,5 +1,6 @@
<%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %> +<%= additional_emails_link(@user) %> <%= change_status_link(@user) %> <%= delete_link user_path(@user) if User.current != @user %>
diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb index 63532b491..e01b4c2d7 100644 --- a/config/initializers/10-patches.rb +++ b/config/initializers/10-patches.rb @@ -5,8 +5,7 @@ module ActiveRecord include Redmine::I18n # Translate attribute names for validation errors display def self.human_attribute_name(attr, *args) - attr = attr.to_s.sub(/_id$/, '') - + attr = attr.to_s.sub(/_id$/, '').sub(/^.+\./, '') l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr]) end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 86d9b50b0..afa401f10 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -227,6 +227,7 @@ en: field_firstname: First name field_lastname: Last name field_mail: Email + field_address: Email field_filename: File field_filesize: Size field_downloads: Downloads @@ -413,6 +414,7 @@ en: setting_force_default_language_for_anonymous: Force default language for anonymous users setting_force_default_language_for_loggedin: Force default language for logged-in users setting_link_copied_issue: Link issues on copy + setting_max_additional_emails: Maximum number of additional email addresses permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -931,6 +933,10 @@ en: label_search_attachments_no: Do not search attachments label_search_attachments_only: Search attachments only label_search_open_issues_only: Open issues only + label_email_address_plural: Emails + label_email_address_add: Add email address + label_enable_notifications: Enable notifications + label_disable_notifications: Disable notifications button_login: Login button_submit: Submit diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1c5fbf889..369eac349 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -247,6 +247,7 @@ fr: field_firstname: Prénom field_lastname: Nom field_mail: Email + field_address: Email field_filename: Fichier field_filesize: Taille field_downloads: Téléchargements @@ -433,6 +434,7 @@ fr: setting_force_default_language_for_anonymous: Forcer la langue par défault pour les utilisateurs anonymes setting_force_default_language_for_loggedin: Forcer la langue par défault pour les utilisateurs identifiés setting_link_copied_issue: Lier les demandes lors de la copie + setting_max_additional_emails: Nombre maximal d'adresses email additionnelles permission_add_project: Créer un projet permission_add_subprojects: Créer des sous-projets @@ -951,6 +953,10 @@ fr: label_search_attachments_no: Ne pas rechercher les fichiers label_search_attachments_only: Rechercher les fichiers uniquement label_search_open_issues_only: Demandes ouvertes uniquement + label_email_address_plural: Emails + label_email_address_add: Ajouter une adresse email + label_enable_notifications: Activer les notifications + label_disable_notifications: Désactiver les notifications button_login: Connexion button_submit: Soumettre diff --git a/config/routes.rb b/config/routes.rb index d330802de..8eb9b4d27 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,6 +75,7 @@ Rails.application.routes.draw do resources :users do resources :memberships, :controller => 'principal_memberships' + resources :email_addresses, :only => [:index, :create, :update, :destroy] end post 'watchers/watch', :to => 'watchers#watch', :as => 'watch' diff --git a/config/settings.yml b/config/settings.yml index 40e4428f0..a0f920da0 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -36,6 +36,10 @@ unsubscribe: password_min_length: format: int default: 8 +# Maximum number of additional email addresses per user +max_additional_emails: + format: int + default: 5 # Maximum lifetime of user sessions in minutes session_lifetime: format: int diff --git a/db/migrate/20150113194759_create_email_addresses.rb b/db/migrate/20150113194759_create_email_addresses.rb new file mode 100644 index 000000000..a0babce62 --- /dev/null +++ b/db/migrate/20150113194759_create_email_addresses.rb @@ -0,0 +1,12 @@ +class CreateEmailAddresses < ActiveRecord::Migration + def change + create_table :email_addresses do |t| + t.column :user_id, :integer, :null => false + t.column :address, :string, :null => false + t.column :is_default, :boolean, :null => false, :default => false + t.column :notify, :boolean, :null => false, :default => true + t.column :created_on, :timestamp, :null => false + t.column :updated_on, :timestamp, :null => false + end + end +end diff --git a/db/migrate/20150113211532_populate_email_addresses.rb b/db/migrate/20150113211532_populate_email_addresses.rb new file mode 100644 index 000000000..80a5fb016 --- /dev/null +++ b/db/migrate/20150113211532_populate_email_addresses.rb @@ -0,0 +1,14 @@ +class PopulateEmailAddresses < ActiveRecord::Migration + def self.up + t = EmailAddress.connection.quoted_true + n = EmailAddress.connection.quoted_date(Time.now) + + sql = "INSERT INTO #{EmailAddress.table_name} (user_id, address, is_default, notify, created_on, updated_on)" + + " SELECT id, mail, #{t}, #{t}, '#{n}', '#{n}' FROM #{User.table_name} WHERE type = 'User' ORDER BY id" + EmailAddress.connection.execute(sql) + end + + def self.down + EmailAddress.delete_all + end +end diff --git a/db/migrate/20150113213922_remove_users_mail.rb b/db/migrate/20150113213922_remove_users_mail.rb new file mode 100644 index 000000000..8a8b48429 --- /dev/null +++ b/db/migrate/20150113213922_remove_users_mail.rb @@ -0,0 +1,9 @@ +class RemoveUsersMail < ActiveRecord::Migration + def self.up + remove_column :users, :mail + end + + def self.down + raise IrreversibleMigration + end +end diff --git a/db/migrate/20150113213955_add_email_addresses_user_id_index.rb b/db/migrate/20150113213955_add_email_addresses_user_id_index.rb new file mode 100644 index 000000000..b7fb90c97 --- /dev/null +++ b/db/migrate/20150113213955_add_email_addresses_user_id_index.rb @@ -0,0 +1,9 @@ +class AddEmailAddressesUserIdIndex < ActiveRecord::Migration + def up + add_index :email_addresses, :user_id + end + + def down + remove_index :email_addresses, :user_id + end +end diff --git a/public/images/email.png b/public/images/email.png new file mode 100644 index 0000000000000000000000000000000000000000..7348aed77fe6a64c2210a202f12c6eccae7fcf24 GIT binary patch literal 641 zcmV-{0)G98P)Az`{eoOom?Tf*9)f$7n8&|1&5M4#i^32;+&E? zC3Q;bRFQN#y*%%=_V)Mfa<$xe^kB0TO;vJPkN*k(2v-CI7)OaWj?&eKPos(H4wGh_ zIC;6#q1B5SMap5{(Hc0~XO7OfqZ=x{kupu8-H&9azl`L1pTuu^Znm3EA)kCoG=JuwsyNLEtY83i->Z~j3y~F)`RA1k>zTES07po!kBVS2y#L{jCt|CMY&v{ zxmqM|`OA#P2{R&)OcQd}v0kt6_Dh#`Z$i5_;q|93je3Q^PcfR{TmBHRmr;rWahz~G z2x-&;d_O~HkmKXt5Cd#Bs?-+qj3zOiUdU24KowBIUPg(gPNmxqX)Fiia~V*$y;5L( zrGNmU;81MA$F2k%oeUXQ@}N%bXz=qOij$4IYk4W=jfhDxfCz{PGXe-#ge#VfYTyoj zh4JvDePrW{lf(Oux2xG;VZmlSvDU+Qf@i=O!B`MLglhttCUHDIKkc7RvVeTifTlZGEZNAHZ7i+XD6HF1R0l=P zfyscSl7Z{b3aDs0m6Ms>RMQy=_$j(NqsYBiu(n~MBTC@sVniQ-D7XOPL4s1%z@zCE z#9N|hjR!nUwAQdZiP;?yq&uRRc&DJEnrMqqk0|m)F^Qn8x?}}ytx;sVA|RXQQLVw6 z-Q)+esR)vZ5Jso+9>FEb93>v#dzD8l7V^#ng}-dn>P-cNcgHd->U2#00Q?OKQ zcz8OiAj99uA_H|SPuZW&3N@X~?e9Wq4Gk3w0(oB67DQMxk;EU&)A-1l*) zwF(-Bfg`;q0Oz2az;d{^L@9A{Qbthn!_By>J%pVcu5B})M)9MP!_mWskZ#$7$!|}P z`+5u2Qa>-D{OhOAee6jGom7INCCqpO8QNurRMbW%$JB@W*x%C&Mj3iL2f>JmzJ2}B zsxKVD*jQa0xIB>v$l(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ-2uVaiRCwB?(ZNnrQ5eMW-*@h*AT_nLsbCXh0#D!@_*T4w&*D4i#>50yLR_gq z7A7VlDTLd5>+L-(YKSXYINSNpVrHWFx6(bn{QAw#i>qOsD0=kPdPQISu77^|oF~s- zj@FS;gd*;;>#Cc-YSQAMk7GAFwHr%It(BtoR-5(?QxnO)Q>|@DrIHLZj!iry(h^g% zJ0xU{4<`0DEtW`T#@=gWS?isVCo*CKgihtD6Sdk$b6co3HCuG9Ol8-;2y>6ETpFRs zO)0T3_CPT<2F13iUmj8N#o7A#ECvyOEfCl->@Qb8zUS+2N4ulpAO?~osr*4pw_03$ gJ&*rB>t5h?046V8KbE0vhyVZp07*qoM6N<$f`-AJ=l}o! literal 0 HcmV?d00001 diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 80d833966..a3ead6b23 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -378,10 +378,10 @@ function setPredecessorFieldsVisibility() { } } -function showModal(id, width) { +function showModal(id, width, title) { var el = $('#'+id).first(); if (el.length === 0 || el.is(':visible')) {return;} - var title = el.find('h3.title').text(); + if (!title) title = el.find('h3.title').text(); el.dialog({ width: width, modal: true, diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 4b9a4ff49..0efd900de 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -132,6 +132,7 @@ table.list td.checkbox { width: 15px; padding: 2px 0 0 0; } table.list td.checkbox input {padding:0px;} table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; } table.list td.buttons a { padding-right: 0.6em; } +table.list td.buttons img {vertical-align:middle;} table.list td.reorder {width:15%; white-space:nowrap; text-align:center; } table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; } @@ -209,7 +210,7 @@ tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70% tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; } tr.user td {width:13%;white-space: nowrap;} -tr.user td.username, tr.user td.firstname, tr.user td.lastname, tr.user td.email {text-align:left;} +td.username, td.firstname, td.lastname, td.email {text-align:left !important;} tr.user td.email { width:18%; } tr.user.locked, tr.user.registered { color: #aaa; } tr.user.locked a, tr.user.registered a { color: #aaa; } @@ -1046,6 +1047,7 @@ a.close-icon:hover {background-image:url('../images/close_hl.png');} .icon-zoom-out { background-image: url(../images/zoom_out.png); } .icon-passwd { background-image: url(../images/textfield_key.png); } .icon-test { background-image: url(../images/bullet_go.png); } +.icon-email-add { background-image: url(../images/email_add.png); } .icon-file { background-image: url(../images/files/default.png); } .icon-file.text-plain { background-image: url(../images/files/text.png); } diff --git a/test/fixtures/email_addresses.yml b/test/fixtures/email_addresses.yml new file mode 100644 index 000000000..a83f81e2f --- /dev/null +++ b/test/fixtures/email_addresses.yml @@ -0,0 +1,57 @@ +--- +email_address_001: + id: 1 + user_id: 1 + address: admin@somenet.foo + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_002: + id: 2 + user_id: 2 + address: jsmith@somenet.foo + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_003: + id: 3 + user_id: 3 + address: dlopper@somenet.foo + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_004: + id: 4 + user_id: 4 + address: rhill@somenet.foo + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_005: + id: 5 + user_id: 5 + address: dlopper2@somenet.foo + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_007: + id: 7 + user_id: 7 + address: someone@foo.bar + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_008: + id: 8 + user_id: 8 + address: miscuser8@foo.bar + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 +email_address_009: + id: 9 + user_id: 9 + address: miscuser9@foo.bar + is_default: true + created_on: 2006-07-19 19:34:07 +02:00 + updated_on: 2006-07-19 19:34:07 +02:00 diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 9adab5edd..986ed5d67 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,22 +1,4 @@ --- -users_004: - created_on: 2006-07-19 19:34:07 +02:00 - status: 1 - last_login_on: - language: en - # password = foo - salt: 3126f764c3c5ac61cbfc103f25f934cf - hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b - updated_on: 2006-07-19 19:34:07 +02:00 - admin: false - mail: rhill@somenet.foo - lastname: Hill - firstname: Robert - id: 4 - auth_source_id: - mail_notification: all - login: rhill - type: User users_001: created_on: 2006-07-19 19:12:21 +02:00 status: 1 @@ -27,7 +9,6 @@ users_001: hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 updated_on: 2006-07-19 22:57:52 +02:00 admin: true - mail: admin@somenet.foo lastname: Admin firstname: Redmine id: 1 @@ -45,7 +26,6 @@ users_002: hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc updated_on: 2006-07-19 22:42:15 +02:00 admin: false - mail: jsmith@somenet.foo lastname: Smith firstname: John id: 2 @@ -63,7 +43,6 @@ users_003: hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: dlopper@somenet.foo lastname: Lopper firstname: Dave id: 3 @@ -71,6 +50,23 @@ users_003: mail_notification: all login: dlopper type: User +users_004: + created_on: 2006-07-19 19:34:07 +02:00 + status: 1 + last_login_on: + language: en + # password = foo + salt: 3126f764c3c5ac61cbfc103f25f934cf + hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b + updated_on: 2006-07-19 19:34:07 +02:00 + admin: false + lastname: Hill + firstname: Robert + id: 4 + auth_source_id: + mail_notification: all + login: rhill + type: User users_005: id: 5 created_on: 2006-07-19 19:33:19 +02:00 @@ -81,7 +77,6 @@ users_005: hashed_password: 1 updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: dlopper2@somenet.foo lastname: Lopper2 firstname: Dave2 auth_source_id: @@ -97,7 +92,6 @@ users_006: hashed_password: 1 updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: '' lastname: Anonymous firstname: '' auth_source_id: @@ -116,7 +110,6 @@ users_007: hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: someone@foo.bar lastname: One firstname: Some auth_source_id: @@ -134,7 +127,6 @@ users_008: hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: miscuser8@foo.bar lastname: Misc firstname: User auth_source_id: @@ -150,7 +142,6 @@ users_009: hashed_password: 1 updated_on: 2006-07-19 19:33:19 +02:00 admin: false - mail: miscuser9@foo.bar lastname: Misc firstname: User auth_source_id: diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 00a503051..bef3e4284 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class AdminControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles + fixtures :projects, :users, :email_addresses, :roles def setup User.current = nil diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb index 065922a56..c53142b5b 100644 --- a/test/functional/documents_controller_test.rb +++ b/test/functional/documents_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class DocumentsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :documents, :enumerations, :groups_users, :attachments diff --git a/test/functional/email_addresses_controller_test.rb b/test/functional/email_addresses_controller_test.rb new file mode 100644 index 000000000..7c52d9c1d --- /dev/null +++ b/test/functional/email_addresses_controller_test.rb @@ -0,0 +1,144 @@ +# Redmine - project management software +# Copyright (C) 2006-2015 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class EmailAddressesControllerTest < ActionController::TestCase + fixtures :users, :email_addresses + + def setup + User.current = nil + end + + def test_index_with_no_additional_emails + @request.session[:user_id] = 2 + get :index, :user_id => 2 + assert_response :success + assert_template 'index' + end + + def test_index_with_additional_emails + @request.session[:user_id] = 2 + EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + get :index, :user_id => 2 + assert_response :success + assert_template 'index' + assert_select '.email', :text => 'another@somenet.foo' + end + + def test_index_with_additional_emails_as_js + @request.session[:user_id] = 2 + EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + xhr :get, :index, :user_id => 2 + assert_response :success + assert_template 'index' + assert_include 'another@somenet.foo', response.body + end + + def test_index_by_admin_should_be_allowed + @request.session[:user_id] = 1 + get :index, :user_id => 2 + assert_response :success + assert_template 'index' + end + + def test_index_by_another_user_should_be_denied + @request.session[:user_id] = 3 + get :index, :user_id => 2 + assert_response 403 + end + + def test_create + @request.session[:user_id] = 2 + assert_difference 'EmailAddress.count' do + post :create, :user_id => 2, :email_address => {:address => 'another@somenet.foo'} + assert_response 302 + assert_redirected_to '/users/2/email_addresses' + end + email = EmailAddress.order('id DESC').first + assert_equal 2, email.user_id + assert_equal 'another@somenet.foo', email.address + end + + def test_create_as_js + @request.session[:user_id] = 2 + assert_difference 'EmailAddress.count' do + xhr :post, :create, :user_id => 2, :email_address => {:address => 'another@somenet.foo'} + assert_response 200 + end + end + + def test_create_with_failure + @request.session[:user_id] = 2 + assert_no_difference 'EmailAddress.count' do + post :create, :user_id => 2, :email_address => {:address => 'invalid'} + assert_response 200 + end + end + + def test_update + @request.session[:user_id] = 2 + email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + put :update, :user_id => 2, :id => email.id, :notify => '0' + assert_response 302 + + assert_equal false, email.reload.notify + end + + def test_update_as_js + @request.session[:user_id] = 2 + email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + xhr :put, :update, :user_id => 2, :id => email.id, :notify => '0' + assert_response 200 + + assert_equal false, email.reload.notify + end + + def test_destroy + @request.session[:user_id] = 2 + email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + assert_difference 'EmailAddress.count', -1 do + delete :destroy, :user_id => 2, :id => email.id + assert_response 302 + assert_redirected_to '/users/2/email_addresses' + end + end + + def test_destroy_as_js + @request.session[:user_id] = 2 + email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo') + + assert_difference 'EmailAddress.count', -1 do + xhr :delete, :destroy, :user_id => 2, :id => email.id + assert_response 200 + end + end + + def test_should_not_destroy_default + @request.session[:user_id] = 2 + + assert_no_difference 'EmailAddress.count' do + delete :destroy, :user_id => 2, :id => User.find(2).email_address.id + assert_response 404 + end + end +end diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 04ebec0df..86c5cdb16 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -19,7 +19,7 @@ require File.expand_path('../../test_helper', __FILE__) class IssuesControllerTest < ActionController::TestCase fixtures :projects, - :users, + :users, :email_addresses, :roles, :members, :member_roles, diff --git a/test/functional/issues_custom_fields_visibility_test.rb b/test/functional/issues_custom_fields_visibility_test.rb index 7d8cfa9a4..6e9ec5f11 100644 --- a/test/functional/issues_custom_fields_visibility_test.rb +++ b/test/functional/issues_custom_fields_visibility_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class IssuesCustomFieldsVisibilityTest < ActionController::TestCase tests IssuesController fixtures :projects, - :users, + :users, :email_addresses, :roles, :members, :member_roles, diff --git a/test/functional/mail_handler_controller_test.rb b/test/functional/mail_handler_controller_test.rb index 91ae34122..3487d5eb2 100644 --- a/test/functional/mail_handler_controller_test.rb +++ b/test/functional/mail_handler_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class MailHandlerControllerTest < ActionController::TestCase - fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, + fixtures :users, :email_addresses, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, :trackers, :projects_trackers, :enumerations FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb index 9669379d9..cc61e5a14 100644 --- a/test/functional/messages_controller_test.rb +++ b/test/functional/messages_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class MessagesControllerTest < ActionController::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules + fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :boards, :messages, :enabled_modules def setup User.current = nil diff --git a/test/functional/my_controller_test.rb b/test/functional/my_controller_test.rb index 32317cd7c..5f14c5791 100644 --- a/test/functional/my_controller_test.rb +++ b/test/functional/my_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class MyControllerTest < ActionController::TestCase - fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, + fixtures :users, :email_addresses, :user_preferences, :roles, :projects, :members, :member_roles, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources def setup diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb index 554d1b290..b957dde6e 100644 --- a/test/functional/news_controller_test.rb +++ b/test/functional/news_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class NewsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :news, :comments, :attachments diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 379fe04da..fa86f1a0f 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class ProjectsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :users, :roles, :members, + fixtures :projects, :versions, :users, :email_addresses, :roles, :members, :member_roles, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, diff --git a/test/functional/repositories_bazaar_controller_test.rb b/test/functional/repositories_bazaar_controller_test.rb index 0374559ba..cde2d1cf7 100644 --- a/test/functional/repositories_bazaar_controller_test.rb +++ b/test/functional/repositories_bazaar_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesBazaarControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s diff --git a/test/functional/repositories_controller_test.rb b/test/functional/repositories_controller_test.rb index 4dedaf0f0..14f6a6355 100644 --- a/test/functional/repositories_controller_test.rb +++ b/test/functional/repositories_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers diff --git a/test/functional/repositories_cvs_controller_test.rb b/test/functional/repositories_cvs_controller_test.rb index 094d027e9..ba35e1ea0 100644 --- a/test/functional/repositories_cvs_controller_test.rb +++ b/test/functional/repositories_cvs_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesCvsControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s diff --git a/test/functional/repositories_darcs_controller_test.rb b/test/functional/repositories_darcs_controller_test.rb index dd41f51ab..9df8b2a24 100644 --- a/test/functional/repositories_darcs_controller_test.rb +++ b/test/functional/repositories_darcs_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesDarcsControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s diff --git a/test/functional/repositories_filesystem_controller_test.rb b/test/functional/repositories_filesystem_controller_test.rb index 0de6f6c61..34333cf89 100644 --- a/test/functional/repositories_filesystem_controller_test.rb +++ b/test/functional/repositories_filesystem_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesFilesystemControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb index f085661dd..c6d7ada9b 100644 --- a/test/functional/repositories_git_controller_test.rb +++ b/test/functional/repositories_git_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesGitControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb index e4485b91c..734fe0e23 100644 --- a/test/functional/repositories_mercurial_controller_test.rb +++ b/test/functional/repositories_mercurial_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesMercurialControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :repositories, :enabled_modules REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb index 9afced4e4..664330ff4 100644 --- a/test/functional/repositories_subversion_controller_test.rb +++ b/test/functional/repositories_subversion_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class RepositoriesSubversionControllerTest < ActionController::TestCase tests RepositoriesController - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index 2fc48dbcc..b34c80945 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class UsersControllerTest < ActionController::TestCase include Redmine::I18n - fixtures :users, :projects, :members, :member_roles, :roles, + fixtures :users, :email_addresses, :projects, :members, :member_roles, :roles, :custom_fields, :custom_values, :groups_users, :auth_sources, :enabled_modules, diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index fb9537a04..9bffe66c7 100644 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class WikiControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments, :issues, :issue_statuses diff --git a/test/integration/account_test.rb b/test/integration/account_test.rb index bf458b734..7f2f0e4ff 100644 --- a/test/integration/account_test.rb +++ b/test/integration/account_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class AccountTest < Redmine::IntegrationTest - fixtures :users, :roles + fixtures :users, :email_addresses, :roles def test_login get "/my/page" diff --git a/test/integration/api_test/users_test.rb b/test/integration/api_test/users_test.rb index f1a0e1055..d8d78ad0e 100644 --- a/test/integration/api_test/users_test.rb +++ b/test/integration/api_test/users_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../../test_helper', __FILE__) class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base - fixtures :users, :members, :member_roles, :roles, :projects + fixtures :users, :email_addresses, :members, :member_roles, :roles, :projects test "GET /users.xml should return users" do get '/users.xml', {}, credentials('admin') diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index d26281172..981fdb760 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -19,7 +19,7 @@ require File.expand_path('../../test_helper', __FILE__) class IssuesTest < Redmine::IntegrationTest fixtures :projects, - :users, + :users, :email_addresses, :roles, :members, :member_roles, diff --git a/test/integration/users_test.rb b/test/integration/users_test.rb index ea7f06c12..1ae2f27d6 100644 --- a/test/integration/users_test.rb +++ b/test/integration/users_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class UsersTest < Redmine::IntegrationTest - fixtures :users + fixtures :users, :email_addresses def test_destroy_should_not_accept_get_requests assert_no_difference 'User.count' do diff --git a/test/unit/document_test.rb b/test/unit/document_test.rb index 98d15c0e0..50decb7c7 100644 --- a/test/unit/document_test.rb +++ b/test/unit/document_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class DocumentTest < ActiveSupport::TestCase fixtures :projects, :enumerations, :documents, :attachments, :enabled_modules, - :users, :members, :member_roles, :roles, + :users, :email_addresses, :members, :member_roles, :roles, :groups_users def test_create diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 03208d397..0384dfb0e 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class IssueTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, + fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :groups_users, :trackers, :projects_trackers, :enabled_modules, diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb index a8a7846ba..bc7e0a0d0 100644 --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -223,6 +223,17 @@ class MailHandlerTest < ActiveSupport::TestCase assert_equal 1, issue.watcher_user_ids.size end + def test_add_issue_from_additional_email_address + user = User.find(2) + user.mail = 'mainaddress@somenet.foo' + user.save! + EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo') + + issue = submit_email('ticket_on_given_project.eml') + assert issue + assert_equal user, issue.author + end + def test_add_issue_by_unknown_user assert_no_difference 'User.count' do assert_equal false, diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index 9728387a4..b020e5303 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -20,7 +20,7 @@ require File.expand_path('../../test_helper', __FILE__) class MailerTest < ActiveSupport::TestCase include Redmine::I18n include ActionDispatch::Assertions::SelectorAssertions - fixtures :projects, :enabled_modules, :issues, :users, :members, + fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members, :member_roles, :roles, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :projects_trackers, @@ -298,6 +298,14 @@ class MailerTest < ActiveSupport::TestCase assert last_email.bcc.include?('dlopper@somenet.foo') end + def test_issue_add_should_send_mail_to_all_user_email_address + EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo') + issue = Issue.find(1) + assert Mailer.deliver_issue_add(issue) + assert last_email.bcc.include?('dlopper@somenet.foo') + assert last_email.bcc.include?('otheremail@somenet.foo') + end + test "#issue_add should not notify project members that are not allow to view the issue" do issue = Issue.find(1) Role.find(2).remove_permission!(:view_issues) @@ -771,6 +779,30 @@ class MailerTest < ActiveSupport::TestCase ActionMailer::Base.delivery_method = :test end + def test_email_addresses_should_keep_addresses + assert_equal ["foo@example.net"], + Mailer.email_addresses("foo@example.net") + + assert_equal ["foo@example.net", "bar@example.net"], + Mailer.email_addresses(["foo@example.net", "bar@example.net"]) + end + + def test_email_addresses_should_replace_users_with_their_email_addresses + assert_equal ["admin@somenet.foo"], + Mailer.email_addresses(User.find(1)) + + assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"], + Mailer.email_addresses(User.where(:id => [1,2])).sort + end + + def test_email_addresses_should_include_notified_emails_addresses_only + EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false) + EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo") + + assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"], + Mailer.email_addresses(User.find(2)).sort + end + private def last_email diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index fbe35f253..429f2908b 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class UserTest < ActiveSupport::TestCase - fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources, + fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources, :trackers, :issue_statuses, :projects_trackers, :watchers, @@ -57,11 +57,41 @@ class UserTest < ActiveSupport::TestCase assert_equal "foo@bar.com", u.mail end - def test_mail_validation - u = User.new + def test_should_create_email_address + u = User.new(:firstname => "new", :lastname => "user") + u.login = "create_email_address" + u.mail = "defaultemail@somenet.foo" + assert u.save + u.reload + assert u.email_address + assert_equal "defaultemail@somenet.foo", u.email_address.address + assert_equal true, u.email_address.is_default + assert_equal true, u.email_address.notify + end + + def test_should_not_create_user_without_mail + set_language_if_valid 'en' + u = User.new(:firstname => "new", :lastname => "user") + u.login = "user_without_mail" + assert !u.save + assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages + end + + def test_should_not_create_user_with_blank_mail + set_language_if_valid 'en' + u = User.new(:firstname => "new", :lastname => "user") + u.login = "user_with_blank_mail" + u.mail = '' + assert !u.save + assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages + end + + def test_should_not_update_user_with_blank_mail + set_language_if_valid 'en' + u = User.find(2) u.mail = '' - assert !u.valid? - assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail] + assert !u.save + assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages end def test_login_length_validation @@ -151,6 +181,7 @@ class UserTest < ActiveSupport::TestCase end def test_mail_uniqueness_should_not_be_case_sensitive + set_language_if_valid 'en' u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") u.login = 'newuser1' u.password, u.password_confirmation = "password", "password" @@ -160,7 +191,7 @@ class UserTest < ActiveSupport::TestCase u.login = 'newuser2' u.password, u.password_confirmation = "password", "password" assert !u.save - assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail] + assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages end def test_update @@ -677,7 +708,7 @@ class UserTest < ActiveSupport::TestCase assert_kind_of AnonymousUser, anon1 anon2 = AnonymousUser.create( :lastname => 'Anonymous', :firstname => '', - :mail => '', :login => '', :status => 0) + :login => '', :status => 0) assert_equal 1, anon2.errors.count end diff --git a/test/unit/watcher_test.rb b/test/unit/watcher_test.rb index f10276ccb..e7417332c 100644 --- a/test/unit/watcher_test.rb +++ b/test/unit/watcher_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class WatcherTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules, + fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules, :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers, :boards, :messages, :wikis, :wiki_pages, -- 2.39.5