##// END OF EJS Templates
Ignore email keywords after truncation line (#8758)....
Jean-Philippe Lang -
r13050:8ae41b4846fc
parent child
Show More
@@ -0,0 +1,25
1 Return-Path: <JSmith@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 From: "John Smith" <JSmith@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket on a given project
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
11 Content-Type: text/plain;
12 format=flowed;
13 charset="iso-8859-1";
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
22
23 Project: ecookbook
24 Priority: High
25 Priority: Low
@@ -0,0 +1,27
1 Return-Path: <JSmith@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 From: "John Smith" <JSmith@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket on a given project
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
11 Content-Type: text/plain;
12 format=flowed;
13 charset="iso-8859-1";
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
22
23 Project: ecookbook
24
25 == DELIMITER ==
26
27 Priority: High
@@ -1,550 +1,550
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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 include Redmine::I18n
20 include Redmine::I18n
21
21
22 class UnauthorizedAction < StandardError; end
22 class UnauthorizedAction < StandardError; end
23 class MissingInformation < StandardError; end
23 class MissingInformation < StandardError; end
24
24
25 attr_reader :email, :user
25 attr_reader :email, :user
26
26
27 def self.receive(email, options={})
27 def self.receive(email, options={})
28 @@handler_options = options.dup
28 @@handler_options = options.dup
29
29
30 @@handler_options[:issue] ||= {}
30 @@handler_options[:issue] ||= {}
31
31
32 if @@handler_options[:allow_override].is_a?(String)
32 if @@handler_options[:allow_override].is_a?(String)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 end
34 end
35 @@handler_options[:allow_override] ||= []
35 @@handler_options[:allow_override] ||= []
36 # Project needs to be overridable if not specified
36 # Project needs to be overridable if not specified
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 # Status overridable by default
38 # Status overridable by default
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40
40
41 @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
41 @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
44
44
45 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
45 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
46 super(email)
46 super(email)
47 end
47 end
48
48
49 # Receives an email and rescues any exception
49 # Receives an email and rescues any exception
50 def self.safe_receive(*args)
50 def self.safe_receive(*args)
51 receive(*args)
51 receive(*args)
52 rescue => e
52 rescue => e
53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
54 return false
54 return false
55 end
55 end
56
56
57 # Extracts MailHandler options from environment variables
57 # Extracts MailHandler options from environment variables
58 # Use when receiving emails with rake tasks
58 # Use when receiving emails with rake tasks
59 def self.extract_options_from_env(env)
59 def self.extract_options_from_env(env)
60 options = {:issue => {}}
60 options = {:issue => {}}
61 %w(project status tracker category priority).each do |option|
61 %w(project status tracker category priority).each do |option|
62 options[:issue][option.to_sym] = env[option] if env[option]
62 options[:issue][option.to_sym] = env[option] if env[option]
63 end
63 end
64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
65 options[option.to_sym] = env[option] if env[option]
65 options[option.to_sym] = env[option] if env[option]
66 end
66 end
67 options
67 options
68 end
68 end
69
69
70 def logger
70 def logger
71 Rails.logger
71 Rails.logger
72 end
72 end
73
73
74 cattr_accessor :ignored_emails_headers
74 cattr_accessor :ignored_emails_headers
75 @@ignored_emails_headers = {
75 @@ignored_emails_headers = {
76 'X-Auto-Response-Suppress' => 'oof',
76 'X-Auto-Response-Suppress' => 'oof',
77 'Auto-Submitted' => /\Aauto-(replied|generated)/
77 'Auto-Submitted' => /\Aauto-(replied|generated)/
78 }
78 }
79
79
80 # Processes incoming emails
80 # Processes incoming emails
81 # Returns the created object (eg. an issue, a message) or false
81 # Returns the created object (eg. an issue, a message) or false
82 def receive(email)
82 def receive(email)
83 @email = email
83 @email = email
84 sender_email = email.from.to_a.first.to_s.strip
84 sender_email = email.from.to_a.first.to_s.strip
85 # Ignore emails received from the application emission address to avoid hell cycles
85 # Ignore emails received from the application emission address to avoid hell cycles
86 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
86 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
87 if logger
87 if logger
88 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
88 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
89 end
89 end
90 return false
90 return false
91 end
91 end
92 # Ignore auto generated emails
92 # Ignore auto generated emails
93 self.class.ignored_emails_headers.each do |key, ignored_value|
93 self.class.ignored_emails_headers.each do |key, ignored_value|
94 value = email.header[key]
94 value = email.header[key]
95 if value
95 if value
96 value = value.to_s.downcase
96 value = value.to_s.downcase
97 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
97 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
98 if logger
98 if logger
99 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
99 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
100 end
100 end
101 return false
101 return false
102 end
102 end
103 end
103 end
104 end
104 end
105 @user = User.find_by_mail(sender_email) if sender_email.present?
105 @user = User.find_by_mail(sender_email) if sender_email.present?
106 if @user && !@user.active?
106 if @user && !@user.active?
107 if logger
107 if logger
108 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
108 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
109 end
109 end
110 return false
110 return false
111 end
111 end
112 if @user.nil?
112 if @user.nil?
113 # Email was submitted by an unknown user
113 # Email was submitted by an unknown user
114 case @@handler_options[:unknown_user]
114 case @@handler_options[:unknown_user]
115 when 'accept'
115 when 'accept'
116 @user = User.anonymous
116 @user = User.anonymous
117 when 'create'
117 when 'create'
118 @user = create_user_from_email
118 @user = create_user_from_email
119 if @user
119 if @user
120 if logger
120 if logger
121 logger.info "MailHandler: [#{@user.login}] account created"
121 logger.info "MailHandler: [#{@user.login}] account created"
122 end
122 end
123 add_user_to_group(@@handler_options[:default_group])
123 add_user_to_group(@@handler_options[:default_group])
124 unless @@handler_options[:no_account_notice]
124 unless @@handler_options[:no_account_notice]
125 Mailer.account_information(@user, @user.password).deliver
125 Mailer.account_information(@user, @user.password).deliver
126 end
126 end
127 else
127 else
128 if logger
128 if logger
129 logger.error "MailHandler: could not create account for [#{sender_email}]"
129 logger.error "MailHandler: could not create account for [#{sender_email}]"
130 end
130 end
131 return false
131 return false
132 end
132 end
133 else
133 else
134 # Default behaviour, emails from unknown users are ignored
134 # Default behaviour, emails from unknown users are ignored
135 if logger
135 if logger
136 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
136 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
137 end
137 end
138 return false
138 return false
139 end
139 end
140 end
140 end
141 User.current = @user
141 User.current = @user
142 dispatch
142 dispatch
143 end
143 end
144
144
145 private
145 private
146
146
147 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
147 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
148 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
148 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
149 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
149 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
150
150
151 def dispatch
151 def dispatch
152 headers = [email.in_reply_to, email.references].flatten.compact
152 headers = [email.in_reply_to, email.references].flatten.compact
153 subject = email.subject.to_s
153 subject = email.subject.to_s
154 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
154 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
155 klass, object_id = $1, $2.to_i
155 klass, object_id = $1, $2.to_i
156 method_name = "receive_#{klass}_reply"
156 method_name = "receive_#{klass}_reply"
157 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
157 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
158 send method_name, object_id
158 send method_name, object_id
159 else
159 else
160 # ignoring it
160 # ignoring it
161 end
161 end
162 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
162 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
163 receive_issue_reply(m[1].to_i)
163 receive_issue_reply(m[1].to_i)
164 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
164 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
165 receive_message_reply(m[1].to_i)
165 receive_message_reply(m[1].to_i)
166 else
166 else
167 dispatch_to_default
167 dispatch_to_default
168 end
168 end
169 rescue ActiveRecord::RecordInvalid => e
169 rescue ActiveRecord::RecordInvalid => e
170 # TODO: send a email to the user
170 # TODO: send a email to the user
171 logger.error e.message if logger
171 logger.error e.message if logger
172 false
172 false
173 rescue MissingInformation => e
173 rescue MissingInformation => e
174 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
174 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
175 false
175 false
176 rescue UnauthorizedAction => e
176 rescue UnauthorizedAction => e
177 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
177 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
178 false
178 false
179 end
179 end
180
180
181 def dispatch_to_default
181 def dispatch_to_default
182 receive_issue
182 receive_issue
183 end
183 end
184
184
185 # Creates a new issue
185 # Creates a new issue
186 def receive_issue
186 def receive_issue
187 project = target_project
187 project = target_project
188 # check permission
188 # check permission
189 unless @@handler_options[:no_permission_check]
189 unless @@handler_options[:no_permission_check]
190 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
190 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
191 end
191 end
192
192
193 issue = Issue.new(:author => user, :project => project)
193 issue = Issue.new(:author => user, :project => project)
194 issue.safe_attributes = issue_attributes_from_keywords(issue)
194 issue.safe_attributes = issue_attributes_from_keywords(issue)
195 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
195 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
196 issue.subject = cleaned_up_subject
196 issue.subject = cleaned_up_subject
197 if issue.subject.blank?
197 if issue.subject.blank?
198 issue.subject = '(no subject)'
198 issue.subject = '(no subject)'
199 end
199 end
200 issue.description = cleaned_up_text_body
200 issue.description = cleaned_up_text_body
201 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
201 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
202
202
203 # add To and Cc as watchers before saving so the watchers can reply to Redmine
203 # add To and Cc as watchers before saving so the watchers can reply to Redmine
204 add_watchers(issue)
204 add_watchers(issue)
205 issue.save!
205 issue.save!
206 add_attachments(issue)
206 add_attachments(issue)
207 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
207 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
208 issue
208 issue
209 end
209 end
210
210
211 # Adds a note to an existing issue
211 # Adds a note to an existing issue
212 def receive_issue_reply(issue_id, from_journal=nil)
212 def receive_issue_reply(issue_id, from_journal=nil)
213 issue = Issue.find_by_id(issue_id)
213 issue = Issue.find_by_id(issue_id)
214 return unless issue
214 return unless issue
215 # check permission
215 # check permission
216 unless @@handler_options[:no_permission_check]
216 unless @@handler_options[:no_permission_check]
217 unless user.allowed_to?(:add_issue_notes, issue.project) ||
217 unless user.allowed_to?(:add_issue_notes, issue.project) ||
218 user.allowed_to?(:edit_issues, issue.project)
218 user.allowed_to?(:edit_issues, issue.project)
219 raise UnauthorizedAction
219 raise UnauthorizedAction
220 end
220 end
221 end
221 end
222
222
223 # ignore CLI-supplied defaults for new issues
223 # ignore CLI-supplied defaults for new issues
224 @@handler_options[:issue].clear
224 @@handler_options[:issue].clear
225
225
226 journal = issue.init_journal(user)
226 journal = issue.init_journal(user)
227 if from_journal && from_journal.private_notes?
227 if from_journal && from_journal.private_notes?
228 # If the received email was a reply to a private note, make the added note private
228 # If the received email was a reply to a private note, make the added note private
229 issue.private_notes = true
229 issue.private_notes = true
230 end
230 end
231 issue.safe_attributes = issue_attributes_from_keywords(issue)
231 issue.safe_attributes = issue_attributes_from_keywords(issue)
232 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
232 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
233 journal.notes = cleaned_up_text_body
233 journal.notes = cleaned_up_text_body
234 add_attachments(issue)
234 add_attachments(issue)
235 issue.save!
235 issue.save!
236 if logger
236 if logger
237 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
237 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
238 end
238 end
239 journal
239 journal
240 end
240 end
241
241
242 # Reply will be added to the issue
242 # Reply will be added to the issue
243 def receive_journal_reply(journal_id)
243 def receive_journal_reply(journal_id)
244 journal = Journal.find_by_id(journal_id)
244 journal = Journal.find_by_id(journal_id)
245 if journal && journal.journalized_type == 'Issue'
245 if journal && journal.journalized_type == 'Issue'
246 receive_issue_reply(journal.journalized_id, journal)
246 receive_issue_reply(journal.journalized_id, journal)
247 end
247 end
248 end
248 end
249
249
250 # Receives a reply to a forum message
250 # Receives a reply to a forum message
251 def receive_message_reply(message_id)
251 def receive_message_reply(message_id)
252 message = Message.find_by_id(message_id)
252 message = Message.find_by_id(message_id)
253 if message
253 if message
254 message = message.root
254 message = message.root
255
255
256 unless @@handler_options[:no_permission_check]
256 unless @@handler_options[:no_permission_check]
257 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
257 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
258 end
258 end
259
259
260 if !message.locked?
260 if !message.locked?
261 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
261 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
262 :content => cleaned_up_text_body)
262 :content => cleaned_up_text_body)
263 reply.author = user
263 reply.author = user
264 reply.board = message.board
264 reply.board = message.board
265 message.children << reply
265 message.children << reply
266 add_attachments(reply)
266 add_attachments(reply)
267 reply
267 reply
268 else
268 else
269 if logger
269 if logger
270 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
270 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
271 end
271 end
272 end
272 end
273 end
273 end
274 end
274 end
275
275
276 def add_attachments(obj)
276 def add_attachments(obj)
277 if email.attachments && email.attachments.any?
277 if email.attachments && email.attachments.any?
278 email.attachments.each do |attachment|
278 email.attachments.each do |attachment|
279 next unless accept_attachment?(attachment)
279 next unless accept_attachment?(attachment)
280 obj.attachments << Attachment.create(:container => obj,
280 obj.attachments << Attachment.create(:container => obj,
281 :file => attachment.decoded,
281 :file => attachment.decoded,
282 :filename => attachment.filename,
282 :filename => attachment.filename,
283 :author => user,
283 :author => user,
284 :content_type => attachment.mime_type)
284 :content_type => attachment.mime_type)
285 end
285 end
286 end
286 end
287 end
287 end
288
288
289 # Returns false if the +attachment+ of the incoming email should be ignored
289 # Returns false if the +attachment+ of the incoming email should be ignored
290 def accept_attachment?(attachment)
290 def accept_attachment?(attachment)
291 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
291 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
292 @excluded.each do |pattern|
292 @excluded.each do |pattern|
293 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
293 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
294 if attachment.filename.to_s =~ regexp
294 if attachment.filename.to_s =~ regexp
295 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
295 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
296 return false
296 return false
297 end
297 end
298 end
298 end
299 true
299 true
300 end
300 end
301
301
302 # Adds To and Cc as watchers of the given object if the sender has the
302 # Adds To and Cc as watchers of the given object if the sender has the
303 # appropriate permission
303 # appropriate permission
304 def add_watchers(obj)
304 def add_watchers(obj)
305 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
305 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
306 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
306 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
307 unless addresses.empty?
307 unless addresses.empty?
308 User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
308 User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
309 obj.add_watcher(w)
309 obj.add_watcher(w)
310 end
310 end
311 end
311 end
312 end
312 end
313 end
313 end
314
314
315 def get_keyword(attr, options={})
315 def get_keyword(attr, options={})
316 @keywords ||= {}
316 @keywords ||= {}
317 if @keywords.has_key?(attr)
317 if @keywords.has_key?(attr)
318 @keywords[attr]
318 @keywords[attr]
319 else
319 else
320 @keywords[attr] = begin
320 @keywords[attr] = begin
321 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
321 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
322 (v = extract_keyword!(plain_text_body, attr, options[:format]))
322 (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
323 v
323 v
324 elsif !@@handler_options[:issue][attr].blank?
324 elsif !@@handler_options[:issue][attr].blank?
325 @@handler_options[:issue][attr]
325 @@handler_options[:issue][attr]
326 end
326 end
327 end
327 end
328 end
328 end
329 end
329 end
330
330
331 # Destructively extracts the value for +attr+ in +text+
331 # Destructively extracts the value for +attr+ in +text+
332 # Returns nil if no matching keyword found
332 # Returns nil if no matching keyword found
333 def extract_keyword!(text, attr, format=nil)
333 def extract_keyword!(text, attr, format=nil)
334 keys = [attr.to_s.humanize]
334 keys = [attr.to_s.humanize]
335 if attr.is_a?(Symbol)
335 if attr.is_a?(Symbol)
336 if user && user.language.present?
336 if user && user.language.present?
337 keys << l("field_#{attr}", :default => '', :locale => user.language)
337 keys << l("field_#{attr}", :default => '', :locale => user.language)
338 end
338 end
339 if Setting.default_language.present?
339 if Setting.default_language.present?
340 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
340 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
341 end
341 end
342 end
342 end
343 keys.reject! {|k| k.blank?}
343 keys.reject! {|k| k.blank?}
344 keys.collect! {|k| Regexp.escape(k)}
344 keys.collect! {|k| Regexp.escape(k)}
345 format ||= '.+'
345 format ||= '.+'
346 keyword = nil
346 keyword = nil
347 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
347 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
348 if m = text.match(regexp)
348 if m = text.match(regexp)
349 keyword = m[2].strip
349 keyword = m[2].strip
350 text.gsub!(regexp, '')
350 text.sub!(regexp, '')
351 end
351 end
352 keyword
352 keyword
353 end
353 end
354
354
355 def target_project
355 def target_project
356 # TODO: other ways to specify project:
356 # TODO: other ways to specify project:
357 # * parse the email To field
357 # * parse the email To field
358 # * specific project (eg. Setting.mail_handler_target_project)
358 # * specific project (eg. Setting.mail_handler_target_project)
359 target = Project.find_by_identifier(get_keyword(:project))
359 target = Project.find_by_identifier(get_keyword(:project))
360 if target.nil?
360 if target.nil?
361 # Invalid project keyword, use the project specified as the default one
361 # Invalid project keyword, use the project specified as the default one
362 default_project = @@handler_options[:issue][:project]
362 default_project = @@handler_options[:issue][:project]
363 if default_project.present?
363 if default_project.present?
364 target = Project.find_by_identifier(default_project)
364 target = Project.find_by_identifier(default_project)
365 end
365 end
366 end
366 end
367 raise MissingInformation.new('Unable to determine target project') if target.nil?
367 raise MissingInformation.new('Unable to determine target project') if target.nil?
368 target
368 target
369 end
369 end
370
370
371 # Returns a Hash of issue attributes extracted from keywords in the email body
371 # Returns a Hash of issue attributes extracted from keywords in the email body
372 def issue_attributes_from_keywords(issue)
372 def issue_attributes_from_keywords(issue)
373 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
373 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
374
374
375 attrs = {
375 attrs = {
376 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
376 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
377 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
377 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
378 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
378 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
379 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
379 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
380 'assigned_to_id' => assigned_to.try(:id),
380 'assigned_to_id' => assigned_to.try(:id),
381 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
381 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
382 issue.project.shared_versions.named(k).first.try(:id),
382 issue.project.shared_versions.named(k).first.try(:id),
383 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
383 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
384 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
384 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
385 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
385 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
386 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
386 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
387 }.delete_if {|k, v| v.blank? }
387 }.delete_if {|k, v| v.blank? }
388
388
389 if issue.new_record? && attrs['tracker_id'].nil?
389 if issue.new_record? && attrs['tracker_id'].nil?
390 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
390 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
391 end
391 end
392
392
393 attrs
393 attrs
394 end
394 end
395
395
396 # Returns a Hash of issue custom field values extracted from keywords in the email body
396 # Returns a Hash of issue custom field values extracted from keywords in the email body
397 def custom_field_values_from_keywords(customized)
397 def custom_field_values_from_keywords(customized)
398 customized.custom_field_values.inject({}) do |h, v|
398 customized.custom_field_values.inject({}) do |h, v|
399 if keyword = get_keyword(v.custom_field.name, :override => true)
399 if keyword = get_keyword(v.custom_field.name, :override => true)
400 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
400 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
401 end
401 end
402 h
402 h
403 end
403 end
404 end
404 end
405
405
406 # Returns the text/plain part of the email
406 # Returns the text/plain part of the email
407 # If not found (eg. HTML-only email), returns the body with tags removed
407 # If not found (eg. HTML-only email), returns the body with tags removed
408 def plain_text_body
408 def plain_text_body
409 return @plain_text_body unless @plain_text_body.nil?
409 return @plain_text_body unless @plain_text_body.nil?
410
410
411 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
411 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
412 text_parts
412 text_parts
413 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
413 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
414 html_parts
414 html_parts
415 else
415 else
416 [email]
416 [email]
417 end
417 end
418
418
419 parts.reject! do |part|
419 parts.reject! do |part|
420 part.header[:content_disposition].try(:disposition_type) == 'attachment'
420 part.header[:content_disposition].try(:disposition_type) == 'attachment'
421 end
421 end
422
422
423 @plain_text_body = parts.map do |p|
423 @plain_text_body = parts.map do |p|
424 body_charset = p.charset.respond_to?(:force_encoding) ?
424 body_charset = p.charset.respond_to?(:force_encoding) ?
425 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
425 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
426 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
426 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
427 end.join("\r\n")
427 end.join("\r\n")
428
428
429 # strip html tags and remove doctype directive
429 # strip html tags and remove doctype directive
430 if parts.any? {|p| p.mime_type == 'text/html'}
430 if parts.any? {|p| p.mime_type == 'text/html'}
431 @plain_text_body = strip_tags(@plain_text_body.strip)
431 @plain_text_body = strip_tags(@plain_text_body.strip)
432 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
432 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
433 end
433 end
434
434
435 @plain_text_body
435 @plain_text_body
436 end
436 end
437
437
438 def cleaned_up_text_body
438 def cleaned_up_text_body
439 cleanup_body(plain_text_body)
439 @cleaned_up_text_body ||= cleanup_body(plain_text_body)
440 end
440 end
441
441
442 def cleaned_up_subject
442 def cleaned_up_subject
443 subject = email.subject.to_s
443 subject = email.subject.to_s
444 subject.strip[0,255]
444 subject.strip[0,255]
445 end
445 end
446
446
447 def self.full_sanitizer
447 def self.full_sanitizer
448 @full_sanitizer ||= HTML::FullSanitizer.new
448 @full_sanitizer ||= HTML::FullSanitizer.new
449 end
449 end
450
450
451 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
451 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
452 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
452 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
453 value = value.to_s.slice(0, limit)
453 value = value.to_s.slice(0, limit)
454 object.send("#{attribute}=", value)
454 object.send("#{attribute}=", value)
455 end
455 end
456
456
457 # Returns a User from an email address and a full name
457 # Returns a User from an email address and a full name
458 def self.new_user_from_attributes(email_address, fullname=nil)
458 def self.new_user_from_attributes(email_address, fullname=nil)
459 user = User.new
459 user = User.new
460
460
461 # Truncating the email address would result in an invalid format
461 # Truncating the email address would result in an invalid format
462 user.mail = email_address
462 user.mail = email_address
463 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
463 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
464
464
465 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
465 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
466 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
466 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
467 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
467 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
468 user.lastname = '-' if user.lastname.blank?
468 user.lastname = '-' if user.lastname.blank?
469 user.language = Setting.default_language
469 user.language = Setting.default_language
470 user.generate_password = true
470 user.generate_password = true
471 user.mail_notification = 'only_my_events'
471 user.mail_notification = 'only_my_events'
472
472
473 unless user.valid?
473 unless user.valid?
474 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
474 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
475 user.firstname = "-" unless user.errors[:firstname].blank?
475 user.firstname = "-" unless user.errors[:firstname].blank?
476 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
476 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
477 end
477 end
478
478
479 user
479 user
480 end
480 end
481
481
482 # Creates a User for the +email+ sender
482 # Creates a User for the +email+ sender
483 # Returns the user or nil if it could not be created
483 # Returns the user or nil if it could not be created
484 def create_user_from_email
484 def create_user_from_email
485 from = email.header['from'].to_s
485 from = email.header['from'].to_s
486 addr, name = from, nil
486 addr, name = from, nil
487 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
487 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
488 addr, name = m[2], m[1]
488 addr, name = m[2], m[1]
489 end
489 end
490 if addr.present?
490 if addr.present?
491 user = self.class.new_user_from_attributes(addr, name)
491 user = self.class.new_user_from_attributes(addr, name)
492 if @@handler_options[:no_notification]
492 if @@handler_options[:no_notification]
493 user.mail_notification = 'none'
493 user.mail_notification = 'none'
494 end
494 end
495 if user.save
495 if user.save
496 user
496 user
497 else
497 else
498 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
498 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
499 nil
499 nil
500 end
500 end
501 else
501 else
502 logger.error "MailHandler: failed to create User: no FROM address found" if logger
502 logger.error "MailHandler: failed to create User: no FROM address found" if logger
503 nil
503 nil
504 end
504 end
505 end
505 end
506
506
507 # Adds the newly created user to default group
507 # Adds the newly created user to default group
508 def add_user_to_group(default_group)
508 def add_user_to_group(default_group)
509 if default_group.present?
509 if default_group.present?
510 default_group.split(',').each do |group_name|
510 default_group.split(',').each do |group_name|
511 if group = Group.named(group_name).first
511 if group = Group.named(group_name).first
512 group.users << @user
512 group.users << @user
513 elsif logger
513 elsif logger
514 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
514 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
515 end
515 end
516 end
516 end
517 end
517 end
518 end
518 end
519
519
520 # Removes the email body of text after the truncation configurations.
520 # Removes the email body of text after the truncation configurations.
521 def cleanup_body(body)
521 def cleanup_body(body)
522 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
522 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
523 unless delimiters.empty?
523 unless delimiters.empty?
524 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
524 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
525 body = body.gsub(regex, '')
525 body = body.gsub(regex, '')
526 end
526 end
527 body.strip
527 body.strip
528 end
528 end
529
529
530 def find_assignee_from_keyword(keyword, issue)
530 def find_assignee_from_keyword(keyword, issue)
531 keyword = keyword.to_s.downcase
531 keyword = keyword.to_s.downcase
532 assignable = issue.assignable_users
532 assignable = issue.assignable_users
533 assignee = nil
533 assignee = nil
534 assignee ||= assignable.detect {|a|
534 assignee ||= assignable.detect {|a|
535 a.mail.to_s.downcase == keyword ||
535 a.mail.to_s.downcase == keyword ||
536 a.login.to_s.downcase == keyword
536 a.login.to_s.downcase == keyword
537 }
537 }
538 if assignee.nil? && keyword.match(/ /)
538 if assignee.nil? && keyword.match(/ /)
539 firstname, lastname = *(keyword.split) # "First Last Throwaway"
539 firstname, lastname = *(keyword.split) # "First Last Throwaway"
540 assignee ||= assignable.detect {|a|
540 assignee ||= assignable.detect {|a|
541 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
541 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
542 a.lastname.to_s.downcase == lastname
542 a.lastname.to_s.downcase == lastname
543 }
543 }
544 end
544 end
545 if assignee.nil?
545 if assignee.nil?
546 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
546 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
547 end
547 end
548 assignee
548 assignee
549 end
549 end
550 end
550 end
@@ -1,60 +1,60
1 Return-Path: <JSmith@somenet.foo>
1 Return-Path: <JSmith@somenet.foo>
2 Received: from osiris ([127.0.0.1])
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 From: "John Smith" <JSmith@somenet.foo>
6 From: "John Smith" <JSmith@somenet.foo>
7 To: <redmine@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket on a given project
8 Subject: New ticket on a given project
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
10 MIME-Version: 1.0
11 Content-Type: text/plain;
11 Content-Type: text/plain;
12 format=flowed;
12 format=flowed;
13 charset="iso-8859-1";
13 charset="iso-8859-1";
14 reply-type=original
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
16 X-Priority: 3
17 X-MSMail-Priority: Normal
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
29 sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
29 sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
31 platea dictumst.
31 platea dictumst.
32
32
33 Project: onlinestore
34 Status: Resolved
35 due date: 2010-12-31
36 Start Date:2010-01-01
37 Assigned to: John Smith
38 fixed version: alpha
39 estimated hours: 2.5
40 done ratio: 30
41
33 --- This line starts with a delimiter and should not be stripped
42 --- This line starts with a delimiter and should not be stripped
34
43
35 This paragraph is before delimiters.
44 This paragraph is before delimiters.
36
45
37 BREAK
46 BREAK
38
47
39 This paragraph is between delimiters.
48 This paragraph is between delimiters.
40
49
41 ---
50 ---
42
51
43 This paragraph is after the delimiter so it shouldn't appear.
52 This paragraph is after the delimiter so it shouldn't appear.
44
53
45 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
54 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
46 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
55 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
47 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
56 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
48 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
57 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
49 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
58 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
50 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
59 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
51
60
52 Project: onlinestore
53 Status: Resolved
54 due date: 2010-12-31
55 Start Date:2010-01-01
56 Assigned to: John Smith
57 fixed version: alpha
58 estimated hours: 2.5
59 done ratio: 30
60
@@ -1,933 +1,947
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 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.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class MailHandlerTest < ActiveSupport::TestCase
22 class MailHandlerTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :enabled_modules, :roles,
23 fixtures :users, :projects, :enabled_modules, :roles,
24 :members, :member_roles, :users,
24 :members, :member_roles, :users,
25 :issues, :issue_statuses,
25 :issues, :issue_statuses,
26 :workflows, :trackers, :projects_trackers,
26 :workflows, :trackers, :projects_trackers,
27 :versions, :enumerations, :issue_categories,
27 :versions, :enumerations, :issue_categories,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 :boards, :messages
29 :boards, :messages
30
30
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32
32
33 def setup
33 def setup
34 ActionMailer::Base.deliveries.clear
34 ActionMailer::Base.deliveries.clear
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 end
36 end
37
37
38 def teardown
38 def teardown
39 Setting.clear_cache
39 Setting.clear_cache
40 end
40 end
41
41
42 def test_add_issue
42 def test_add_issue
43 ActionMailer::Base.deliveries.clear
43 ActionMailer::Base.deliveries.clear
44 lft1 = new_issue_lft
44 lft1 = new_issue_lft
45 # This email contains: 'Project: onlinestore'
45 # This email contains: 'Project: onlinestore'
46 issue = submit_email('ticket_on_given_project.eml')
46 issue = submit_email('ticket_on_given_project.eml')
47 assert issue.is_a?(Issue)
47 assert issue.is_a?(Issue)
48 assert !issue.new_record?
48 assert !issue.new_record?
49 issue.reload
49 issue.reload
50 assert_equal Project.find(2), issue.project
50 assert_equal Project.find(2), issue.project
51 assert_equal issue.project.trackers.first, issue.tracker
51 assert_equal issue.project.trackers.first, issue.tracker
52 assert_equal 'New ticket on a given project', issue.subject
52 assert_equal 'New ticket on a given project', issue.subject
53 assert_equal User.find_by_login('jsmith'), issue.author
53 assert_equal User.find_by_login('jsmith'), issue.author
54 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
55 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
56 assert_equal '2010-01-01', issue.start_date.to_s
56 assert_equal '2010-01-01', issue.start_date.to_s
57 assert_equal '2010-12-31', issue.due_date.to_s
57 assert_equal '2010-12-31', issue.due_date.to_s
58 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 assert_equal User.find_by_login('jsmith'), issue.assigned_to
59 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
60 assert_equal 2.5, issue.estimated_hours
60 assert_equal 2.5, issue.estimated_hours
61 assert_equal 30, issue.done_ratio
61 assert_equal 30, issue.done_ratio
62 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
62 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
63 # keywords should be removed from the email body
63 # keywords should be removed from the email body
64 assert !issue.description.match(/^Project:/i)
64 assert !issue.description.match(/^Project:/i)
65 assert !issue.description.match(/^Status:/i)
65 assert !issue.description.match(/^Status:/i)
66 assert !issue.description.match(/^Start Date:/i)
66 assert !issue.description.match(/^Start Date:/i)
67 # Email notification should be sent
67 # Email notification should be sent
68 mail = ActionMailer::Base.deliveries.last
68 mail = ActionMailer::Base.deliveries.last
69 assert_not_nil mail
69 assert_not_nil mail
70 assert mail.subject.include?("##{issue.id}")
70 assert mail.subject.include?("##{issue.id}")
71 assert mail.subject.include?('New ticket on a given project')
71 assert mail.subject.include?('New ticket on a given project')
72 end
72 end
73
73
74 def test_add_issue_with_default_tracker
74 def test_add_issue_with_default_tracker
75 # This email contains: 'Project: onlinestore'
75 # This email contains: 'Project: onlinestore'
76 issue = submit_email(
76 issue = submit_email(
77 'ticket_on_given_project.eml',
77 'ticket_on_given_project.eml',
78 :issue => {:tracker => 'Support request'}
78 :issue => {:tracker => 'Support request'}
79 )
79 )
80 assert issue.is_a?(Issue)
80 assert issue.is_a?(Issue)
81 assert !issue.new_record?
81 assert !issue.new_record?
82 issue.reload
82 issue.reload
83 assert_equal 'Support request', issue.tracker.name
83 assert_equal 'Support request', issue.tracker.name
84 end
84 end
85
85
86 def test_add_issue_with_status
86 def test_add_issue_with_status
87 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
87 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
88 issue = submit_email('ticket_on_given_project.eml')
88 issue = submit_email('ticket_on_given_project.eml')
89 assert issue.is_a?(Issue)
89 assert issue.is_a?(Issue)
90 assert !issue.new_record?
90 assert !issue.new_record?
91 issue.reload
91 issue.reload
92 assert_equal Project.find(2), issue.project
92 assert_equal Project.find(2), issue.project
93 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
93 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
94 end
94 end
95
95
96 def test_add_issue_with_attributes_override
96 def test_add_issue_with_attributes_override
97 issue = submit_email(
97 issue = submit_email(
98 'ticket_with_attributes.eml',
98 'ticket_with_attributes.eml',
99 :allow_override => 'tracker,category,priority'
99 :allow_override => 'tracker,category,priority'
100 )
100 )
101 assert issue.is_a?(Issue)
101 assert issue.is_a?(Issue)
102 assert !issue.new_record?
102 assert !issue.new_record?
103 issue.reload
103 issue.reload
104 assert_equal 'New ticket on a given project', issue.subject
104 assert_equal 'New ticket on a given project', issue.subject
105 assert_equal User.find_by_login('jsmith'), issue.author
105 assert_equal User.find_by_login('jsmith'), issue.author
106 assert_equal Project.find(2), issue.project
106 assert_equal Project.find(2), issue.project
107 assert_equal 'Feature request', issue.tracker.to_s
107 assert_equal 'Feature request', issue.tracker.to_s
108 assert_equal 'Stock management', issue.category.to_s
108 assert_equal 'Stock management', issue.category.to_s
109 assert_equal 'Urgent', issue.priority.to_s
109 assert_equal 'Urgent', issue.priority.to_s
110 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
110 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
111 end
111 end
112
112
113 def test_add_issue_with_group_assignment
113 def test_add_issue_with_group_assignment
114 with_settings :issue_group_assignment => '1' do
114 with_settings :issue_group_assignment => '1' do
115 issue = submit_email('ticket_on_given_project.eml') do |email|
115 issue = submit_email('ticket_on_given_project.eml') do |email|
116 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
116 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
117 end
117 end
118 assert issue.is_a?(Issue)
118 assert issue.is_a?(Issue)
119 assert !issue.new_record?
119 assert !issue.new_record?
120 issue.reload
120 issue.reload
121 assert_equal Group.find(11), issue.assigned_to
121 assert_equal Group.find(11), issue.assigned_to
122 end
122 end
123 end
123 end
124
124
125 def test_add_issue_with_partial_attributes_override
125 def test_add_issue_with_partial_attributes_override
126 issue = submit_email(
126 issue = submit_email(
127 'ticket_with_attributes.eml',
127 'ticket_with_attributes.eml',
128 :issue => {:priority => 'High'},
128 :issue => {:priority => 'High'},
129 :allow_override => ['tracker']
129 :allow_override => ['tracker']
130 )
130 )
131 assert issue.is_a?(Issue)
131 assert issue.is_a?(Issue)
132 assert !issue.new_record?
132 assert !issue.new_record?
133 issue.reload
133 issue.reload
134 assert_equal 'New ticket on a given project', issue.subject
134 assert_equal 'New ticket on a given project', issue.subject
135 assert_equal User.find_by_login('jsmith'), issue.author
135 assert_equal User.find_by_login('jsmith'), issue.author
136 assert_equal Project.find(2), issue.project
136 assert_equal Project.find(2), issue.project
137 assert_equal 'Feature request', issue.tracker.to_s
137 assert_equal 'Feature request', issue.tracker.to_s
138 assert_nil issue.category
138 assert_nil issue.category
139 assert_equal 'High', issue.priority.to_s
139 assert_equal 'High', issue.priority.to_s
140 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
140 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
141 end
141 end
142
142
143 def test_add_issue_with_spaces_between_attribute_and_separator
143 def test_add_issue_with_spaces_between_attribute_and_separator
144 issue = submit_email(
144 issue = submit_email(
145 'ticket_with_spaces_between_attribute_and_separator.eml',
145 'ticket_with_spaces_between_attribute_and_separator.eml',
146 :allow_override => 'tracker,category,priority'
146 :allow_override => 'tracker,category,priority'
147 )
147 )
148 assert issue.is_a?(Issue)
148 assert issue.is_a?(Issue)
149 assert !issue.new_record?
149 assert !issue.new_record?
150 issue.reload
150 issue.reload
151 assert_equal 'New ticket on a given project', issue.subject
151 assert_equal 'New ticket on a given project', issue.subject
152 assert_equal User.find_by_login('jsmith'), issue.author
152 assert_equal User.find_by_login('jsmith'), issue.author
153 assert_equal Project.find(2), issue.project
153 assert_equal Project.find(2), issue.project
154 assert_equal 'Feature request', issue.tracker.to_s
154 assert_equal 'Feature request', issue.tracker.to_s
155 assert_equal 'Stock management', issue.category.to_s
155 assert_equal 'Stock management', issue.category.to_s
156 assert_equal 'Urgent', issue.priority.to_s
156 assert_equal 'Urgent', issue.priority.to_s
157 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
157 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
158 end
158 end
159
159
160 def test_add_issue_with_attachment_to_specific_project
160 def test_add_issue_with_attachment_to_specific_project
161 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
161 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
162 assert issue.is_a?(Issue)
162 assert issue.is_a?(Issue)
163 assert !issue.new_record?
163 assert !issue.new_record?
164 issue.reload
164 issue.reload
165 assert_equal 'Ticket created by email with attachment', issue.subject
165 assert_equal 'Ticket created by email with attachment', issue.subject
166 assert_equal User.find_by_login('jsmith'), issue.author
166 assert_equal User.find_by_login('jsmith'), issue.author
167 assert_equal Project.find(2), issue.project
167 assert_equal Project.find(2), issue.project
168 assert_equal 'This is a new ticket with attachments', issue.description
168 assert_equal 'This is a new ticket with attachments', issue.description
169 # Attachment properties
169 # Attachment properties
170 assert_equal 1, issue.attachments.size
170 assert_equal 1, issue.attachments.size
171 assert_equal 'Paella.jpg', issue.attachments.first.filename
171 assert_equal 'Paella.jpg', issue.attachments.first.filename
172 assert_equal 'image/jpeg', issue.attachments.first.content_type
172 assert_equal 'image/jpeg', issue.attachments.first.content_type
173 assert_equal 10790, issue.attachments.first.filesize
173 assert_equal 10790, issue.attachments.first.filesize
174 end
174 end
175
175
176 def test_add_issue_with_custom_fields
176 def test_add_issue_with_custom_fields
177 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
177 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
178 assert issue.is_a?(Issue)
178 assert issue.is_a?(Issue)
179 assert !issue.new_record?
179 assert !issue.new_record?
180 issue.reload
180 issue.reload
181 assert_equal 'New ticket with custom field values', issue.subject
181 assert_equal 'New ticket with custom field values', issue.subject
182 assert_equal 'PostgreSQL', issue.custom_field_value(1)
182 assert_equal 'PostgreSQL', issue.custom_field_value(1)
183 assert_equal 'Value for a custom field', issue.custom_field_value(2)
183 assert_equal 'Value for a custom field', issue.custom_field_value(2)
184 assert !issue.description.match(/^searchable field:/i)
184 assert !issue.description.match(/^searchable field:/i)
185 end
185 end
186
186
187 def test_add_issue_with_version_custom_fields
187 def test_add_issue_with_version_custom_fields
188 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
188 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
189
189
190 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
190 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
191 email << "Affected version: 1.0\n"
191 email << "Affected version: 1.0\n"
192 end
192 end
193 assert issue.is_a?(Issue)
193 assert issue.is_a?(Issue)
194 assert !issue.new_record?
194 assert !issue.new_record?
195 issue.reload
195 issue.reload
196 assert_equal '2', issue.custom_field_value(field)
196 assert_equal '2', issue.custom_field_value(field)
197 end
197 end
198
198
199 def test_add_issue_should_match_assignee_on_display_name
199 def test_add_issue_should_match_assignee_on_display_name
200 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
200 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
201 User.add_to_project(user, Project.find(2))
201 User.add_to_project(user, Project.find(2))
202 issue = submit_email('ticket_on_given_project.eml') do |email|
202 issue = submit_email('ticket_on_given_project.eml') do |email|
203 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
203 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
204 end
204 end
205 assert issue.is_a?(Issue)
205 assert issue.is_a?(Issue)
206 assert_equal user, issue.assigned_to
206 assert_equal user, issue.assigned_to
207 end
207 end
208
208
209 def test_add_issue_should_set_default_start_date
209 def test_add_issue_should_set_default_start_date
210 with_settings :default_issue_start_date_to_creation_date => '1' do
210 with_settings :default_issue_start_date_to_creation_date => '1' do
211 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
211 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
212 assert issue.is_a?(Issue)
212 assert issue.is_a?(Issue)
213 assert_equal Date.today, issue.start_date
213 assert_equal Date.today, issue.start_date
214 end
214 end
215 end
215 end
216
216
217 def test_add_issue_with_cc
217 def test_add_issue_with_cc
218 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
218 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
219 assert issue.is_a?(Issue)
219 assert issue.is_a?(Issue)
220 assert !issue.new_record?
220 assert !issue.new_record?
221 issue.reload
221 issue.reload
222 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
222 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
223 assert_equal 1, issue.watcher_user_ids.size
223 assert_equal 1, issue.watcher_user_ids.size
224 end
224 end
225
225
226 def test_add_issue_by_unknown_user
226 def test_add_issue_by_unknown_user
227 assert_no_difference 'User.count' do
227 assert_no_difference 'User.count' do
228 assert_equal false,
228 assert_equal false,
229 submit_email(
229 submit_email(
230 'ticket_by_unknown_user.eml',
230 'ticket_by_unknown_user.eml',
231 :issue => {:project => 'ecookbook'}
231 :issue => {:project => 'ecookbook'}
232 )
232 )
233 end
233 end
234 end
234 end
235
235
236 def test_add_issue_by_anonymous_user
236 def test_add_issue_by_anonymous_user
237 Role.anonymous.add_permission!(:add_issues)
237 Role.anonymous.add_permission!(:add_issues)
238 assert_no_difference 'User.count' do
238 assert_no_difference 'User.count' do
239 issue = submit_email(
239 issue = submit_email(
240 'ticket_by_unknown_user.eml',
240 'ticket_by_unknown_user.eml',
241 :issue => {:project => 'ecookbook'},
241 :issue => {:project => 'ecookbook'},
242 :unknown_user => 'accept'
242 :unknown_user => 'accept'
243 )
243 )
244 assert issue.is_a?(Issue)
244 assert issue.is_a?(Issue)
245 assert issue.author.anonymous?
245 assert issue.author.anonymous?
246 end
246 end
247 end
247 end
248
248
249 def test_add_issue_by_anonymous_user_with_no_from_address
249 def test_add_issue_by_anonymous_user_with_no_from_address
250 Role.anonymous.add_permission!(:add_issues)
250 Role.anonymous.add_permission!(:add_issues)
251 assert_no_difference 'User.count' do
251 assert_no_difference 'User.count' do
252 issue = submit_email(
252 issue = submit_email(
253 'ticket_by_empty_user.eml',
253 'ticket_by_empty_user.eml',
254 :issue => {:project => 'ecookbook'},
254 :issue => {:project => 'ecookbook'},
255 :unknown_user => 'accept'
255 :unknown_user => 'accept'
256 )
256 )
257 assert issue.is_a?(Issue)
257 assert issue.is_a?(Issue)
258 assert issue.author.anonymous?
258 assert issue.author.anonymous?
259 end
259 end
260 end
260 end
261
261
262 def test_add_issue_by_anonymous_user_on_private_project
262 def test_add_issue_by_anonymous_user_on_private_project
263 Role.anonymous.add_permission!(:add_issues)
263 Role.anonymous.add_permission!(:add_issues)
264 assert_no_difference 'User.count' do
264 assert_no_difference 'User.count' do
265 assert_no_difference 'Issue.count' do
265 assert_no_difference 'Issue.count' do
266 assert_equal false,
266 assert_equal false,
267 submit_email(
267 submit_email(
268 'ticket_by_unknown_user.eml',
268 'ticket_by_unknown_user.eml',
269 :issue => {:project => 'onlinestore'},
269 :issue => {:project => 'onlinestore'},
270 :unknown_user => 'accept'
270 :unknown_user => 'accept'
271 )
271 )
272 end
272 end
273 end
273 end
274 end
274 end
275
275
276 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
276 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
277 lft1 = new_issue_lft
277 lft1 = new_issue_lft
278 assert_no_difference 'User.count' do
278 assert_no_difference 'User.count' do
279 assert_difference 'Issue.count' do
279 assert_difference 'Issue.count' do
280 issue = submit_email(
280 issue = submit_email(
281 'ticket_by_unknown_user.eml',
281 'ticket_by_unknown_user.eml',
282 :issue => {:project => 'onlinestore'},
282 :issue => {:project => 'onlinestore'},
283 :no_permission_check => '1',
283 :no_permission_check => '1',
284 :unknown_user => 'accept'
284 :unknown_user => 'accept'
285 )
285 )
286 assert issue.is_a?(Issue)
286 assert issue.is_a?(Issue)
287 assert issue.author.anonymous?
287 assert issue.author.anonymous?
288 assert !issue.project.is_public?
288 assert !issue.project.is_public?
289 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
289 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
290 end
290 end
291 end
291 end
292 end
292 end
293
293
294 def test_add_issue_by_created_user
294 def test_add_issue_by_created_user
295 Setting.default_language = 'en'
295 Setting.default_language = 'en'
296 assert_difference 'User.count' do
296 assert_difference 'User.count' do
297 issue = submit_email(
297 issue = submit_email(
298 'ticket_by_unknown_user.eml',
298 'ticket_by_unknown_user.eml',
299 :issue => {:project => 'ecookbook'},
299 :issue => {:project => 'ecookbook'},
300 :unknown_user => 'create'
300 :unknown_user => 'create'
301 )
301 )
302 assert issue.is_a?(Issue)
302 assert issue.is_a?(Issue)
303 assert issue.author.active?
303 assert issue.author.active?
304 assert_equal 'john.doe@somenet.foo', issue.author.mail
304 assert_equal 'john.doe@somenet.foo', issue.author.mail
305 assert_equal 'John', issue.author.firstname
305 assert_equal 'John', issue.author.firstname
306 assert_equal 'Doe', issue.author.lastname
306 assert_equal 'Doe', issue.author.lastname
307
307
308 # account information
308 # account information
309 email = ActionMailer::Base.deliveries.first
309 email = ActionMailer::Base.deliveries.first
310 assert_not_nil email
310 assert_not_nil email
311 assert email.subject.include?('account activation')
311 assert email.subject.include?('account activation')
312 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
312 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
313 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
313 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
314 assert_equal issue.author, User.try_to_login(login, password)
314 assert_equal issue.author, User.try_to_login(login, password)
315 end
315 end
316 end
316 end
317
317
318 def test_created_user_should_be_added_to_groups
318 def test_created_user_should_be_added_to_groups
319 group1 = Group.generate!
319 group1 = Group.generate!
320 group2 = Group.generate!
320 group2 = Group.generate!
321
321
322 assert_difference 'User.count' do
322 assert_difference 'User.count' do
323 submit_email(
323 submit_email(
324 'ticket_by_unknown_user.eml',
324 'ticket_by_unknown_user.eml',
325 :issue => {:project => 'ecookbook'},
325 :issue => {:project => 'ecookbook'},
326 :unknown_user => 'create',
326 :unknown_user => 'create',
327 :default_group => "#{group1.name},#{group2.name}"
327 :default_group => "#{group1.name},#{group2.name}"
328 )
328 )
329 end
329 end
330 user = User.order('id DESC').first
330 user = User.order('id DESC').first
331 assert_same_elements [group1, group2], user.groups
331 assert_same_elements [group1, group2], user.groups
332 end
332 end
333
333
334 def test_created_user_should_not_receive_account_information_with_no_account_info_option
334 def test_created_user_should_not_receive_account_information_with_no_account_info_option
335 assert_difference 'User.count' do
335 assert_difference 'User.count' do
336 submit_email(
336 submit_email(
337 'ticket_by_unknown_user.eml',
337 'ticket_by_unknown_user.eml',
338 :issue => {:project => 'ecookbook'},
338 :issue => {:project => 'ecookbook'},
339 :unknown_user => 'create',
339 :unknown_user => 'create',
340 :no_account_notice => '1'
340 :no_account_notice => '1'
341 )
341 )
342 end
342 end
343
343
344 # only 1 email for the new issue notification
344 # only 1 email for the new issue notification
345 assert_equal 1, ActionMailer::Base.deliveries.size
345 assert_equal 1, ActionMailer::Base.deliveries.size
346 email = ActionMailer::Base.deliveries.first
346 email = ActionMailer::Base.deliveries.first
347 assert_include 'Ticket by unknown user', email.subject
347 assert_include 'Ticket by unknown user', email.subject
348 end
348 end
349
349
350 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
350 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
351 assert_difference 'User.count' do
351 assert_difference 'User.count' do
352 submit_email(
352 submit_email(
353 'ticket_by_unknown_user.eml',
353 'ticket_by_unknown_user.eml',
354 :issue => {:project => 'ecookbook'},
354 :issue => {:project => 'ecookbook'},
355 :unknown_user => 'create',
355 :unknown_user => 'create',
356 :no_notification => '1'
356 :no_notification => '1'
357 )
357 )
358 end
358 end
359 user = User.order('id DESC').first
359 user = User.order('id DESC').first
360 assert_equal 'none', user.mail_notification
360 assert_equal 'none', user.mail_notification
361 end
361 end
362
362
363 def test_add_issue_without_from_header
363 def test_add_issue_without_from_header
364 Role.anonymous.add_permission!(:add_issues)
364 Role.anonymous.add_permission!(:add_issues)
365 assert_equal false, submit_email('ticket_without_from_header.eml')
365 assert_equal false, submit_email('ticket_without_from_header.eml')
366 end
366 end
367
367
368 def test_add_issue_with_invalid_attributes
368 def test_add_issue_with_invalid_attributes
369 with_settings :default_issue_start_date_to_creation_date => '0' do
369 with_settings :default_issue_start_date_to_creation_date => '0' do
370 issue = submit_email(
370 issue = submit_email(
371 'ticket_with_invalid_attributes.eml',
371 'ticket_with_invalid_attributes.eml',
372 :allow_override => 'tracker,category,priority'
372 :allow_override => 'tracker,category,priority'
373 )
373 )
374 assert issue.is_a?(Issue)
374 assert issue.is_a?(Issue)
375 assert !issue.new_record?
375 assert !issue.new_record?
376 issue.reload
376 issue.reload
377 assert_nil issue.assigned_to
377 assert_nil issue.assigned_to
378 assert_nil issue.start_date
378 assert_nil issue.start_date
379 assert_nil issue.due_date
379 assert_nil issue.due_date
380 assert_equal 0, issue.done_ratio
380 assert_equal 0, issue.done_ratio
381 assert_equal 'Normal', issue.priority.to_s
381 assert_equal 'Normal', issue.priority.to_s
382 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
382 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
383 end
383 end
384 end
384 end
385
385
386 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
386 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
387 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
387 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
388 email.gsub!(/^Project:.+$/, 'Project: invalid')
388 email.gsub!(/^Project:.+$/, 'Project: invalid')
389 end
389 end
390 assert issue.is_a?(Issue)
390 assert issue.is_a?(Issue)
391 assert !issue.new_record?
391 assert !issue.new_record?
392 assert_equal 'ecookbook', issue.project.identifier
392 assert_equal 'ecookbook', issue.project.identifier
393 end
393 end
394
394
395 def test_add_issue_with_localized_attributes
395 def test_add_issue_with_localized_attributes
396 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
396 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
397 issue = submit_email(
397 issue = submit_email(
398 'ticket_with_localized_attributes.eml',
398 'ticket_with_localized_attributes.eml',
399 :allow_override => 'tracker,category,priority'
399 :allow_override => 'tracker,category,priority'
400 )
400 )
401 assert issue.is_a?(Issue)
401 assert issue.is_a?(Issue)
402 assert !issue.new_record?
402 assert !issue.new_record?
403 issue.reload
403 issue.reload
404 assert_equal 'New ticket on a given project', issue.subject
404 assert_equal 'New ticket on a given project', issue.subject
405 assert_equal User.find_by_login('jsmith'), issue.author
405 assert_equal User.find_by_login('jsmith'), issue.author
406 assert_equal Project.find(2), issue.project
406 assert_equal Project.find(2), issue.project
407 assert_equal 'Feature request', issue.tracker.to_s
407 assert_equal 'Feature request', issue.tracker.to_s
408 assert_equal 'Stock management', issue.category.to_s
408 assert_equal 'Stock management', issue.category.to_s
409 assert_equal 'Urgent', issue.priority.to_s
409 assert_equal 'Urgent', issue.priority.to_s
410 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
410 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
411 end
411 end
412
412
413 def test_add_issue_with_japanese_keywords
413 def test_add_issue_with_japanese_keywords
414 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
414 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
415 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
415 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
416 tracker = Tracker.create!(:name => ja_dev)
416 tracker = Tracker.create!(:name => ja_dev)
417 Project.find(1).trackers << tracker
417 Project.find(1).trackers << tracker
418 issue = submit_email(
418 issue = submit_email(
419 'japanese_keywords_iso_2022_jp.eml',
419 'japanese_keywords_iso_2022_jp.eml',
420 :issue => {:project => 'ecookbook'},
420 :issue => {:project => 'ecookbook'},
421 :allow_override => 'tracker'
421 :allow_override => 'tracker'
422 )
422 )
423 assert_kind_of Issue, issue
423 assert_kind_of Issue, issue
424 assert_equal tracker, issue.tracker
424 assert_equal tracker, issue.tracker
425 end
425 end
426
426
427 def test_add_issue_from_apple_mail
427 def test_add_issue_from_apple_mail
428 issue = submit_email(
428 issue = submit_email(
429 'apple_mail_with_attachment.eml',
429 'apple_mail_with_attachment.eml',
430 :issue => {:project => 'ecookbook'}
430 :issue => {:project => 'ecookbook'}
431 )
431 )
432 assert_kind_of Issue, issue
432 assert_kind_of Issue, issue
433 assert_equal 1, issue.attachments.size
433 assert_equal 1, issue.attachments.size
434
434
435 attachment = issue.attachments.first
435 attachment = issue.attachments.first
436 assert_equal 'paella.jpg', attachment.filename
436 assert_equal 'paella.jpg', attachment.filename
437 assert_equal 10790, attachment.filesize
437 assert_equal 10790, attachment.filesize
438 assert File.exist?(attachment.diskfile)
438 assert File.exist?(attachment.diskfile)
439 assert_equal 10790, File.size(attachment.diskfile)
439 assert_equal 10790, File.size(attachment.diskfile)
440 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
440 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
441 end
441 end
442
442
443 def test_thunderbird_with_attachment_ja
443 def test_thunderbird_with_attachment_ja
444 issue = submit_email(
444 issue = submit_email(
445 'thunderbird_with_attachment_ja.eml',
445 'thunderbird_with_attachment_ja.eml',
446 :issue => {:project => 'ecookbook'}
446 :issue => {:project => 'ecookbook'}
447 )
447 )
448 assert_kind_of Issue, issue
448 assert_kind_of Issue, issue
449 assert_equal 1, issue.attachments.size
449 assert_equal 1, issue.attachments.size
450 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
450 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
451 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
451 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
452 attachment = issue.attachments.first
452 attachment = issue.attachments.first
453 assert_equal ja, attachment.filename
453 assert_equal ja, attachment.filename
454 assert_equal 5, attachment.filesize
454 assert_equal 5, attachment.filesize
455 assert File.exist?(attachment.diskfile)
455 assert File.exist?(attachment.diskfile)
456 assert_equal 5, File.size(attachment.diskfile)
456 assert_equal 5, File.size(attachment.diskfile)
457 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
457 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
458 end
458 end
459
459
460 def test_gmail_with_attachment_ja
460 def test_gmail_with_attachment_ja
461 issue = submit_email(
461 issue = submit_email(
462 'gmail_with_attachment_ja.eml',
462 'gmail_with_attachment_ja.eml',
463 :issue => {:project => 'ecookbook'}
463 :issue => {:project => 'ecookbook'}
464 )
464 )
465 assert_kind_of Issue, issue
465 assert_kind_of Issue, issue
466 assert_equal 1, issue.attachments.size
466 assert_equal 1, issue.attachments.size
467 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
467 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
468 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
468 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
469 attachment = issue.attachments.first
469 attachment = issue.attachments.first
470 assert_equal ja, attachment.filename
470 assert_equal ja, attachment.filename
471 assert_equal 5, attachment.filesize
471 assert_equal 5, attachment.filesize
472 assert File.exist?(attachment.diskfile)
472 assert File.exist?(attachment.diskfile)
473 assert_equal 5, File.size(attachment.diskfile)
473 assert_equal 5, File.size(attachment.diskfile)
474 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
474 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
475 end
475 end
476
476
477 def test_thunderbird_with_attachment_latin1
477 def test_thunderbird_with_attachment_latin1
478 issue = submit_email(
478 issue = submit_email(
479 'thunderbird_with_attachment_iso-8859-1.eml',
479 'thunderbird_with_attachment_iso-8859-1.eml',
480 :issue => {:project => 'ecookbook'}
480 :issue => {:project => 'ecookbook'}
481 )
481 )
482 assert_kind_of Issue, issue
482 assert_kind_of Issue, issue
483 assert_equal 1, issue.attachments.size
483 assert_equal 1, issue.attachments.size
484 u = ""
484 u = ""
485 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
485 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
486 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
486 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
487 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
487 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
488 11.times { u << u1 }
488 11.times { u << u1 }
489 attachment = issue.attachments.first
489 attachment = issue.attachments.first
490 assert_equal "#{u}.png", attachment.filename
490 assert_equal "#{u}.png", attachment.filename
491 assert_equal 130, attachment.filesize
491 assert_equal 130, attachment.filesize
492 assert File.exist?(attachment.diskfile)
492 assert File.exist?(attachment.diskfile)
493 assert_equal 130, File.size(attachment.diskfile)
493 assert_equal 130, File.size(attachment.diskfile)
494 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
494 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
495 end
495 end
496
496
497 def test_gmail_with_attachment_latin1
497 def test_gmail_with_attachment_latin1
498 issue = submit_email(
498 issue = submit_email(
499 'gmail_with_attachment_iso-8859-1.eml',
499 'gmail_with_attachment_iso-8859-1.eml',
500 :issue => {:project => 'ecookbook'}
500 :issue => {:project => 'ecookbook'}
501 )
501 )
502 assert_kind_of Issue, issue
502 assert_kind_of Issue, issue
503 assert_equal 1, issue.attachments.size
503 assert_equal 1, issue.attachments.size
504 u = ""
504 u = ""
505 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
505 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
506 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
506 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
507 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
507 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
508 11.times { u << u1 }
508 11.times { u << u1 }
509 attachment = issue.attachments.first
509 attachment = issue.attachments.first
510 assert_equal "#{u}.txt", attachment.filename
510 assert_equal "#{u}.txt", attachment.filename
511 assert_equal 5, attachment.filesize
511 assert_equal 5, attachment.filesize
512 assert File.exist?(attachment.diskfile)
512 assert File.exist?(attachment.diskfile)
513 assert_equal 5, File.size(attachment.diskfile)
513 assert_equal 5, File.size(attachment.diskfile)
514 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
514 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
515 end
515 end
516
516
517 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
517 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
518 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
518 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
519 assert_include 'first', issue.description
519 assert_include 'first', issue.description
520 assert_include 'second', issue.description
520 assert_include 'second', issue.description
521 assert_include 'third', issue.description
521 assert_include 'third', issue.description
522 end
522 end
523
523
524 def test_attachment_text_part_should_be_added_as_issue_attachment
524 def test_attachment_text_part_should_be_added_as_issue_attachment
525 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
525 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
526 assert_not_include 'Plain text attachment', issue.description
526 assert_not_include 'Plain text attachment', issue.description
527 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
527 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
528 assert_not_nil attachment
528 assert_not_nil attachment
529 assert_include 'Plain text attachment', File.read(attachment.diskfile)
529 assert_include 'Plain text attachment', File.read(attachment.diskfile)
530 end
530 end
531
531
532 def test_add_issue_with_iso_8859_1_subject
532 def test_add_issue_with_iso_8859_1_subject
533 issue = submit_email(
533 issue = submit_email(
534 'subject_as_iso-8859-1.eml',
534 'subject_as_iso-8859-1.eml',
535 :issue => {:project => 'ecookbook'}
535 :issue => {:project => 'ecookbook'}
536 )
536 )
537 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..."
537 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..."
538 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
538 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
539 assert_kind_of Issue, issue
539 assert_kind_of Issue, issue
540 assert_equal str, issue.subject
540 assert_equal str, issue.subject
541 end
541 end
542
542
543 def test_quoted_printable_utf8
543 def test_quoted_printable_utf8
544 issue = submit_email(
544 issue = submit_email(
545 'quoted_printable_utf8.eml',
545 'quoted_printable_utf8.eml',
546 :issue => {:project => 'ecookbook'}
546 :issue => {:project => 'ecookbook'}
547 )
547 )
548 assert_kind_of Issue, issue
548 assert_kind_of Issue, issue
549 str = "Freundliche Gr\xc3\xbcsse"
549 str = "Freundliche Gr\xc3\xbcsse"
550 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
550 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
551 assert_equal str, issue.description
551 assert_equal str, issue.description
552 end
552 end
553
553
554 def test_gmail_iso8859_2
554 def test_gmail_iso8859_2
555 issue = submit_email(
555 issue = submit_email(
556 'gmail-iso8859-2.eml',
556 'gmail-iso8859-2.eml',
557 :issue => {:project => 'ecookbook'}
557 :issue => {:project => 'ecookbook'}
558 )
558 )
559 assert_kind_of Issue, issue
559 assert_kind_of Issue, issue
560 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87."
560 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87."
561 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
561 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
562 assert issue.description.include?(str)
562 assert issue.description.include?(str)
563 end
563 end
564
564
565 def test_add_issue_with_japanese_subject
565 def test_add_issue_with_japanese_subject
566 issue = submit_email(
566 issue = submit_email(
567 'subject_japanese_1.eml',
567 'subject_japanese_1.eml',
568 :issue => {:project => 'ecookbook'}
568 :issue => {:project => 'ecookbook'}
569 )
569 )
570 assert_kind_of Issue, issue
570 assert_kind_of Issue, issue
571 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
571 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
572 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
572 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
573 assert_equal ja, issue.subject
573 assert_equal ja, issue.subject
574 end
574 end
575
575
576 def test_add_issue_with_korean_body
576 def test_add_issue_with_korean_body
577 # Make sure mail bodies with a charset unknown to Ruby
577 # Make sure mail bodies with a charset unknown to Ruby
578 # but known to the Mail gem 2.5.4 are handled correctly
578 # but known to the Mail gem 2.5.4 are handled correctly
579 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4."
579 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4."
580 if !kr.respond_to?(:force_encoding)
580 if !kr.respond_to?(:force_encoding)
581 puts "\nOn Ruby 1.8, skip Korean encoding mail body test"
581 puts "\nOn Ruby 1.8, skip Korean encoding mail body test"
582 else
582 else
583 kr.force_encoding('UTF-8')
583 kr.force_encoding('UTF-8')
584 issue = submit_email(
584 issue = submit_email(
585 'body_ks_c_5601-1987.eml',
585 'body_ks_c_5601-1987.eml',
586 :issue => {:project => 'ecookbook'}
586 :issue => {:project => 'ecookbook'}
587 )
587 )
588 assert_kind_of Issue, issue
588 assert_kind_of Issue, issue
589 assert_equal kr, issue.description
589 assert_equal kr, issue.description
590 end
590 end
591 end
591 end
592
592
593 def test_add_issue_with_no_subject_header
593 def test_add_issue_with_no_subject_header
594 issue = submit_email(
594 issue = submit_email(
595 'no_subject_header.eml',
595 'no_subject_header.eml',
596 :issue => {:project => 'ecookbook'}
596 :issue => {:project => 'ecookbook'}
597 )
597 )
598 assert_kind_of Issue, issue
598 assert_kind_of Issue, issue
599 assert_equal '(no subject)', issue.subject
599 assert_equal '(no subject)', issue.subject
600 end
600 end
601
601
602 def test_add_issue_with_mixed_japanese_subject
602 def test_add_issue_with_mixed_japanese_subject
603 issue = submit_email(
603 issue = submit_email(
604 'subject_japanese_2.eml',
604 'subject_japanese_2.eml',
605 :issue => {:project => 'ecookbook'}
605 :issue => {:project => 'ecookbook'}
606 )
606 )
607 assert_kind_of Issue, issue
607 assert_kind_of Issue, issue
608 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
608 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
609 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
609 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
610 assert_equal ja, issue.subject
610 assert_equal ja, issue.subject
611 end
611 end
612
612
613 def test_should_ignore_emails_from_locked_users
613 def test_should_ignore_emails_from_locked_users
614 User.find(2).lock!
614 User.find(2).lock!
615
615
616 MailHandler.any_instance.expects(:dispatch).never
616 MailHandler.any_instance.expects(:dispatch).never
617 assert_no_difference 'Issue.count' do
617 assert_no_difference 'Issue.count' do
618 assert_equal false, submit_email('ticket_on_given_project.eml')
618 assert_equal false, submit_email('ticket_on_given_project.eml')
619 end
619 end
620 end
620 end
621
621
622 def test_should_ignore_emails_from_emission_address
622 def test_should_ignore_emails_from_emission_address
623 Role.anonymous.add_permission!(:add_issues)
623 Role.anonymous.add_permission!(:add_issues)
624 assert_no_difference 'User.count' do
624 assert_no_difference 'User.count' do
625 assert_equal false,
625 assert_equal false,
626 submit_email(
626 submit_email(
627 'ticket_from_emission_address.eml',
627 'ticket_from_emission_address.eml',
628 :issue => {:project => 'ecookbook'},
628 :issue => {:project => 'ecookbook'},
629 :unknown_user => 'create'
629 :unknown_user => 'create'
630 )
630 )
631 end
631 end
632 end
632 end
633
633
634 def test_should_ignore_auto_replied_emails
634 def test_should_ignore_auto_replied_emails
635 MailHandler.any_instance.expects(:dispatch).never
635 MailHandler.any_instance.expects(:dispatch).never
636 [
636 [
637 "X-Auto-Response-Suppress: OOF",
637 "X-Auto-Response-Suppress: OOF",
638 "Auto-Submitted: auto-replied",
638 "Auto-Submitted: auto-replied",
639 "Auto-Submitted: Auto-Replied",
639 "Auto-Submitted: Auto-Replied",
640 "Auto-Submitted: auto-generated"
640 "Auto-Submitted: auto-generated"
641 ].each do |header|
641 ].each do |header|
642 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
642 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
643 raw = header + "\n" + raw
643 raw = header + "\n" + raw
644
644
645 assert_no_difference 'Issue.count' do
645 assert_no_difference 'Issue.count' do
646 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
646 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
647 end
647 end
648 end
648 end
649 end
649 end
650
650
651 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
651 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
652 [
652 [
653 "Auto-Submitted: auto-forwarded"
653 "Auto-Submitted: auto-forwarded"
654 ].each do |header|
654 ].each do |header|
655 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
655 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
656 raw = header + "\n" + raw
656 raw = header + "\n" + raw
657
657
658 assert_difference 'Issue.count', 1 do
658 assert_difference 'Issue.count', 1 do
659 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
659 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
660 end
660 end
661 end
661 end
662 end
662 end
663
663
664 def test_add_issue_should_send_email_notification
664 def test_add_issue_should_send_email_notification
665 Setting.notified_events = ['issue_added']
665 Setting.notified_events = ['issue_added']
666 ActionMailer::Base.deliveries.clear
666 ActionMailer::Base.deliveries.clear
667 # This email contains: 'Project: onlinestore'
667 # This email contains: 'Project: onlinestore'
668 issue = submit_email('ticket_on_given_project.eml')
668 issue = submit_email('ticket_on_given_project.eml')
669 assert issue.is_a?(Issue)
669 assert issue.is_a?(Issue)
670 assert_equal 1, ActionMailer::Base.deliveries.size
670 assert_equal 1, ActionMailer::Base.deliveries.size
671 end
671 end
672
672
673 def test_update_issue
673 def test_update_issue
674 journal = submit_email('ticket_reply.eml')
674 journal = submit_email('ticket_reply.eml')
675 assert journal.is_a?(Journal)
675 assert journal.is_a?(Journal)
676 assert_equal User.find_by_login('jsmith'), journal.user
676 assert_equal User.find_by_login('jsmith'), journal.user
677 assert_equal Issue.find(2), journal.journalized
677 assert_equal Issue.find(2), journal.journalized
678 assert_match /This is reply/, journal.notes
678 assert_match /This is reply/, journal.notes
679 assert_equal false, journal.private_notes
679 assert_equal false, journal.private_notes
680 assert_equal 'Feature request', journal.issue.tracker.name
680 assert_equal 'Feature request', journal.issue.tracker.name
681 end
681 end
682
682
683 def test_update_issue_with_attribute_changes
683 def test_update_issue_with_attribute_changes
684 # This email contains: 'Status: Resolved'
684 # This email contains: 'Status: Resolved'
685 journal = submit_email('ticket_reply_with_status.eml')
685 journal = submit_email('ticket_reply_with_status.eml')
686 assert journal.is_a?(Journal)
686 assert journal.is_a?(Journal)
687 issue = Issue.find(journal.issue.id)
687 issue = Issue.find(journal.issue.id)
688 assert_equal User.find_by_login('jsmith'), journal.user
688 assert_equal User.find_by_login('jsmith'), journal.user
689 assert_equal Issue.find(2), journal.journalized
689 assert_equal Issue.find(2), journal.journalized
690 assert_match /This is reply/, journal.notes
690 assert_match /This is reply/, journal.notes
691 assert_equal 'Feature request', journal.issue.tracker.name
691 assert_equal 'Feature request', journal.issue.tracker.name
692 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
692 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
693 assert_equal '2010-01-01', issue.start_date.to_s
693 assert_equal '2010-01-01', issue.start_date.to_s
694 assert_equal '2010-12-31', issue.due_date.to_s
694 assert_equal '2010-12-31', issue.due_date.to_s
695 assert_equal User.find_by_login('jsmith'), issue.assigned_to
695 assert_equal User.find_by_login('jsmith'), issue.assigned_to
696 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
696 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
697 # keywords should be removed from the email body
697 # keywords should be removed from the email body
698 assert !journal.notes.match(/^Status:/i)
698 assert !journal.notes.match(/^Status:/i)
699 assert !journal.notes.match(/^Start Date:/i)
699 assert !journal.notes.match(/^Start Date:/i)
700 end
700 end
701
701
702 def test_update_issue_with_attachment
702 def test_update_issue_with_attachment
703 assert_difference 'Journal.count' do
703 assert_difference 'Journal.count' do
704 assert_difference 'JournalDetail.count' do
704 assert_difference 'JournalDetail.count' do
705 assert_difference 'Attachment.count' do
705 assert_difference 'Attachment.count' do
706 assert_no_difference 'Issue.count' do
706 assert_no_difference 'Issue.count' do
707 journal = submit_email('ticket_with_attachment.eml') do |raw|
707 journal = submit_email('ticket_with_attachment.eml') do |raw|
708 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
708 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
709 end
709 end
710 end
710 end
711 end
711 end
712 end
712 end
713 end
713 end
714 journal = Journal.order('id DESC').first
714 journal = Journal.order('id DESC').first
715 assert_equal Issue.find(2), journal.journalized
715 assert_equal Issue.find(2), journal.journalized
716 assert_equal 1, journal.details.size
716 assert_equal 1, journal.details.size
717
717
718 detail = journal.details.first
718 detail = journal.details.first
719 assert_equal 'attachment', detail.property
719 assert_equal 'attachment', detail.property
720 assert_equal 'Paella.jpg', detail.value
720 assert_equal 'Paella.jpg', detail.value
721 end
721 end
722
722
723 def test_update_issue_should_send_email_notification
723 def test_update_issue_should_send_email_notification
724 ActionMailer::Base.deliveries.clear
724 ActionMailer::Base.deliveries.clear
725 journal = submit_email('ticket_reply.eml')
725 journal = submit_email('ticket_reply.eml')
726 assert journal.is_a?(Journal)
726 assert journal.is_a?(Journal)
727 assert_equal 1, ActionMailer::Base.deliveries.size
727 assert_equal 1, ActionMailer::Base.deliveries.size
728 end
728 end
729
729
730 def test_update_issue_should_not_set_defaults
730 def test_update_issue_should_not_set_defaults
731 journal = submit_email(
731 journal = submit_email(
732 'ticket_reply.eml',
732 'ticket_reply.eml',
733 :issue => {:tracker => 'Support request', :priority => 'High'}
733 :issue => {:tracker => 'Support request', :priority => 'High'}
734 )
734 )
735 assert journal.is_a?(Journal)
735 assert journal.is_a?(Journal)
736 assert_match /This is reply/, journal.notes
736 assert_match /This is reply/, journal.notes
737 assert_equal 'Feature request', journal.issue.tracker.name
737 assert_equal 'Feature request', journal.issue.tracker.name
738 assert_equal 'Normal', journal.issue.priority.name
738 assert_equal 'Normal', journal.issue.priority.name
739 end
739 end
740
740
741 def test_replying_to_a_private_note_should_add_reply_as_private
741 def test_replying_to_a_private_note_should_add_reply_as_private
742 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
742 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
743
743
744 assert_difference 'Journal.count' do
744 assert_difference 'Journal.count' do
745 journal = submit_email('ticket_reply.eml') do |email|
745 journal = submit_email('ticket_reply.eml') do |email|
746 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
746 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
747 end
747 end
748
748
749 assert_kind_of Journal, journal
749 assert_kind_of Journal, journal
750 assert_match /This is reply/, journal.notes
750 assert_match /This is reply/, journal.notes
751 assert_equal true, journal.private_notes
751 assert_equal true, journal.private_notes
752 end
752 end
753 end
753 end
754
754
755 def test_reply_to_a_message
755 def test_reply_to_a_message
756 m = submit_email('message_reply.eml')
756 m = submit_email('message_reply.eml')
757 assert m.is_a?(Message)
757 assert m.is_a?(Message)
758 assert !m.new_record?
758 assert !m.new_record?
759 m.reload
759 m.reload
760 assert_equal 'Reply via email', m.subject
760 assert_equal 'Reply via email', m.subject
761 # The email replies to message #2 which is part of the thread of message #1
761 # The email replies to message #2 which is part of the thread of message #1
762 assert_equal Message.find(1), m.parent
762 assert_equal Message.find(1), m.parent
763 end
763 end
764
764
765 def test_reply_to_a_message_by_subject
765 def test_reply_to_a_message_by_subject
766 m = submit_email('message_reply_by_subject.eml')
766 m = submit_email('message_reply_by_subject.eml')
767 assert m.is_a?(Message)
767 assert m.is_a?(Message)
768 assert !m.new_record?
768 assert !m.new_record?
769 m.reload
769 m.reload
770 assert_equal 'Reply to the first post', m.subject
770 assert_equal 'Reply to the first post', m.subject
771 assert_equal Message.find(1), m.parent
771 assert_equal Message.find(1), m.parent
772 end
772 end
773
773
774 def test_should_strip_tags_of_html_only_emails
774 def test_should_strip_tags_of_html_only_emails
775 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
775 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
776 assert issue.is_a?(Issue)
776 assert issue.is_a?(Issue)
777 assert !issue.new_record?
777 assert !issue.new_record?
778 issue.reload
778 issue.reload
779 assert_equal 'HTML email', issue.subject
779 assert_equal 'HTML email', issue.subject
780 assert_equal 'This is a html-only email.', issue.description
780 assert_equal 'This is a html-only email.', issue.description
781 end
781 end
782
782
783 test "truncate emails with no setting should add the entire email into the issue" do
783 test "truncate emails with no setting should add the entire email into the issue" do
784 with_settings :mail_handler_body_delimiters => '' do
784 with_settings :mail_handler_body_delimiters => '' do
785 issue = submit_email('ticket_on_given_project.eml')
785 issue = submit_email('ticket_on_given_project.eml')
786 assert_issue_created(issue)
786 assert_issue_created(issue)
787 assert issue.description.include?('---')
787 assert issue.description.include?('---')
788 assert issue.description.include?('This paragraph is after the delimiter')
788 assert issue.description.include?('This paragraph is after the delimiter')
789 end
789 end
790 end
790 end
791
791
792 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
792 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
793 with_settings :mail_handler_body_delimiters => '---' do
793 with_settings :mail_handler_body_delimiters => '---' do
794 issue = submit_email('ticket_on_given_project.eml')
794 issue = submit_email('ticket_on_given_project.eml')
795 assert_issue_created(issue)
795 assert_issue_created(issue)
796 assert issue.description.include?('This paragraph is before delimiters')
796 assert issue.description.include?('This paragraph is before delimiters')
797 assert issue.description.include?('--- This line starts with a delimiter')
797 assert issue.description.include?('--- This line starts with a delimiter')
798 assert !issue.description.match(/^---$/)
798 assert !issue.description.match(/^---$/)
799 assert !issue.description.include?('This paragraph is after the delimiter')
799 assert !issue.description.include?('This paragraph is after the delimiter')
800 end
800 end
801 end
801 end
802
802
803 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
803 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
804 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
804 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
805 journal = submit_email('issue_update_with_quoted_reply_above.eml')
805 journal = submit_email('issue_update_with_quoted_reply_above.eml')
806 assert journal.is_a?(Journal)
806 assert journal.is_a?(Journal)
807 assert journal.notes.include?('An update to the issue by the sender.')
807 assert journal.notes.include?('An update to the issue by the sender.')
808 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
808 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
809 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
809 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
810 end
810 end
811 end
811 end
812
812
813 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
813 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
814 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
814 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
815 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
815 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
816 assert journal.is_a?(Journal)
816 assert journal.is_a?(Journal)
817 assert journal.notes.include?('An update to the issue by the sender.')
817 assert journal.notes.include?('An update to the issue by the sender.')
818 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
818 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
819 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
819 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
820 end
820 end
821 end
821 end
822
822
823 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
823 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
824 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
824 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
825 issue = submit_email('ticket_on_given_project.eml')
825 issue = submit_email('ticket_on_given_project.eml')
826 assert_issue_created(issue)
826 assert_issue_created(issue)
827 assert issue.description.include?('This paragraph is before delimiters')
827 assert issue.description.include?('This paragraph is before delimiters')
828 assert !issue.description.include?('BREAK')
828 assert !issue.description.include?('BREAK')
829 assert !issue.description.include?('This paragraph is between delimiters')
829 assert !issue.description.include?('This paragraph is between delimiters')
830 assert !issue.description.match(/^---$/)
830 assert !issue.description.match(/^---$/)
831 assert !issue.description.include?('This paragraph is after the delimiter')
831 assert !issue.description.include?('This paragraph is after the delimiter')
832 end
832 end
833 end
833 end
834
834
835 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
835 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
836 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
836 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
837 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
837 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
838 assert issue.is_a?(Issue)
838 assert issue.is_a?(Issue)
839 assert !issue.new_record?
839 assert !issue.new_record?
840 assert_equal 0, issue.reload.attachments.size
840 assert_equal 0, issue.reload.attachments.size
841 end
841 end
842 end
842 end
843
843
844 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
844 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
845 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
845 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
846 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
846 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
847 assert issue.is_a?(Issue)
847 assert issue.is_a?(Issue)
848 assert !issue.new_record?
848 assert !issue.new_record?
849 assert_equal 1, issue.reload.attachments.size
849 assert_equal 1, issue.reload.attachments.size
850 end
850 end
851 end
851 end
852
852
853 def test_email_with_long_subject_line
853 def test_email_with_long_subject_line
854 issue = submit_email('ticket_with_long_subject.eml')
854 issue = submit_email('ticket_with_long_subject.eml')
855 assert issue.is_a?(Issue)
855 assert issue.is_a?(Issue)
856 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
856 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
857 end
857 end
858
858
859 def test_first_keyword_should_be_matched
860 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
861 assert issue.is_a?(Issue)
862 assert_equal 'High', issue.priority.name
863 end
864
865 def test_keyword_after_delimiter_should_be_ignored
866 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
867 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
868 assert issue.is_a?(Issue)
869 assert_equal 'Normal', issue.priority.name
870 end
871 end
872
859 def test_new_user_from_attributes_should_return_valid_user
873 def test_new_user_from_attributes_should_return_valid_user
860 to_test = {
874 to_test = {
861 # [address, name] => [login, firstname, lastname]
875 # [address, name] => [login, firstname, lastname]
862 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
876 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
863 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
877 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
864 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
878 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
865 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
879 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
866 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
880 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
867 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
881 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
868 }
882 }
869
883
870 to_test.each do |attrs, expected|
884 to_test.each do |attrs, expected|
871 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
885 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
872
886
873 assert user.valid?, user.errors.full_messages.to_s
887 assert user.valid?, user.errors.full_messages.to_s
874 assert_equal attrs.first, user.mail
888 assert_equal attrs.first, user.mail
875 assert_equal expected[0], user.login
889 assert_equal expected[0], user.login
876 assert_equal expected[1], user.firstname
890 assert_equal expected[1], user.firstname
877 assert_equal expected[2], user.lastname
891 assert_equal expected[2], user.lastname
878 assert_equal 'only_my_events', user.mail_notification
892 assert_equal 'only_my_events', user.mail_notification
879 end
893 end
880 end
894 end
881
895
882 def test_new_user_from_attributes_should_use_default_login_if_invalid
896 def test_new_user_from_attributes_should_use_default_login_if_invalid
883 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
897 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
884 assert user.valid?
898 assert user.valid?
885 assert user.login =~ /^user[a-f0-9]+$/
899 assert user.login =~ /^user[a-f0-9]+$/
886 assert_equal 'foo+bar@example.net', user.mail
900 assert_equal 'foo+bar@example.net', user.mail
887 end
901 end
888
902
889 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
903 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
890 assert_difference 'User.count' do
904 assert_difference 'User.count' do
891 issue = submit_email(
905 issue = submit_email(
892 'fullname_of_sender_as_utf8_encoded.eml',
906 'fullname_of_sender_as_utf8_encoded.eml',
893 :issue => {:project => 'ecookbook'},
907 :issue => {:project => 'ecookbook'},
894 :unknown_user => 'create'
908 :unknown_user => 'create'
895 )
909 )
896 end
910 end
897 user = User.order('id DESC').first
911 user = User.order('id DESC').first
898 assert_equal "foo@example.org", user.mail
912 assert_equal "foo@example.org", user.mail
899 str1 = "\xc3\x84\xc3\xa4"
913 str1 = "\xc3\x84\xc3\xa4"
900 str2 = "\xc3\x96\xc3\xb6"
914 str2 = "\xc3\x96\xc3\xb6"
901 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
915 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
902 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
916 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
903 assert_equal str1, user.firstname
917 assert_equal str1, user.firstname
904 assert_equal str2, user.lastname
918 assert_equal str2, user.lastname
905 end
919 end
906
920
907 def test_extract_options_from_env_should_return_options
921 def test_extract_options_from_env_should_return_options
908 options = MailHandler.extract_options_from_env({
922 options = MailHandler.extract_options_from_env({
909 'tracker' => 'defect',
923 'tracker' => 'defect',
910 'project' => 'foo',
924 'project' => 'foo',
911 'unknown_user' => 'create'
925 'unknown_user' => 'create'
912 })
926 })
913
927
914 assert_equal({
928 assert_equal({
915 :issue => {:tracker => 'defect', :project => 'foo'},
929 :issue => {:tracker => 'defect', :project => 'foo'},
916 :unknown_user => 'create'
930 :unknown_user => 'create'
917 }, options)
931 }, options)
918 end
932 end
919
933
920 private
934 private
921
935
922 def submit_email(filename, options={})
936 def submit_email(filename, options={})
923 raw = IO.read(File.join(FIXTURES_PATH, filename))
937 raw = IO.read(File.join(FIXTURES_PATH, filename))
924 yield raw if block_given?
938 yield raw if block_given?
925 MailHandler.receive(raw, options)
939 MailHandler.receive(raw, options)
926 end
940 end
927
941
928 def assert_issue_created(issue)
942 def assert_issue_created(issue)
929 assert issue.is_a?(Issue)
943 assert issue.is_a?(Issue)
930 assert !issue.new_record?
944 assert !issue.new_record?
931 issue.reload
945 issue.reload
932 end
946 end
933 end
947 end
General Comments 0
You need to be logged in to leave comments. Login now