##// END OF EJS Templates
Mail handler: more control over issue attributes (#1110)....
Jean-Philippe Lang -
r1629:40efaae6d5a2
parent child
Show More
@@ -0,0 +1,43
1 Return-Path: <jsmith@somenet.foo>
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 From: "John Smith" <jsmith@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket on a given project
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
11 Content-Type: text/plain;
12 format=flowed;
13 charset="iso-8859-1";
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
29 sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
31 platea dictumst.
32
33 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
34 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
35 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
36 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
37 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
38 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
39
40 Project: onlinestore
41 Tracker: Feature request
42 category: Stock management
43 priority: Urgent
@@ -1,134 +1,145
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
19
20 class UnauthorizedAction < StandardError; end
20 class UnauthorizedAction < StandardError; end
21 class MissingInformation < StandardError; end
21 class MissingInformation < StandardError; end
22
22
23 attr_reader :email, :user
23 attr_reader :email, :user
24
24
25 def self.receive(email, options={})
25 def self.receive(email, options={})
26 @@handler_options = options
26 @@handler_options = options.dup
27
28 @@handler_options[:issue] ||= {}
29
30 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] ||= []
32 # Project needs to be overridable if not specified
33 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
27 super email
34 super email
28 end
35 end
29
36
30 # Processes incoming emails
37 # Processes incoming emails
31 def receive(email)
38 def receive(email)
32 @email = email
39 @email = email
33 @user = User.find_active(:first, :conditions => {:mail => email.from.first})
40 @user = User.find_active(:first, :conditions => {:mail => email.from.first})
34 unless @user
41 unless @user
35 # Unknown user => the email is ignored
42 # Unknown user => the email is ignored
36 # TODO: ability to create the user's account
43 # TODO: ability to create the user's account
37 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
44 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
38 return false
45 return false
39 end
46 end
40 User.current = @user
47 User.current = @user
41 dispatch
48 dispatch
42 end
49 end
43
50
44 private
51 private
45
52
46 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
53 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
47
54
48 def dispatch
55 def dispatch
49 if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
56 if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
50 receive_issue_update(m[1].to_i)
57 receive_issue_update(m[1].to_i)
51 else
58 else
52 receive_issue
59 receive_issue
53 end
60 end
54 rescue ActiveRecord::RecordInvalid => e
61 rescue ActiveRecord::RecordInvalid => e
55 # TODO: send a email to the user
62 # TODO: send a email to the user
56 logger.error e.message if logger
63 logger.error e.message if logger
57 false
64 false
58 rescue MissingInformation => e
65 rescue MissingInformation => e
59 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
66 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
60 false
67 false
61 rescue UnauthorizedAction => e
68 rescue UnauthorizedAction => e
62 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
69 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
63 false
70 false
64 end
71 end
65
72
66 # Creates a new issue
73 # Creates a new issue
67 def receive_issue
74 def receive_issue
68 project = target_project
75 project = target_project
69 # TODO: make the tracker configurable
76 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
70 tracker = project.trackers.find(:first)
77 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
78 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
79
71 # check permission
80 # check permission
72 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
81 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
73 issue = Issue.new(:author => user, :project => project, :tracker => tracker)
82 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
74 issue.subject = email.subject.chomp
83 issue.subject = email.subject.chomp
75 issue.description = email.plain_text_body.chomp
84 issue.description = email.plain_text_body.chomp
76 issue.save!
85 issue.save!
77 add_attachments(issue)
86 add_attachments(issue)
78 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
87 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
79 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
88 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
80 issue
89 issue
81 end
90 end
82
91
83 def target_project
92 def target_project
84 # TODO: other ways to specify project:
93 # TODO: other ways to specify project:
85 # * parse the email To field
94 # * parse the email To field
86 # * specific project (eg. Setting.mail_handler_target_project)
95 # * specific project (eg. Setting.mail_handler_target_project)
87 identifier = if !@@handler_options[:project].blank?
96 target = Project.find_by_identifier(get_keyword(:project))
88 @@handler_options[:project]
89 elsif email.plain_text_body =~ %r{^Project:[ \t]*(.+)$}i
90 $1
91 end
92
93 target = Project.find_by_identifier(identifier.to_s)
94 raise MissingInformation.new('Unable to determine target project') if target.nil?
97 raise MissingInformation.new('Unable to determine target project') if target.nil?
95 target
98 target
96 end
99 end
97
100
98 # Adds a note to an existing issue
101 # Adds a note to an existing issue
99 def receive_issue_update(issue_id)
102 def receive_issue_update(issue_id)
100 issue = Issue.find_by_id(issue_id)
103 issue = Issue.find_by_id(issue_id)
101 return unless issue
104 return unless issue
102 # check permission
105 # check permission
103 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
106 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
104 # add the note
107 # add the note
105 journal = issue.init_journal(user, email.plain_text_body.chomp)
108 journal = issue.init_journal(user, email.plain_text_body.chomp)
106 add_attachments(issue)
109 add_attachments(issue)
107 issue.save!
110 issue.save!
108 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
111 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
112 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
110 journal
113 journal
111 end
114 end
112
115
113 def add_attachments(obj)
116 def add_attachments(obj)
114 if email.has_attachments?
117 if email.has_attachments?
115 email.attachments.each do |attachment|
118 email.attachments.each do |attachment|
116 Attachment.create(:container => obj,
119 Attachment.create(:container => obj,
117 :file => attachment,
120 :file => attachment,
118 :author => user,
121 :author => user,
119 :content_type => attachment.content_type)
122 :content_type => attachment.content_type)
120 end
123 end
121 end
124 end
122 end
125 end
126
127 def get_keyword(attr)
128 if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
129 $1.strip
130 elsif !@@handler_options[:issue][attr].blank?
131 @@handler_options[:issue][attr]
132 end
133 end
123 end
134 end
124
135
125 class TMail::Mail
136 class TMail::Mail
126 # Returns body of the first plain text part found if any
137 # Returns body of the first plain text part found if any
127 def plain_text_body
138 def plain_text_body
128 return @plain_text_body unless @plain_text_body.nil?
139 return @plain_text_body unless @plain_text_body.nil?
129 p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
140 p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
130 plain = p.detect {|c| c.content_type == 'text/plain'}
141 plain = p.detect {|c| c.content_type == 'text/plain'}
131 @plain_text_body = plain.nil? ? self.body : plain.body
142 @plain_text_body = plain.nil? ? self.body : plain.body
132 end
143 end
133 end
144 end
134
145
@@ -1,79 +1,112
1 #!/usr/bin/ruby
1 #!/usr/bin/ruby
2
2
3 # rdm-mailhandler
3 # rdm-mailhandler
4 # Reads an email from standard input and forward it to a Redmine server
4 # Reads an email from standard input and forward it to a Redmine server
5 # Can be used from a remote mail server
5 # Can be used from a remote mail server
6
6
7 require 'net/http'
7 require 'net/http'
8 require 'net/https'
8 require 'net/https'
9 require 'uri'
9 require 'uri'
10 require 'getoptlong'
10 require 'getoptlong'
11
11
12 class RedmineMailHandler
12 class RedmineMailHandler
13 VERSION = '0.1'
13 VERSION = '0.1'
14
14
15 attr_accessor :verbose, :project, :url, :key
15 attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key
16
16
17 def initialize
17 def initialize
18 self.issue_attributes = {}
19
18 opts = GetoptLong.new(
20 opts = GetoptLong.new(
19 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
21 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
20 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
22 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
21 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
23 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
22 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
24 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
23 [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
25 [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
24 [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ]
26 [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
27 [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT],
28 [ '--category', GetoptLong::REQUIRED_ARGUMENT],
29 [ '--priority', GetoptLong::REQUIRED_ARGUMENT],
30 [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT]
25 )
31 )
26
32
27 opts.each do |opt, arg|
33 opts.each do |opt, arg|
28 case opt
34 case opt
29 when '--url'
35 when '--url'
30 self.url = arg.dup
36 self.url = arg.dup
31 when '--key'
37 when '--key'
32 self.key = arg.dup
38 self.key = arg.dup
33 when '--help'
39 when '--help'
34 usage
40 usage
35 when '--verbose'
41 when '--verbose'
36 self.verbose = true
42 self.verbose = true
37 when '--version'
43 when '--version'
38 puts VERSION; exit
44 puts VERSION; exit
39 when '--project'
45 when '--project', '--tracker', '--category', '--priority'
40 self.project = arg.dup
46 self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
47 when '--allow-override'
48 self.allow_override = arg.dup
41 end
49 end
42 end
50 end
43
51
44 usage if url.nil?
52 usage if url.nil?
45 end
53 end
46
54
47 def submit(email)
55 def submit(email)
48 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
56 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
57
58 data = { 'key' => key, 'email' => email, 'allow_override' => allow_override }
59 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
60
49 debug "Posting to #{uri}..."
61 debug "Posting to #{uri}..."
50 data = { 'key' => key, 'project' => project, 'email' => email }
51 response = Net::HTTP.post_form(URI.parse(uri), data)
62 response = Net::HTTP.post_form(URI.parse(uri), data)
52 debug "Response received: #{response.code}"
63 debug "Response received: #{response.code}"
53 response.code == 201 ? 0 : 1
64 response.code == 201 ? 0 : 1
54 end
65 end
55
66
56 private
67 private
57
68
58 def usage
69 def usage
59 puts "Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>"
70 puts <<-USAGE
60 puts "Reads an email from standard input and forward it to a Redmine server"
71 Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>
61 puts
72 Reads an email from standard input and forward it to a Redmine server
62 puts "Options:"
73
63 puts " --help show this help"
74 Required:
64 puts " --verbose show extra information"
75 -u, --url URL of the Redmine server
65 puts " --project identifier of the target project"
76 -k, --key Redmine API key
66 puts
77
67 puts "Examples:"
78 General options:
68 puts " rdm-mailhandler --url http://redmine.domain.foo --key secret"
79 -h, --help show this help
69 puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo"
80 -v, --verbose show extra information
81 -V, --version show version information and exit
82
83 Issue attributes control options:
84 -p, --project=PROJECT identifier of the target project
85 -t, --tracker=TRACKER name of the target tracker
86 --category=CATEGORY name of the target category
87 --priority=PRIORITY name of the target priority
88 -o, --allow-override=ATTRS allow email content to override attributes
89 specified by previous options
90 ATTRS is a comma separated list of attributes
91
92 Examples:
93 # No project specified. Emails MUST contain the 'Project' keyword:
94 rdm-mailhandler --url http://redmine.domain.foo --key secret
95
96 # Fixed project and default tracker specified, but emails can override
97 # both tracker and priority attributes:
98 rdm-mailhandler --url https://domain.foo/redmine --key secret \\
99 --project foo \\
100 --tracker bug \\
101 --allow-override tracker,priority
102 USAGE
70 exit
103 exit
71 end
104 end
72
105
73 def debug(msg)
106 def debug(msg)
74 puts msg if verbose
107 puts msg if verbose
75 end
108 end
76 end
109 end
77
110
78 handler = RedmineMailHandler.new
111 handler = RedmineMailHandler.new
79 handler.submit(STDIN.read)
112 handler.submit(STDIN.read)
@@ -1,69 +1,105
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 namespace :redmine do
18 namespace :redmine do
19 namespace :email do
19 namespace :email do
20
20
21 desc <<-END_DESC
21 desc <<-END_DESC
22 Read an email from standard input.
22 Read an email from standard input.
23
23
24 Available options:
24 Issue attributes control options:
25 * project => identifier of the project the issue should be added to
25 project=PROJECT identifier of the target project
26
26 tracker=TRACKER name of the target tracker
27 Example:
27 category=CATEGORY name of the target category
28 rake redmine:email:receive project=foo RAILS_ENV="production"
28 priority=PRIORITY name of the target priority
29 allow_override=ATTRS allow email content to override attributes
30 specified by previous options
31 ATTRS is a comma separated list of attributes
32
33 Examples:
34 # No project specified. Emails MUST contain the 'Project' keyword:
35 rake redmine:email:read RAILS_ENV="production" < raw_email
36
37 # Fixed project and default tracker specified, but emails can override
38 # both tracker and priority attributes:
39 rake redmine:email:read RAILS_ENV="production" \\
40 project=foo \\
41 tracker=bug \\
42 allow_override=tracker,priority < raw_email
29 END_DESC
43 END_DESC
30
44
31 task :receive => :environment do
45 task :read => :environment do
32 options = {}
46 options = { :issue => {} }
33 options[:project] = ENV['project'] if ENV['project']
47 %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
48 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
34
49
35 MailHandler.receive(STDIN.read, options)
50 MailHandler.receive(STDIN.read, options)
36 end
51 end
37
52
38 desc <<-END_DESC
53 desc <<-END_DESC
39 Read emails from an IMAP server.
54 Read emails from an IMAP server.
40
55
41 Available IMAP options:
56 Available IMAP options:
42 * host => IMAP server host (default: 127.0.0.1)
57 host=HOST IMAP server host (default: 127.0.0.1)
43 * port => IMAP server port (default: 143)
58 port=PORT IMAP server port (default: 143)
44 * ssl => Use SSL? (default: false)
59 ssl=SSL Use SSL? (default: false)
45 * username => IMAP account
60 username=USERNAME IMAP account
46 * password => IMAP password
61 password=PASSWORD IMAP password
47 * folder => IMAP folder to read (default: INBOX)
62 folder=FOLDER IMAP folder to read (default: INBOX)
48 Other options:
63
49 * project => identifier of the project the issue should be added to
64 Issue attributes control options:
65 project=PROJECT identifier of the target project
66 tracker=TRACKER name of the target tracker
67 category=CATEGORY name of the target category
68 priority=PRIORITY name of the target priority
69 allow_override=ATTRS allow email content to override attributes
70 specified by previous options
71 ATTRS is a comma separated list of attributes
72
73 Examples:
74 # No project specified. Emails MUST contain the 'Project' keyword:
75
76 rake redmine:email:receive_iamp RAILS_ENV="production" \\
77 host=imap.foo.bar username=redmine@somenet.foo password=xxx
78
79
80 # Fixed project and default tracker specified, but emails can override
81 # both tracker and priority attributes:
50
82
51 Example:
83 rake redmine:email:receive_iamp RAILS_ENV="production" \\
52 rake redmine:email:receive_iamp host=imap.foo.bar username=redmine@somenet.foo password=xxx project=foo RAILS_ENV="production"
84 host=imap.foo.bar username=redmine@somenet.foo password=xxx ssl=1 \\
85 project=foo \\
86 tracker=bug \\
87 allow_override=tracker,priority
53 END_DESC
88 END_DESC
54
89
55 task :receive_imap => :environment do
90 task :receive_imap => :environment do
56 imap_options = {:host => ENV['host'],
91 imap_options = {:host => ENV['host'],
57 :port => ENV['port'],
92 :port => ENV['port'],
58 :ssl => ENV['ssl'],
93 :ssl => ENV['ssl'],
59 :username => ENV['username'],
94 :username => ENV['username'],
60 :password => ENV['password'],
95 :password => ENV['password'],
61 :folder => ENV['folder']}
96 :folder => ENV['folder']}
62
97
63 options = {}
98 options = { :issue => {} }
64 options[:project] = ENV['project'] if ENV['project']
99 %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
100 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
65
101
66 Redmine::IMAP.check(imap_options, options)
102 Redmine::IMAP.check(imap_options, options)
67 end
103 end
68 end
104 end
69 end
105 end
@@ -1,11 +1,17
1 ---
1 ---
2 issue_categories_001:
2 issue_categories_001:
3 name: Printing
3 name: Printing
4 project_id: 1
4 project_id: 1
5 assigned_to_id: 2
5 assigned_to_id: 2
6 id: 1
6 id: 1
7 issue_categories_002:
7 issue_categories_002:
8 name: Recipes
8 name: Recipes
9 project_id: 1
9 project_id: 1
10 assigned_to_id:
10 assigned_to_id:
11 id: 2
11 id: 2
12 issue_categories_003:
13 name: Stock management
14 project_id: 2
15 assigned_to_id:
16 id: 3
17 No newline at end of file
@@ -1,71 +1,107
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, :enabled_modules, :roles, :members, :issues, :trackers, :enumerations
21 fixtures :users, :projects,
22 :enabled_modules,
23 :roles,
24 :members,
25 :issues,
26 :trackers,
27 :projects_trackers,
28 :enumerations,
29 :issue_categories
22
30
23 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
24
32
25 def setup
33 def setup
26 ActionMailer::Base.deliveries.clear
34 ActionMailer::Base.deliveries.clear
27 end
35 end
28
36
29 def test_add_issue
37 def test_add_issue
30 # This email contains: 'Project: onlinestore'
38 # This email contains: 'Project: onlinestore'
31 issue = submit_email('ticket_on_given_project.eml')
39 issue = submit_email('ticket_on_given_project.eml')
32 assert issue.is_a?(Issue)
40 assert issue.is_a?(Issue)
33 assert !issue.new_record?
41 assert !issue.new_record?
34 issue.reload
42 issue.reload
35 assert_equal 'New ticket on a given project', issue.subject
43 assert_equal 'New ticket on a given project', issue.subject
36 assert_equal User.find_by_login('jsmith'), issue.author
44 assert_equal User.find_by_login('jsmith'), issue.author
37 assert_equal Project.find(2), issue.project
45 assert_equal Project.find(2), issue.project
38 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
46 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
39 end
47 end
40
48
49 def test_add_issue_with_attributes_override
50 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
51 assert issue.is_a?(Issue)
52 assert !issue.new_record?
53 issue.reload
54 assert_equal 'New ticket on a given project', issue.subject
55 assert_equal User.find_by_login('jsmith'), issue.author
56 assert_equal Project.find(2), issue.project
57 assert_equal 'Feature request', issue.tracker.to_s
58 assert_equal 'Stock management', issue.category.to_s
59 assert_equal 'Urgent', issue.priority.to_s
60 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
61 end
62
63 def test_add_issue_with_partial_attributes_override
64 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
65 assert issue.is_a?(Issue)
66 assert !issue.new_record?
67 issue.reload
68 assert_equal 'New ticket on a given project', issue.subject
69 assert_equal User.find_by_login('jsmith'), issue.author
70 assert_equal Project.find(2), issue.project
71 assert_equal 'Feature request', issue.tracker.to_s
72 assert_nil issue.category
73 assert_equal 'High', issue.priority.to_s
74 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
75 end
76
41 def test_add_issue_with_attachment_to_specific_project
77 def test_add_issue_with_attachment_to_specific_project
42 issue = submit_email('ticket_with_attachment.eml', :project => 'onlinestore')
78 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
43 assert issue.is_a?(Issue)
79 assert issue.is_a?(Issue)
44 assert !issue.new_record?
80 assert !issue.new_record?
45 issue.reload
81 issue.reload
46 assert_equal 'Ticket created by email with attachment', issue.subject
82 assert_equal 'Ticket created by email with attachment', issue.subject
47 assert_equal User.find_by_login('jsmith'), issue.author
83 assert_equal User.find_by_login('jsmith'), issue.author
48 assert_equal Project.find(2), issue.project
84 assert_equal Project.find(2), issue.project
49 assert_equal 'This is a new ticket with attachments', issue.description
85 assert_equal 'This is a new ticket with attachments', issue.description
50 # Attachment properties
86 # Attachment properties
51 assert_equal 1, issue.attachments.size
87 assert_equal 1, issue.attachments.size
52 assert_equal 'Paella.jpg', issue.attachments.first.filename
88 assert_equal 'Paella.jpg', issue.attachments.first.filename
53 assert_equal 'image/jpeg', issue.attachments.first.content_type
89 assert_equal 'image/jpeg', issue.attachments.first.content_type
54 assert_equal 10790, issue.attachments.first.filesize
90 assert_equal 10790, issue.attachments.first.filesize
55 end
91 end
56
92
57 def test_add_issue_note
93 def test_add_issue_note
58 journal = submit_email('ticket_reply.eml')
94 journal = submit_email('ticket_reply.eml')
59 assert journal.is_a?(Journal)
95 assert journal.is_a?(Journal)
60 assert_equal User.find_by_login('jsmith'), journal.user
96 assert_equal User.find_by_login('jsmith'), journal.user
61 assert_equal Issue.find(2), journal.journalized
97 assert_equal Issue.find(2), journal.journalized
62 assert_equal 'This is reply', journal.notes
98 assert_equal 'This is reply', journal.notes
63 end
99 end
64
100
65 private
101 private
66
102
67 def submit_email(filename, options={})
103 def submit_email(filename, options={})
68 raw = IO.read(File.join(FIXTURES_PATH, filename))
104 raw = IO.read(File.join(FIXTURES_PATH, filename))
69 MailHandler.receive(raw, options)
105 MailHandler.receive(raw, options)
70 end
106 end
71 end
107 end
General Comments 0
You need to be logged in to leave comments. Login now