##// END OF EJS Templates
Merged r10992 from trunk (#12400)....
Jean-Philippe Lang -
r10776:0ebf95d8199f
parent child
Show More
@@ -1,292 +1,298
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 CustomField < ActiveRecord::Base
18 class CustomField < ActiveRecord::Base
19 include Redmine::SubclassFactory
19 include Redmine::SubclassFactory
20
20
21 has_many :custom_values, :dependent => :delete_all
21 has_many :custom_values, :dependent => :delete_all
22 acts_as_list :scope => 'type = \'#{self.class}\''
22 acts_as_list :scope => 'type = \'#{self.class}\''
23 serialize :possible_values
23 serialize :possible_values
24
24
25 validates_presence_of :name, :field_format
25 validates_presence_of :name, :field_format
26 validates_uniqueness_of :name, :scope => :type
26 validates_uniqueness_of :name, :scope => :type
27 validates_length_of :name, :maximum => 30
27 validates_length_of :name, :maximum => 30
28 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
28 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
29
29
30 validate :validate_custom_field
30 validate :validate_custom_field
31 before_validation :set_searchable
31 before_validation :set_searchable
32
32
33 def set_searchable
33 def set_searchable
34 # make sure these fields are not searchable
34 # make sure these fields are not searchable
35 self.searchable = false if %w(int float date bool).include?(field_format)
35 self.searchable = false if %w(int float date bool).include?(field_format)
36 # make sure only these fields can have multiple values
36 # make sure only these fields can have multiple values
37 self.multiple = false unless %w(list user version).include?(field_format)
37 self.multiple = false unless %w(list user version).include?(field_format)
38 true
38 true
39 end
39 end
40
40
41 def validate_custom_field
41 def validate_custom_field
42 if self.field_format == "list"
42 if self.field_format == "list"
43 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
43 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
44 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
44 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
45 end
45 end
46
46
47 if regexp.present?
47 if regexp.present?
48 begin
48 begin
49 Regexp.new(regexp)
49 Regexp.new(regexp)
50 rescue
50 rescue
51 errors.add(:regexp, :invalid)
51 errors.add(:regexp, :invalid)
52 end
52 end
53 end
53 end
54
54
55 if default_value.present? && !valid_field_value?(default_value)
55 if default_value.present? && !valid_field_value?(default_value)
56 errors.add(:default_value, :invalid)
56 errors.add(:default_value, :invalid)
57 end
57 end
58 end
58 end
59
59
60 def possible_values_options(obj=nil)
60 def possible_values_options(obj=nil)
61 case field_format
61 case field_format
62 when 'user', 'version'
62 when 'user', 'version'
63 if obj.respond_to?(:project) && obj.project
63 if obj.respond_to?(:project) && obj.project
64 case field_format
64 case field_format
65 when 'user'
65 when 'user'
66 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
66 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
67 when 'version'
67 when 'version'
68 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
68 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
69 end
69 end
70 elsif obj.is_a?(Array)
70 elsif obj.is_a?(Array)
71 obj.collect {|o| possible_values_options(o)}.reduce(:&)
71 obj.collect {|o| possible_values_options(o)}.reduce(:&)
72 else
72 else
73 []
73 []
74 end
74 end
75 when 'bool'
75 when 'bool'
76 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
76 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
77 else
77 else
78 possible_values || []
78 possible_values || []
79 end
79 end
80 end
80 end
81
81
82 def possible_values(obj=nil)
82 def possible_values(obj=nil)
83 case field_format
83 case field_format
84 when 'user', 'version'
84 when 'user', 'version'
85 possible_values_options(obj).collect(&:last)
85 possible_values_options(obj).collect(&:last)
86 when 'bool'
86 when 'bool'
87 ['1', '0']
87 ['1', '0']
88 else
88 else
89 values = super()
89 values = super()
90 if values.is_a?(Array)
90 if values.is_a?(Array)
91 values.each do |value|
91 values.each do |value|
92 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
92 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
93 end
93 end
94 end
94 end
95 values || []
95 values || []
96 end
96 end
97 end
97 end
98
98
99 # Makes possible_values accept a multiline string
99 # Makes possible_values accept a multiline string
100 def possible_values=(arg)
100 def possible_values=(arg)
101 if arg.is_a?(Array)
101 if arg.is_a?(Array)
102 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
102 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
103 else
103 else
104 self.possible_values = arg.to_s.split(/[\n\r]+/)
104 self.possible_values = arg.to_s.split(/[\n\r]+/)
105 end
105 end
106 end
106 end
107
107
108 def cast_value(value)
108 def cast_value(value)
109 casted = nil
109 casted = nil
110 unless value.blank?
110 unless value.blank?
111 case field_format
111 case field_format
112 when 'string', 'text', 'list'
112 when 'string', 'text', 'list'
113 casted = value
113 casted = value
114 when 'date'
114 when 'date'
115 casted = begin; value.to_date; rescue; nil end
115 casted = begin; value.to_date; rescue; nil end
116 when 'bool'
116 when 'bool'
117 casted = (value == '1' ? true : false)
117 casted = (value == '1' ? true : false)
118 when 'int'
118 when 'int'
119 casted = value.to_i
119 casted = value.to_i
120 when 'float'
120 when 'float'
121 casted = value.to_f
121 casted = value.to_f
122 when 'user', 'version'
122 when 'user', 'version'
123 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
123 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
124 end
124 end
125 end
125 end
126 casted
126 casted
127 end
127 end
128
128
129 def value_from_keyword(keyword, customized)
129 def value_from_keyword(keyword, customized)
130 possible_values_options = possible_values_options(customized)
130 possible_values_options = possible_values_options(customized)
131 if possible_values_options.present?
131 if possible_values_options.present?
132 keyword = keyword.to_s.downcase
132 keyword = keyword.to_s.downcase
133 possible_values_options.detect {|text, id| text.downcase == keyword}.try(:last)
133 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
134 if v.is_a?(Array)
135 v.last
136 else
137 v
138 end
139 end
134 else
140 else
135 keyword
141 keyword
136 end
142 end
137 end
143 end
138
144
139 # Returns a ORDER BY clause that can used to sort customized
145 # Returns a ORDER BY clause that can used to sort customized
140 # objects by their value of the custom field.
146 # objects by their value of the custom field.
141 # Returns nil if the custom field can not be used for sorting.
147 # Returns nil if the custom field can not be used for sorting.
142 def order_statement
148 def order_statement
143 return nil if multiple?
149 return nil if multiple?
144 case field_format
150 case field_format
145 when 'string', 'text', 'list', 'date', 'bool'
151 when 'string', 'text', 'list', 'date', 'bool'
146 # COALESCE is here to make sure that blank and NULL values are sorted equally
152 # COALESCE is here to make sure that blank and NULL values are sorted equally
147 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
153 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
148 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
154 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
149 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
155 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
150 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
156 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
151 when 'int', 'float'
157 when 'int', 'float'
152 # Make the database cast values into numeric
158 # Make the database cast values into numeric
153 # Postgresql will raise an error if a value can not be casted!
159 # Postgresql will raise an error if a value can not be casted!
154 # CustomValue validations should ensure that it doesn't occur
160 # CustomValue validations should ensure that it doesn't occur
155 "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
161 "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
156 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
162 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
157 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
163 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
158 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
164 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
159 when 'user', 'version'
165 when 'user', 'version'
160 value_class.fields_for_order_statement(value_join_alias)
166 value_class.fields_for_order_statement(value_join_alias)
161 else
167 else
162 nil
168 nil
163 end
169 end
164 end
170 end
165
171
166 # Returns a GROUP BY clause that can used to group by custom value
172 # Returns a GROUP BY clause that can used to group by custom value
167 # Returns nil if the custom field can not be used for grouping.
173 # Returns nil if the custom field can not be used for grouping.
168 def group_statement
174 def group_statement
169 return nil if multiple?
175 return nil if multiple?
170 case field_format
176 case field_format
171 when 'list', 'date', 'bool', 'int'
177 when 'list', 'date', 'bool', 'int'
172 order_statement
178 order_statement
173 when 'user', 'version'
179 when 'user', 'version'
174 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
180 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
175 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
181 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
176 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
182 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
177 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
183 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
178 else
184 else
179 nil
185 nil
180 end
186 end
181 end
187 end
182
188
183 def join_for_order_statement
189 def join_for_order_statement
184 case field_format
190 case field_format
185 when 'user', 'version'
191 when 'user', 'version'
186 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
192 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
187 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
193 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
188 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
194 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
189 " AND #{join_alias}.custom_field_id = #{id}" +
195 " AND #{join_alias}.custom_field_id = #{id}" +
190 " AND #{join_alias}.value <> ''" +
196 " AND #{join_alias}.value <> ''" +
191 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
197 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
192 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
198 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
193 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
199 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
194 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
200 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
195 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
201 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
196 " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id"
202 " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id"
197 else
203 else
198 nil
204 nil
199 end
205 end
200 end
206 end
201
207
202 def join_alias
208 def join_alias
203 "cf_#{id}"
209 "cf_#{id}"
204 end
210 end
205
211
206 def value_join_alias
212 def value_join_alias
207 join_alias + "_" + field_format
213 join_alias + "_" + field_format
208 end
214 end
209
215
210 def <=>(field)
216 def <=>(field)
211 position <=> field.position
217 position <=> field.position
212 end
218 end
213
219
214 # Returns the class that values represent
220 # Returns the class that values represent
215 def value_class
221 def value_class
216 case field_format
222 case field_format
217 when 'user', 'version'
223 when 'user', 'version'
218 field_format.classify.constantize
224 field_format.classify.constantize
219 else
225 else
220 nil
226 nil
221 end
227 end
222 end
228 end
223
229
224 def self.customized_class
230 def self.customized_class
225 self.name =~ /^(.+)CustomField$/
231 self.name =~ /^(.+)CustomField$/
226 begin; $1.constantize; rescue nil; end
232 begin; $1.constantize; rescue nil; end
227 end
233 end
228
234
229 # to move in project_custom_field
235 # to move in project_custom_field
230 def self.for_all
236 def self.for_all
231 find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
237 find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
232 end
238 end
233
239
234 def type_name
240 def type_name
235 nil
241 nil
236 end
242 end
237
243
238 # Returns the error messages for the given value
244 # Returns the error messages for the given value
239 # or an empty array if value is a valid value for the custom field
245 # or an empty array if value is a valid value for the custom field
240 def validate_field_value(value)
246 def validate_field_value(value)
241 errs = []
247 errs = []
242 if value.is_a?(Array)
248 if value.is_a?(Array)
243 if !multiple?
249 if !multiple?
244 errs << ::I18n.t('activerecord.errors.messages.invalid')
250 errs << ::I18n.t('activerecord.errors.messages.invalid')
245 end
251 end
246 if is_required? && value.detect(&:present?).nil?
252 if is_required? && value.detect(&:present?).nil?
247 errs << ::I18n.t('activerecord.errors.messages.blank')
253 errs << ::I18n.t('activerecord.errors.messages.blank')
248 end
254 end
249 value.each {|v| errs += validate_field_value_format(v)}
255 value.each {|v| errs += validate_field_value_format(v)}
250 else
256 else
251 if is_required? && value.blank?
257 if is_required? && value.blank?
252 errs << ::I18n.t('activerecord.errors.messages.blank')
258 errs << ::I18n.t('activerecord.errors.messages.blank')
253 end
259 end
254 errs += validate_field_value_format(value)
260 errs += validate_field_value_format(value)
255 end
261 end
256 errs
262 errs
257 end
263 end
258
264
259 # Returns true if value is a valid value for the custom field
265 # Returns true if value is a valid value for the custom field
260 def valid_field_value?(value)
266 def valid_field_value?(value)
261 validate_field_value(value).empty?
267 validate_field_value(value).empty?
262 end
268 end
263
269
264 def format_in?(*args)
270 def format_in?(*args)
265 args.include?(field_format)
271 args.include?(field_format)
266 end
272 end
267
273
268 protected
274 protected
269
275
270 # Returns the error message for the given value regarding its format
276 # Returns the error message for the given value regarding its format
271 def validate_field_value_format(value)
277 def validate_field_value_format(value)
272 errs = []
278 errs = []
273 if value.present?
279 if value.present?
274 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
280 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
275 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
281 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
276 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
282 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
277
283
278 # Format specific validations
284 # Format specific validations
279 case field_format
285 case field_format
280 when 'int'
286 when 'int'
281 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
287 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
282 when 'float'
288 when 'float'
283 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
289 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
284 when 'date'
290 when 'date'
285 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
291 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
286 when 'list'
292 when 'list'
287 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
293 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
288 end
294 end
289 end
295 end
290 errs
296 errs
291 end
297 end
292 end
298 end
@@ -1,41 +1,42
1 Return-Path: <jsmith@somenet.foo>
1 Return-Path: <jsmith@somenet.foo>
2 Received: from osiris ([127.0.0.1])
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 From: "John Smith" <jsmith@somenet.foo>
6 From: "John Smith" <jsmith@somenet.foo>
7 To: <redmine@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket with custom field values
8 Subject: New ticket with custom field values
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
10 MIME-Version: 1.0
11 Content-Type: text/plain;
11 Content-Type: text/plain;
12 format=flowed;
12 format=flowed;
13 charset="iso-8859-1";
13 charset="iso-8859-1";
14 reply-type=original
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
16 X-Priority: 3
17 X-MSMail-Priority: Normal
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
29 sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
29 sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
31 platea dictumst.
31 platea dictumst.
32
32
33 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
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.
34 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
35 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
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,
36 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
37 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
37 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
38 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
38 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
39
39
40 category: Stock management
40 category: Stock management
41 searchable field: Value for a custom field
41 searchable field: Value for a custom field
42 Database: postgresql
@@ -1,215 +1,220
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class CustomFieldTest < ActiveSupport::TestCase
20 class CustomFieldTest < ActiveSupport::TestCase
21 fixtures :custom_fields
21 fixtures :custom_fields
22
22
23 def test_create
23 def test_create
24 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
24 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
25 assert field.save
25 assert field.save
26 end
26 end
27
27
28 def test_before_validation
28 def test_before_validation
29 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
29 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
30 field.searchable = true
30 field.searchable = true
31 assert field.save
31 assert field.save
32 assert_equal false, field.searchable
32 assert_equal false, field.searchable
33 field.searchable = true
33 field.searchable = true
34 assert field.save
34 assert field.save
35 assert_equal false, field.searchable
35 assert_equal false, field.searchable
36 end
36 end
37
37
38 def test_regexp_validation
38 def test_regexp_validation
39 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
39 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
40 assert !field.save
40 assert !field.save
41 assert_include I18n.t('activerecord.errors.messages.invalid'),
41 assert_include I18n.t('activerecord.errors.messages.invalid'),
42 field.errors[:regexp]
42 field.errors[:regexp]
43 field.regexp = '[a-z0-9]'
43 field.regexp = '[a-z0-9]'
44 assert field.save
44 assert field.save
45 end
45 end
46
46
47 def test_default_value_should_be_validated
47 def test_default_value_should_be_validated
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
49 field.default_value = 'abc'
49 field.default_value = 'abc'
50 assert !field.valid?
50 assert !field.valid?
51 field.default_value = '6'
51 field.default_value = '6'
52 assert field.valid?
52 assert field.valid?
53 end
53 end
54
54
55 def test_default_value_should_not_be_validated_when_blank
55 def test_default_value_should_not_be_validated_when_blank
56 field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '')
56 field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '')
57 assert field.valid?
57 assert field.valid?
58 end
58 end
59
59
60 def test_possible_values_should_accept_an_array
60 def test_possible_values_should_accept_an_array
61 field = CustomField.new
61 field = CustomField.new
62 field.possible_values = ["One value", ""]
62 field.possible_values = ["One value", ""]
63 assert_equal ["One value"], field.possible_values
63 assert_equal ["One value"], field.possible_values
64 end
64 end
65
65
66 def test_possible_values_should_accept_a_string
66 def test_possible_values_should_accept_a_string
67 field = CustomField.new
67 field = CustomField.new
68 field.possible_values = "One value"
68 field.possible_values = "One value"
69 assert_equal ["One value"], field.possible_values
69 assert_equal ["One value"], field.possible_values
70 end
70 end
71
71
72 def test_possible_values_should_accept_a_multiline_string
72 def test_possible_values_should_accept_a_multiline_string
73 field = CustomField.new
73 field = CustomField.new
74 field.possible_values = "One value\nAnd another one \r\n \n"
74 field.possible_values = "One value\nAnd another one \r\n \n"
75 assert_equal ["One value", "And another one"], field.possible_values
75 assert_equal ["One value", "And another one"], field.possible_values
76 end
76 end
77
77
78 if "string".respond_to?(:encoding)
78 if "string".respond_to?(:encoding)
79 def test_possible_values_stored_as_binary_should_be_utf8_encoded
79 def test_possible_values_stored_as_binary_should_be_utf8_encoded
80 field = CustomField.find(11)
80 field = CustomField.find(11)
81 assert_kind_of Array, field.possible_values
81 assert_kind_of Array, field.possible_values
82 assert field.possible_values.size > 0
82 assert field.possible_values.size > 0
83 field.possible_values.each do |value|
83 field.possible_values.each do |value|
84 assert_equal "UTF-8", value.encoding.name
84 assert_equal "UTF-8", value.encoding.name
85 end
85 end
86 end
86 end
87 end
87 end
88
88
89 def test_destroy
89 def test_destroy
90 field = CustomField.find(1)
90 field = CustomField.find(1)
91 assert field.destroy
91 assert field.destroy
92 end
92 end
93
93
94 def test_new_subclass_instance_should_return_an_instance
94 def test_new_subclass_instance_should_return_an_instance
95 f = CustomField.new_subclass_instance('IssueCustomField')
95 f = CustomField.new_subclass_instance('IssueCustomField')
96 assert_kind_of IssueCustomField, f
96 assert_kind_of IssueCustomField, f
97 end
97 end
98
98
99 def test_new_subclass_instance_should_set_attributes
99 def test_new_subclass_instance_should_set_attributes
100 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
100 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
101 assert_kind_of IssueCustomField, f
101 assert_kind_of IssueCustomField, f
102 assert_equal 'Test', f.name
102 assert_equal 'Test', f.name
103 end
103 end
104
104
105 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
105 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
106 assert_nil CustomField.new_subclass_instance('WrongClassName')
106 assert_nil CustomField.new_subclass_instance('WrongClassName')
107 end
107 end
108
108
109 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
109 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
110 assert_nil CustomField.new_subclass_instance('Project')
110 assert_nil CustomField.new_subclass_instance('Project')
111 end
111 end
112
112
113 def test_string_field_validation_with_blank_value
113 def test_string_field_validation_with_blank_value
114 f = CustomField.new(:field_format => 'string')
114 f = CustomField.new(:field_format => 'string')
115
115
116 assert f.valid_field_value?(nil)
116 assert f.valid_field_value?(nil)
117 assert f.valid_field_value?('')
117 assert f.valid_field_value?('')
118
118
119 f.is_required = true
119 f.is_required = true
120 assert !f.valid_field_value?(nil)
120 assert !f.valid_field_value?(nil)
121 assert !f.valid_field_value?('')
121 assert !f.valid_field_value?('')
122 end
122 end
123
123
124 def test_string_field_validation_with_min_and_max_lengths
124 def test_string_field_validation_with_min_and_max_lengths
125 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
125 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
126
126
127 assert f.valid_field_value?(nil)
127 assert f.valid_field_value?(nil)
128 assert f.valid_field_value?('')
128 assert f.valid_field_value?('')
129 assert f.valid_field_value?('a' * 2)
129 assert f.valid_field_value?('a' * 2)
130 assert !f.valid_field_value?('a')
130 assert !f.valid_field_value?('a')
131 assert !f.valid_field_value?('a' * 6)
131 assert !f.valid_field_value?('a' * 6)
132 end
132 end
133
133
134 def test_string_field_validation_with_regexp
134 def test_string_field_validation_with_regexp
135 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
135 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
136
136
137 assert f.valid_field_value?(nil)
137 assert f.valid_field_value?(nil)
138 assert f.valid_field_value?('')
138 assert f.valid_field_value?('')
139 assert f.valid_field_value?('ABC')
139 assert f.valid_field_value?('ABC')
140 assert !f.valid_field_value?('abc')
140 assert !f.valid_field_value?('abc')
141 end
141 end
142
142
143 def test_date_field_validation
143 def test_date_field_validation
144 f = CustomField.new(:field_format => 'date')
144 f = CustomField.new(:field_format => 'date')
145
145
146 assert f.valid_field_value?(nil)
146 assert f.valid_field_value?(nil)
147 assert f.valid_field_value?('')
147 assert f.valid_field_value?('')
148 assert f.valid_field_value?('1975-07-14')
148 assert f.valid_field_value?('1975-07-14')
149 assert !f.valid_field_value?('1975-07-33')
149 assert !f.valid_field_value?('1975-07-33')
150 assert !f.valid_field_value?('abc')
150 assert !f.valid_field_value?('abc')
151 end
151 end
152
152
153 def test_list_field_validation
153 def test_list_field_validation
154 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
154 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
155
155
156 assert f.valid_field_value?(nil)
156 assert f.valid_field_value?(nil)
157 assert f.valid_field_value?('')
157 assert f.valid_field_value?('')
158 assert f.valid_field_value?('value2')
158 assert f.valid_field_value?('value2')
159 assert !f.valid_field_value?('abc')
159 assert !f.valid_field_value?('abc')
160 end
160 end
161
161
162 def test_int_field_validation
162 def test_int_field_validation
163 f = CustomField.new(:field_format => 'int')
163 f = CustomField.new(:field_format => 'int')
164
164
165 assert f.valid_field_value?(nil)
165 assert f.valid_field_value?(nil)
166 assert f.valid_field_value?('')
166 assert f.valid_field_value?('')
167 assert f.valid_field_value?('123')
167 assert f.valid_field_value?('123')
168 assert f.valid_field_value?('+123')
168 assert f.valid_field_value?('+123')
169 assert f.valid_field_value?('-123')
169 assert f.valid_field_value?('-123')
170 assert !f.valid_field_value?('6abc')
170 assert !f.valid_field_value?('6abc')
171 end
171 end
172
172
173 def test_float_field_validation
173 def test_float_field_validation
174 f = CustomField.new(:field_format => 'float')
174 f = CustomField.new(:field_format => 'float')
175
175
176 assert f.valid_field_value?(nil)
176 assert f.valid_field_value?(nil)
177 assert f.valid_field_value?('')
177 assert f.valid_field_value?('')
178 assert f.valid_field_value?('11.2')
178 assert f.valid_field_value?('11.2')
179 assert f.valid_field_value?('-6.250')
179 assert f.valid_field_value?('-6.250')
180 assert f.valid_field_value?('5')
180 assert f.valid_field_value?('5')
181 assert !f.valid_field_value?('6abc')
181 assert !f.valid_field_value?('6abc')
182 end
182 end
183
183
184 def test_multi_field_validation
184 def test_multi_field_validation
185 f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2'])
185 f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2'])
186
186
187 assert f.valid_field_value?(nil)
187 assert f.valid_field_value?(nil)
188 assert f.valid_field_value?('')
188 assert f.valid_field_value?('')
189 assert f.valid_field_value?([])
189 assert f.valid_field_value?([])
190 assert f.valid_field_value?([nil])
190 assert f.valid_field_value?([nil])
191 assert f.valid_field_value?([''])
191 assert f.valid_field_value?([''])
192
192
193 assert f.valid_field_value?('value2')
193 assert f.valid_field_value?('value2')
194 assert !f.valid_field_value?('abc')
194 assert !f.valid_field_value?('abc')
195
195
196 assert f.valid_field_value?(['value2'])
196 assert f.valid_field_value?(['value2'])
197 assert !f.valid_field_value?(['abc'])
197 assert !f.valid_field_value?(['abc'])
198
198
199 assert f.valid_field_value?(['', 'value2'])
199 assert f.valid_field_value?(['', 'value2'])
200 assert !f.valid_field_value?(['', 'abc'])
200 assert !f.valid_field_value?(['', 'abc'])
201
201
202 assert f.valid_field_value?(['value1', 'value2'])
202 assert f.valid_field_value?(['value1', 'value2'])
203 assert !f.valid_field_value?(['value1', 'abc'])
203 assert !f.valid_field_value?(['value1', 'abc'])
204 end
204 end
205
205
206 def test_value_class_should_return_the_class_used_for_fields_values
206 def test_value_class_should_return_the_class_used_for_fields_values
207 assert_equal User, CustomField.new(:field_format => 'user').value_class
207 assert_equal User, CustomField.new(:field_format => 'user').value_class
208 assert_equal Version, CustomField.new(:field_format => 'version').value_class
208 assert_equal Version, CustomField.new(:field_format => 'version').value_class
209 end
209 end
210
210
211 def test_value_class_should_return_nil_for_other_fields
211 def test_value_class_should_return_nil_for_other_fields
212 assert_nil CustomField.new(:field_format => 'text').value_class
212 assert_nil CustomField.new(:field_format => 'text').value_class
213 assert_nil CustomField.new.value_class
213 assert_nil CustomField.new.value_class
214 end
214 end
215
216 def test_value_from_keyword_for_list_custom_field
217 field = CustomField.find(1)
218 assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1))
219 end
215 end
220 end
@@ -1,777 +1,777
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class MailHandlerTest < ActiveSupport::TestCase
22 class MailHandlerTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :enabled_modules, :roles,
23 fixtures :users, :projects, :enabled_modules, :roles,
24 :members, :member_roles, :users,
24 :members, :member_roles, :users,
25 :issues, :issue_statuses,
25 :issues, :issue_statuses,
26 :workflows, :trackers, :projects_trackers,
26 :workflows, :trackers, :projects_trackers,
27 :versions, :enumerations, :issue_categories,
27 :versions, :enumerations, :issue_categories,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 :boards, :messages
29 :boards, :messages
30
30
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32
32
33 def setup
33 def setup
34 ActionMailer::Base.deliveries.clear
34 ActionMailer::Base.deliveries.clear
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 end
36 end
37
37
38 def teardown
38 def teardown
39 Setting.clear_cache
39 Setting.clear_cache
40 end
40 end
41
41
42 def test_add_issue
42 def test_add_issue
43 ActionMailer::Base.deliveries.clear
43 ActionMailer::Base.deliveries.clear
44 # 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 Project.find(2), issue.project
49 assert_equal Project.find(2), issue.project
50 assert_equal issue.project.trackers.first, issue.tracker
50 assert_equal issue.project.trackers.first, issue.tracker
51 assert_equal 'New ticket on a given project', issue.subject
51 assert_equal 'New ticket on a given project', issue.subject
52 assert_equal User.find_by_login('jsmith'), issue.author
52 assert_equal User.find_by_login('jsmith'), issue.author
53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 assert_equal '2010-01-01', issue.start_date.to_s
55 assert_equal '2010-01-01', issue.start_date.to_s
56 assert_equal '2010-12-31', issue.due_date.to_s
56 assert_equal '2010-12-31', issue.due_date.to_s
57 assert_equal User.find_by_login('jsmith'), issue.assigned_to
57 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
58 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 assert_equal 2.5, issue.estimated_hours
59 assert_equal 2.5, issue.estimated_hours
60 assert_equal 30, issue.done_ratio
60 assert_equal 30, issue.done_ratio
61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
62 # keywords should be removed from the email body
62 # keywords should be removed from the email body
63 assert !issue.description.match(/^Project:/i)
63 assert !issue.description.match(/^Project:/i)
64 assert !issue.description.match(/^Status:/i)
64 assert !issue.description.match(/^Status:/i)
65 assert !issue.description.match(/^Start Date:/i)
65 assert !issue.description.match(/^Start Date:/i)
66 # Email notification should be sent
66 # Email notification should be sent
67 mail = ActionMailer::Base.deliveries.last
67 mail = ActionMailer::Base.deliveries.last
68 assert_not_nil mail
68 assert_not_nil mail
69 assert mail.subject.include?('New ticket on a given project')
69 assert mail.subject.include?('New ticket on a given project')
70 end
70 end
71
71
72 def test_add_issue_with_default_tracker
72 def test_add_issue_with_default_tracker
73 # This email contains: 'Project: onlinestore'
73 # This email contains: 'Project: onlinestore'
74 issue = submit_email(
74 issue = submit_email(
75 'ticket_on_given_project.eml',
75 'ticket_on_given_project.eml',
76 :issue => {:tracker => 'Support request'}
76 :issue => {:tracker => 'Support request'}
77 )
77 )
78 assert issue.is_a?(Issue)
78 assert issue.is_a?(Issue)
79 assert !issue.new_record?
79 assert !issue.new_record?
80 issue.reload
80 issue.reload
81 assert_equal 'Support request', issue.tracker.name
81 assert_equal 'Support request', issue.tracker.name
82 end
82 end
83
83
84 def test_add_issue_with_status
84 def test_add_issue_with_status
85 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
85 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
86 issue = submit_email('ticket_on_given_project.eml')
86 issue = submit_email('ticket_on_given_project.eml')
87 assert issue.is_a?(Issue)
87 assert issue.is_a?(Issue)
88 assert !issue.new_record?
88 assert !issue.new_record?
89 issue.reload
89 issue.reload
90 assert_equal Project.find(2), issue.project
90 assert_equal Project.find(2), issue.project
91 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
91 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
92 end
92 end
93
93
94 def test_add_issue_with_attributes_override
94 def test_add_issue_with_attributes_override
95 issue = submit_email(
95 issue = submit_email(
96 'ticket_with_attributes.eml',
96 'ticket_with_attributes.eml',
97 :allow_override => 'tracker,category,priority'
97 :allow_override => 'tracker,category,priority'
98 )
98 )
99 assert issue.is_a?(Issue)
99 assert issue.is_a?(Issue)
100 assert !issue.new_record?
100 assert !issue.new_record?
101 issue.reload
101 issue.reload
102 assert_equal 'New ticket on a given project', issue.subject
102 assert_equal 'New ticket on a given project', issue.subject
103 assert_equal User.find_by_login('jsmith'), issue.author
103 assert_equal User.find_by_login('jsmith'), issue.author
104 assert_equal Project.find(2), issue.project
104 assert_equal Project.find(2), issue.project
105 assert_equal 'Feature request', issue.tracker.to_s
105 assert_equal 'Feature request', issue.tracker.to_s
106 assert_equal 'Stock management', issue.category.to_s
106 assert_equal 'Stock management', issue.category.to_s
107 assert_equal 'Urgent', issue.priority.to_s
107 assert_equal 'Urgent', issue.priority.to_s
108 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
108 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 end
109 end
110
110
111 def test_add_issue_with_group_assignment
111 def test_add_issue_with_group_assignment
112 with_settings :issue_group_assignment => '1' do
112 with_settings :issue_group_assignment => '1' do
113 issue = submit_email('ticket_on_given_project.eml') do |email|
113 issue = submit_email('ticket_on_given_project.eml') do |email|
114 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
114 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
115 end
115 end
116 assert issue.is_a?(Issue)
116 assert issue.is_a?(Issue)
117 assert !issue.new_record?
117 assert !issue.new_record?
118 issue.reload
118 issue.reload
119 assert_equal Group.find(11), issue.assigned_to
119 assert_equal Group.find(11), issue.assigned_to
120 end
120 end
121 end
121 end
122
122
123 def test_add_issue_with_partial_attributes_override
123 def test_add_issue_with_partial_attributes_override
124 issue = submit_email(
124 issue = submit_email(
125 'ticket_with_attributes.eml',
125 'ticket_with_attributes.eml',
126 :issue => {:priority => 'High'},
126 :issue => {:priority => 'High'},
127 :allow_override => ['tracker']
127 :allow_override => ['tracker']
128 )
128 )
129 assert issue.is_a?(Issue)
129 assert issue.is_a?(Issue)
130 assert !issue.new_record?
130 assert !issue.new_record?
131 issue.reload
131 issue.reload
132 assert_equal 'New ticket on a given project', issue.subject
132 assert_equal 'New ticket on a given project', issue.subject
133 assert_equal User.find_by_login('jsmith'), issue.author
133 assert_equal User.find_by_login('jsmith'), issue.author
134 assert_equal Project.find(2), issue.project
134 assert_equal Project.find(2), issue.project
135 assert_equal 'Feature request', issue.tracker.to_s
135 assert_equal 'Feature request', issue.tracker.to_s
136 assert_nil issue.category
136 assert_nil issue.category
137 assert_equal 'High', issue.priority.to_s
137 assert_equal 'High', issue.priority.to_s
138 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
138 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
139 end
139 end
140
140
141 def test_add_issue_with_spaces_between_attribute_and_separator
141 def test_add_issue_with_spaces_between_attribute_and_separator
142 issue = submit_email(
142 issue = submit_email(
143 'ticket_with_spaces_between_attribute_and_separator.eml',
143 'ticket_with_spaces_between_attribute_and_separator.eml',
144 :allow_override => 'tracker,category,priority'
144 :allow_override => 'tracker,category,priority'
145 )
145 )
146 assert issue.is_a?(Issue)
146 assert issue.is_a?(Issue)
147 assert !issue.new_record?
147 assert !issue.new_record?
148 issue.reload
148 issue.reload
149 assert_equal 'New ticket on a given project', issue.subject
149 assert_equal 'New ticket on a given project', issue.subject
150 assert_equal User.find_by_login('jsmith'), issue.author
150 assert_equal User.find_by_login('jsmith'), issue.author
151 assert_equal Project.find(2), issue.project
151 assert_equal Project.find(2), issue.project
152 assert_equal 'Feature request', issue.tracker.to_s
152 assert_equal 'Feature request', issue.tracker.to_s
153 assert_equal 'Stock management', issue.category.to_s
153 assert_equal 'Stock management', issue.category.to_s
154 assert_equal 'Urgent', issue.priority.to_s
154 assert_equal 'Urgent', issue.priority.to_s
155 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
155 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
156 end
156 end
157
157
158 def test_add_issue_with_attachment_to_specific_project
158 def test_add_issue_with_attachment_to_specific_project
159 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
159 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
160 assert issue.is_a?(Issue)
160 assert issue.is_a?(Issue)
161 assert !issue.new_record?
161 assert !issue.new_record?
162 issue.reload
162 issue.reload
163 assert_equal 'Ticket created by email with attachment', issue.subject
163 assert_equal 'Ticket created by email with attachment', issue.subject
164 assert_equal User.find_by_login('jsmith'), issue.author
164 assert_equal User.find_by_login('jsmith'), issue.author
165 assert_equal Project.find(2), issue.project
165 assert_equal Project.find(2), issue.project
166 assert_equal 'This is a new ticket with attachments', issue.description
166 assert_equal 'This is a new ticket with attachments', issue.description
167 # Attachment properties
167 # Attachment properties
168 assert_equal 1, issue.attachments.size
168 assert_equal 1, issue.attachments.size
169 assert_equal 'Paella.jpg', issue.attachments.first.filename
169 assert_equal 'Paella.jpg', issue.attachments.first.filename
170 assert_equal 'image/jpeg', issue.attachments.first.content_type
170 assert_equal 'image/jpeg', issue.attachments.first.content_type
171 assert_equal 10790, issue.attachments.first.filesize
171 assert_equal 10790, issue.attachments.first.filesize
172 end
172 end
173
173
174 def test_add_issue_with_custom_fields
174 def test_add_issue_with_custom_fields
175 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
175 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
176 assert issue.is_a?(Issue)
176 assert issue.is_a?(Issue)
177 assert !issue.new_record?
177 assert !issue.new_record?
178 issue.reload
178 issue.reload
179 assert_equal 'New ticket with custom field values', issue.subject
179 assert_equal 'New ticket with custom field values', issue.subject
180 assert_equal 'Value for a custom field',
180 assert_equal 'PostgreSQL', issue.custom_field_value(1)
181 issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
181 assert_equal 'Value for a custom field', issue.custom_field_value(2)
182 assert !issue.description.match(/^searchable field:/i)
182 assert !issue.description.match(/^searchable field:/i)
183 end
183 end
184
184
185 def test_add_issue_with_version_custom_fields
185 def test_add_issue_with_version_custom_fields
186 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
186 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
187
187
188 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
188 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
189 email << "Affected version: 1.0\n"
189 email << "Affected version: 1.0\n"
190 end
190 end
191 assert issue.is_a?(Issue)
191 assert issue.is_a?(Issue)
192 assert !issue.new_record?
192 assert !issue.new_record?
193 issue.reload
193 issue.reload
194 assert_equal '2', issue.custom_field_value(field)
194 assert_equal '2', issue.custom_field_value(field)
195 end
195 end
196
196
197 def test_add_issue_should_match_assignee_on_display_name
197 def test_add_issue_should_match_assignee_on_display_name
198 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
198 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
199 User.add_to_project(user, Project.find(2))
199 User.add_to_project(user, Project.find(2))
200 issue = submit_email('ticket_on_given_project.eml') do |email|
200 issue = submit_email('ticket_on_given_project.eml') do |email|
201 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
201 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
202 end
202 end
203 assert issue.is_a?(Issue)
203 assert issue.is_a?(Issue)
204 assert_equal user, issue.assigned_to
204 assert_equal user, issue.assigned_to
205 end
205 end
206
206
207 def test_add_issue_with_cc
207 def test_add_issue_with_cc
208 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
208 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
209 assert issue.is_a?(Issue)
209 assert issue.is_a?(Issue)
210 assert !issue.new_record?
210 assert !issue.new_record?
211 issue.reload
211 issue.reload
212 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
212 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
213 assert_equal 1, issue.watcher_user_ids.size
213 assert_equal 1, issue.watcher_user_ids.size
214 end
214 end
215
215
216 def test_add_issue_by_unknown_user
216 def test_add_issue_by_unknown_user
217 assert_no_difference 'User.count' do
217 assert_no_difference 'User.count' do
218 assert_equal false,
218 assert_equal false,
219 submit_email(
219 submit_email(
220 'ticket_by_unknown_user.eml',
220 'ticket_by_unknown_user.eml',
221 :issue => {:project => 'ecookbook'}
221 :issue => {:project => 'ecookbook'}
222 )
222 )
223 end
223 end
224 end
224 end
225
225
226 def test_add_issue_by_anonymous_user
226 def test_add_issue_by_anonymous_user
227 Role.anonymous.add_permission!(:add_issues)
227 Role.anonymous.add_permission!(:add_issues)
228 assert_no_difference 'User.count' do
228 assert_no_difference 'User.count' do
229 issue = submit_email(
229 issue = submit_email(
230 'ticket_by_unknown_user.eml',
230 'ticket_by_unknown_user.eml',
231 :issue => {:project => 'ecookbook'},
231 :issue => {:project => 'ecookbook'},
232 :unknown_user => 'accept'
232 :unknown_user => 'accept'
233 )
233 )
234 assert issue.is_a?(Issue)
234 assert issue.is_a?(Issue)
235 assert issue.author.anonymous?
235 assert issue.author.anonymous?
236 end
236 end
237 end
237 end
238
238
239 def test_add_issue_by_anonymous_user_with_no_from_address
239 def test_add_issue_by_anonymous_user_with_no_from_address
240 Role.anonymous.add_permission!(:add_issues)
240 Role.anonymous.add_permission!(:add_issues)
241 assert_no_difference 'User.count' do
241 assert_no_difference 'User.count' do
242 issue = submit_email(
242 issue = submit_email(
243 'ticket_by_empty_user.eml',
243 'ticket_by_empty_user.eml',
244 :issue => {:project => 'ecookbook'},
244 :issue => {:project => 'ecookbook'},
245 :unknown_user => 'accept'
245 :unknown_user => 'accept'
246 )
246 )
247 assert issue.is_a?(Issue)
247 assert issue.is_a?(Issue)
248 assert issue.author.anonymous?
248 assert issue.author.anonymous?
249 end
249 end
250 end
250 end
251
251
252 def test_add_issue_by_anonymous_user_on_private_project
252 def test_add_issue_by_anonymous_user_on_private_project
253 Role.anonymous.add_permission!(:add_issues)
253 Role.anonymous.add_permission!(:add_issues)
254 assert_no_difference 'User.count' do
254 assert_no_difference 'User.count' do
255 assert_no_difference 'Issue.count' do
255 assert_no_difference 'Issue.count' do
256 assert_equal false,
256 assert_equal false,
257 submit_email(
257 submit_email(
258 'ticket_by_unknown_user.eml',
258 'ticket_by_unknown_user.eml',
259 :issue => {:project => 'onlinestore'},
259 :issue => {:project => 'onlinestore'},
260 :unknown_user => 'accept'
260 :unknown_user => 'accept'
261 )
261 )
262 end
262 end
263 end
263 end
264 end
264 end
265
265
266 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
266 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
267 assert_no_difference 'User.count' do
267 assert_no_difference 'User.count' do
268 assert_difference 'Issue.count' do
268 assert_difference 'Issue.count' do
269 issue = submit_email(
269 issue = submit_email(
270 'ticket_by_unknown_user.eml',
270 'ticket_by_unknown_user.eml',
271 :issue => {:project => 'onlinestore'},
271 :issue => {:project => 'onlinestore'},
272 :no_permission_check => '1',
272 :no_permission_check => '1',
273 :unknown_user => 'accept'
273 :unknown_user => 'accept'
274 )
274 )
275 assert issue.is_a?(Issue)
275 assert issue.is_a?(Issue)
276 assert issue.author.anonymous?
276 assert issue.author.anonymous?
277 assert !issue.project.is_public?
277 assert !issue.project.is_public?
278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
279 end
279 end
280 end
280 end
281 end
281 end
282
282
283 def test_add_issue_by_created_user
283 def test_add_issue_by_created_user
284 Setting.default_language = 'en'
284 Setting.default_language = 'en'
285 assert_difference 'User.count' do
285 assert_difference 'User.count' do
286 issue = submit_email(
286 issue = submit_email(
287 'ticket_by_unknown_user.eml',
287 'ticket_by_unknown_user.eml',
288 :issue => {:project => 'ecookbook'},
288 :issue => {:project => 'ecookbook'},
289 :unknown_user => 'create'
289 :unknown_user => 'create'
290 )
290 )
291 assert issue.is_a?(Issue)
291 assert issue.is_a?(Issue)
292 assert issue.author.active?
292 assert issue.author.active?
293 assert_equal 'john.doe@somenet.foo', issue.author.mail
293 assert_equal 'john.doe@somenet.foo', issue.author.mail
294 assert_equal 'John', issue.author.firstname
294 assert_equal 'John', issue.author.firstname
295 assert_equal 'Doe', issue.author.lastname
295 assert_equal 'Doe', issue.author.lastname
296
296
297 # account information
297 # account information
298 email = ActionMailer::Base.deliveries.first
298 email = ActionMailer::Base.deliveries.first
299 assert_not_nil email
299 assert_not_nil email
300 assert email.subject.include?('account activation')
300 assert email.subject.include?('account activation')
301 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
301 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
302 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
302 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
303 assert_equal issue.author, User.try_to_login(login, password)
303 assert_equal issue.author, User.try_to_login(login, password)
304 end
304 end
305 end
305 end
306
306
307 def test_add_issue_without_from_header
307 def test_add_issue_without_from_header
308 Role.anonymous.add_permission!(:add_issues)
308 Role.anonymous.add_permission!(:add_issues)
309 assert_equal false, submit_email('ticket_without_from_header.eml')
309 assert_equal false, submit_email('ticket_without_from_header.eml')
310 end
310 end
311
311
312 def test_add_issue_with_invalid_attributes
312 def test_add_issue_with_invalid_attributes
313 issue = submit_email(
313 issue = submit_email(
314 'ticket_with_invalid_attributes.eml',
314 'ticket_with_invalid_attributes.eml',
315 :allow_override => 'tracker,category,priority'
315 :allow_override => 'tracker,category,priority'
316 )
316 )
317 assert issue.is_a?(Issue)
317 assert issue.is_a?(Issue)
318 assert !issue.new_record?
318 assert !issue.new_record?
319 issue.reload
319 issue.reload
320 assert_nil issue.assigned_to
320 assert_nil issue.assigned_to
321 assert_nil issue.start_date
321 assert_nil issue.start_date
322 assert_nil issue.due_date
322 assert_nil issue.due_date
323 assert_equal 0, issue.done_ratio
323 assert_equal 0, issue.done_ratio
324 assert_equal 'Normal', issue.priority.to_s
324 assert_equal 'Normal', issue.priority.to_s
325 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
325 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
326 end
326 end
327
327
328 def test_add_issue_with_localized_attributes
328 def test_add_issue_with_localized_attributes
329 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
329 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
330 issue = submit_email(
330 issue = submit_email(
331 'ticket_with_localized_attributes.eml',
331 'ticket_with_localized_attributes.eml',
332 :allow_override => 'tracker,category,priority'
332 :allow_override => 'tracker,category,priority'
333 )
333 )
334 assert issue.is_a?(Issue)
334 assert issue.is_a?(Issue)
335 assert !issue.new_record?
335 assert !issue.new_record?
336 issue.reload
336 issue.reload
337 assert_equal 'New ticket on a given project', issue.subject
337 assert_equal 'New ticket on a given project', issue.subject
338 assert_equal User.find_by_login('jsmith'), issue.author
338 assert_equal User.find_by_login('jsmith'), issue.author
339 assert_equal Project.find(2), issue.project
339 assert_equal Project.find(2), issue.project
340 assert_equal 'Feature request', issue.tracker.to_s
340 assert_equal 'Feature request', issue.tracker.to_s
341 assert_equal 'Stock management', issue.category.to_s
341 assert_equal 'Stock management', issue.category.to_s
342 assert_equal 'Urgent', issue.priority.to_s
342 assert_equal 'Urgent', issue.priority.to_s
343 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
343 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
344 end
344 end
345
345
346 def test_add_issue_with_japanese_keywords
346 def test_add_issue_with_japanese_keywords
347 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
347 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
348 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
348 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
349 tracker = Tracker.create!(:name => ja_dev)
349 tracker = Tracker.create!(:name => ja_dev)
350 Project.find(1).trackers << tracker
350 Project.find(1).trackers << tracker
351 issue = submit_email(
351 issue = submit_email(
352 'japanese_keywords_iso_2022_jp.eml',
352 'japanese_keywords_iso_2022_jp.eml',
353 :issue => {:project => 'ecookbook'},
353 :issue => {:project => 'ecookbook'},
354 :allow_override => 'tracker'
354 :allow_override => 'tracker'
355 )
355 )
356 assert_kind_of Issue, issue
356 assert_kind_of Issue, issue
357 assert_equal tracker, issue.tracker
357 assert_equal tracker, issue.tracker
358 end
358 end
359
359
360 def test_add_issue_from_apple_mail
360 def test_add_issue_from_apple_mail
361 issue = submit_email(
361 issue = submit_email(
362 'apple_mail_with_attachment.eml',
362 'apple_mail_with_attachment.eml',
363 :issue => {:project => 'ecookbook'}
363 :issue => {:project => 'ecookbook'}
364 )
364 )
365 assert_kind_of Issue, issue
365 assert_kind_of Issue, issue
366 assert_equal 1, issue.attachments.size
366 assert_equal 1, issue.attachments.size
367
367
368 attachment = issue.attachments.first
368 attachment = issue.attachments.first
369 assert_equal 'paella.jpg', attachment.filename
369 assert_equal 'paella.jpg', attachment.filename
370 assert_equal 10790, attachment.filesize
370 assert_equal 10790, attachment.filesize
371 assert File.exist?(attachment.diskfile)
371 assert File.exist?(attachment.diskfile)
372 assert_equal 10790, File.size(attachment.diskfile)
372 assert_equal 10790, File.size(attachment.diskfile)
373 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
373 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
374 end
374 end
375
375
376 def test_thunderbird_with_attachment_ja
376 def test_thunderbird_with_attachment_ja
377 issue = submit_email(
377 issue = submit_email(
378 'thunderbird_with_attachment_ja.eml',
378 'thunderbird_with_attachment_ja.eml',
379 :issue => {:project => 'ecookbook'}
379 :issue => {:project => 'ecookbook'}
380 )
380 )
381 assert_kind_of Issue, issue
381 assert_kind_of Issue, issue
382 assert_equal 1, issue.attachments.size
382 assert_equal 1, issue.attachments.size
383 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
383 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
384 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
384 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
385 attachment = issue.attachments.first
385 attachment = issue.attachments.first
386 assert_equal ja, attachment.filename
386 assert_equal ja, attachment.filename
387 assert_equal 5, attachment.filesize
387 assert_equal 5, attachment.filesize
388 assert File.exist?(attachment.diskfile)
388 assert File.exist?(attachment.diskfile)
389 assert_equal 5, File.size(attachment.diskfile)
389 assert_equal 5, File.size(attachment.diskfile)
390 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
390 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
391 end
391 end
392
392
393 def test_gmail_with_attachment_ja
393 def test_gmail_with_attachment_ja
394 issue = submit_email(
394 issue = submit_email(
395 'gmail_with_attachment_ja.eml',
395 'gmail_with_attachment_ja.eml',
396 :issue => {:project => 'ecookbook'}
396 :issue => {:project => 'ecookbook'}
397 )
397 )
398 assert_kind_of Issue, issue
398 assert_kind_of Issue, issue
399 assert_equal 1, issue.attachments.size
399 assert_equal 1, issue.attachments.size
400 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
400 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
401 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
401 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
402 attachment = issue.attachments.first
402 attachment = issue.attachments.first
403 assert_equal ja, attachment.filename
403 assert_equal ja, attachment.filename
404 assert_equal 5, attachment.filesize
404 assert_equal 5, attachment.filesize
405 assert File.exist?(attachment.diskfile)
405 assert File.exist?(attachment.diskfile)
406 assert_equal 5, File.size(attachment.diskfile)
406 assert_equal 5, File.size(attachment.diskfile)
407 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
407 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
408 end
408 end
409
409
410 def test_thunderbird_with_attachment_latin1
410 def test_thunderbird_with_attachment_latin1
411 issue = submit_email(
411 issue = submit_email(
412 'thunderbird_with_attachment_iso-8859-1.eml',
412 'thunderbird_with_attachment_iso-8859-1.eml',
413 :issue => {:project => 'ecookbook'}
413 :issue => {:project => 'ecookbook'}
414 )
414 )
415 assert_kind_of Issue, issue
415 assert_kind_of Issue, issue
416 assert_equal 1, issue.attachments.size
416 assert_equal 1, issue.attachments.size
417 u = ""
417 u = ""
418 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
418 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
419 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
419 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
420 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
420 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
421 11.times { u << u1 }
421 11.times { u << u1 }
422 attachment = issue.attachments.first
422 attachment = issue.attachments.first
423 assert_equal "#{u}.png", attachment.filename
423 assert_equal "#{u}.png", attachment.filename
424 assert_equal 130, attachment.filesize
424 assert_equal 130, attachment.filesize
425 assert File.exist?(attachment.diskfile)
425 assert File.exist?(attachment.diskfile)
426 assert_equal 130, File.size(attachment.diskfile)
426 assert_equal 130, File.size(attachment.diskfile)
427 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
427 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
428 end
428 end
429
429
430 def test_gmail_with_attachment_latin1
430 def test_gmail_with_attachment_latin1
431 issue = submit_email(
431 issue = submit_email(
432 'gmail_with_attachment_iso-8859-1.eml',
432 'gmail_with_attachment_iso-8859-1.eml',
433 :issue => {:project => 'ecookbook'}
433 :issue => {:project => 'ecookbook'}
434 )
434 )
435 assert_kind_of Issue, issue
435 assert_kind_of Issue, issue
436 assert_equal 1, issue.attachments.size
436 assert_equal 1, issue.attachments.size
437 u = ""
437 u = ""
438 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
438 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
439 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
439 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
440 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
440 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
441 11.times { u << u1 }
441 11.times { u << u1 }
442 attachment = issue.attachments.first
442 attachment = issue.attachments.first
443 assert_equal "#{u}.txt", attachment.filename
443 assert_equal "#{u}.txt", attachment.filename
444 assert_equal 5, attachment.filesize
444 assert_equal 5, attachment.filesize
445 assert File.exist?(attachment.diskfile)
445 assert File.exist?(attachment.diskfile)
446 assert_equal 5, File.size(attachment.diskfile)
446 assert_equal 5, File.size(attachment.diskfile)
447 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
447 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
448 end
448 end
449
449
450 def test_add_issue_with_iso_8859_1_subject
450 def test_add_issue_with_iso_8859_1_subject
451 issue = submit_email(
451 issue = submit_email(
452 'subject_as_iso-8859-1.eml',
452 'subject_as_iso-8859-1.eml',
453 :issue => {:project => 'ecookbook'}
453 :issue => {:project => 'ecookbook'}
454 )
454 )
455 assert_kind_of Issue, issue
455 assert_kind_of Issue, issue
456 assert_equal 'Testmail from Webmail: Γ€ ΓΆ ΓΌ...', issue.subject
456 assert_equal 'Testmail from Webmail: Γ€ ΓΆ ΓΌ...', issue.subject
457 end
457 end
458
458
459 def test_add_issue_with_japanese_subject
459 def test_add_issue_with_japanese_subject
460 issue = submit_email(
460 issue = submit_email(
461 'subject_japanese_1.eml',
461 'subject_japanese_1.eml',
462 :issue => {:project => 'ecookbook'}
462 :issue => {:project => 'ecookbook'}
463 )
463 )
464 assert_kind_of Issue, issue
464 assert_kind_of Issue, issue
465 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
465 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
466 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
466 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
467 assert_equal ja, issue.subject
467 assert_equal ja, issue.subject
468 end
468 end
469
469
470 def test_add_issue_with_no_subject_header
470 def test_add_issue_with_no_subject_header
471 issue = submit_email(
471 issue = submit_email(
472 'no_subject_header.eml',
472 'no_subject_header.eml',
473 :issue => {:project => 'ecookbook'}
473 :issue => {:project => 'ecookbook'}
474 )
474 )
475 assert_kind_of Issue, issue
475 assert_kind_of Issue, issue
476 assert_equal '(no subject)', issue.subject
476 assert_equal '(no subject)', issue.subject
477 end
477 end
478
478
479 def test_add_issue_with_mixed_japanese_subject
479 def test_add_issue_with_mixed_japanese_subject
480 issue = submit_email(
480 issue = submit_email(
481 'subject_japanese_2.eml',
481 'subject_japanese_2.eml',
482 :issue => {:project => 'ecookbook'}
482 :issue => {:project => 'ecookbook'}
483 )
483 )
484 assert_kind_of Issue, issue
484 assert_kind_of Issue, issue
485 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
485 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
486 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
486 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
487 assert_equal ja, issue.subject
487 assert_equal ja, issue.subject
488 end
488 end
489
489
490 def test_should_ignore_emails_from_locked_users
490 def test_should_ignore_emails_from_locked_users
491 User.find(2).lock!
491 User.find(2).lock!
492
492
493 MailHandler.any_instance.expects(:dispatch).never
493 MailHandler.any_instance.expects(:dispatch).never
494 assert_no_difference 'Issue.count' do
494 assert_no_difference 'Issue.count' do
495 assert_equal false, submit_email('ticket_on_given_project.eml')
495 assert_equal false, submit_email('ticket_on_given_project.eml')
496 end
496 end
497 end
497 end
498
498
499 def test_should_ignore_emails_from_emission_address
499 def test_should_ignore_emails_from_emission_address
500 Role.anonymous.add_permission!(:add_issues)
500 Role.anonymous.add_permission!(:add_issues)
501 assert_no_difference 'User.count' do
501 assert_no_difference 'User.count' do
502 assert_equal false,
502 assert_equal false,
503 submit_email(
503 submit_email(
504 'ticket_from_emission_address.eml',
504 'ticket_from_emission_address.eml',
505 :issue => {:project => 'ecookbook'},
505 :issue => {:project => 'ecookbook'},
506 :unknown_user => 'create'
506 :unknown_user => 'create'
507 )
507 )
508 end
508 end
509 end
509 end
510
510
511 def test_should_ignore_auto_replied_emails
511 def test_should_ignore_auto_replied_emails
512 MailHandler.any_instance.expects(:dispatch).never
512 MailHandler.any_instance.expects(:dispatch).never
513 [
513 [
514 "X-Auto-Response-Suppress: OOF",
514 "X-Auto-Response-Suppress: OOF",
515 "Auto-Submitted: auto-replied",
515 "Auto-Submitted: auto-replied",
516 "Auto-Submitted: Auto-Replied",
516 "Auto-Submitted: Auto-Replied",
517 "Auto-Submitted: auto-generated"
517 "Auto-Submitted: auto-generated"
518 ].each do |header|
518 ].each do |header|
519 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
519 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
520 raw = header + "\n" + raw
520 raw = header + "\n" + raw
521
521
522 assert_no_difference 'Issue.count' do
522 assert_no_difference 'Issue.count' do
523 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
523 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
524 end
524 end
525 end
525 end
526 end
526 end
527
527
528 def test_add_issue_should_send_email_notification
528 def test_add_issue_should_send_email_notification
529 Setting.notified_events = ['issue_added']
529 Setting.notified_events = ['issue_added']
530 ActionMailer::Base.deliveries.clear
530 ActionMailer::Base.deliveries.clear
531 # This email contains: 'Project: onlinestore'
531 # This email contains: 'Project: onlinestore'
532 issue = submit_email('ticket_on_given_project.eml')
532 issue = submit_email('ticket_on_given_project.eml')
533 assert issue.is_a?(Issue)
533 assert issue.is_a?(Issue)
534 assert_equal 1, ActionMailer::Base.deliveries.size
534 assert_equal 1, ActionMailer::Base.deliveries.size
535 end
535 end
536
536
537 def test_update_issue
537 def test_update_issue
538 journal = submit_email('ticket_reply.eml')
538 journal = submit_email('ticket_reply.eml')
539 assert journal.is_a?(Journal)
539 assert journal.is_a?(Journal)
540 assert_equal User.find_by_login('jsmith'), journal.user
540 assert_equal User.find_by_login('jsmith'), journal.user
541 assert_equal Issue.find(2), journal.journalized
541 assert_equal Issue.find(2), journal.journalized
542 assert_match /This is reply/, journal.notes
542 assert_match /This is reply/, journal.notes
543 assert_equal 'Feature request', journal.issue.tracker.name
543 assert_equal 'Feature request', journal.issue.tracker.name
544 end
544 end
545
545
546 def test_update_issue_with_attribute_changes
546 def test_update_issue_with_attribute_changes
547 # This email contains: 'Status: Resolved'
547 # This email contains: 'Status: Resolved'
548 journal = submit_email('ticket_reply_with_status.eml')
548 journal = submit_email('ticket_reply_with_status.eml')
549 assert journal.is_a?(Journal)
549 assert journal.is_a?(Journal)
550 issue = Issue.find(journal.issue.id)
550 issue = Issue.find(journal.issue.id)
551 assert_equal User.find_by_login('jsmith'), journal.user
551 assert_equal User.find_by_login('jsmith'), journal.user
552 assert_equal Issue.find(2), journal.journalized
552 assert_equal Issue.find(2), journal.journalized
553 assert_match /This is reply/, journal.notes
553 assert_match /This is reply/, journal.notes
554 assert_equal 'Feature request', journal.issue.tracker.name
554 assert_equal 'Feature request', journal.issue.tracker.name
555 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
555 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
556 assert_equal '2010-01-01', issue.start_date.to_s
556 assert_equal '2010-01-01', issue.start_date.to_s
557 assert_equal '2010-12-31', issue.due_date.to_s
557 assert_equal '2010-12-31', issue.due_date.to_s
558 assert_equal User.find_by_login('jsmith'), issue.assigned_to
558 assert_equal User.find_by_login('jsmith'), issue.assigned_to
559 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
559 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
560 # keywords should be removed from the email body
560 # keywords should be removed from the email body
561 assert !journal.notes.match(/^Status:/i)
561 assert !journal.notes.match(/^Status:/i)
562 assert !journal.notes.match(/^Start Date:/i)
562 assert !journal.notes.match(/^Start Date:/i)
563 end
563 end
564
564
565 def test_update_issue_with_attachment
565 def test_update_issue_with_attachment
566 assert_difference 'Journal.count' do
566 assert_difference 'Journal.count' do
567 assert_difference 'JournalDetail.count' do
567 assert_difference 'JournalDetail.count' do
568 assert_difference 'Attachment.count' do
568 assert_difference 'Attachment.count' do
569 assert_no_difference 'Issue.count' do
569 assert_no_difference 'Issue.count' do
570 journal = submit_email('ticket_with_attachment.eml') do |raw|
570 journal = submit_email('ticket_with_attachment.eml') do |raw|
571 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
571 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
572 end
572 end
573 end
573 end
574 end
574 end
575 end
575 end
576 end
576 end
577 journal = Journal.first(:order => 'id DESC')
577 journal = Journal.first(:order => 'id DESC')
578 assert_equal Issue.find(2), journal.journalized
578 assert_equal Issue.find(2), journal.journalized
579 assert_equal 1, journal.details.size
579 assert_equal 1, journal.details.size
580
580
581 detail = journal.details.first
581 detail = journal.details.first
582 assert_equal 'attachment', detail.property
582 assert_equal 'attachment', detail.property
583 assert_equal 'Paella.jpg', detail.value
583 assert_equal 'Paella.jpg', detail.value
584 end
584 end
585
585
586 def test_update_issue_should_send_email_notification
586 def test_update_issue_should_send_email_notification
587 ActionMailer::Base.deliveries.clear
587 ActionMailer::Base.deliveries.clear
588 journal = submit_email('ticket_reply.eml')
588 journal = submit_email('ticket_reply.eml')
589 assert journal.is_a?(Journal)
589 assert journal.is_a?(Journal)
590 assert_equal 1, ActionMailer::Base.deliveries.size
590 assert_equal 1, ActionMailer::Base.deliveries.size
591 end
591 end
592
592
593 def test_update_issue_should_not_set_defaults
593 def test_update_issue_should_not_set_defaults
594 journal = submit_email(
594 journal = submit_email(
595 'ticket_reply.eml',
595 'ticket_reply.eml',
596 :issue => {:tracker => 'Support request', :priority => 'High'}
596 :issue => {:tracker => 'Support request', :priority => 'High'}
597 )
597 )
598 assert journal.is_a?(Journal)
598 assert journal.is_a?(Journal)
599 assert_match /This is reply/, journal.notes
599 assert_match /This is reply/, journal.notes
600 assert_equal 'Feature request', journal.issue.tracker.name
600 assert_equal 'Feature request', journal.issue.tracker.name
601 assert_equal 'Normal', journal.issue.priority.name
601 assert_equal 'Normal', journal.issue.priority.name
602 end
602 end
603
603
604 def test_reply_to_a_message
604 def test_reply_to_a_message
605 m = submit_email('message_reply.eml')
605 m = submit_email('message_reply.eml')
606 assert m.is_a?(Message)
606 assert m.is_a?(Message)
607 assert !m.new_record?
607 assert !m.new_record?
608 m.reload
608 m.reload
609 assert_equal 'Reply via email', m.subject
609 assert_equal 'Reply via email', m.subject
610 # The email replies to message #2 which is part of the thread of message #1
610 # The email replies to message #2 which is part of the thread of message #1
611 assert_equal Message.find(1), m.parent
611 assert_equal Message.find(1), m.parent
612 end
612 end
613
613
614 def test_reply_to_a_message_by_subject
614 def test_reply_to_a_message_by_subject
615 m = submit_email('message_reply_by_subject.eml')
615 m = submit_email('message_reply_by_subject.eml')
616 assert m.is_a?(Message)
616 assert m.is_a?(Message)
617 assert !m.new_record?
617 assert !m.new_record?
618 m.reload
618 m.reload
619 assert_equal 'Reply to the first post', m.subject
619 assert_equal 'Reply to the first post', m.subject
620 assert_equal Message.find(1), m.parent
620 assert_equal Message.find(1), m.parent
621 end
621 end
622
622
623 def test_should_strip_tags_of_html_only_emails
623 def test_should_strip_tags_of_html_only_emails
624 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
624 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
625 assert issue.is_a?(Issue)
625 assert issue.is_a?(Issue)
626 assert !issue.new_record?
626 assert !issue.new_record?
627 issue.reload
627 issue.reload
628 assert_equal 'HTML email', issue.subject
628 assert_equal 'HTML email', issue.subject
629 assert_equal 'This is a html-only email.', issue.description
629 assert_equal 'This is a html-only email.', issue.description
630 end
630 end
631
631
632 context "truncate emails based on the Setting" do
632 context "truncate emails based on the Setting" do
633 context "with no setting" do
633 context "with no setting" do
634 setup do
634 setup do
635 Setting.mail_handler_body_delimiters = ''
635 Setting.mail_handler_body_delimiters = ''
636 end
636 end
637
637
638 should "add the entire email into the issue" do
638 should "add the entire email into the issue" do
639 issue = submit_email('ticket_on_given_project.eml')
639 issue = submit_email('ticket_on_given_project.eml')
640 assert_issue_created(issue)
640 assert_issue_created(issue)
641 assert issue.description.include?('---')
641 assert issue.description.include?('---')
642 assert issue.description.include?('This paragraph is after the delimiter')
642 assert issue.description.include?('This paragraph is after the delimiter')
643 end
643 end
644 end
644 end
645
645
646 context "with a single string" do
646 context "with a single string" do
647 setup do
647 setup do
648 Setting.mail_handler_body_delimiters = '---'
648 Setting.mail_handler_body_delimiters = '---'
649 end
649 end
650 should "truncate the email at the delimiter for the issue" do
650 should "truncate the email at the delimiter for the issue" do
651 issue = submit_email('ticket_on_given_project.eml')
651 issue = submit_email('ticket_on_given_project.eml')
652 assert_issue_created(issue)
652 assert_issue_created(issue)
653 assert issue.description.include?('This paragraph is before delimiters')
653 assert issue.description.include?('This paragraph is before delimiters')
654 assert issue.description.include?('--- This line starts with a delimiter')
654 assert issue.description.include?('--- This line starts with a delimiter')
655 assert !issue.description.match(/^---$/)
655 assert !issue.description.match(/^---$/)
656 assert !issue.description.include?('This paragraph is after the delimiter')
656 assert !issue.description.include?('This paragraph is after the delimiter')
657 end
657 end
658 end
658 end
659
659
660 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
660 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
661 setup do
661 setup do
662 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
662 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
663 end
663 end
664 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
664 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
665 journal = submit_email('issue_update_with_quoted_reply_above.eml')
665 journal = submit_email('issue_update_with_quoted_reply_above.eml')
666 assert journal.is_a?(Journal)
666 assert journal.is_a?(Journal)
667 assert journal.notes.include?('An update to the issue by the sender.')
667 assert journal.notes.include?('An update to the issue by the sender.')
668 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
668 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
669 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
669 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
670 end
670 end
671 end
671 end
672
672
673 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
673 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
674 setup do
674 setup do
675 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
675 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
676 end
676 end
677 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
677 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
678 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
678 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
679 assert journal.is_a?(Journal)
679 assert journal.is_a?(Journal)
680 assert journal.notes.include?('An update to the issue by the sender.')
680 assert journal.notes.include?('An update to the issue by the sender.')
681 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
681 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
682 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
682 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
683 end
683 end
684 end
684 end
685
685
686 context "with multiple strings" do
686 context "with multiple strings" do
687 setup do
687 setup do
688 Setting.mail_handler_body_delimiters = "---\nBREAK"
688 Setting.mail_handler_body_delimiters = "---\nBREAK"
689 end
689 end
690 should "truncate the email at the first delimiter found (BREAK)" do
690 should "truncate the email at the first delimiter found (BREAK)" do
691 issue = submit_email('ticket_on_given_project.eml')
691 issue = submit_email('ticket_on_given_project.eml')
692 assert_issue_created(issue)
692 assert_issue_created(issue)
693 assert issue.description.include?('This paragraph is before delimiters')
693 assert issue.description.include?('This paragraph is before delimiters')
694 assert !issue.description.include?('BREAK')
694 assert !issue.description.include?('BREAK')
695 assert !issue.description.include?('This paragraph is between delimiters')
695 assert !issue.description.include?('This paragraph is between delimiters')
696 assert !issue.description.match(/^---$/)
696 assert !issue.description.match(/^---$/)
697 assert !issue.description.include?('This paragraph is after the delimiter')
697 assert !issue.description.include?('This paragraph is after the delimiter')
698 end
698 end
699 end
699 end
700 end
700 end
701
701
702 def test_email_with_long_subject_line
702 def test_email_with_long_subject_line
703 issue = submit_email('ticket_with_long_subject.eml')
703 issue = submit_email('ticket_with_long_subject.eml')
704 assert issue.is_a?(Issue)
704 assert issue.is_a?(Issue)
705 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
705 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
706 end
706 end
707
707
708 def test_new_user_from_attributes_should_return_valid_user
708 def test_new_user_from_attributes_should_return_valid_user
709 to_test = {
709 to_test = {
710 # [address, name] => [login, firstname, lastname]
710 # [address, name] => [login, firstname, lastname]
711 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
711 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
712 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
712 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
713 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
713 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
714 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
714 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
715 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
715 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
716 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
716 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
717 }
717 }
718
718
719 to_test.each do |attrs, expected|
719 to_test.each do |attrs, expected|
720 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
720 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
721
721
722 assert user.valid?, user.errors.full_messages.to_s
722 assert user.valid?, user.errors.full_messages.to_s
723 assert_equal attrs.first, user.mail
723 assert_equal attrs.first, user.mail
724 assert_equal expected[0], user.login
724 assert_equal expected[0], user.login
725 assert_equal expected[1], user.firstname
725 assert_equal expected[1], user.firstname
726 assert_equal expected[2], user.lastname
726 assert_equal expected[2], user.lastname
727 end
727 end
728 end
728 end
729
729
730 def test_new_user_from_attributes_should_respect_minimum_password_length
730 def test_new_user_from_attributes_should_respect_minimum_password_length
731 with_settings :password_min_length => 15 do
731 with_settings :password_min_length => 15 do
732 user = MailHandler.new_user_from_attributes('jsmith@example.net')
732 user = MailHandler.new_user_from_attributes('jsmith@example.net')
733 assert user.valid?
733 assert user.valid?
734 assert user.password.length >= 15
734 assert user.password.length >= 15
735 end
735 end
736 end
736 end
737
737
738 def test_new_user_from_attributes_should_use_default_login_if_invalid
738 def test_new_user_from_attributes_should_use_default_login_if_invalid
739 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
739 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
740 assert user.valid?
740 assert user.valid?
741 assert user.login =~ /^user[a-f0-9]+$/
741 assert user.login =~ /^user[a-f0-9]+$/
742 assert_equal 'foo+bar@example.net', user.mail
742 assert_equal 'foo+bar@example.net', user.mail
743 end
743 end
744
744
745 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
745 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
746 assert_difference 'User.count' do
746 assert_difference 'User.count' do
747 issue = submit_email(
747 issue = submit_email(
748 'fullname_of_sender_as_utf8_encoded.eml',
748 'fullname_of_sender_as_utf8_encoded.eml',
749 :issue => {:project => 'ecookbook'},
749 :issue => {:project => 'ecookbook'},
750 :unknown_user => 'create'
750 :unknown_user => 'create'
751 )
751 )
752 end
752 end
753
753
754 user = User.first(:order => 'id DESC')
754 user = User.first(:order => 'id DESC')
755 assert_equal "foo@example.org", user.mail
755 assert_equal "foo@example.org", user.mail
756 str1 = "\xc3\x84\xc3\xa4"
756 str1 = "\xc3\x84\xc3\xa4"
757 str2 = "\xc3\x96\xc3\xb6"
757 str2 = "\xc3\x96\xc3\xb6"
758 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
758 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
759 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
759 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
760 assert_equal str1, user.firstname
760 assert_equal str1, user.firstname
761 assert_equal str2, user.lastname
761 assert_equal str2, user.lastname
762 end
762 end
763
763
764 private
764 private
765
765
766 def submit_email(filename, options={})
766 def submit_email(filename, options={})
767 raw = IO.read(File.join(FIXTURES_PATH, filename))
767 raw = IO.read(File.join(FIXTURES_PATH, filename))
768 yield raw if block_given?
768 yield raw if block_given?
769 MailHandler.receive(raw, options)
769 MailHandler.receive(raw, options)
770 end
770 end
771
771
772 def assert_issue_created(issue)
772 def assert_issue_created(issue)
773 assert issue.is_a?(Issue)
773 assert issue.is_a?(Issue)
774 assert !issue.new_record?
774 assert !issue.new_record?
775 issue.reload
775 issue.reload
776 end
776 end
777 end
777 end
General Comments 0
You need to be logged in to leave comments. Login now