##// END OF EJS Templates
Accept email from anonymous users with an empty from address. #5604...
Eric Davis -
r3669:d48eb2de47c3
parent child
Show More
@@ -0,0 +1,17
1 Return-Path: <john.doe@somenet.foo>
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 To: <redmine@somenet.foo>
7 Subject: Ticket by unknown user
8 Date: Sun, 22 Jun 2008 12:28:07 +0200
9 MIME-Version: 1.0
10 Content-Type: text/plain;
11 format=flowed;
12 charset="iso-8859-1";
13 reply-type=original
14 Content-Transfer-Encoding: 7bit
15
16 This is a ticket submitted by an unknown user.
17
@@ -1,336 +1,336
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20
20
21 class UnauthorizedAction < StandardError; end
21 class UnauthorizedAction < StandardError; end
22 class MissingInformation < StandardError; end
22 class MissingInformation < StandardError; end
23
23
24 attr_reader :email, :user
24 attr_reader :email, :user
25
25
26 def self.receive(email, options={})
26 def self.receive(email, options={})
27 @@handler_options = options.dup
27 @@handler_options = options.dup
28
28
29 @@handler_options[:issue] ||= {}
29 @@handler_options[:issue] ||= {}
30
30
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 @@handler_options[:allow_override] ||= []
32 @@handler_options[:allow_override] ||= []
33 # Project needs to be overridable if not specified
33 # Project needs to be overridable if not specified
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 # Status overridable by default
35 # Status overridable by default
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37
37
38 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
38 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
39 super email
39 super email
40 end
40 end
41
41
42 # Processes incoming emails
42 # Processes incoming emails
43 # Returns the created object (eg. an issue, a message) or false
43 # Returns the created object (eg. an issue, a message) or false
44 def receive(email)
44 def receive(email)
45 @email = email
45 @email = email
46 sender_email = email.from.to_a.first.to_s.strip
46 sender_email = email.from.to_a.first.to_s.strip
47 # Ignore emails received from the application emission address to avoid hell cycles
47 # Ignore emails received from the application emission address to avoid hell cycles
48 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
48 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
49 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
49 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
50 return false
50 return false
51 end
51 end
52 @user = User.find_by_mail(sender_email)
52 @user = User.find_by_mail(sender_email) if sender_email.present?
53 if @user && !@user.active?
53 if @user && !@user.active?
54 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
54 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
55 return false
55 return false
56 end
56 end
57 if @user.nil?
57 if @user.nil?
58 # Email was submitted by an unknown user
58 # Email was submitted by an unknown user
59 case @@handler_options[:unknown_user]
59 case @@handler_options[:unknown_user]
60 when 'accept'
60 when 'accept'
61 @user = User.anonymous
61 @user = User.anonymous
62 when 'create'
62 when 'create'
63 @user = MailHandler.create_user_from_email(email)
63 @user = MailHandler.create_user_from_email(email)
64 if @user
64 if @user
65 logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
65 logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
66 Mailer.deliver_account_information(@user, @user.password)
66 Mailer.deliver_account_information(@user, @user.password)
67 else
67 else
68 logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
68 logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
69 return false
69 return false
70 end
70 end
71 else
71 else
72 # Default behaviour, emails from unknown users are ignored
72 # Default behaviour, emails from unknown users are ignored
73 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
73 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
74 return false
74 return false
75 end
75 end
76 end
76 end
77 User.current = @user
77 User.current = @user
78 dispatch
78 dispatch
79 end
79 end
80
80
81 private
81 private
82
82
83 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
83 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
84 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
84 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
85 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
85 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
86
86
87 def dispatch
87 def dispatch
88 headers = [email.in_reply_to, email.references].flatten.compact
88 headers = [email.in_reply_to, email.references].flatten.compact
89 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
89 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
90 klass, object_id = $1, $2.to_i
90 klass, object_id = $1, $2.to_i
91 method_name = "receive_#{klass}_reply"
91 method_name = "receive_#{klass}_reply"
92 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
92 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
93 send method_name, object_id
93 send method_name, object_id
94 else
94 else
95 # ignoring it
95 # ignoring it
96 end
96 end
97 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
97 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
98 receive_issue_reply(m[1].to_i)
98 receive_issue_reply(m[1].to_i)
99 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
99 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
100 receive_message_reply(m[1].to_i)
100 receive_message_reply(m[1].to_i)
101 else
101 else
102 receive_issue
102 receive_issue
103 end
103 end
104 rescue ActiveRecord::RecordInvalid => e
104 rescue ActiveRecord::RecordInvalid => e
105 # TODO: send a email to the user
105 # TODO: send a email to the user
106 logger.error e.message if logger
106 logger.error e.message if logger
107 false
107 false
108 rescue MissingInformation => e
108 rescue MissingInformation => e
109 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
109 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
110 false
110 false
111 rescue UnauthorizedAction => e
111 rescue UnauthorizedAction => e
112 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
112 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
113 false
113 false
114 end
114 end
115
115
116 # Creates a new issue
116 # Creates a new issue
117 def receive_issue
117 def receive_issue
118 project = target_project
118 project = target_project
119 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
119 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
120 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
120 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
121 priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
121 priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
122 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
122 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
123 assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
123 assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
124 due_date = get_keyword(:due_date, :override => true)
124 due_date = get_keyword(:due_date, :override => true)
125 start_date = get_keyword(:start_date, :override => true)
125 start_date = get_keyword(:start_date, :override => true)
126
126
127 # check permission
127 # check permission
128 unless @@handler_options[:no_permission_check]
128 unless @@handler_options[:no_permission_check]
129 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
129 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
130 end
130 end
131
131
132 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority, :due_date => due_date, :start_date => start_date, :assigned_to => assigned_to)
132 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority, :due_date => due_date, :start_date => start_date, :assigned_to => assigned_to)
133 # check workflow
133 # check workflow
134 if status && issue.new_statuses_allowed_to(user).include?(status)
134 if status && issue.new_statuses_allowed_to(user).include?(status)
135 issue.status = status
135 issue.status = status
136 end
136 end
137 issue.subject = email.subject.chomp
137 issue.subject = email.subject.chomp
138 if issue.subject.blank?
138 if issue.subject.blank?
139 issue.subject = '(no subject)'
139 issue.subject = '(no subject)'
140 end
140 end
141 # custom fields
141 # custom fields
142 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
142 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
143 if value = get_keyword(c.name, :override => true)
143 if value = get_keyword(c.name, :override => true)
144 h[c.id] = value
144 h[c.id] = value
145 end
145 end
146 h
146 h
147 end
147 end
148 issue.description = cleaned_up_text_body
148 issue.description = cleaned_up_text_body
149 # add To and Cc as watchers before saving so the watchers can reply to Redmine
149 # add To and Cc as watchers before saving so the watchers can reply to Redmine
150 add_watchers(issue)
150 add_watchers(issue)
151 issue.save!
151 issue.save!
152 add_attachments(issue)
152 add_attachments(issue)
153 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
153 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
154 issue
154 issue
155 end
155 end
156
156
157 def target_project
157 def target_project
158 # TODO: other ways to specify project:
158 # TODO: other ways to specify project:
159 # * parse the email To field
159 # * parse the email To field
160 # * specific project (eg. Setting.mail_handler_target_project)
160 # * specific project (eg. Setting.mail_handler_target_project)
161 target = Project.find_by_identifier(get_keyword(:project))
161 target = Project.find_by_identifier(get_keyword(:project))
162 raise MissingInformation.new('Unable to determine target project') if target.nil?
162 raise MissingInformation.new('Unable to determine target project') if target.nil?
163 target
163 target
164 end
164 end
165
165
166 # Adds a note to an existing issue
166 # Adds a note to an existing issue
167 def receive_issue_reply(issue_id)
167 def receive_issue_reply(issue_id)
168 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
168 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
169 due_date = get_keyword(:due_date, :override => true)
169 due_date = get_keyword(:due_date, :override => true)
170 start_date = get_keyword(:start_date, :override => true)
170 start_date = get_keyword(:start_date, :override => true)
171 assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
171 assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
172
172
173 issue = Issue.find_by_id(issue_id)
173 issue = Issue.find_by_id(issue_id)
174 return unless issue
174 return unless issue
175 # check permission
175 # check permission
176 unless @@handler_options[:no_permission_check]
176 unless @@handler_options[:no_permission_check]
177 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
177 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
178 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
178 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
179 end
179 end
180
180
181 # add the note
181 # add the note
182 journal = issue.init_journal(user, cleaned_up_text_body)
182 journal = issue.init_journal(user, cleaned_up_text_body)
183 add_attachments(issue)
183 add_attachments(issue)
184 # check workflow
184 # check workflow
185 if status && issue.new_statuses_allowed_to(user).include?(status)
185 if status && issue.new_statuses_allowed_to(user).include?(status)
186 issue.status = status
186 issue.status = status
187 end
187 end
188 issue.start_date = start_date if start_date
188 issue.start_date = start_date if start_date
189 issue.due_date = due_date if due_date
189 issue.due_date = due_date if due_date
190 issue.assigned_to = assigned_to if assigned_to
190 issue.assigned_to = assigned_to if assigned_to
191
191
192 issue.save!
192 issue.save!
193 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
193 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
194 journal
194 journal
195 end
195 end
196
196
197 # Reply will be added to the issue
197 # Reply will be added to the issue
198 def receive_journal_reply(journal_id)
198 def receive_journal_reply(journal_id)
199 journal = Journal.find_by_id(journal_id)
199 journal = Journal.find_by_id(journal_id)
200 if journal && journal.journalized_type == 'Issue'
200 if journal && journal.journalized_type == 'Issue'
201 receive_issue_reply(journal.journalized_id)
201 receive_issue_reply(journal.journalized_id)
202 end
202 end
203 end
203 end
204
204
205 # Receives a reply to a forum message
205 # Receives a reply to a forum message
206 def receive_message_reply(message_id)
206 def receive_message_reply(message_id)
207 message = Message.find_by_id(message_id)
207 message = Message.find_by_id(message_id)
208 if message
208 if message
209 message = message.root
209 message = message.root
210
210
211 unless @@handler_options[:no_permission_check]
211 unless @@handler_options[:no_permission_check]
212 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
212 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
213 end
213 end
214
214
215 if !message.locked?
215 if !message.locked?
216 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
216 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
217 :content => cleaned_up_text_body)
217 :content => cleaned_up_text_body)
218 reply.author = user
218 reply.author = user
219 reply.board = message.board
219 reply.board = message.board
220 message.children << reply
220 message.children << reply
221 add_attachments(reply)
221 add_attachments(reply)
222 reply
222 reply
223 else
223 else
224 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info
224 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info
225 end
225 end
226 end
226 end
227 end
227 end
228
228
229 def add_attachments(obj)
229 def add_attachments(obj)
230 if email.has_attachments?
230 if email.has_attachments?
231 email.attachments.each do |attachment|
231 email.attachments.each do |attachment|
232 Attachment.create(:container => obj,
232 Attachment.create(:container => obj,
233 :file => attachment,
233 :file => attachment,
234 :author => user,
234 :author => user,
235 :content_type => attachment.content_type)
235 :content_type => attachment.content_type)
236 end
236 end
237 end
237 end
238 end
238 end
239
239
240 # Adds To and Cc as watchers of the given object if the sender has the
240 # Adds To and Cc as watchers of the given object if the sender has the
241 # appropriate permission
241 # appropriate permission
242 def add_watchers(obj)
242 def add_watchers(obj)
243 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
243 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
244 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
244 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
245 unless addresses.empty?
245 unless addresses.empty?
246 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
246 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
247 watchers.each {|w| obj.add_watcher(w)}
247 watchers.each {|w| obj.add_watcher(w)}
248 end
248 end
249 end
249 end
250 end
250 end
251
251
252 def get_keyword(attr, options={})
252 def get_keyword(attr, options={})
253 @keywords ||= {}
253 @keywords ||= {}
254 if @keywords.has_key?(attr)
254 if @keywords.has_key?(attr)
255 @keywords[attr]
255 @keywords[attr]
256 else
256 else
257 @keywords[attr] = begin
257 @keywords[attr] = begin
258 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr.to_s.humanize}[ \t]*:[ \t]*(.+)\s*$/i, '')
258 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr.to_s.humanize}[ \t]*:[ \t]*(.+)\s*$/i, '')
259 $1.strip
259 $1.strip
260 elsif !@@handler_options[:issue][attr].blank?
260 elsif !@@handler_options[:issue][attr].blank?
261 @@handler_options[:issue][attr]
261 @@handler_options[:issue][attr]
262 end
262 end
263 end
263 end
264 end
264 end
265 end
265 end
266
266
267 # Returns the text/plain part of the email
267 # Returns the text/plain part of the email
268 # If not found (eg. HTML-only email), returns the body with tags removed
268 # If not found (eg. HTML-only email), returns the body with tags removed
269 def plain_text_body
269 def plain_text_body
270 return @plain_text_body unless @plain_text_body.nil?
270 return @plain_text_body unless @plain_text_body.nil?
271 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
271 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
272 if parts.empty?
272 if parts.empty?
273 parts << @email
273 parts << @email
274 end
274 end
275 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
275 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
276 if plain_text_part.nil?
276 if plain_text_part.nil?
277 # no text/plain part found, assuming html-only email
277 # no text/plain part found, assuming html-only email
278 # strip html tags and remove doctype directive
278 # strip html tags and remove doctype directive
279 @plain_text_body = strip_tags(@email.body.to_s)
279 @plain_text_body = strip_tags(@email.body.to_s)
280 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
280 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
281 else
281 else
282 @plain_text_body = plain_text_part.body.to_s
282 @plain_text_body = plain_text_part.body.to_s
283 end
283 end
284 @plain_text_body.strip!
284 @plain_text_body.strip!
285 @plain_text_body
285 @plain_text_body
286 end
286 end
287
287
288 def cleaned_up_text_body
288 def cleaned_up_text_body
289 cleanup_body(plain_text_body)
289 cleanup_body(plain_text_body)
290 end
290 end
291
291
292 def self.full_sanitizer
292 def self.full_sanitizer
293 @full_sanitizer ||= HTML::FullSanitizer.new
293 @full_sanitizer ||= HTML::FullSanitizer.new
294 end
294 end
295
295
296 # Creates a user account for the +email+ sender
296 # Creates a user account for the +email+ sender
297 def self.create_user_from_email(email)
297 def self.create_user_from_email(email)
298 addr = email.from_addrs.to_a.first
298 addr = email.from_addrs.to_a.first
299 if addr && !addr.spec.blank?
299 if addr && !addr.spec.blank?
300 user = User.new
300 user = User.new
301 user.mail = addr.spec
301 user.mail = addr.spec
302
302
303 names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
303 names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
304 user.firstname = names.shift
304 user.firstname = names.shift
305 user.lastname = names.join(' ')
305 user.lastname = names.join(' ')
306 user.lastname = '-' if user.lastname.blank?
306 user.lastname = '-' if user.lastname.blank?
307
307
308 user.login = user.mail
308 user.login = user.mail
309 user.password = ActiveSupport::SecureRandom.hex(5)
309 user.password = ActiveSupport::SecureRandom.hex(5)
310 user.language = Setting.default_language
310 user.language = Setting.default_language
311 user.save ? user : nil
311 user.save ? user : nil
312 end
312 end
313 end
313 end
314
314
315 private
315 private
316
316
317 # Removes the email body of text after the truncation configurations.
317 # Removes the email body of text after the truncation configurations.
318 def cleanup_body(body)
318 def cleanup_body(body)
319 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
319 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
320 unless delimiters.empty?
320 unless delimiters.empty?
321 regex = Regexp.new("^(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
321 regex = Regexp.new("^(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
322 body = body.gsub(regex, '')
322 body = body.gsub(regex, '')
323 end
323 end
324 body.strip
324 body.strip
325 end
325 end
326
326
327 def find_user_from_keyword(keyword)
327 def find_user_from_keyword(keyword)
328 user ||= User.find_by_mail(keyword)
328 user ||= User.find_by_mail(keyword)
329 user ||= User.find_by_login(keyword)
329 user ||= User.find_by_login(keyword)
330 if user.nil? && keyword.match(/ /)
330 if user.nil? && keyword.match(/ /)
331 firstname, lastname = *(keyword.split) # "First Last Throwaway"
331 firstname, lastname = *(keyword.split) # "First Last Throwaway"
332 user ||= User.find_by_firstname_and_lastname(firstname, lastname)
332 user ||= User.find_by_firstname_and_lastname(firstname, lastname)
333 end
333 end
334 user
334 user
335 end
335 end
336 end
336 end
@@ -1,356 +1,365
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2009 Jean-Philippe Lang
4 # Copyright (C) 2006-2009 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.dirname(__FILE__) + '/../test_helper'
20 require File.dirname(__FILE__) + '/../test_helper'
21
21
22 class MailHandlerTest < ActiveSupport::TestCase
22 class MailHandlerTest < ActiveSupport::TestCase
23 fixtures :users, :projects,
23 fixtures :users, :projects,
24 :enabled_modules,
24 :enabled_modules,
25 :roles,
25 :roles,
26 :members,
26 :members,
27 :member_roles,
27 :member_roles,
28 :issues,
28 :issues,
29 :issue_statuses,
29 :issue_statuses,
30 :workflows,
30 :workflows,
31 :trackers,
31 :trackers,
32 :projects_trackers,
32 :projects_trackers,
33 :enumerations,
33 :enumerations,
34 :issue_categories,
34 :issue_categories,
35 :custom_fields,
35 :custom_fields,
36 :custom_fields_trackers,
36 :custom_fields_trackers,
37 :boards,
37 :boards,
38 :messages
38 :messages
39
39
40 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
40 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
41
41
42 def setup
42 def setup
43 ActionMailer::Base.deliveries.clear
43 ActionMailer::Base.deliveries.clear
44 end
44 end
45
45
46 def test_add_issue
46 def test_add_issue
47 ActionMailer::Base.deliveries.clear
47 ActionMailer::Base.deliveries.clear
48 # This email contains: 'Project: onlinestore'
48 # This email contains: 'Project: onlinestore'
49 issue = submit_email('ticket_on_given_project.eml')
49 issue = submit_email('ticket_on_given_project.eml')
50 assert issue.is_a?(Issue)
50 assert issue.is_a?(Issue)
51 assert !issue.new_record?
51 assert !issue.new_record?
52 issue.reload
52 issue.reload
53 assert_equal 'New ticket on a given project', issue.subject
53 assert_equal 'New ticket on a given project', issue.subject
54 assert_equal User.find_by_login('jsmith'), issue.author
54 assert_equal User.find_by_login('jsmith'), issue.author
55 assert_equal Project.find(2), issue.project
55 assert_equal Project.find(2), issue.project
56 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
56 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
57 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
57 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
58 assert_equal '2010-01-01', issue.start_date.to_s
58 assert_equal '2010-01-01', issue.start_date.to_s
59 assert_equal '2010-12-31', issue.due_date.to_s
59 assert_equal '2010-12-31', issue.due_date.to_s
60 assert_equal User.find_by_login('jsmith'), issue.assigned_to
60 assert_equal User.find_by_login('jsmith'), issue.assigned_to
61 # keywords should be removed from the email body
61 # keywords should be removed from the email body
62 assert !issue.description.match(/^Project:/i)
62 assert !issue.description.match(/^Project:/i)
63 assert !issue.description.match(/^Status:/i)
63 assert !issue.description.match(/^Status:/i)
64 # Email notification should be sent
64 # Email notification should be sent
65 mail = ActionMailer::Base.deliveries.last
65 mail = ActionMailer::Base.deliveries.last
66 assert_not_nil mail
66 assert_not_nil mail
67 assert mail.subject.include?('New ticket on a given project')
67 assert mail.subject.include?('New ticket on a given project')
68 end
68 end
69
69
70 def test_add_issue_with_status
70 def test_add_issue_with_status
71 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
71 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
72 issue = submit_email('ticket_on_given_project.eml')
72 issue = submit_email('ticket_on_given_project.eml')
73 assert issue.is_a?(Issue)
73 assert issue.is_a?(Issue)
74 assert !issue.new_record?
74 assert !issue.new_record?
75 issue.reload
75 issue.reload
76 assert_equal Project.find(2), issue.project
76 assert_equal Project.find(2), issue.project
77 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
77 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
78 end
78 end
79
79
80 def test_add_issue_with_attributes_override
80 def test_add_issue_with_attributes_override
81 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
81 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
82 assert issue.is_a?(Issue)
82 assert issue.is_a?(Issue)
83 assert !issue.new_record?
83 assert !issue.new_record?
84 issue.reload
84 issue.reload
85 assert_equal 'New ticket on a given project', issue.subject
85 assert_equal 'New ticket on a given project', issue.subject
86 assert_equal User.find_by_login('jsmith'), issue.author
86 assert_equal User.find_by_login('jsmith'), issue.author
87 assert_equal Project.find(2), issue.project
87 assert_equal Project.find(2), issue.project
88 assert_equal 'Feature request', issue.tracker.to_s
88 assert_equal 'Feature request', issue.tracker.to_s
89 assert_equal 'Stock management', issue.category.to_s
89 assert_equal 'Stock management', issue.category.to_s
90 assert_equal 'Urgent', issue.priority.to_s
90 assert_equal 'Urgent', issue.priority.to_s
91 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
91 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
92 end
92 end
93
93
94 def test_add_issue_with_partial_attributes_override
94 def test_add_issue_with_partial_attributes_override
95 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
95 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
96 assert issue.is_a?(Issue)
96 assert issue.is_a?(Issue)
97 assert !issue.new_record?
97 assert !issue.new_record?
98 issue.reload
98 issue.reload
99 assert_equal 'New ticket on a given project', issue.subject
99 assert_equal 'New ticket on a given project', issue.subject
100 assert_equal User.find_by_login('jsmith'), issue.author
100 assert_equal User.find_by_login('jsmith'), issue.author
101 assert_equal Project.find(2), issue.project
101 assert_equal Project.find(2), issue.project
102 assert_equal 'Feature request', issue.tracker.to_s
102 assert_equal 'Feature request', issue.tracker.to_s
103 assert_nil issue.category
103 assert_nil issue.category
104 assert_equal 'High', issue.priority.to_s
104 assert_equal 'High', issue.priority.to_s
105 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
105 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
106 end
106 end
107
107
108 def test_add_issue_with_spaces_between_attribute_and_separator
108 def test_add_issue_with_spaces_between_attribute_and_separator
109 issue = submit_email('ticket_with_spaces_between_attribute_and_separator.eml', :allow_override => 'tracker,category,priority')
109 issue = submit_email('ticket_with_spaces_between_attribute_and_separator.eml', :allow_override => 'tracker,category,priority')
110 assert issue.is_a?(Issue)
110 assert issue.is_a?(Issue)
111 assert !issue.new_record?
111 assert !issue.new_record?
112 issue.reload
112 issue.reload
113 assert_equal 'New ticket on a given project', issue.subject
113 assert_equal 'New ticket on a given project', issue.subject
114 assert_equal User.find_by_login('jsmith'), issue.author
114 assert_equal User.find_by_login('jsmith'), issue.author
115 assert_equal Project.find(2), issue.project
115 assert_equal Project.find(2), issue.project
116 assert_equal 'Feature request', issue.tracker.to_s
116 assert_equal 'Feature request', issue.tracker.to_s
117 assert_equal 'Stock management', issue.category.to_s
117 assert_equal 'Stock management', issue.category.to_s
118 assert_equal 'Urgent', issue.priority.to_s
118 assert_equal 'Urgent', issue.priority.to_s
119 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
119 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
120 end
120 end
121
121
122
122
123 def test_add_issue_with_attachment_to_specific_project
123 def test_add_issue_with_attachment_to_specific_project
124 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
124 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
125 assert issue.is_a?(Issue)
125 assert issue.is_a?(Issue)
126 assert !issue.new_record?
126 assert !issue.new_record?
127 issue.reload
127 issue.reload
128 assert_equal 'Ticket created by email with attachment', issue.subject
128 assert_equal 'Ticket created by email with attachment', issue.subject
129 assert_equal User.find_by_login('jsmith'), issue.author
129 assert_equal User.find_by_login('jsmith'), issue.author
130 assert_equal Project.find(2), issue.project
130 assert_equal Project.find(2), issue.project
131 assert_equal 'This is a new ticket with attachments', issue.description
131 assert_equal 'This is a new ticket with attachments', issue.description
132 # Attachment properties
132 # Attachment properties
133 assert_equal 1, issue.attachments.size
133 assert_equal 1, issue.attachments.size
134 assert_equal 'Paella.jpg', issue.attachments.first.filename
134 assert_equal 'Paella.jpg', issue.attachments.first.filename
135 assert_equal 'image/jpeg', issue.attachments.first.content_type
135 assert_equal 'image/jpeg', issue.attachments.first.content_type
136 assert_equal 10790, issue.attachments.first.filesize
136 assert_equal 10790, issue.attachments.first.filesize
137 end
137 end
138
138
139 def test_add_issue_with_custom_fields
139 def test_add_issue_with_custom_fields
140 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
140 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
141 assert issue.is_a?(Issue)
141 assert issue.is_a?(Issue)
142 assert !issue.new_record?
142 assert !issue.new_record?
143 issue.reload
143 issue.reload
144 assert_equal 'New ticket with custom field values', issue.subject
144 assert_equal 'New ticket with custom field values', issue.subject
145 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
145 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
146 assert !issue.description.match(/^searchable field:/i)
146 assert !issue.description.match(/^searchable field:/i)
147 end
147 end
148
148
149 def test_add_issue_with_cc
149 def test_add_issue_with_cc
150 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
150 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
151 assert issue.is_a?(Issue)
151 assert issue.is_a?(Issue)
152 assert !issue.new_record?
152 assert !issue.new_record?
153 issue.reload
153 issue.reload
154 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
154 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
155 assert_equal 1, issue.watchers.size
155 assert_equal 1, issue.watchers.size
156 end
156 end
157
157
158 def test_add_issue_by_unknown_user
158 def test_add_issue_by_unknown_user
159 assert_no_difference 'User.count' do
159 assert_no_difference 'User.count' do
160 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
160 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
161 end
161 end
162 end
162 end
163
163
164 def test_add_issue_by_anonymous_user
164 def test_add_issue_by_anonymous_user
165 Role.anonymous.add_permission!(:add_issues)
165 Role.anonymous.add_permission!(:add_issues)
166 assert_no_difference 'User.count' do
166 assert_no_difference 'User.count' do
167 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
167 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
168 assert issue.is_a?(Issue)
168 assert issue.is_a?(Issue)
169 assert issue.author.anonymous?
169 assert issue.author.anonymous?
170 end
170 end
171 end
171 end
172
172
173 def test_add_issue_by_anonymous_user_with_no_from_address
174 Role.anonymous.add_permission!(:add_issues)
175 assert_no_difference 'User.count' do
176 issue = submit_email('ticket_by_empty_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
177 assert issue.is_a?(Issue)
178 assert issue.author.anonymous?
179 end
180 end
181
173 def test_add_issue_by_anonymous_user_on_private_project
182 def test_add_issue_by_anonymous_user_on_private_project
174 Role.anonymous.add_permission!(:add_issues)
183 Role.anonymous.add_permission!(:add_issues)
175 assert_no_difference 'User.count' do
184 assert_no_difference 'User.count' do
176 assert_no_difference 'Issue.count' do
185 assert_no_difference 'Issue.count' do
177 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :unknown_user => 'accept')
186 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :unknown_user => 'accept')
178 end
187 end
179 end
188 end
180 end
189 end
181
190
182 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
191 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
183 assert_no_difference 'User.count' do
192 assert_no_difference 'User.count' do
184 assert_difference 'Issue.count' do
193 assert_difference 'Issue.count' do
185 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :no_permission_check => '1', :unknown_user => 'accept')
194 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :no_permission_check => '1', :unknown_user => 'accept')
186 assert issue.is_a?(Issue)
195 assert issue.is_a?(Issue)
187 assert issue.author.anonymous?
196 assert issue.author.anonymous?
188 assert !issue.project.is_public?
197 assert !issue.project.is_public?
189 end
198 end
190 end
199 end
191 end
200 end
192
201
193 def test_add_issue_by_created_user
202 def test_add_issue_by_created_user
194 Setting.default_language = 'en'
203 Setting.default_language = 'en'
195 assert_difference 'User.count' do
204 assert_difference 'User.count' do
196 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
205 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
197 assert issue.is_a?(Issue)
206 assert issue.is_a?(Issue)
198 assert issue.author.active?
207 assert issue.author.active?
199 assert_equal 'john.doe@somenet.foo', issue.author.mail
208 assert_equal 'john.doe@somenet.foo', issue.author.mail
200 assert_equal 'John', issue.author.firstname
209 assert_equal 'John', issue.author.firstname
201 assert_equal 'Doe', issue.author.lastname
210 assert_equal 'Doe', issue.author.lastname
202
211
203 # account information
212 # account information
204 email = ActionMailer::Base.deliveries.first
213 email = ActionMailer::Base.deliveries.first
205 assert_not_nil email
214 assert_not_nil email
206 assert email.subject.include?('account activation')
215 assert email.subject.include?('account activation')
207 login = email.body.match(/\* Login: (.*)$/)[1]
216 login = email.body.match(/\* Login: (.*)$/)[1]
208 password = email.body.match(/\* Password: (.*)$/)[1]
217 password = email.body.match(/\* Password: (.*)$/)[1]
209 assert_equal issue.author, User.try_to_login(login, password)
218 assert_equal issue.author, User.try_to_login(login, password)
210 end
219 end
211 end
220 end
212
221
213 def test_add_issue_without_from_header
222 def test_add_issue_without_from_header
214 Role.anonymous.add_permission!(:add_issues)
223 Role.anonymous.add_permission!(:add_issues)
215 assert_equal false, submit_email('ticket_without_from_header.eml')
224 assert_equal false, submit_email('ticket_without_from_header.eml')
216 end
225 end
217
226
218 def test_add_issue_with_japanese_keywords
227 def test_add_issue_with_japanese_keywords
219 tracker = Tracker.create!(:name => 'ι–‹η™Ί')
228 tracker = Tracker.create!(:name => 'ι–‹η™Ί')
220 Project.find(1).trackers << tracker
229 Project.find(1).trackers << tracker
221 issue = submit_email('japanese_keywords_iso_2022_jp.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'tracker')
230 issue = submit_email('japanese_keywords_iso_2022_jp.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'tracker')
222 assert_kind_of Issue, issue
231 assert_kind_of Issue, issue
223 assert_equal tracker, issue.tracker
232 assert_equal tracker, issue.tracker
224 end
233 end
225
234
226 def test_should_ignore_emails_from_emission_address
235 def test_should_ignore_emails_from_emission_address
227 Role.anonymous.add_permission!(:add_issues)
236 Role.anonymous.add_permission!(:add_issues)
228 assert_no_difference 'User.count' do
237 assert_no_difference 'User.count' do
229 assert_equal false, submit_email('ticket_from_emission_address.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
238 assert_equal false, submit_email('ticket_from_emission_address.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
230 end
239 end
231 end
240 end
232
241
233 def test_add_issue_should_send_email_notification
242 def test_add_issue_should_send_email_notification
234 ActionMailer::Base.deliveries.clear
243 ActionMailer::Base.deliveries.clear
235 # This email contains: 'Project: onlinestore'
244 # This email contains: 'Project: onlinestore'
236 issue = submit_email('ticket_on_given_project.eml')
245 issue = submit_email('ticket_on_given_project.eml')
237 assert issue.is_a?(Issue)
246 assert issue.is_a?(Issue)
238 assert_equal 1, ActionMailer::Base.deliveries.size
247 assert_equal 1, ActionMailer::Base.deliveries.size
239 end
248 end
240
249
241 def test_add_issue_note
250 def test_add_issue_note
242 journal = submit_email('ticket_reply.eml')
251 journal = submit_email('ticket_reply.eml')
243 assert journal.is_a?(Journal)
252 assert journal.is_a?(Journal)
244 assert_equal User.find_by_login('jsmith'), journal.user
253 assert_equal User.find_by_login('jsmith'), journal.user
245 assert_equal Issue.find(2), journal.journalized
254 assert_equal Issue.find(2), journal.journalized
246 assert_match /This is reply/, journal.notes
255 assert_match /This is reply/, journal.notes
247 end
256 end
248
257
249 def test_add_issue_note_with_attribute_changes
258 def test_add_issue_note_with_attribute_changes
250 # This email contains: 'Status: Resolved'
259 # This email contains: 'Status: Resolved'
251 journal = submit_email('ticket_reply_with_status.eml')
260 journal = submit_email('ticket_reply_with_status.eml')
252 assert journal.is_a?(Journal)
261 assert journal.is_a?(Journal)
253 issue = Issue.find(journal.issue.id)
262 issue = Issue.find(journal.issue.id)
254 assert_equal User.find_by_login('jsmith'), journal.user
263 assert_equal User.find_by_login('jsmith'), journal.user
255 assert_equal Issue.find(2), journal.journalized
264 assert_equal Issue.find(2), journal.journalized
256 assert_match /This is reply/, journal.notes
265 assert_match /This is reply/, journal.notes
257 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
266 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
258 assert_equal '2010-01-01', issue.start_date.to_s
267 assert_equal '2010-01-01', issue.start_date.to_s
259 assert_equal '2010-12-31', issue.due_date.to_s
268 assert_equal '2010-12-31', issue.due_date.to_s
260 assert_equal User.find_by_login('jsmith'), issue.assigned_to
269 assert_equal User.find_by_login('jsmith'), issue.assigned_to
261 end
270 end
262
271
263 def test_add_issue_note_should_send_email_notification
272 def test_add_issue_note_should_send_email_notification
264 ActionMailer::Base.deliveries.clear
273 ActionMailer::Base.deliveries.clear
265 journal = submit_email('ticket_reply.eml')
274 journal = submit_email('ticket_reply.eml')
266 assert journal.is_a?(Journal)
275 assert journal.is_a?(Journal)
267 assert_equal 1, ActionMailer::Base.deliveries.size
276 assert_equal 1, ActionMailer::Base.deliveries.size
268 end
277 end
269
278
270 def test_reply_to_a_message
279 def test_reply_to_a_message
271 m = submit_email('message_reply.eml')
280 m = submit_email('message_reply.eml')
272 assert m.is_a?(Message)
281 assert m.is_a?(Message)
273 assert !m.new_record?
282 assert !m.new_record?
274 m.reload
283 m.reload
275 assert_equal 'Reply via email', m.subject
284 assert_equal 'Reply via email', m.subject
276 # The email replies to message #2 which is part of the thread of message #1
285 # The email replies to message #2 which is part of the thread of message #1
277 assert_equal Message.find(1), m.parent
286 assert_equal Message.find(1), m.parent
278 end
287 end
279
288
280 def test_reply_to_a_message_by_subject
289 def test_reply_to_a_message_by_subject
281 m = submit_email('message_reply_by_subject.eml')
290 m = submit_email('message_reply_by_subject.eml')
282 assert m.is_a?(Message)
291 assert m.is_a?(Message)
283 assert !m.new_record?
292 assert !m.new_record?
284 m.reload
293 m.reload
285 assert_equal 'Reply to the first post', m.subject
294 assert_equal 'Reply to the first post', m.subject
286 assert_equal Message.find(1), m.parent
295 assert_equal Message.find(1), m.parent
287 end
296 end
288
297
289 def test_should_strip_tags_of_html_only_emails
298 def test_should_strip_tags_of_html_only_emails
290 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
299 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
291 assert issue.is_a?(Issue)
300 assert issue.is_a?(Issue)
292 assert !issue.new_record?
301 assert !issue.new_record?
293 issue.reload
302 issue.reload
294 assert_equal 'HTML email', issue.subject
303 assert_equal 'HTML email', issue.subject
295 assert_equal 'This is a html-only email.', issue.description
304 assert_equal 'This is a html-only email.', issue.description
296 end
305 end
297
306
298 context "truncate emails based on the Setting" do
307 context "truncate emails based on the Setting" do
299 context "with no setting" do
308 context "with no setting" do
300 setup do
309 setup do
301 Setting.mail_handler_body_delimiters = ''
310 Setting.mail_handler_body_delimiters = ''
302 end
311 end
303
312
304 should "add the entire email into the issue" do
313 should "add the entire email into the issue" do
305 issue = submit_email('ticket_on_given_project.eml')
314 issue = submit_email('ticket_on_given_project.eml')
306 assert_issue_created(issue)
315 assert_issue_created(issue)
307 assert issue.description.include?('---')
316 assert issue.description.include?('---')
308 assert issue.description.include?('This paragraph is after the delimiter')
317 assert issue.description.include?('This paragraph is after the delimiter')
309 end
318 end
310 end
319 end
311
320
312 context "with a single string" do
321 context "with a single string" do
313 setup do
322 setup do
314 Setting.mail_handler_body_delimiters = '---'
323 Setting.mail_handler_body_delimiters = '---'
315 end
324 end
316
325
317 should "truncate the email at the delimiter for the issue" do
326 should "truncate the email at the delimiter for the issue" do
318 issue = submit_email('ticket_on_given_project.eml')
327 issue = submit_email('ticket_on_given_project.eml')
319 assert_issue_created(issue)
328 assert_issue_created(issue)
320 assert issue.description.include?('This paragraph is before delimiters')
329 assert issue.description.include?('This paragraph is before delimiters')
321 assert issue.description.include?('--- This line starts with a delimiter')
330 assert issue.description.include?('--- This line starts with a delimiter')
322 assert !issue.description.match(/^---$/)
331 assert !issue.description.match(/^---$/)
323 assert !issue.description.include?('This paragraph is after the delimiter')
332 assert !issue.description.include?('This paragraph is after the delimiter')
324 end
333 end
325 end
334 end
326
335
327 context "with multiple strings" do
336 context "with multiple strings" do
328 setup do
337 setup do
329 Setting.mail_handler_body_delimiters = "---\nBREAK"
338 Setting.mail_handler_body_delimiters = "---\nBREAK"
330 end
339 end
331
340
332 should "truncate the email at the first delimiter found (BREAK)" do
341 should "truncate the email at the first delimiter found (BREAK)" do
333 issue = submit_email('ticket_on_given_project.eml')
342 issue = submit_email('ticket_on_given_project.eml')
334 assert_issue_created(issue)
343 assert_issue_created(issue)
335 assert issue.description.include?('This paragraph is before delimiters')
344 assert issue.description.include?('This paragraph is before delimiters')
336 assert !issue.description.include?('BREAK')
345 assert !issue.description.include?('BREAK')
337 assert !issue.description.include?('This paragraph is between delimiters')
346 assert !issue.description.include?('This paragraph is between delimiters')
338 assert !issue.description.match(/^---$/)
347 assert !issue.description.match(/^---$/)
339 assert !issue.description.include?('This paragraph is after the delimiter')
348 assert !issue.description.include?('This paragraph is after the delimiter')
340 end
349 end
341 end
350 end
342 end
351 end
343
352
344 private
353 private
345
354
346 def submit_email(filename, options={})
355 def submit_email(filename, options={})
347 raw = IO.read(File.join(FIXTURES_PATH, filename))
356 raw = IO.read(File.join(FIXTURES_PATH, filename))
348 MailHandler.receive(raw, options)
357 MailHandler.receive(raw, options)
349 end
358 end
350
359
351 def assert_issue_created(issue)
360 def assert_issue_created(issue)
352 assert issue.is_a?(Issue)
361 assert issue.is_a?(Issue)
353 assert !issue.new_record?
362 assert !issue.new_record?
354 issue.reload
363 issue.reload
355 end
364 end
356 end
365 end
General Comments 0
You need to be logged in to leave comments. Login now