##// END OF EJS Templates
Strip keywords from received email body (#2436)....
Jean-Philippe Lang -
r2365:2d3b3cee156c
parent child
Show More
@@ -1,232 +1,239
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.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 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
206 @keywords ||= {}
207 $1.strip
207 if @keywords.has_key?(attr)
208 elsif !@@handler_options[:issue][attr].blank?
208 @keywords[attr]
209 @@handler_options[:issue][attr]
209 else
210 @keywords[attr] = begin
211 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
212 $1.strip
213 elsif !@@handler_options[:issue][attr].blank?
214 @@handler_options[:issue][attr]
215 end
216 end
210 end
217 end
211 end
218 end
212
219
213 # Returns the text/plain part of the email
220 # Returns the text/plain part of the email
214 # 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
215 def plain_text_body
222 def plain_text_body
216 return @plain_text_body unless @plain_text_body.nil?
223 return @plain_text_body unless @plain_text_body.nil?
217 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
218 if parts.empty?
225 if parts.empty?
219 parts << @email
226 parts << @email
220 end
227 end
221 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
228 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
222 if plain_text_part.nil?
229 if plain_text_part.nil?
223 # no text/plain part found, assuming html-only email
230 # no text/plain part found, assuming html-only email
224 # strip html tags and remove doctype directive
231 # strip html tags and remove doctype directive
225 @plain_text_body = strip_tags(@email.body.to_s)
232 @plain_text_body = strip_tags(@email.body.to_s)
226 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
233 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
227 else
234 else
228 @plain_text_body = plain_text_part.body.to_s
235 @plain_text_body = plain_text_part.body.to_s
229 end
236 end
230 @plain_text_body.strip!
237 @plain_text_body.strip!
231 end
238 end
232 end
239 end
@@ -1,180 +1,185
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 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
55 assert !issue.description.match(/^Project:/i)
56 assert !issue.description.match(/^Status:/i)
53 end
57 end
54
58
55 def test_add_issue_with_status
59 def test_add_issue_with_status
56 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
60 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
57 issue = submit_email('ticket_on_given_project.eml')
61 issue = submit_email('ticket_on_given_project.eml')
58 assert issue.is_a?(Issue)
62 assert issue.is_a?(Issue)
59 assert !issue.new_record?
63 assert !issue.new_record?
60 issue.reload
64 issue.reload
61 assert_equal Project.find(2), issue.project
65 assert_equal Project.find(2), issue.project
62 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
66 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
63 end
67 end
64
68
65 def test_add_issue_with_attributes_override
69 def test_add_issue_with_attributes_override
66 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')
67 assert issue.is_a?(Issue)
71 assert issue.is_a?(Issue)
68 assert !issue.new_record?
72 assert !issue.new_record?
69 issue.reload
73 issue.reload
70 assert_equal 'New ticket on a given project', issue.subject
74 assert_equal 'New ticket on a given project', issue.subject
71 assert_equal User.find_by_login('jsmith'), issue.author
75 assert_equal User.find_by_login('jsmith'), issue.author
72 assert_equal Project.find(2), issue.project
76 assert_equal Project.find(2), issue.project
73 assert_equal 'Feature request', issue.tracker.to_s
77 assert_equal 'Feature request', issue.tracker.to_s
74 assert_equal 'Stock management', issue.category.to_s
78 assert_equal 'Stock management', issue.category.to_s
75 assert_equal 'Urgent', issue.priority.to_s
79 assert_equal 'Urgent', issue.priority.to_s
76 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.')
77 end
81 end
78
82
79 def test_add_issue_with_partial_attributes_override
83 def test_add_issue_with_partial_attributes_override
80 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'])
81 assert issue.is_a?(Issue)
85 assert issue.is_a?(Issue)
82 assert !issue.new_record?
86 assert !issue.new_record?
83 issue.reload
87 issue.reload
84 assert_equal 'New ticket on a given project', issue.subject
88 assert_equal 'New ticket on a given project', issue.subject
85 assert_equal User.find_by_login('jsmith'), issue.author
89 assert_equal User.find_by_login('jsmith'), issue.author
86 assert_equal Project.find(2), issue.project
90 assert_equal Project.find(2), issue.project
87 assert_equal 'Feature request', issue.tracker.to_s
91 assert_equal 'Feature request', issue.tracker.to_s
88 assert_nil issue.category
92 assert_nil issue.category
89 assert_equal 'High', issue.priority.to_s
93 assert_equal 'High', issue.priority.to_s
90 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.')
91 end
95 end
92
96
93 def test_add_issue_with_attachment_to_specific_project
97 def test_add_issue_with_attachment_to_specific_project
94 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
98 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
95 assert issue.is_a?(Issue)
99 assert issue.is_a?(Issue)
96 assert !issue.new_record?
100 assert !issue.new_record?
97 issue.reload
101 issue.reload
98 assert_equal 'Ticket created by email with attachment', issue.subject
102 assert_equal 'Ticket created by email with attachment', issue.subject
99 assert_equal User.find_by_login('jsmith'), issue.author
103 assert_equal User.find_by_login('jsmith'), issue.author
100 assert_equal Project.find(2), issue.project
104 assert_equal Project.find(2), issue.project
101 assert_equal 'This is a new ticket with attachments', issue.description
105 assert_equal 'This is a new ticket with attachments', issue.description
102 # Attachment properties
106 # Attachment properties
103 assert_equal 1, issue.attachments.size
107 assert_equal 1, issue.attachments.size
104 assert_equal 'Paella.jpg', issue.attachments.first.filename
108 assert_equal 'Paella.jpg', issue.attachments.first.filename
105 assert_equal 'image/jpeg', issue.attachments.first.content_type
109 assert_equal 'image/jpeg', issue.attachments.first.content_type
106 assert_equal 10790, issue.attachments.first.filesize
110 assert_equal 10790, issue.attachments.first.filesize
107 end
111 end
108
112
109 def test_add_issue_with_custom_fields
113 def test_add_issue_with_custom_fields
110 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
114 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
111 assert issue.is_a?(Issue)
115 assert issue.is_a?(Issue)
112 assert !issue.new_record?
116 assert !issue.new_record?
113 issue.reload
117 issue.reload
114 assert_equal 'New ticket with custom field values', issue.subject
118 assert_equal 'New ticket with custom field values', issue.subject
115 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)
116 end
121 end
117
122
118 def test_add_issue_with_cc
123 def test_add_issue_with_cc
119 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
124 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
120 assert issue.is_a?(Issue)
125 assert issue.is_a?(Issue)
121 assert !issue.new_record?
126 assert !issue.new_record?
122 issue.reload
127 issue.reload
123 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
128 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
124 assert_equal 1, issue.watchers.size
129 assert_equal 1, issue.watchers.size
125 end
130 end
126
131
127 def test_add_issue_note
132 def test_add_issue_note
128 journal = submit_email('ticket_reply.eml')
133 journal = submit_email('ticket_reply.eml')
129 assert journal.is_a?(Journal)
134 assert journal.is_a?(Journal)
130 assert_equal User.find_by_login('jsmith'), journal.user
135 assert_equal User.find_by_login('jsmith'), journal.user
131 assert_equal Issue.find(2), journal.journalized
136 assert_equal Issue.find(2), journal.journalized
132 assert_match /This is reply/, journal.notes
137 assert_match /This is reply/, journal.notes
133 end
138 end
134
139
135 def test_add_issue_note_with_status_change
140 def test_add_issue_note_with_status_change
136 # This email contains: 'Status: Resolved'
141 # This email contains: 'Status: Resolved'
137 journal = submit_email('ticket_reply_with_status.eml')
142 journal = submit_email('ticket_reply_with_status.eml')
138 assert journal.is_a?(Journal)
143 assert journal.is_a?(Journal)
139 issue = Issue.find(journal.issue.id)
144 issue = Issue.find(journal.issue.id)
140 assert_equal User.find_by_login('jsmith'), journal.user
145 assert_equal User.find_by_login('jsmith'), journal.user
141 assert_equal Issue.find(2), journal.journalized
146 assert_equal Issue.find(2), journal.journalized
142 assert_match /This is reply/, journal.notes
147 assert_match /This is reply/, journal.notes
143 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
148 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
144 end
149 end
145
150
146 def test_reply_to_a_message
151 def test_reply_to_a_message
147 m = submit_email('message_reply.eml')
152 m = submit_email('message_reply.eml')
148 assert m.is_a?(Message)
153 assert m.is_a?(Message)
149 assert !m.new_record?
154 assert !m.new_record?
150 m.reload
155 m.reload
151 assert_equal 'Reply via email', m.subject
156 assert_equal 'Reply via email', m.subject
152 # The email replies to message #2 which is part of the thread of message #1
157 # The email replies to message #2 which is part of the thread of message #1
153 assert_equal Message.find(1), m.parent
158 assert_equal Message.find(1), m.parent
154 end
159 end
155
160
156 def test_reply_to_a_message_by_subject
161 def test_reply_to_a_message_by_subject
157 m = submit_email('message_reply_by_subject.eml')
162 m = submit_email('message_reply_by_subject.eml')
158 assert m.is_a?(Message)
163 assert m.is_a?(Message)
159 assert !m.new_record?
164 assert !m.new_record?
160 m.reload
165 m.reload
161 assert_equal 'Reply to the first post', m.subject
166 assert_equal 'Reply to the first post', m.subject
162 assert_equal Message.find(1), m.parent
167 assert_equal Message.find(1), m.parent
163 end
168 end
164
169
165 def test_should_strip_tags_of_html_only_emails
170 def test_should_strip_tags_of_html_only_emails
166 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
171 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
167 assert issue.is_a?(Issue)
172 assert issue.is_a?(Issue)
168 assert !issue.new_record?
173 assert !issue.new_record?
169 issue.reload
174 issue.reload
170 assert_equal 'HTML email', issue.subject
175 assert_equal 'HTML email', issue.subject
171 assert_equal 'This is a html-only email.', issue.description
176 assert_equal 'This is a html-only email.', issue.description
172 end
177 end
173
178
174 private
179 private
175
180
176 def submit_email(filename, options={})
181 def submit_email(filename, options={})
177 raw = IO.read(File.join(FIXTURES_PATH, filename))
182 raw = IO.read(File.join(FIXTURES_PATH, filename))
178 MailHandler.receive(raw, options)
183 MailHandler.receive(raw, options)
179 end
184 end
180 end
185 end
General Comments 0
You need to be logged in to leave comments. Login now