##// END OF EJS Templates
Fixed: MailHandler raises an error when processing an email without From header (#2916)....
Jean-Philippe Lang -
r2485:009b685b1d64
parent child
Show More
@@ -0,0 +1,40
1 Received: from osiris ([127.0.0.1])
2 by OSIRIS
3 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
4 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
5 To: <redmine@somenet.foo>
6 Subject: New ticket on a given project
7 Date: Sun, 22 Jun 2008 12:28:07 +0200
8 MIME-Version: 1.0
9 Content-Type: text/plain;
10 format=flowed;
11 charset="iso-8859-1";
12 reply-type=original
13 Content-Transfer-Encoding: 7bit
14 X-Priority: 3
15 X-MSMail-Priority: Normal
16 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
17 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
18
19 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
20 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
21 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
22 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
23 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
24 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
25 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
26 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
27 sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
28 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
29 platea dictumst.
30
31 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
32 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
33 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
34 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
35 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
36 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
37
38 Project: onlinestore
39 Status: Resolved
40
@@ -1,245 +1,245
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20
20
21 class UnauthorizedAction < StandardError; end
21 class UnauthorizedAction < StandardError; end
22 class MissingInformation < StandardError; end
22 class MissingInformation < StandardError; end
23
23
24 attr_reader :email, :user
24 attr_reader :email, :user
25
25
26 def self.receive(email, options={})
26 def self.receive(email, options={})
27 @@handler_options = options.dup
27 @@handler_options = options.dup
28
28
29 @@handler_options[:issue] ||= {}
29 @@handler_options[:issue] ||= {}
30
30
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 @@handler_options[:allow_override] ||= []
32 @@handler_options[:allow_override] ||= []
33 # Project needs to be overridable if not specified
33 # Project needs to be overridable if not specified
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 # Status overridable by default
35 # Status overridable by default
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37 super email
37 super email
38 end
38 end
39
39
40 # Processes incoming emails
40 # Processes incoming emails
41 def receive(email)
41 def receive(email)
42 @email = email
42 @email = email
43 @user = User.active.find_by_mail(email.from.first.to_s.strip)
43 @user = User.active.find_by_mail(email.from.to_a.first.to_s.strip)
44 unless @user
44 unless @user
45 # Unknown user => the email is ignored
45 # Unknown user => the email is ignored
46 # TODO: ability to create the user's account
46 # TODO: ability to create the user's account
47 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
47 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
48 return false
48 return false
49 end
49 end
50 User.current = @user
50 User.current = @user
51 dispatch
51 dispatch
52 end
52 end
53
53
54 private
54 private
55
55
56 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
56 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
57 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
57 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
58 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
58 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
59
59
60 def dispatch
60 def dispatch
61 headers = [email.in_reply_to, email.references].flatten.compact
61 headers = [email.in_reply_to, email.references].flatten.compact
62 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
62 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
63 klass, object_id = $1, $2.to_i
63 klass, object_id = $1, $2.to_i
64 method_name = "receive_#{klass}_reply"
64 method_name = "receive_#{klass}_reply"
65 if self.class.private_instance_methods.include?(method_name)
65 if self.class.private_instance_methods.include?(method_name)
66 send method_name, object_id
66 send method_name, object_id
67 else
67 else
68 # ignoring it
68 # ignoring it
69 end
69 end
70 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
70 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
71 receive_issue_reply(m[1].to_i)
71 receive_issue_reply(m[1].to_i)
72 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
72 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
73 receive_message_reply(m[1].to_i)
73 receive_message_reply(m[1].to_i)
74 else
74 else
75 receive_issue
75 receive_issue
76 end
76 end
77 rescue ActiveRecord::RecordInvalid => e
77 rescue ActiveRecord::RecordInvalid => e
78 # TODO: send a email to the user
78 # TODO: send a email to the user
79 logger.error e.message if logger
79 logger.error e.message if logger
80 false
80 false
81 rescue MissingInformation => e
81 rescue MissingInformation => e
82 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
82 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
83 false
83 false
84 rescue UnauthorizedAction => e
84 rescue UnauthorizedAction => e
85 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
85 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
86 false
86 false
87 end
87 end
88
88
89 # Creates a new issue
89 # Creates a new issue
90 def receive_issue
90 def receive_issue
91 project = target_project
91 project = target_project
92 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
92 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
93 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
93 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
94 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
94 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
95 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
95 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
96
96
97 # check permission
97 # check permission
98 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
98 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
99 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
99 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
100 # check workflow
100 # check workflow
101 if status && issue.new_statuses_allowed_to(user).include?(status)
101 if status && issue.new_statuses_allowed_to(user).include?(status)
102 issue.status = status
102 issue.status = status
103 end
103 end
104 issue.subject = email.subject.chomp.toutf8
104 issue.subject = email.subject.chomp.toutf8
105 issue.description = plain_text_body
105 issue.description = plain_text_body
106 # custom fields
106 # custom fields
107 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
107 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
108 if value = get_keyword(c.name, :override => true)
108 if value = get_keyword(c.name, :override => true)
109 h[c.id] = value
109 h[c.id] = value
110 end
110 end
111 h
111 h
112 end
112 end
113 issue.save!
113 issue.save!
114 add_attachments(issue)
114 add_attachments(issue)
115 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
115 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
116 # add To and Cc as watchers
116 # add To and Cc as watchers
117 add_watchers(issue)
117 add_watchers(issue)
118 # send notification after adding watchers so that they can reply to Redmine
118 # send notification after adding watchers so that they can reply to Redmine
119 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
119 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
120 issue
120 issue
121 end
121 end
122
122
123 def target_project
123 def target_project
124 # TODO: other ways to specify project:
124 # TODO: other ways to specify project:
125 # * parse the email To field
125 # * parse the email To field
126 # * specific project (eg. Setting.mail_handler_target_project)
126 # * specific project (eg. Setting.mail_handler_target_project)
127 target = Project.find_by_identifier(get_keyword(:project))
127 target = Project.find_by_identifier(get_keyword(:project))
128 raise MissingInformation.new('Unable to determine target project') if target.nil?
128 raise MissingInformation.new('Unable to determine target project') if target.nil?
129 target
129 target
130 end
130 end
131
131
132 # Adds a note to an existing issue
132 # Adds a note to an existing issue
133 def receive_issue_reply(issue_id)
133 def receive_issue_reply(issue_id)
134 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
134 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
135
135
136 issue = Issue.find_by_id(issue_id)
136 issue = Issue.find_by_id(issue_id)
137 return unless issue
137 return unless issue
138 # check permission
138 # check permission
139 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
139 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
140 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
140 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
141
141
142 # add the note
142 # add the note
143 journal = issue.init_journal(user, plain_text_body)
143 journal = issue.init_journal(user, plain_text_body)
144 add_attachments(issue)
144 add_attachments(issue)
145 # check workflow
145 # check workflow
146 if status && issue.new_statuses_allowed_to(user).include?(status)
146 if status && issue.new_statuses_allowed_to(user).include?(status)
147 issue.status = status
147 issue.status = status
148 end
148 end
149 issue.save!
149 issue.save!
150 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
150 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
151 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
151 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
152 journal
152 journal
153 end
153 end
154
154
155 # Reply will be added to the issue
155 # Reply will be added to the issue
156 def receive_journal_reply(journal_id)
156 def receive_journal_reply(journal_id)
157 journal = Journal.find_by_id(journal_id)
157 journal = Journal.find_by_id(journal_id)
158 if journal && journal.journalized_type == 'Issue'
158 if journal && journal.journalized_type == 'Issue'
159 receive_issue_reply(journal.journalized_id)
159 receive_issue_reply(journal.journalized_id)
160 end
160 end
161 end
161 end
162
162
163 # Receives a reply to a forum message
163 # Receives a reply to a forum message
164 def receive_message_reply(message_id)
164 def receive_message_reply(message_id)
165 message = Message.find_by_id(message_id)
165 message = Message.find_by_id(message_id)
166 if message
166 if message
167 message = message.root
167 message = message.root
168 if user.allowed_to?(:add_messages, message.project) && !message.locked?
168 if user.allowed_to?(:add_messages, message.project) && !message.locked?
169 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
169 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
170 :content => plain_text_body)
170 :content => plain_text_body)
171 reply.author = user
171 reply.author = user
172 reply.board = message.board
172 reply.board = message.board
173 message.children << reply
173 message.children << reply
174 add_attachments(reply)
174 add_attachments(reply)
175 reply
175 reply
176 else
176 else
177 raise UnauthorizedAction
177 raise UnauthorizedAction
178 end
178 end
179 end
179 end
180 end
180 end
181
181
182 def add_attachments(obj)
182 def add_attachments(obj)
183 if email.has_attachments?
183 if email.has_attachments?
184 email.attachments.each do |attachment|
184 email.attachments.each do |attachment|
185 Attachment.create(:container => obj,
185 Attachment.create(:container => obj,
186 :file => attachment,
186 :file => attachment,
187 :author => user,
187 :author => user,
188 :content_type => attachment.content_type)
188 :content_type => attachment.content_type)
189 end
189 end
190 end
190 end
191 end
191 end
192
192
193 # Adds To and Cc as watchers of the given object if the sender has the
193 # Adds To and Cc as watchers of the given object if the sender has the
194 # appropriate permission
194 # appropriate permission
195 def add_watchers(obj)
195 def add_watchers(obj)
196 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
196 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
197 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
197 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
198 unless addresses.empty?
198 unless addresses.empty?
199 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
199 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
200 watchers.each {|w| obj.add_watcher(w)}
200 watchers.each {|w| obj.add_watcher(w)}
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 def get_keyword(attr, options={})
205 def get_keyword(attr, options={})
206 @keywords ||= {}
206 @keywords ||= {}
207 if @keywords.has_key?(attr)
207 if @keywords.has_key?(attr)
208 @keywords[attr]
208 @keywords[attr]
209 else
209 else
210 @keywords[attr] = begin
210 @keywords[attr] = begin
211 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
211 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
212 $1.strip
212 $1.strip
213 elsif !@@handler_options[:issue][attr].blank?
213 elsif !@@handler_options[:issue][attr].blank?
214 @@handler_options[:issue][attr]
214 @@handler_options[:issue][attr]
215 end
215 end
216 end
216 end
217 end
217 end
218 end
218 end
219
219
220 # Returns the text/plain part of the email
220 # Returns the text/plain part of the email
221 # If not found (eg. HTML-only email), returns the body with tags removed
221 # If not found (eg. HTML-only email), returns the body with tags removed
222 def plain_text_body
222 def plain_text_body
223 return @plain_text_body unless @plain_text_body.nil?
223 return @plain_text_body unless @plain_text_body.nil?
224 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
224 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
225 if parts.empty?
225 if parts.empty?
226 parts << @email
226 parts << @email
227 end
227 end
228 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
228 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
229 if plain_text_part.nil?
229 if plain_text_part.nil?
230 # no text/plain part found, assuming html-only email
230 # no text/plain part found, assuming html-only email
231 # strip html tags and remove doctype directive
231 # strip html tags and remove doctype directive
232 @plain_text_body = strip_tags(@email.body.to_s)
232 @plain_text_body = strip_tags(@email.body.to_s)
233 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
233 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
234 else
234 else
235 @plain_text_body = plain_text_part.body.to_s
235 @plain_text_body = plain_text_part.body.to_s
236 end
236 end
237 @plain_text_body.strip!
237 @plain_text_body.strip!
238 @plain_text_body
238 @plain_text_body
239 end
239 end
240
240
241
241
242 def self.full_sanitizer
242 def self.full_sanitizer
243 @full_sanitizer ||= HTML::FullSanitizer.new
243 @full_sanitizer ||= HTML::FullSanitizer.new
244 end
244 end
245 end
245 end
@@ -1,185 +1,190
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailHandlerTest < Test::Unit::TestCase
20 class MailHandlerTest < Test::Unit::TestCase
21 fixtures :users, :projects,
21 fixtures :users, :projects,
22 :enabled_modules,
22 :enabled_modules,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :issues,
25 :issues,
26 :issue_statuses,
26 :issue_statuses,
27 :workflows,
27 :workflows,
28 :trackers,
28 :trackers,
29 :projects_trackers,
29 :projects_trackers,
30 :enumerations,
30 :enumerations,
31 :issue_categories,
31 :issue_categories,
32 :custom_fields,
32 :custom_fields,
33 :custom_fields_trackers,
33 :custom_fields_trackers,
34 :boards,
34 :boards,
35 :messages
35 :messages
36
36
37 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
37 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
38
38
39 def setup
39 def setup
40 ActionMailer::Base.deliveries.clear
40 ActionMailer::Base.deliveries.clear
41 end
41 end
42
42
43 def test_add_issue
43 def test_add_issue
44 # This email contains: 'Project: onlinestore'
44 # This email contains: 'Project: onlinestore'
45 issue = submit_email('ticket_on_given_project.eml')
45 issue = submit_email('ticket_on_given_project.eml')
46 assert issue.is_a?(Issue)
46 assert issue.is_a?(Issue)
47 assert !issue.new_record?
47 assert !issue.new_record?
48 issue.reload
48 issue.reload
49 assert_equal 'New ticket on a given project', issue.subject
49 assert_equal 'New ticket on a given project', issue.subject
50 assert_equal User.find_by_login('jsmith'), issue.author
50 assert_equal User.find_by_login('jsmith'), issue.author
51 assert_equal Project.find(2), issue.project
51 assert_equal Project.find(2), issue.project
52 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
52 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
53 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
53 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
54 # keywords should be removed from the email body
54 # keywords should be removed from the email body
55 assert !issue.description.match(/^Project:/i)
55 assert !issue.description.match(/^Project:/i)
56 assert !issue.description.match(/^Status:/i)
56 assert !issue.description.match(/^Status:/i)
57 end
57 end
58
58
59 def test_add_issue_with_status
59 def test_add_issue_with_status
60 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
60 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
61 issue = submit_email('ticket_on_given_project.eml')
61 issue = submit_email('ticket_on_given_project.eml')
62 assert issue.is_a?(Issue)
62 assert issue.is_a?(Issue)
63 assert !issue.new_record?
63 assert !issue.new_record?
64 issue.reload
64 issue.reload
65 assert_equal Project.find(2), issue.project
65 assert_equal Project.find(2), issue.project
66 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
66 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
67 end
67 end
68
68
69 def test_add_issue_with_attributes_override
69 def test_add_issue_with_attributes_override
70 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
70 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
71 assert issue.is_a?(Issue)
71 assert issue.is_a?(Issue)
72 assert !issue.new_record?
72 assert !issue.new_record?
73 issue.reload
73 issue.reload
74 assert_equal 'New ticket on a given project', issue.subject
74 assert_equal 'New ticket on a given project', issue.subject
75 assert_equal User.find_by_login('jsmith'), issue.author
75 assert_equal User.find_by_login('jsmith'), issue.author
76 assert_equal Project.find(2), issue.project
76 assert_equal Project.find(2), issue.project
77 assert_equal 'Feature request', issue.tracker.to_s
77 assert_equal 'Feature request', issue.tracker.to_s
78 assert_equal 'Stock management', issue.category.to_s
78 assert_equal 'Stock management', issue.category.to_s
79 assert_equal 'Urgent', issue.priority.to_s
79 assert_equal 'Urgent', issue.priority.to_s
80 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
80 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
81 end
81 end
82
82
83 def test_add_issue_with_partial_attributes_override
83 def test_add_issue_with_partial_attributes_override
84 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
84 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
85 assert issue.is_a?(Issue)
85 assert issue.is_a?(Issue)
86 assert !issue.new_record?
86 assert !issue.new_record?
87 issue.reload
87 issue.reload
88 assert_equal 'New ticket on a given project', issue.subject
88 assert_equal 'New ticket on a given project', issue.subject
89 assert_equal User.find_by_login('jsmith'), issue.author
89 assert_equal User.find_by_login('jsmith'), issue.author
90 assert_equal Project.find(2), issue.project
90 assert_equal Project.find(2), issue.project
91 assert_equal 'Feature request', issue.tracker.to_s
91 assert_equal 'Feature request', issue.tracker.to_s
92 assert_nil issue.category
92 assert_nil issue.category
93 assert_equal 'High', issue.priority.to_s
93 assert_equal 'High', issue.priority.to_s
94 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
94 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
95 end
95 end
96
96
97 def test_add_issue_with_attachment_to_specific_project
97 def test_add_issue_with_attachment_to_specific_project
98 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
98 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
99 assert issue.is_a?(Issue)
99 assert issue.is_a?(Issue)
100 assert !issue.new_record?
100 assert !issue.new_record?
101 issue.reload
101 issue.reload
102 assert_equal 'Ticket created by email with attachment', issue.subject
102 assert_equal 'Ticket created by email with attachment', issue.subject
103 assert_equal User.find_by_login('jsmith'), issue.author
103 assert_equal User.find_by_login('jsmith'), issue.author
104 assert_equal Project.find(2), issue.project
104 assert_equal Project.find(2), issue.project
105 assert_equal 'This is a new ticket with attachments', issue.description
105 assert_equal 'This is a new ticket with attachments', issue.description
106 # Attachment properties
106 # Attachment properties
107 assert_equal 1, issue.attachments.size
107 assert_equal 1, issue.attachments.size
108 assert_equal 'Paella.jpg', issue.attachments.first.filename
108 assert_equal 'Paella.jpg', issue.attachments.first.filename
109 assert_equal 'image/jpeg', issue.attachments.first.content_type
109 assert_equal 'image/jpeg', issue.attachments.first.content_type
110 assert_equal 10790, issue.attachments.first.filesize
110 assert_equal 10790, issue.attachments.first.filesize
111 end
111 end
112
112
113 def test_add_issue_with_custom_fields
113 def test_add_issue_with_custom_fields
114 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
114 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
115 assert issue.is_a?(Issue)
115 assert issue.is_a?(Issue)
116 assert !issue.new_record?
116 assert !issue.new_record?
117 issue.reload
117 issue.reload
118 assert_equal 'New ticket with custom field values', issue.subject
118 assert_equal 'New ticket with custom field values', issue.subject
119 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
119 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
120 assert !issue.description.match(/^searchable field:/i)
120 assert !issue.description.match(/^searchable field:/i)
121 end
121 end
122
122
123 def test_add_issue_with_cc
123 def test_add_issue_with_cc
124 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
124 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
125 assert issue.is_a?(Issue)
125 assert issue.is_a?(Issue)
126 assert !issue.new_record?
126 assert !issue.new_record?
127 issue.reload
127 issue.reload
128 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
128 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
129 assert_equal 1, issue.watchers.size
129 assert_equal 1, issue.watchers.size
130 end
130 end
131
131
132 def test_add_issue_without_from_header
133 Role.anonymous.add_permission!(:add_issues)
134 assert_equal false, submit_email('ticket_without_from_header.eml')
135 end
136
132 def test_add_issue_note
137 def test_add_issue_note
133 journal = submit_email('ticket_reply.eml')
138 journal = submit_email('ticket_reply.eml')
134 assert journal.is_a?(Journal)
139 assert journal.is_a?(Journal)
135 assert_equal User.find_by_login('jsmith'), journal.user
140 assert_equal User.find_by_login('jsmith'), journal.user
136 assert_equal Issue.find(2), journal.journalized
141 assert_equal Issue.find(2), journal.journalized
137 assert_match /This is reply/, journal.notes
142 assert_match /This is reply/, journal.notes
138 end
143 end
139
144
140 def test_add_issue_note_with_status_change
145 def test_add_issue_note_with_status_change
141 # This email contains: 'Status: Resolved'
146 # This email contains: 'Status: Resolved'
142 journal = submit_email('ticket_reply_with_status.eml')
147 journal = submit_email('ticket_reply_with_status.eml')
143 assert journal.is_a?(Journal)
148 assert journal.is_a?(Journal)
144 issue = Issue.find(journal.issue.id)
149 issue = Issue.find(journal.issue.id)
145 assert_equal User.find_by_login('jsmith'), journal.user
150 assert_equal User.find_by_login('jsmith'), journal.user
146 assert_equal Issue.find(2), journal.journalized
151 assert_equal Issue.find(2), journal.journalized
147 assert_match /This is reply/, journal.notes
152 assert_match /This is reply/, journal.notes
148 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
153 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
149 end
154 end
150
155
151 def test_reply_to_a_message
156 def test_reply_to_a_message
152 m = submit_email('message_reply.eml')
157 m = submit_email('message_reply.eml')
153 assert m.is_a?(Message)
158 assert m.is_a?(Message)
154 assert !m.new_record?
159 assert !m.new_record?
155 m.reload
160 m.reload
156 assert_equal 'Reply via email', m.subject
161 assert_equal 'Reply via email', m.subject
157 # The email replies to message #2 which is part of the thread of message #1
162 # The email replies to message #2 which is part of the thread of message #1
158 assert_equal Message.find(1), m.parent
163 assert_equal Message.find(1), m.parent
159 end
164 end
160
165
161 def test_reply_to_a_message_by_subject
166 def test_reply_to_a_message_by_subject
162 m = submit_email('message_reply_by_subject.eml')
167 m = submit_email('message_reply_by_subject.eml')
163 assert m.is_a?(Message)
168 assert m.is_a?(Message)
164 assert !m.new_record?
169 assert !m.new_record?
165 m.reload
170 m.reload
166 assert_equal 'Reply to the first post', m.subject
171 assert_equal 'Reply to the first post', m.subject
167 assert_equal Message.find(1), m.parent
172 assert_equal Message.find(1), m.parent
168 end
173 end
169
174
170 def test_should_strip_tags_of_html_only_emails
175 def test_should_strip_tags_of_html_only_emails
171 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
176 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
172 assert issue.is_a?(Issue)
177 assert issue.is_a?(Issue)
173 assert !issue.new_record?
178 assert !issue.new_record?
174 issue.reload
179 issue.reload
175 assert_equal 'HTML email', issue.subject
180 assert_equal 'HTML email', issue.subject
176 assert_equal 'This is a html-only email.', issue.description
181 assert_equal 'This is a html-only email.', issue.description
177 end
182 end
178
183
179 private
184 private
180
185
181 def submit_email(filename, options={})
186 def submit_email(filename, options={})
182 raw = IO.read(File.join(FIXTURES_PATH, filename))
187 raw = IO.read(File.join(FIXTURES_PATH, filename))
183 MailHandler.receive(raw, options)
188 MailHandler.receive(raw, options)
184 end
189 end
185 end
190 end
General Comments 0
You need to be logged in to leave comments. Login now