##// END OF EJS Templates
Allow email to reply to a forum message (#1616)....
Jean-Philippe Lang -
r2287:b9e3fbcd8376
parent child
Show More
@@ -0,0 +1,15
1 Message-ID: <4974C93E.3070005@somenet.foo>
2 Date: Mon, 19 Jan 2009 19:41:02 +0100
3 From: "John Smith" <jsmith@somenet.foo>
4 User-Agent: Thunderbird 2.0.0.19 (Windows/20081209)
5 MIME-Version: 1.0
6 To: redmine@somenet.foo
7 Subject: Reply via email
8 References: <redmine.message-2.20070512171800@somenet.foo>
9 In-Reply-To: <redmine.message-2.20070512171800@somenet.foo>
10 Content-Type: text/plain; charset=UTF-8; format=flowed
11 Content-Transfer-Encoding: 7bit
12
13 This is a reply to a forum message.
14
15
@@ -1,211 +1,229
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
58
59 def dispatch
59 def dispatch
60 headers = [email.in_reply_to, email.references].flatten.compact
60 headers = [email.in_reply_to, email.references].flatten.compact
61 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
61 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
62 klass, object_id = $1, $2.to_i
62 klass, object_id = $1, $2.to_i
63 method_name = "receive_#{klass}_reply"
63 method_name = "receive_#{klass}_reply"
64 if self.class.private_instance_methods.include?(method_name)
64 if self.class.private_instance_methods.include?(method_name)
65 send method_name, object_id
65 send method_name, object_id
66 else
66 else
67 # ignoring it
67 # ignoring it
68 end
68 end
69 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
69 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
70 # for compatibility
70 # for compatibility
71 receive_issue_reply(m[1].to_i)
71 receive_issue_reply(m[1].to_i)
72 else
72 else
73 receive_issue
73 receive_issue
74 end
74 end
75 rescue ActiveRecord::RecordInvalid => e
75 rescue ActiveRecord::RecordInvalid => e
76 # TODO: send a email to the user
76 # TODO: send a email to the user
77 logger.error e.message if logger
77 logger.error e.message if logger
78 false
78 false
79 rescue MissingInformation => e
79 rescue MissingInformation => e
80 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
80 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
81 false
81 false
82 rescue UnauthorizedAction => e
82 rescue UnauthorizedAction => e
83 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
83 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
84 false
84 false
85 end
85 end
86
86
87 # Creates a new issue
87 # Creates a new issue
88 def receive_issue
88 def receive_issue
89 project = target_project
89 project = target_project
90 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
90 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
91 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
91 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
92 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
92 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
93 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
93 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
94
94
95 # check permission
95 # check permission
96 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
96 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
97 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
97 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
98 # check workflow
98 # check workflow
99 if status && issue.new_statuses_allowed_to(user).include?(status)
99 if status && issue.new_statuses_allowed_to(user).include?(status)
100 issue.status = status
100 issue.status = status
101 end
101 end
102 issue.subject = email.subject.chomp.toutf8
102 issue.subject = email.subject.chomp.toutf8
103 issue.description = plain_text_body
103 issue.description = plain_text_body
104 # custom fields
104 # custom fields
105 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
105 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
106 if value = get_keyword(c.name, :override => true)
106 if value = get_keyword(c.name, :override => true)
107 h[c.id] = value
107 h[c.id] = value
108 end
108 end
109 h
109 h
110 end
110 end
111 issue.save!
111 issue.save!
112 add_attachments(issue)
112 add_attachments(issue)
113 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
113 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
114 # add To and Cc as watchers
114 # add To and Cc as watchers
115 add_watchers(issue)
115 add_watchers(issue)
116 # send notification after adding watchers so that they can reply to Redmine
116 # send notification after adding watchers so that they can reply to Redmine
117 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
117 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
118 issue
118 issue
119 end
119 end
120
120
121 def target_project
121 def target_project
122 # TODO: other ways to specify project:
122 # TODO: other ways to specify project:
123 # * parse the email To field
123 # * parse the email To field
124 # * specific project (eg. Setting.mail_handler_target_project)
124 # * specific project (eg. Setting.mail_handler_target_project)
125 target = Project.find_by_identifier(get_keyword(:project))
125 target = Project.find_by_identifier(get_keyword(:project))
126 raise MissingInformation.new('Unable to determine target project') if target.nil?
126 raise MissingInformation.new('Unable to determine target project') if target.nil?
127 target
127 target
128 end
128 end
129
129
130 # Adds a note to an existing issue
130 # Adds a note to an existing issue
131 def receive_issue_reply(issue_id)
131 def receive_issue_reply(issue_id)
132 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
132 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
133
133
134 issue = Issue.find_by_id(issue_id)
134 issue = Issue.find_by_id(issue_id)
135 return unless issue
135 return unless issue
136 # check permission
136 # check permission
137 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
137 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
138 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
138 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
139
139
140 # add the note
140 # add the note
141 journal = issue.init_journal(user, plain_text_body)
141 journal = issue.init_journal(user, plain_text_body)
142 add_attachments(issue)
142 add_attachments(issue)
143 # check workflow
143 # check workflow
144 if status && issue.new_statuses_allowed_to(user).include?(status)
144 if status && issue.new_statuses_allowed_to(user).include?(status)
145 issue.status = status
145 issue.status = status
146 end
146 end
147 issue.save!
147 issue.save!
148 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
148 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
149 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
149 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
150 journal
150 journal
151 end
151 end
152
152
153 # Reply will be added to the issue
153 # Reply will be added to the issue
154 def receive_journal_reply(journal_id)
154 def receive_journal_reply(journal_id)
155 journal = Journal.find_by_id(journal_id)
155 journal = Journal.find_by_id(journal_id)
156 if journal && journal.journalized_type == 'Issue'
156 if journal && journal.journalized_type == 'Issue'
157 receive_issue_reply(journal.journalized_id)
157 receive_issue_reply(journal.journalized_id)
158 end
158 end
159 end
159 end
160
160
161 # Receives a reply to a forum message
162 def receive_message_reply(message_id)
163 message = Message.find_by_id(message_id)
164 if message
165 message = message.root
166 if user.allowed_to?(:add_messages, message.project) && !message.locked?
167 reply = Message.new(:subject => email.subject, :content => plain_text_body)
168 reply.author = user
169 reply.board = message.board
170 message.children << reply
171 add_attachments(reply)
172 reply
173 else
174 raise UnauthorizedAction
175 end
176 end
177 end
178
161 def add_attachments(obj)
179 def add_attachments(obj)
162 if email.has_attachments?
180 if email.has_attachments?
163 email.attachments.each do |attachment|
181 email.attachments.each do |attachment|
164 Attachment.create(:container => obj,
182 Attachment.create(:container => obj,
165 :file => attachment,
183 :file => attachment,
166 :author => user,
184 :author => user,
167 :content_type => attachment.content_type)
185 :content_type => attachment.content_type)
168 end
186 end
169 end
187 end
170 end
188 end
171
189
172 # Adds To and Cc as watchers of the given object if the sender has the
190 # Adds To and Cc as watchers of the given object if the sender has the
173 # appropriate permission
191 # appropriate permission
174 def add_watchers(obj)
192 def add_watchers(obj)
175 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
193 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
176 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
194 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
177 unless addresses.empty?
195 unless addresses.empty?
178 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
196 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
179 watchers.each {|w| obj.add_watcher(w)}
197 watchers.each {|w| obj.add_watcher(w)}
180 end
198 end
181 end
199 end
182 end
200 end
183
201
184 def get_keyword(attr, options={})
202 def get_keyword(attr, options={})
185 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
203 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
186 $1.strip
204 $1.strip
187 elsif !@@handler_options[:issue][attr].blank?
205 elsif !@@handler_options[:issue][attr].blank?
188 @@handler_options[:issue][attr]
206 @@handler_options[:issue][attr]
189 end
207 end
190 end
208 end
191
209
192 # Returns the text/plain part of the email
210 # Returns the text/plain part of the email
193 # If not found (eg. HTML-only email), returns the body with tags removed
211 # If not found (eg. HTML-only email), returns the body with tags removed
194 def plain_text_body
212 def plain_text_body
195 return @plain_text_body unless @plain_text_body.nil?
213 return @plain_text_body unless @plain_text_body.nil?
196 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
214 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
197 if parts.empty?
215 if parts.empty?
198 parts << @email
216 parts << @email
199 end
217 end
200 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
218 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
201 if plain_text_part.nil?
219 if plain_text_part.nil?
202 # no text/plain part found, assuming html-only email
220 # no text/plain part found, assuming html-only email
203 # strip html tags and remove doctype directive
221 # strip html tags and remove doctype directive
204 @plain_text_body = strip_tags(@email.body.to_s)
222 @plain_text_body = strip_tags(@email.body.to_s)
205 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
223 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
206 else
224 else
207 @plain_text_body = plain_text_part.body.to_s
225 @plain_text_body = plain_text_part.body.to_s
208 end
226 end
209 @plain_text_body.strip!
227 @plain_text_body.strip!
210 end
228 end
211 end
229 end
@@ -1,159 +1,171
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,
35 :messages
34
36
35 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
37 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
36
38
37 def setup
39 def setup
38 ActionMailer::Base.deliveries.clear
40 ActionMailer::Base.deliveries.clear
39 end
41 end
40
42
41 def test_add_issue
43 def test_add_issue
42 # This email contains: 'Project: onlinestore'
44 # This email contains: 'Project: onlinestore'
43 issue = submit_email('ticket_on_given_project.eml')
45 issue = submit_email('ticket_on_given_project.eml')
44 assert issue.is_a?(Issue)
46 assert issue.is_a?(Issue)
45 assert !issue.new_record?
47 assert !issue.new_record?
46 issue.reload
48 issue.reload
47 assert_equal 'New ticket on a given project', issue.subject
49 assert_equal 'New ticket on a given project', issue.subject
48 assert_equal User.find_by_login('jsmith'), issue.author
50 assert_equal User.find_by_login('jsmith'), issue.author
49 assert_equal Project.find(2), issue.project
51 assert_equal Project.find(2), issue.project
50 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
52 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
51 end
53 end
52
54
53 def test_add_issue_with_status
55 def test_add_issue_with_status
54 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
56 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
55 issue = submit_email('ticket_on_given_project.eml')
57 issue = submit_email('ticket_on_given_project.eml')
56 assert issue.is_a?(Issue)
58 assert issue.is_a?(Issue)
57 assert !issue.new_record?
59 assert !issue.new_record?
58 issue.reload
60 issue.reload
59 assert_equal Project.find(2), issue.project
61 assert_equal Project.find(2), issue.project
60 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
62 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
61 end
63 end
62
64
63 def test_add_issue_with_attributes_override
65 def test_add_issue_with_attributes_override
64 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
66 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
65 assert issue.is_a?(Issue)
67 assert issue.is_a?(Issue)
66 assert !issue.new_record?
68 assert !issue.new_record?
67 issue.reload
69 issue.reload
68 assert_equal 'New ticket on a given project', issue.subject
70 assert_equal 'New ticket on a given project', issue.subject
69 assert_equal User.find_by_login('jsmith'), issue.author
71 assert_equal User.find_by_login('jsmith'), issue.author
70 assert_equal Project.find(2), issue.project
72 assert_equal Project.find(2), issue.project
71 assert_equal 'Feature request', issue.tracker.to_s
73 assert_equal 'Feature request', issue.tracker.to_s
72 assert_equal 'Stock management', issue.category.to_s
74 assert_equal 'Stock management', issue.category.to_s
73 assert_equal 'Urgent', issue.priority.to_s
75 assert_equal 'Urgent', issue.priority.to_s
74 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
76 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
75 end
77 end
76
78
77 def test_add_issue_with_partial_attributes_override
79 def test_add_issue_with_partial_attributes_override
78 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
80 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
79 assert issue.is_a?(Issue)
81 assert issue.is_a?(Issue)
80 assert !issue.new_record?
82 assert !issue.new_record?
81 issue.reload
83 issue.reload
82 assert_equal 'New ticket on a given project', issue.subject
84 assert_equal 'New ticket on a given project', issue.subject
83 assert_equal User.find_by_login('jsmith'), issue.author
85 assert_equal User.find_by_login('jsmith'), issue.author
84 assert_equal Project.find(2), issue.project
86 assert_equal Project.find(2), issue.project
85 assert_equal 'Feature request', issue.tracker.to_s
87 assert_equal 'Feature request', issue.tracker.to_s
86 assert_nil issue.category
88 assert_nil issue.category
87 assert_equal 'High', issue.priority.to_s
89 assert_equal 'High', issue.priority.to_s
88 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
90 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
89 end
91 end
90
92
91 def test_add_issue_with_attachment_to_specific_project
93 def test_add_issue_with_attachment_to_specific_project
92 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
94 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
93 assert issue.is_a?(Issue)
95 assert issue.is_a?(Issue)
94 assert !issue.new_record?
96 assert !issue.new_record?
95 issue.reload
97 issue.reload
96 assert_equal 'Ticket created by email with attachment', issue.subject
98 assert_equal 'Ticket created by email with attachment', issue.subject
97 assert_equal User.find_by_login('jsmith'), issue.author
99 assert_equal User.find_by_login('jsmith'), issue.author
98 assert_equal Project.find(2), issue.project
100 assert_equal Project.find(2), issue.project
99 assert_equal 'This is a new ticket with attachments', issue.description
101 assert_equal 'This is a new ticket with attachments', issue.description
100 # Attachment properties
102 # Attachment properties
101 assert_equal 1, issue.attachments.size
103 assert_equal 1, issue.attachments.size
102 assert_equal 'Paella.jpg', issue.attachments.first.filename
104 assert_equal 'Paella.jpg', issue.attachments.first.filename
103 assert_equal 'image/jpeg', issue.attachments.first.content_type
105 assert_equal 'image/jpeg', issue.attachments.first.content_type
104 assert_equal 10790, issue.attachments.first.filesize
106 assert_equal 10790, issue.attachments.first.filesize
105 end
107 end
106
108
107 def test_add_issue_with_custom_fields
109 def test_add_issue_with_custom_fields
108 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
110 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
109 assert issue.is_a?(Issue)
111 assert issue.is_a?(Issue)
110 assert !issue.new_record?
112 assert !issue.new_record?
111 issue.reload
113 issue.reload
112 assert_equal 'New ticket with custom field values', issue.subject
114 assert_equal 'New ticket with custom field values', issue.subject
113 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
115 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
114 end
116 end
115
117
116 def test_add_issue_with_cc
118 def test_add_issue_with_cc
117 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
119 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
118 assert issue.is_a?(Issue)
120 assert issue.is_a?(Issue)
119 assert !issue.new_record?
121 assert !issue.new_record?
120 issue.reload
122 issue.reload
121 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
123 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
122 assert_equal 1, issue.watchers.size
124 assert_equal 1, issue.watchers.size
123 end
125 end
124
126
125 def test_add_issue_note
127 def test_add_issue_note
126 journal = submit_email('ticket_reply.eml')
128 journal = submit_email('ticket_reply.eml')
127 assert journal.is_a?(Journal)
129 assert journal.is_a?(Journal)
128 assert_equal User.find_by_login('jsmith'), journal.user
130 assert_equal User.find_by_login('jsmith'), journal.user
129 assert_equal Issue.find(2), journal.journalized
131 assert_equal Issue.find(2), journal.journalized
130 assert_match /This is reply/, journal.notes
132 assert_match /This is reply/, journal.notes
131 end
133 end
132
134
133 def test_add_issue_note_with_status_change
135 def test_add_issue_note_with_status_change
134 # This email contains: 'Status: Resolved'
136 # This email contains: 'Status: Resolved'
135 journal = submit_email('ticket_reply_with_status.eml')
137 journal = submit_email('ticket_reply_with_status.eml')
136 assert journal.is_a?(Journal)
138 assert journal.is_a?(Journal)
137 issue = Issue.find(journal.issue.id)
139 issue = Issue.find(journal.issue.id)
138 assert_equal User.find_by_login('jsmith'), journal.user
140 assert_equal User.find_by_login('jsmith'), journal.user
139 assert_equal Issue.find(2), journal.journalized
141 assert_equal Issue.find(2), journal.journalized
140 assert_match /This is reply/, journal.notes
142 assert_match /This is reply/, journal.notes
141 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
143 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
142 end
144 end
143
145
146 def test_reply_to_a_message
147 m = submit_email('message_reply.eml')
148 assert m.is_a?(Message)
149 assert !m.new_record?
150 m.reload
151 assert_equal 'Reply via email', m.subject
152 # The email replies to message #2 which is part of the thread of message #1
153 assert_equal Message.find(1), m.parent
154 end
155
144 def test_should_strip_tags_of_html_only_emails
156 def test_should_strip_tags_of_html_only_emails
145 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
157 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
146 assert issue.is_a?(Issue)
158 assert issue.is_a?(Issue)
147 assert !issue.new_record?
159 assert !issue.new_record?
148 issue.reload
160 issue.reload
149 assert_equal 'HTML email', issue.subject
161 assert_equal 'HTML email', issue.subject
150 assert_equal 'This is a html-only email.', issue.description
162 assert_equal 'This is a html-only email.', issue.description
151 end
163 end
152
164
153 private
165 private
154
166
155 def submit_email(filename, options={})
167 def submit_email(filename, options={})
156 raw = IO.read(File.join(FIXTURES_PATH, filename))
168 raw = IO.read(File.join(FIXTURES_PATH, filename))
157 MailHandler.receive(raw, options)
169 MailHandler.receive(raw, options)
158 end
170 end
159 end
171 end
General Comments 0
You need to be logged in to leave comments. Login now